graphql-java-kickstart / graphql-java-tools

A schema-first tool for graphql-java inspired by graphql-tools for JS
https://www.graphql-java-kickstart.com/tools/
MIT License
812 stars 174 forks source link

Field resolving with argument does not work? #171

Closed nilshartmann closed 6 years ago

nilshartmann commented 6 years ago

Version of graphl-java-tools: 5.2.3

In my schema I have a Type like this:

type Person {
  name: String!
  friends(friendName: String!): [Friend!]!
  # omit other fields...
}

For the friends field on Person I wrote an own GraphQLResolver:

public class PersonFieldResolver implements GraphQLResolver<Person> {
  public List<Friend> friends(Person p, String friendName) {
    // this is never inkoved due to Exception thrown at runtime (see below)
  } 
}

At runtime I receive the following Exception when adding friends to my query:

2018-08-26 11:09:09.942  WARN 67826 --- [nio-9000-exec-2] g.e.SimpleDataFetcherExceptionHandler    : Exception while fetching data (/person/friends) : Cannot construct instance of `nh.graphql.Person` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('klaus')
 at [Source: UNKNOWN; line: -1, column: -1]

java.lang.IllegalArgumentException: Cannot construct instance of `nh.graphql.Person` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('klaus')
 at [Source: UNKNOWN; line: -1, column: -1]
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3750) ~[jackson-databind-2.9.6.jar:2.9.6]
    at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3678) ~[jackson-databind-2.9.6.jar:2.9.6]
    at com.coxautodev.graphql.tools.MethodFieldResolver$createDataFetcher$$inlined$forEachIndexed$lambda$1.invoke(MethodFieldResolver.kt:82) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.MethodFieldResolver$createDataFetcher$$inlined$forEachIndexed$lambda$1.invoke(MethodFieldResolver.kt:20) ~[graphql-java-tools-5.2.0.jar:na]
    at com.coxautodev.graphql.tools.MethodFieldResolverDataFetcher.get(MethodFieldResolver.kt:146) ~[graphql-java-tools-5.2.0.jar:na]
    at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:258) [graphql-java-9.2.jar:na]
    at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:199) [graphql-java-9.2.jar:na]
    at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:70) ~[graphql-java-9.2.jar:na]
    at graphql.execution.ExecutionStrategy.completeValueForObject(ExecutionStrategy.java:631) [graphql-java-9.2.jar:na]
    at graphql.execution.ExecutionStrategy.completeValue(ExecutionStrategy.java:413) [graphql-java-9.2.jar:na]
    at graphql.execution.ExecutionStrategy.completeField(ExecutionStrategy.java:363) [graphql-java-9.2.jar:na]
    at graphql.execution.ExecutionStrategy.lambda$resolveFieldWithInfo$0(ExecutionStrategy.java:201) [graphql-java-9.2.jar:na]
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:602) ~[na:1.8.0_73]
    at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:614) ~[na:1.8.0_73]
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1983) ~[na:1.8.0_73]
    at graphql.execution.ExecutionStrategy.resolveFieldWithInfo(ExecutionStrategy.java:200) [graphql-java-9.2.jar:na]
    at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:70) ~[graphql-java-9.2.jar:na]
    at graphql.execution.Execution.executeOperation(Execution.java:158) ~[graphql-java-9.2.jar:na]
    at graphql.execution.Execution.execute(Execution.java:100) ~[graphql-java-9.2.jar:na]
    at graphql.GraphQL.execute(GraphQL.java:554) ~[graphql-java-9.2.jar:na]
    at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:496) ~[graphql-java-9.2.jar:na]
    at graphql.GraphQL.executeAsync(GraphQL.java:470) ~[graphql-java-9.2.jar:na]
    at graphql.GraphQL.execute(GraphQL.java:401) ~[graphql-java-9.2.jar:na]

For me it seems, that MethodFieldResolver uses the wrong target type when deserializing the argument (it uses Person instead of String).

Defining a resolver function with an argument on root level works fine and also defining a field resolver without arguments on Person works as expected:

public class PersonFieldResolver implements GraphQLResolver<Person> {
  public List<Friend> allFriends(Person p) {
    // this is  inkoved as expected with a correctly initialized Person object

  return ...;
  } 
}

Update: it might be related to having more than one GraphQLResolver for the same data class:

Is this a bug? Or am I doing something wrong? Should multiple resolvers work? (Field resolvers without arguments seem to have no problems)

Thanks for your help!

oliemansm commented 6 years ago

I created a unit test in the project to reproduce this issue and I indeed see the same error. I haven't used multiple resolvers for the same data class myself before, and not sure if it is intended to work with multiple resolvers this way actually. They are supported for other (root) types though, so would expect them to be supported in this case as well. Will mark it as a bug.

oliemansm commented 6 years ago

Thanks for your investigation and the info about the getIndexOffset() value. Adding a check for MultiResolverInfo fixed the issue.