javagraphql-spqr

How to pass parameters/variables to query or mutation?


I'm trying to read some parameters passed down through variables on my backend, lets see: (this method is inside AuthenticationService, injected in my graphql controller, see bellow)

@GraphQLMutation(name = "getSessionToken")
public AuthReturn getSessionToken(@GraphQLArgument(name = "user") String u, @GraphQLArgument(name = "password") String p) {...}

And here is my graphQL request:

mutation ($user: String!, $password: String!) {
  getSessionToken(user: $user, password: $password) {
    status
    payload
  }
}

and my variables:

{ "user": "myuser", "password": "mypass"}

but when I try to run this sample codes, the following error is showed:

{"timestamp":"2019-07-29T17:18:32.753+0000","status":400,"error":"Bad Request","message":"JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token\n at [Source: (PushbackInputStream); line: 1, column: 162] (through reference chain: java.util.LinkedHashMap[\"variables\"])","path":"/graphql"}

[Edit] And here is my Controller:

@RestController
public class GraphQLController {

    private final GraphQL graphQL;

    public GraphQLController(AgendamentoService agendamentos, ConfiguracaoService config, ProcessoService processos, ParametroService parametros, AuthenticationService autenticacao) {
        GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withResolverBuilders(
                        //Resolve by annotations
                        new AnnotatedResolverBuilder())
                .withOperationsFromSingletons(agendamentos, config, processos, parametros, autenticacao)
                .withValueMapperFactory(new JacksonValueMapperFactory())
                .generate();
        graphQL = GraphQL.newGraphQL(schema).build();
    }

    @CrossOrigin
    @PostMapping(value = "/graphql", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Map<String, Object> graphql(@RequestBody Map<String, String> request, HttpServletRequest raw) {
        // em context estamos passando o Request, usamos para fazer as verificacoes de autenticacao com GraphQl 
        ExecutionResult executionResult = graphQL.execute(ExecutionInput.newExecutionInput()
                .query(request.get("query"))
                .operationName(request.get("operationName"))
                .context(raw)
                .build());
        return executionResult.toSpecification();
    }
}

but if I run this mutation without passing parameters as variables on request, every thing works properly. What can I do to pass variables to my graphQl requests? Thanks in advance.


Solution

  • You're not actually passing the variable to graphql-java. This has to be done via ExecutionInput. I'd suggest creating a class such as:

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class GraphQLRequest {
    
        private final String query;
        private final String operationName;
        private final Map<String, Object> variables;
    
        @JsonCreator
        public GraphQLRequest(@JsonProperty("query") String query,
                              @JsonProperty("operationName") String operationName,
                              @JsonProperty("variables") Map<String, Object> variables) {
            this.query = query;
            this.operationName = operationName;
            this.variables = variables != null ? variables : Collections.emptyMap();
        }
    
        public String getQuery() {
            return query;
        }
    
        public String getOperationName() {
            return operationName;
        }
    
        public Map<String, Object> getVariables() {
            return variables;
        }
    }
    

    and use that as the parameter in the controller method:

    @CrossOrigin
    @PostMapping(value = "/graphql", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public Map<String, Object> graphql(@RequestBody GraphQLRequest graphQLRequest, HttpServletRequest httpRequest) {
        // em context estamos passando o Request, usamos para fazer as verificacoes de autenticacao com GraphQl 
        ExecutionInput.Builder inputBuilder = ExecutionInput.newExecutionInput()
                    .query(graphQLRequest.getQuery())
                    .operationName(graphQLRequest.getOperationName())
                    .variables(graphQLRequest.getVariables()) //this is the line you were missing
                    .context(httpRequest);
        return executionResult.toSpecification();
    }
    

    Missing variables in ExecutionInput still don't explain the deserialization error you were getting though. It say that an object was found in JSON where a string was expected. Not sure where that is comping from, but I suspect the web part more than the actual code.

    Either way, place a breakpoint inside the controller code and see if the request is deserialized correctly and if GraphQL engine is getting hit at all.

    I also suggest you simplify the setup:

    public GraphQLController(AgendamentoService agendamentos, ConfiguracaoService config, ProcessoService processos, ParametroService parametros, AuthenticationService autenticacao) {
        GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withResolverBuilders(
                        //Resolve by annotations
                        new AnnotatedResolverBuilder())
                .withOperationsFromSingletons(agendamentos, config, processos, parametros, autenticacao)
                .withValueMapperFactory(new JacksonValueMapperFactory())
                .generate();
        graphQL = GraphQL.newGraphQL(schema).build();
    }
    

    to

    public GraphQLController(AgendamentoService agendamentos, ConfiguracaoService config, ProcessoService processos, ParametroService parametros, AuthenticationService autenticacao) {
        GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withOperationsFromSingletons(agendamentos, config, processos, parametros, autenticacao)
                .generate();
        graphQL = GraphQL.newGraphQL(schema).build();
    }
    

    as the other lines are redundant. They're only setting what's already the default behavior.