GithubHelp home page GithubHelp logo

graphql-spqr's People

Contributors

austinarbor avatar baldylocks avatar dependabot[bot] avatar ellebrecht avatar esc-sbarden avatar goodforgod avatar jestan avatar kaqqao avatar manishmaheshwari avatar miguelocarvajal avatar pep1 avatar rigiytip avatar sergebg avatar ttyniwa avatar viqtor avatar vojtechhabarta avatar ysamsonov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-spqr's Issues

Relay pagination and sub-query exception

Hello again,

I am back again with the relay pagination.
While waiting for the 0.9.4 which will have the duplicate parameter problem fixed, i am using @GraphQLEnvironment ResolutionEnvironment to recover the values.
It works fine.

Now i want to paginate sub-queries and i have a problem.

The signature is now :

public Page<Vote> getUserVotes(@GraphQLContext User user, @GraphQLEnvironment ResolutionEnvironment env)

Below the users query is working fine with pagination.
userVotes is another paginated query, as long as i don't specify any parameters (like first) it works fine and it uses my default value for first.

{
	users(first:50) {
		pageInfo {
			startCursor
			endCursor
			hasNextPage
		}
		edges {
			node {
				id
				firstName
				lastName
				displayName
				userVotes {
					pageInfo {
						startCursor
						endCursor
						hasNextPage
					}
					edges {
						node {
							id
						}
					}
				}
			}
		}
	}
}

But if i do userVotes(first:50) { i receive the following exception.
Caused by: graphql.GraphQLException: Resolver for operation userVotes accepting arguments: [arg0, before, after, first, last] not implemented

So apparently it cannot find my method signature because it has something else then a ResolutionEnvironment.

Maybe i am doing something really wrong here :)
Thanks in advance for your insight.

StreamToCollectionTypeAdapter should close streams

Could you update StreamToCollectionTypeAdapter so that it closes the Stream after it has served its output?

I have some Streams which perform IO operations, and therefore I need these streams to be closed to release IO resources whenever GraphQL SPQR has finished reading from them.

I applied the following patch locally which resolves the problem. It would be great if you could incorporate it upstream. Thanks again for this great library!

public class StreamToCollectionTypeAdapter extends AbstractTypeAdapter<Stream<?>, List<?>> {

    @Override
    public List<?> convertOutput(Stream<?> original, AnnotatedType type, ResolutionEnvironment resolutionEnvironment) {
        try (Stream<?> stream = original) { // ensures the stream gets closed
            return stream
                    .map(item -> resolutionEnvironment.convertOutput(item, getElementType(type)))
                    .collect(Collectors.toList());
        }
    }

    @Override
    public Stream<?> convertInput(List<?> substitute, AnnotatedType type, ResolutionEnvironment resolutionEnvironment) {
        return substitute.stream().map(item -> resolutionEnvironment.convertInput(item, getElementType(type), resolutionEnvironment));
    }

    @Override
    public AnnotatedType getSubstituteType(AnnotatedType original) {
        return TypeFactory.parameterizedAnnotatedClass(List.class, original.getAnnotations(), getElementType(original));
    }

    private AnnotatedType getElementType(AnnotatedType type) {
        return GenericTypeReflector.getTypeParameter(type, Stream.class.getTypeParameters()[0]);
    }
}

Question about Subscription response

Hi can anyone explain me following situation.

I hava a simple subscription which work:

class TimerGraph {

  @GraphQLSubscription
  fun timer(): Publisher<Tick> {
    return Flux.generate(
        Consumer<SynchronousSink<LocalDateTime>> { sink ->
          sink.next(LocalDateTime.now())
        })
        .map { Tick(it) }
        .delayElements(Duration.ofSeconds(1))
  }

}

data class Tick(val x: LocalDateTime)

When I make following request:

subscription timer {
    timer {
      x
    }
}

I get response:

{
  "data": {
    "x": "2018-01-05T15:11:32.538"
  },
  "errors": [],
  "extensions": null
}

But I think that response should be:

{
  "data": {
     "Timer": {
         "x": "2018-01-05T15:11:32.538"
     }
  },
  "errors": [],
  "extensions": null
}

The response is unwrapped by something, and frontend library throw a lot of warnings about this, and its really disappointed.
Any suggestion why its happens and how prevent this?
Do not exclude that maybe its graphql-java issue.

Spring Pageable Mapping

How could I map the Pageable interface of Spring. Now this is interface is being mapped to the client as PageableInput that has only the "type" property so I can't specify values for page and size.

Is there some type of resolver I can use to handle this scenario?

Relay - adding fields to Connection and Edge types

I have a question regarding Relay protocol. I would like to add some fields (for example totalCount) to some Connection Types. Something similar to GitHub's API at https://developer.github.com/v4/explorer/.

I found that this is explicitly allowed by Relay Cursor Connections Specification (2.1 and 3.1).

I implemented io.leangen.graphql.execution.relay.Page<N> interface and added public long getTotalCount() method but it didn't show up in introspection query result.

Is it currently possible to somehow add fields to Connection and Edge types? Or could this be added to graphql-spqr?

Question regarding schema generation with GraphQL-Interfaces

I have got Java types with SPQR-Annotations the following manner:

@GraphQLInterface(name = "IAnimal")
public interface IAnimal {
	@GraphQLQuery(name = "name")
	public String getName();
}
@GraphQLType(name = "IBat")
public interface IBat extends IAnimal {
	@GraphQLQuery(name = "wingSpan")
	public int getWingSpan();
}
@GraphQLType(name = "ICat")
public interface ICat extends IAnimal {
	@GraphQLQuery(name = "smart")
	public boolean isSmart();
}

With corresponding implementations of my interfaces which I have annotated with @GraphQLIgnore().

public class Service {
	@GraphQLQuery(name = "zoo")
	public List<IAnimal> getAnimals() {
		List<IAnimal> retval = new ArrayList<>();
		retval.add(new BatImpl());
		retval.add(new CatImpl());
		return retval;
	}
	
	@GraphQLQuery(name = "cat")
	public ICat getCat() {
		return new CatImpl();
	}

//      (if I comment the following method in everything works as expected)	
//	@GraphQLQuery(name = "bat")
//	public IBat getBat() {
//		return new BatImpl();
//	}

}

Having all this I create a schema with the GraphQLSchemaGenerator with

GraphQLSchema schemaFromAnnotated = new GraphQLSchemaGenerator()//
				.withBasePackage("test.graphql.model")//
				.withOperationsFromSingleton(service)//
				.generate();

Now, when I query { zoo { name, __typename } } the library throws a GraphQLException, telling me that it "Could not determine the exact type of IAnimal". However, when I add the getBat()-method to my Service class (and thus having every type of my schema referenced directly) -- everything works fine. Seemingly, the schema generator does not, at least when invoked with the default parameters, include all implemented interfaces automatically. The result of the query

{ __type(name: "IAnimal") { name, possibleTypes { name } } },

listing only ICat as possible type, adds to the suspicion.

Now I'd like to know if there's a way to tell the generator to also include derived types even if only a base class is referenced in a service class.

Thanks in advance.

Abstract Service Class NullPointerException

I'm trying to implement an abstract class with some generics to cut down on code duplication.

Abstract class:

public abstract class AbstractResolver<T extends AbstractEntity> {
  protected QueryDslRepository<T> repository;

  public AbstractResolver(QueryDslRepository<T> repository) {
    this.repository = repository;
  }

  public List<T> findAll(T entity, Integer limit, Integer offset) {
    ...
  }
}

Concrete class:

@Component
public class BankResolver extends AbstractResolver<Bank> {
  @Autowired
  public BankResolver(BankRepository bankRepository) {
    super(bankRepository);
  }

  @GraphQLQuery(name = "bank")
  public Bank findById(Long id) {
    return super.repository.findOne(id);
  }

  @GraphQLQuery(name = "banks")
  public List<Bank> findAll(Bank bank, Integer limit, Integer offset) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
    return super.findAll(bank, limit, offset);
  }

  @GraphQLMutation(name = "bankUpdate")
  public Bank save(Bank bank) {
    return super.repository.save(bank);
  }
}

However upon making this change I get the following NullPointerException:

Caused by: java.lang.NullPointerException
	at io.leangen.graphql.generator.mapping.common.NonNullMapper.supports(NonNullMapper.java:52)
	at io.leangen.graphql.generator.mapping.TypeMapperRepository.lambda$getTypeMapper$46(TypeMapperRepository.java:19)
	at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:174)
	at java.util.ArrayList$ArrayListSpliterator.tryAdvance(ArrayList.java:1351)
	at java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126)
	at java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464)
	at io.leangen.graphql.generator.mapping.TypeMapperRepository.getTypeMapper(TypeMapperRepository.java:19)
	at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:150)
	at io.leangen.graphql.generator.mapping.common.ListMapper.toGraphQLType(ListMapper.java:23)
	at io.leangen.graphql.generator.OperationMapper.toGraphQLType(OperationMapper.java:150)
	at io.leangen.graphql.generator.OperationMapper.toGraphQLOperation(OperationMapper.java:122)
	at io.leangen.graphql.generator.OperationMapper.lambda$generateQueries$20(OperationMapper.java:82)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at io.leangen.graphql.generator.OperationMapper.generateQueries(OperationMapper.java:83)
	at io.leangen.graphql.generator.OperationMapper.<init>(OperationMapper.java:67)
	at io.leangen.graphql.GraphQLSchemaGenerator.generate(GraphQLSchemaGenerator.java:692)
	at p4.config.GraphQLConfig.graphQLSchema(GraphQLConfig.java:30)
	at p4.config.GraphQLConfig$$EnhancerBySpringCGLIB$$a5f5ad92.CGLIB$graphQLSchema$1(<generated>)
	at p4.config.GraphQLConfig$$EnhancerBySpringCGLIB$$a5f5ad92$$FastClassBySpringCGLIB$$ca17212f.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:358)
	at p4.config.GraphQLConfig$$EnhancerBySpringCGLIB$$a5f5ad92.graphQLSchema(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
	... 86 more

JPA Indirect Lists are not Instantiated

Dear,

this is a wonderful library. However, I am using my Entities from EclipseLink (JPA) to be retrieved automatically and the @OneToMany and @manytomany are LAZY loaded.

For some reason, the GraphQL library is retrieving an array list of 0 records instead of instantiating them before loading.

I tried to debug and the getters/setters from my Entity are being executed accordingly (like, customer.getPhoneList()) which returns an IndirectList from EclipseLink.

Is there a way that I could tell the Resolver to instantiate the list before returning it as an ArrayList in the OperationExecute method? Maybe I am missing some point in the TypeResolvers and probably could try to teach a TypeResolver to instantiate the list before returning as an array.

Question regarding mapped variables

GraphQL takes in a Map<String, Object> mappedVariables as part of the execute() request. When processing GraphQL JSON (content type: application/json), we are parsing out the variables as strings from the JSON itself.

This has worked fine for String, Enum types, but last week we ran into an issue when trying to process ZonedDateTime strings. I ended up realizing that I had to convert the ZonedDateTime string into an actual ZonedDateTime object in order for the query to work with GraphQL.

Is there an easier way to handle this besides having to manually find and parse out ZonedDateTime variables from the query and replacing the value in the Map<String, Object> mappedVariables with an actual ZonedDateTime?

Here's an example GraphQL JSON query (Note: starts_at and ends_at are ZonedDateTime!):

[
  {
    "query": "mutation updateCoupon($application_id: String!, $id: String!, $status: CouponRuleStatusInput!, $name: String!, $allocation_method: AllocationMethodInput!, $amount: BigDecimal!, $custom_word: String!, $discount_type: String!, $ends_at: ZonedDateTime!, $entitled_collection_ids: [String]!, $entitled_product_ids: [String]!, $entitled_variant_ids: [String]!, $format: String!, $merge_field: String!, $starts_at: ZonedDateTime!, $suffix: String!, $valid_days: String!, $validity_type: ValidityTypeInput!) { coupon_rule(coupon_rule: {application_id: $application_id, id: $id, name: $name, status: $status, allocation_method: $allocation_method, amount: $amount, custom_word: $custom_word, discount_type: $discount_type, ends_at: $ends_at, entitled_collection_ids: $entitled_collection_ids, entitled_product_ids: $entitled_product_ids, entitled_variant_ids: $entitled_variant_ids, format: $format, merge_field: $merge_field, starts_at: $starts_at, suffix: $suffix, valid_days: $valid_days, validity_type: $validity_type}) { id name status allocation_method amount custom_word discount_type ends_at entitled_collection_ids entitled_product_ids entitled_variant_ids format merge_field starts_at suffix valid_days validity_type __typename }}",
    "variables": {
      "application_id": "15e58062-54d0-4614-8498-485ac7a4933b",
      "id": "e3f69a41-6439-4522-9f18-a5083ad5b174",
      "name": "50% off",
      "status": "ACTIVE",
      "allocation_method": "ACROSS",
      "amount": 3,
      "custom_word": "asdasd",
      "discount_type": "Percent Off",
      "ends_at": "2017-11-13T17:00:00.000Z",
      "entitled_collection_ids": [],
      "entitled_product_ids": [
        
      ],
      "entitled_variant_ids": [
        
      ],
      "format": "merge_field-custom_word",
      "merge_field": "firstName",
      "starts_at": "2017-10-04T16:00:00.000Z",
      "suffix": "33",
      "valid_days": "Sunday",
      "validity_type": "BETWEEN_SPECIFIC_DATES",
      "__typename": "CouponRule",
      "random_characters": "AUTOGENERATED",
      "prefix_1": "firstName",
      "prefix_2": "asdasd"
    },
    "operationName": "updateCoupon"
  }
]

Here's the code we are using to parse out the ZonedDateTime String and manually convert it to an actual ZonedDateTime:

private ExecutionResult getExecutionResultFromJson(String applicationId, JsonNode firstNode) {
        String query = firstNode.get("query").asText();
        String operationName =
                firstNode.has("operationName") ? firstNode.get("operationName").asText("") : "";
        JsonNode variables = firstNode.has("variables") ? firstNode.get("variables") : null;
        Map<String, Object> mappedVariables =
                (variables != null) ? MAPPER.convertValue(variables, HashMap.class) : Collections.EMPTY_MAP;

        // Grab the GraphQL variables from the query
        String graphQLVariables = org.apache.commons.lang3.StringUtils.substringBetween(query, "(", ")");

        if (!StringUtils.isNullOrEmpty(graphQLVariables)) {
            // Split based on comma, then colon and remove $ and ! characters from the variables
            Map<String, String> variablesByNameAndType = COMMA.splitAsStream(graphQLVariables)
                    .collect(Collectors.toMap(e -> COLON.split(e)[0].replace("$", "").trim(),
                                              e -> COLON.split(e)[1].replace("!", "").trim()));

            // Get all ZonedDateTime variables
            List<String> zonedDateTimeKeys = variablesByNameAndType.entrySet().stream()
                    .filter(map -> "ZonedDateTime".equalsIgnoreCase(map.getValue()))
                    .map(Entry::getKey)
                    .collect(Collectors.toList());

            // If present, convert any keys of type ZonedDateTime from a String to a ZonedDateTime to work with GraphQL
            if (!CollectionUtils.isNullOrEmpty(zonedDateTimeKeys)) {
                for (String zonedDateTimeKey : zonedDateTimeKeys) {
                    Object zonedDateTimeString = mappedVariables.get(zonedDateTimeKey);

                    // Replace ZonedDateTime String with actual ZonedDateTime
                    if (zonedDateTimeString != null) {
                        mappedVariables.remove(zonedDateTimeKey);
                        mappedVariables.put(zonedDateTimeKey, ZonedDateTime.parse(zonedDateTimeString.toString()));
                    }
                }
            }
        }

        String applicationIdKey = "application_id";

        if (mappedVariables.containsKey(applicationIdKey)) {
            mappedVariables.remove(applicationIdKey);
        }

        mappedVariables.put(applicationIdKey, applicationId);

        LOG.debug("Running GraphQL query: '{}', Operation name: '{}', Variables: '{}'", query, operationName,
                  variables);

        // Execute GraphQL query or mutation
        return graphQLExecutor.execute(query, operationName, mappedVariables);
    }

Thanks for your help!

Subquery/Edges/...

Hello,

I have been trying for the past 2 days to use this and i am completely lost and confused.
The only thing i was able to achieve is a simple query that simply returns some users for instance.
But when there is subquery or edges involved i could not find any example/documentation on how to do it.
I tried everything i could trying to decipher the tests code but either nothing in my code for the subquery is called or i have a GraphQL exception which i have no idea how to resolve using spqr.

I see there is a Edge class but i don't know what to do with it, is it mandatory?
Why do my GraphQLQuery annotated method for a subquery is never called?
How do i pass parent information like a field to the subquery?
What relayId on GraphQLId do?

Is there an example somewhere that could help me? I have so many questions and no answer that i could find.
Something simple like post/comments and you want to have all the comments of a specific post in a single query would be a good example i think.

I feel this product could help me a lot but since there is no documentation/example whatsoever, i have no idea what to do.

Right now i want to use GraphQLContext User user in a subquery method to retrieve the parent.

public List<Vote> getUserVotes(@GraphQLContext User user) {...}

My user object has a Gender enum class and when the method is declared i receive the following exception :

graphql.AssertException: All types within a GraphQL schema must have unique names. No two provided types may have the same name. No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types). You have redefined the type 'GenderInput' from being a 'GraphQLEnumType' to a 'GraphQLEnumType'

Thanks a lot.

Not all jackson modules working in JacksonValueMapperFactory

Default implementation configurate objectMapper as following:

objectMapper .findAndRegisterModules() .setAnnotationIntrospector(new AnnotationIntrospector(collectSubtypes(abstractTypes, basePackage, metaDataGen)))

what AnnotationIntrospector actually do?
I see that for example jackson KotlinModule not proper work when it used.
(need manually provide @JacksonCreator and @JacksonProperty with kotlin module its not need by default)

Error finding query implementation

In the following two examples, the first works as expected with different arguments missing, the second does not.

  @GraphQLQuery(name = "allEntities")
  public List<Entity> getEntities(
      @GraphQLArgument(name = "type", description = "The type of the entity") String type,
      @GraphQLArgument(name = "value", description = "A value of the entity") String value,
      @GraphQLArgument(name = "limit", defaultValue = "0") int limit) {
    return null; //todo
  }

  @GraphQLQuery(name = "entities")
  public List<Entity> getByDocumentAndType(@GraphQLContext Document document,
      @GraphQLArgument(name = "type", description = "The type of the entity") String type,
      @GraphQLArgument(name = "value", description = "A value of the entity") String value,
      @GraphQLArgument(name = "limit", defaultValue = "0") int limit) {
    return null; //todo

For example, the following query works fine:

{
  allEntities(type: "Person"){
     value
   }
}

but this one gives the exception below :

{
  documents {
    entities(type: "Person"){
      value
    }
  }
}
Caused by: graphql.GraphQLException: Resolver for operation entities accepting arguments: [limit, type] not implemented
	at io.leangen.graphql.execution.OperationExecutor.execute(OperationExecutor.java:56) ~[spqr-0.9.3.jar:na]
	... 63 common frames omitted

Unresolvable dependencies in the POM

Hi,

I'd really like to try this out in a new project but there are some custom dependencies in the POM (snapshot and also a custom version of the graphql library) that I don't have access to.

    <dependency>
        <groupId>io.leangen.geantyref</groupId>
        <artifactId>geantyref</artifactId>
        <version>1.1.1-SNAPSHOT</version>
    </dependency>
    <dependency>
        <groupId>com.graphql-java</groupId>
        <artifactId>graphql-java</artifactId>
        <version>LEANGEN</version>
    </dependency>

Would it be possible to resolve those or offer a published binary? :0)

Many thanks!

Adam

Error with two classes with the same name in different packages

If a schema contains two classes with identical names but in different packages the second class to be processed by OperationMapper.toGraphQLOperation(Operation operation, BuildContext buildContext) will not be mapped (I think) or be mapped to the methods of the first class if field names match (yeah, unfortunate I know). So if any fields match names it will result in an error at execution time like this:

ERROR io.leangen.graphql.execution.OperationExecutor - Operation resolution exception
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_151]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_151]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_151]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_151]
at io.leangen.graphql.metadata.execution.MethodInvoker.execute(MethodInvoker.java:36) ~[BigBirdPlexus/:?]
at io.leangen.graphql.metadata.Resolver.resolve(Resolver.java:89) ~[Maven-io-leangen-graphql_spqr-0.9.3.jar:?]
at io.leangen.graphql.execution.OperationExecutor.execute(OperationExecutor.java:93) ~[Maven-io-leangen-graphql_spqr-0.9.3.jar:?]
at io.leangen.graphql.execution.OperationExecutor.execute(OperationExecutor.java:59) ~[Maven-io-leangen-graphql_spqr-0.9.3.jar:?]
at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:99) [graphql-java-3.0.0.jar:?]

Looking at the source code I see two points which appear to contribute to this problem:

  • BuildContext knownTypes is a Set storing class simple names, since the package qualification is not included this set will not treat two classes from different packages as different entities. Checks for the presence of classes in CachingMapper and other mappers therefore return true when they should return false.
  • TypeRepository has covariantOutputTypes and knownObjecTypes are maps which also use unqualified type names as keys (sometimes the class name compounded with the field name) and therefore will also treat two different classes from different packages with the same name as the same class.

I don't know if there's a way to work around this right now. I tried to use a MappedType, but that works only for leaf classes which can be converted directly to serializable results. I couldn't get it to work for classes which contain other selectable fields. Any tips would be appreciated.

Attribute naming strategy?

We are migrating from REST to GraphQL and the front end code is all setup w/ snake_case attributes. Is there a way to configure spqr to create the schema to resolve attributes named this way? Right now we are getting UnknownArgument errors for attributes like campaign_id vs campaignId.

edit: I've fixed the UnknownArgument, but now I'm getting FieldUndefined.

Can not create multiple queries with the same name

When I try to use the same query name, with different arguments, I get a an exception

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [graphql.schema.GraphQLSchema]: Factory method 'schema' threw exception; nested exception is java.lang.IllegalStateException: Not all resolvers expect the same source types
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189) ~[spring-beans-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588) ~[spring-beans-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	... 67 common frames omitted
Caused by: java.lang.IllegalStateException: Not all resolvers expect the same source types
	at io.leangen.graphql.metadata.strategy.query.DefaultOperationBuilder.resolveContextTypes(DefaultOperationBuilder.java:77) ~[spqr-0.9.3.jar:na]
	at io.leangen.graphql.metadata.strategy.query.DefaultOperationBuilder.buildQuery(DefaultOperationBuilder.java:34) ~[spqr-0.9.3.jar:na]
	at io.leangen.graphql.generator.OperationRepository.lambda$buildQueries$39(OperationRepository.java:38) ~[spqr-0.9.3.jar:na]
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_112]
	at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1691) ~[na:1.8.0_112]
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_112]
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_112]
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_112]
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_112]
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_112]
	at io.leangen.graphql.generator.OperationRepository.buildQueries(OperationRepository.java:39) ~[spqr-0.9.3.jar:na]
	at io.leangen.graphql.generator.OperationRepository.<init>(OperationRepository.java:31) ~[spqr-0.9.3.jar:na]
	at io.leangen.graphql.GraphQLSchemaGenerator.generate(GraphQLSchemaGenerator.java:685) ~[spqr-0.9.3.jar:na]
	at io.committed.BaleenGrahpQl.schema(BaleenGrahpQl.java:43) [classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_112]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_112]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_112]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_112]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162) ~[spring-beans-4.3.11.RELEASE.jar:4.3.11.RELEASE]
	... 68 common frames omitted

maximumQueryComplexity doesn't work with INTROSPECTION_QUERY

I've tried to add maximumQueryComplexity but i've seen that it breaks with INTROSPECTION_QUERY execution and some times in mutations. Simple Queries worked fine.

code:
GraphQLRuntime.newGraphQL(schema).maximumQueryComplexity(50).queryExecutionStrategy(new BatchedExecutionStrategy()).build();
graphQL.execute(IntrospectionQuery.INTROSPECTION_QUERY);

Error stack:
java.lang.NullPointerException
at io.leangen.graphql.execution.complexity.ComplexityAnalyzer.collectFields(ComplexityAnalyzer.java:58)
at io.leangen.graphql.execution.complexity.ComplexityAnalysisInstrumentation.beginDataFetch(ComplexityAnalysisInstrumentation.java:50)
at graphql.execution.Execution.executeOperation(Execution.java:72)
at graphql.execution.Execution.execute(Execution.java:49)
at graphql.GraphQL.execute(GraphQL.java:222)
at io.leangen.graphql.GraphQLRuntime.execute(GraphQLRuntime.java:40)
at graphql.GraphQL.execute(GraphQL.java:187)
at graphql.GraphQL.execute(GraphQL.java:179)
...

Pagination variables null value

Hi, We are having a problem using pagination variables, the query is being executed but returning all available records and ignoring any pagination variables passed. Having inspected our GraphQL controller the variables/values are correct but once they reach the OperationExecutor they each have a null value.

Getting a NullPointerException with customized BeanResolverBuilder

Hi,
First of all thanks so much for this impressive library!

I am quite new to GraphQL, but I am hoping to use this library to add GraphQL support to a lot of existing APIs which are currently designed for JSON REST services. The aim is to provide both REST and GraphQL endpoints.

For this to work, I'm hoping to configure GraphQL SPQR library to leverage the existing jackson-annotations which are already present in the codebase. For the most part this actually works quite well. I have been able to configure the library to read GraphQL query names from @JsonProperty annotations, and to ignore properties which are annotated with @JsonIgnore. So effectively I've been able to make a bunch of APIs available via GraphQL endpoints without having to modify the codebase at all.

However while it works for the most part, I've hit a problem where the library is throwing an NPE when there's a particular combination of annotated members in a class. It seems to be an edge case where two methods have different return types. One of them should be ignored, but it seems to cause an NPE anyway. I was able to make a small test case below which reproduces the problem.

Is there anything I could do to work around the problem? Or is this a bug which could be fixed?

package io.leangen.graphql;

import com.fasterxml.jackson.annotation.JsonIgnore; // Needs a dependency on jackson-annotations
import com.fasterxml.jackson.annotation.JsonProperty; // Needs a dependency on jackson-annotations
import graphql.schema.GraphQLSchema;
import io.leangen.graphql.metadata.strategy.query.*;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import org.junit.Test;

public class NPETest {

    /** This test reproduces a NullPointerException when generating the schema using BeanResolverBuilder. */
    @Test
    public void testForNPE() {

        UserService userService = new UserService();

        final ResolverBuilder resolverBuilder = new BeanResolverBuilder(null)
                .withFilters(inclusionFilter)
                .withOperationNameGenerator(jsonAnnotationAwareNameGenerator);

        GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withResolverBuilders(resolverBuilder)
                .withNestedResolverBuilders(resolverBuilder)
                .withOperationsFromSingleton(userService)
                .withDefaults()
                .generate();
    }

    /** An example service we want to expose via GraphQL. */
    public static class UserService {

        public User getUser() {
            return new User();
        }
    }

    /** An example POJO the service returns, which has Jackson annotations we want to leverage for marshalling. */
    public static class User {

        @Deprecated
        @JsonIgnore // This property should be ignored, but its presence seems to cause an NPE anyway.
        public ArrayList<String> getName() { // Note the return type is ArrayList.
            return new ArrayList<>();
        }

        @JsonProperty("name") // The replacement for the method above.
        public List<String> getFullName() { // Note the return type is a List (it differs from above).
            return new ArrayList<>();
        }
    }

    /**
     * An inclusion filter/predicate which indicates that a field or method should be included
     * if it is NOT annotated with @JsonIgnore.
     */
    static final Predicate<Member> inclusionFilter = member ->
                        !((AnnotatedElement)member).isAnnotationPresent(JsonIgnore.class);

    /**
     * An operation name generator which reads the name from a @JsonProperty annotation on a method if present,
     * but otherwise delegates to the superclass which reads the name of the method itself.
     */
    static final OperationNameGenerator jsonAnnotationAwareNameGenerator = new BeanOperationNameGenerator() {

        @Override
        public String generateQueryName(Method queryMethod, AnnotatedType declaringType, Object instance) {
            JsonProperty jsonProperty = queryMethod.getAnnotation(JsonProperty.class);
            return jsonProperty != null ? jsonProperty.value() : super.generateQueryName(queryMethod, declaringType, instance);
        }
    };
}

How do I add enum to my GraphQLSchema?

In my app I have this enum as a separate class:

public enum UserSortFields {
    ID,  
    USERNAME,
    EMAIL,
    VALID_FROM,
    ACCESS_PERIOD,
    MAX_SESSION_AMOUNT
}

I can't seem to understand how do I add this one when generating the GraphQLSchema?

Implementing where clause fails

I have been requested to add a where clause to the codebase so that queries like this can be executed users( where: { name: "Bob" } ) { name }, but there appears to be a name conflict when doing this with more than one entity.

If I add in my UserQuery:

public class UsersQuery {
private UserRepository userRepository;
@GraphQLQuery(name = "users")
public List<Users> userSearch(@GraphQLArgument(name = "where",
                                                    description = "User where clause")
                                             Map<String, Object> where)
                                                    throws Exception {
    ExampleMatcher matcher = ExampleMatcher.matching();
    User user = new User();
    for (String key : where.keySet()) {
            matcher.withMatcher(key, ignoreCase());
            PropertyUtils.setSimpleProperty(user, key, where.get(key));
    }

    Example<User> example = Example.of(user, matcher);
    return userRepository.findAll(example);
}
}

It appears to start correctly, but if I add a second one in a different query such as:

public class BuildingsQuery {
private BuildingRepository buildingRepository;
@GraphQLQuery(name = "buildings")
public List<Building> buildingSearch(@GraphQLArgument(name = "where",
                                                    description = "Building where clause")
                                             Map<String, Object> where)
                                                    throws Exception {
    ExampleMatcher matcher = ExampleMatcher.matching();
    Building building = new Building();
    for (String key : where.keySet()) {
            matcher.withMatcher(key, ignoreCase());
            PropertyUtils.setSimpleProperty(building, key, where.get(key));
    }

    Example<Building> example = Example.of(building, matcher);
    return buildingRepository.findAll(example);
}
}

I get conflicts in the SchemaUtils.assertTypeUniqueness claiming "All types within a GraphQL schema must have unique names. No two provided types may have the same name." where my type has the name "mapEntry_String_Object_input" even though I defined the name as buildings. Am I misunderstanding what the name is, and if so, why are my other queries not conflicting?

Pagination arguments duplicated

Hello there,

I am looking at how to paginate my results and lists.
My database supports cursor so i wanted to use that.
I managed to make it work but i have a question about what seems to be generated behind the scene.

I have a query method that look like this :

@GraphQLQuery(name = "entities", description = "Paginate entities navigation")
    public Page<Entities> getPagedAll(@GraphQLArgument(name = "first", defaultValue = "20") int first, @GraphQLArgument(name = "after") String after) {
        return ...;
    }

It does the job perfectly.

What seems weird is when i ask for the introspection query https://github.com/graphql/graphql-js/blob/master/src/utilities/introspectionQuery.js

I found that pagination argument are already injected added to mine. Therefore i have 2 "first" and 2 "after" alongside before and last which i am not using at the moment.

"args": [
                                {
                                    "name": "after",
                                    "description": "",
                                    "type": {
                                        "kind": "SCALAR",
                                        "name": "String",
                                        "ofType": null
                                    },
                                    "defaultValue": null
                                },
                                {
                                    "name": "first",
                                    "description": "",
                                    "type": {
                                        "kind": "SCALAR",
                                        "name": "Int",
                                        "ofType": null
                                    },
                                    "defaultValue": null
                                },
                                {
                                    "name": "before",
                                    "description": "fetching only nodes before this node (exclusive)",
                                    "type": {
                                        "kind": "SCALAR",
                                        "name": "String",
                                        "ofType": null
                                    },
                                    "defaultValue": null
                                },
                                {
                                    "name": "after",
                                    "description": "fetching only nodes after this node (exclusive)",
                                    "type": {
                                        "kind": "SCALAR",
                                        "name": "String",
                                        "ofType": null
                                    },
                                    "defaultValue": null
                                },
                                {
                                    "name": "first",
                                    "description": "fetching only the first certain number of nodes",
                                    "type": {
                                        "kind": "SCALAR",
                                        "name": "Int",
                                        "ofType": null
                                    },
                                    "defaultValue": null
                                },
                                {
                                    "name": "last",
                                    "description": "fetching only the last certain number of nodes",
                                    "type": {
                                        "kind": "SCALAR",
                                        "name": "Int",
                                        "ofType": null
                                    },
                                    "defaultValue": null
                                }
                            ]

This is very confusing for the front-end developer who is also using a code generator from this schema.
I would not mind using the already injected fields but i have no idea how and if i can set a default value to the existing field.

Could you enlighten me on this behavior ?

Thanks a lot!

Returning Either<T, A>

Hi,

I'm using varv.io library and I would like to return a Either<Throwable, T> from query methods. What should I write or plug-in to enable this?

Thank you.

Remove hardcode realization of Executable from ResolverBuilder

Please provide possibility to inject own Executable realization in ResolverBuilder (PublicBeanResolver, AnnotatedResolverBuilder and so on..).

PublicResolverBuilder.class
querySourceBean == null ? new MethodInvoker(method, beanType) : new SingletonMethodInvoker(querySourceBean, method, beanType)

Inject custom MethodInvoker realization would be great for provide authentication and security for example.

Fields order

I am building GraphQL API and I really like code-first style so thank you for this tool.

While code-first style is more convenient and eliminates duplication the generated schema and introspection query (used by GraphiQL) doesn't look as good as handwritten. So I would like to improve it.

First think is order of fields. Currently introspection query returns fields in no particular order. I think they should be at least sorted alphabetically but even better would be possibility to specify their order. Since order in source code is not much useful here (fields can come from different classes) I would add some annotation which could be used to list names of fields in required order, something simitar to @XmlType.propOrder.

In 680f20d commit I added @GraphQLType.propOrder but it could be for example @GraphQLType.fieldOrder or separate annotation. It should also be added to @GraphQLInterface.

Does this feature make sense for you and should I finish it and create PR? If yes, how exactly the annotation should look like?

Batched nested fields queries

Overall Im pretty happy with this API. I was able to create successfully a graphQl server exposing liferay's ORM model entities. The problem is that when i tried to implement a batched query for a nested model field i ended with java.lang.IllegalArgumentException: argument type mismatch and graphql.GraphQLException: Operation resolution exception.

Searching the examples i found the education batched query method inside RecService in the tests of the repo and i used it to see if Im doing something wrong. But i get the same error even with this examples.
method:
@GraphQLQuery
@Batched
public List education(@GraphQLContext List users) {
System.out.println(users.size());
return users.stream()
.map(u -> u.getEducation(2000 + u.getFullName().charAt(0)))
.collect(Collectors.toList());
}
Query:
{
user(name:"Test") {
fullName
education {
startYear
tier
schoolName
endYear
extra
}
}
}
Errors:
Operation resolution exception
java.lang.IllegalArgumentException: argument type mismatch
Exception while fetching data (/user/education) : Operation resolution exception
graphql.GraphQLException: Operation resolution exception
at io.leangen.graphql.execution.OperationExecutor.execute(OperationExecutor.java:64)
at io.leangen.graphql.generator.OperationMapper$1.get(OperationMapper.java:278)
at graphql.execution.ExecutionStrategy.fetchField(ExecutionStrategy.java:218)
at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:166)
at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:49)
at graphql.execution.ExecutionStrategy.completeValue(ExecutionStrategy.java:379)
at graphql.execution.ExecutionStrategy.completeField(ExecutionStrategy.java:297)
at graphql.execution.ExecutionStrategy.lambda$resolveField$0(ExecutionStrategy.java:168)
at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:981)
at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2124)
at graphql.execution.ExecutionStrategy.resolveField(ExecutionStrategy.java:167)
at graphql.execution.AsyncExecutionStrategy.execute(AsyncExecutionStrategy.java:49)
at graphql.execution.Execution.executeOperation(Execution.java:112)
at graphql.execution.Execution.execute(Execution.java:65)
at graphql.GraphQL.execute(GraphQL.java:466)
at graphql.GraphQL.parseValidateAndExecute(GraphQL.java:414)
at graphql.GraphQL.executeAsync(GraphQL.java:392)
at graphql.GraphQL.execute(GraphQL.java:329)

GraphQL interfaces for Java inheritance

Hi,

I am having some trouble getting GraphQL SPQR to work with inheritance hierarchies in Java code.

That is where a class B extends class A. B inherits some fields or methods from the superclass, and provides some subclass-specific fields or methods of its own as well.

I find if I annotate the superclass A with @GraphQLInterface and implementationAutoDiscovery = true, I get an error at runtime when I try to use inline fragments "... on B { }" to access fields in the subclass B from queries.

I noticed that there is SuperTypeBasedInterfaceStrategy.java in the SPQR codebase but I can't get it to work.

The following test case shows the problem. It's based on the inline fragments example on graphql.org. Could you advise how I can do this?

Many thanks again in advance!

public class JavaInheritanceTest {

    /**
     * This test fails with error:
     * [ValidationError{validationErrorType=UnknownType, sourceLocations=[], description='Unknown type Human'}]
     */
    @Test
    public void testJavaInheritance() {
        GraphQLSchema schema = new GraphQLSchemaGenerator()
            .withOperationsFromSingleton(new StarWarsCharacterService())
            .generate();
        GraphQL graphQL = GraphQL.newGraphQL(schema).build();

        ExecutionResult executionResult = graphQL.execute("{characters {name, ... on Human { starship }}}");

        assertEquals("[]", String.valueOf(executionResult.getErrors()));
        assertEquals("{characters=[{name=Han Solo, starship=Millennium Falcon}, {name=R2D2}]}", String.valueOf(
            (Object)executionResult.getData()));
    }

    // =========================
    // === Domain objects... ===
    // =========================

    @GraphQLInterface(name = "StarWarsCharacter", implementationAutoDiscovery = true)
    public static abstract class StarWarsCharacter {

        private final String name;

        public StarWarsCharacter(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        };
    }

    public static class Human extends StarWarsCharacter {

        public Human() {
            super("Han Solo");
        }

        public String getStarship() {
            return "Millennium Falcon";
        }
    }

    public static class Droid extends StarWarsCharacter {

        public Droid() {
            super("R2D2");
        }

        public String getPrimaryFunction() {
            return "Astromech";
        }
    }

    public static class StarWarsCharacterService {

        @GraphQLQuery(name = "characters")
        public List<StarWarsCharacter> getCharacters() {
            return Arrays.asList(new Human(), new Droid());
        }
    }
}

Question regarging custom errors implementation.

Is it possible to add GraphQLErrors based on business logic?

Eg: an error for each invalid field. (like email invalid or username not available). I was able to show a single Error only by throwing a new GraphQLException.

I'm interested in something like this:
{ "errors": [ { "code": 2222, "field": "username", "message": "username_not_available", "locations": null, "errorType": "DataFetchingException" }, { "code": 1111, "field": "pass", "message": "password_is_weak", "locations": null, "errorType": "DataFetchingException" } ], "data": null, "extensions": null }

Defining two GraphQLQuery with the same name results in IndexOutOfBoundsException

I have a service with two methods and (not knowing GraphQL really well) defined two methods with:
@GraphQLQuery(name = "person")
Resulting in:

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.LinkedList.checkElementIndex(LinkedList.java:555)
	at java.util.LinkedList.get(LinkedList.java:476)
	at io.leangen.graphql.util.ClassUtils.getCommonSuperType(ClassUtils.java:396)
	at io.leangen.graphql.metadata.strategy.query.DefaultOperationBuilder.resolveJavaType(DefaultOperationBuilder.java:58)
	at io.leangen.graphql.metadata.strategy.query.DefaultOperationBuilder.buildQuery(DefaultOperationBuilder.java:33)
	at io.leangen.graphql.generator.OperationRepository.lambda$buildQueries$39(OperationRepository.java:38)
	at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
	at java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1691)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at io.leangen.graphql.generator.OperationRepository.buildQueries(OperationRepository.java:39)
	at io.leangen.graphql.generator.OperationRepository.<init>(OperationRepository.java:31)
	at io.leangen.graphql.GraphQLSchemaGenerator.generate(GraphQLSchemaGenerator.java:685)

Would be nice if the generator throws an exception while initializing that this is not allowed.

Support injecting sub-field names via @GraphQLEnvironment Set<String> in all cases

Currently, sub-field names can be injected via @GraphQLEnvironment Set<String> subSelection only when the current field is of GraphQLObjectType. It injects an empty set for interfaces, unions.
Knowing the sub-selection ahead of time can be important in order to optimizes the fetching, e.g. "SELECT subFiel1, subField2 FROM table" instead of SELECT * FROM table.

Should be implemented in grapqhl-spqr first and then submitted as a PR to graphql-java as the limitation comes from there.

Querying null dates cause an exception

When issuing queries that contain a Date field. If the date is null, then the query will fail with an exception. I have tracked down the problem to io.leangen.graphql.util.Scalars. In particular the temporalScalar serialize function on line 198 assumes that the input is non null which is wrong. This causes the input.toString() to throw a NullPointerException.

This function should be changed to return null if the input is null, otherwise continue as written.

Override GraphQLArgument object type to provide more details to clients

I'm probably doing this completely wrong, but I have a simple GraphQLQuery where the method signature is essentially

@GraphQLQuery(name = url)
public List<Entity> searchAll(@GraphQLArgument(name="filter") Entity filter) {
return search(filter)
}

That wraps up a matcher search so that some of our clients that want a general purpose search can pass an id into the filter or another attribute and get back a filtered list. The problem becomes that in an attempt to extend this to be able to take nulls, the method signature has to move to being a Map so that we can distinguish when a value is null because it wasn't set, or null because the client want to search on null (such as figuring out which customers don't have a telephone number set).
So my first issue I ran into is that you can't have a Map<String, Object> as the GraphQLArgument as I get an error saying

[
"Validation error of type WrongType: argument value ObjectValue{objectFields=[ObjectField{name='id', value=IntValue{value=1}}]} has wrong type"
]

where it doesn't like converting from an Int to an Object .
That's not the best, but I can set the argument to be an object and then cast it to a map so that it can still be searched like so:

@GraphQLQuery(name = url)
public List<Entity> searchAll(@GraphQLArgument(name = "filter", ) Object filter) {
Map<String, Object> where = ((LinkedHashMap) filter);
return search(where)
}

My issue then becomes GraphiQL which loses information about the argument and can only refer to it as "ObjectScalar". Is there a way to override how it is reported back to GraphiQL, such as setting a type in the argument so that it can be mapped to the relevant entity in GraphiQL allowing clients to know that the argument should really be in the layout of the Entity?

Question: how to perform complex database query fetching?

Hi!
Right now I have a working REST API, described by bunch of DTOs for all occasions. GraphQL seems like an ideal tool to get rid of that zoo. So I am trying to describe a simple schema to test the SPQR library:

type Product {
    id: ID!
    title: String
    offsers: [Offer]
}

type Offer {
    id: ID!
    product: Product
}

It's easy to move this schema to SPQR-powered POJOs and provide services with injected jOOQ-powered repositories, just like I have for now with REST controllers. But there is a problem: how to map GraphQL queries to SQL? Let's say I have a simple service class(I've moved repository code into service for shorthand):

class ProductsService(private val jooq: DSLContext) {
    @GraphQLQuery(name = "product")
    fun findOne(@GraphQLArgument(name = "id") id: Long): Product {
        return jooq
                .selectFrom(PRODUCTS)
                .where(PRODUCTS.ID.eq(id))
                .fetchOne(mapper()) ?: throw RecordNotFoundException("product", id)
    }
}

So the question is, how to know that I should provide a JOIN to offers table here, if query will look like:

product(id: 1) {
    id,
    offers
}

which would be requested as:

jooq.select().from(PRODUCTS).join(OFFERS).on(PRODUCTS.ID.eq(OFFERS.PRODUCT_ID)).fetch { ... }

Parsing Scalar Variables in a Query is Invoking the Serialise Method instead of Parse Literal

I have declared the following method signature:

	@GraphQLQuery(name="findNextGymDayProgram")
	List<GymDayProgram> findNextGymDayProgram(@GraphQLArgument(name = "id") long id,
			@GraphQLArgument(name = "startDate") LocalDate startDate,
			@GraphQLArgument(name = "days") int days);

When using the following variables, for instance:

query deviceService_findNextGymDayProgram($id:Long!,$startDate:LocalDate!,$days:Int!) {
  deviceService_findNextGymDayProgram(id:$id,startDate:$startDate,days:$days) {
    id
  }
}

{"id":"1", "startDate": "2017-01-01","days":30}

The method Scalar Temporal Serialize is being invoked throwing the following exception:

	at io.leangen.graphql.util.Scalars$5.serialize(Scalars.java:190)
	at io.leangen.graphql.util.Scalars$5.parseValue(Scalars.java:195)
	at graphql.execution.ValuesResolver.coerceValueForScalar(ValuesResolver.java:109)
	at graphql.execution.ValuesResolver.coerceValue(ValuesResolver.java:78)
	at graphql.execution.ValuesResolver.coerceValue(ValuesResolver.java:68)
	at graphql.execution.ValuesResolver.getVariableValue(ValuesResolver.java:59)
	at graphql.execution.ValuesResolver.getVariableValues(ValuesResolver.java:16)
	at graphql.execution.ExecutionContextBuilder.build(ExecutionContextBuilder.java:63)
	at graphql.execution.Execution.execute(Execution.java:48)
	at graphql.GraphQL.execute(GraphQL.java:222)

This is a minor issue since it is possible to change methods signature to use String directly instead of Scalars.

ResolutionEnvironment as a hierarchy

Am I right in thinking you can only get the current resolution environment, not the parent or its parents. (And specifically the parent context?)

When you have a schema like:

query {
  datasource(name:wikipedia) {
    search {
      results {
        count
      }
    }
  }
}

It might be that count needs to access the search's and 'datasource' ResolutionEnvironment in order to run its own query.

Currently we are passing this down by returning a SearchNode from search which has the information on, then from results we return a ResultsNode which points back to SearchNode etc. That works but its a bit tedious to create all POJO which hold that type of info.

Is that something worth adding to ResolutionEnvironment.getParentResolutionEnvironment() [potentially null/optional.empty if at the root].

Map deserialization inside objects

As mentioned in gitter:

Exception raised when trying to pass in a Map via a mutation:

mutation isoCountryUpdate {
  isoCountryUpdate(input: {
    clientMutationId: "1234"
      isoCountry: {
          id: 2747
          codeAlpha2: "YY"
          codeAlpha3: "YYY"
          name: "TEST"
          codeNumeric: "66"
          confirmationResponses: {key: "TEST", value: "NO"}
      }
    }) {
    clientMutationId
    result {
      id
      name
    }
  }
}
Caused by: java.lang.IllegalArgumentException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
 at [Source: N/A; line: -1, column: -1] (through reference chain: p4.entities.common.IsoCountry["confirmationResponses"])
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3605) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3546) ~[jackson-databind-2.8.9.jar:2.8.9]
    at io.leangen.graphql.metadata.strategy.value.jackson.JacksonValueMapper.fromInput(JacksonValueMapper.java:33) ~[spqr-0.9.3.jar:na]
    at io.leangen.graphql.metadata.strategy.value.ValueMapper.fromInput(ValueMapper.java:11) ~[spqr-0.9.3.jar:na]
    at io.leangen.graphql.generator.mapping.common.InputValueDeserializer.getArgumentValue(InputValueDeserializer.java:19) ~[spqr-0.9.3.jar:na]
    at io.leangen.graphql.execution.ResolutionEnvironment.getInputValue(ResolutionEnvironment.java:63) ~[spqr-0.9.3.jar:na]
    at io.leangen.graphql.execution.OperationExecutor.execute(OperationExecutor.java:91) ~[spqr-0.9.3.jar:na]
    at io.leangen.graphql.execution.OperationExecutor.execute(OperationExecutor.java:59) ~[spqr-0.9.3.jar:na]
    ... 129 common frames omitted
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
 at [Source: N/A; line: -1, column: -1] (through reference chain: p4.entities.common.IsoCountry["confirmationResponses"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1075) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromEmpty(StdDeserializer.java:892) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:358) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:27) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:504) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:104) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:276) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:140) ~[jackson-databind-2.8.9.jar:2.8.9]
    at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3600) ~[jackson-databind-2.8.9.jar:2.8.9]
    ... 136 common frames omitted

Support for graphql-java v6

graphql-java had a major release that brings a ton of new features and some breaking changes.
To properly support it, grahql-spqr needs to:

  1. support v4 with the current feature-set
  2. enable usage of the new features of v4 from within spqr itself

I'll probably split this issue into sub-issues when I start working on it.

Support Publisher<T> for @GraphQLQuery

It would be great if in query also we can use reactive streams, that give chance for use graphQL in reactive way (for example, for me in spring 5 webflux).

Is there is some way to do this now? or can u please suggest some ideas how can i achieve my goal?

Interface methods not being mapped to GraphQLSchema

I'm using SPQR in a Spring Boot application, so in my case the singleton where the queries are being retrieved from is a Repository (interface), queries in this interface are being mapped perfectly in the GraphQLSchema. Now I'm adding custom implementation queries to my repository (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations). To achieve this my repository has to extends from another interface where I specify my additional custom queries.

Queries defined in this new interface are accessible also from the repository interface since this extends from the newer one.

The problem is that the queries defined in this new interface are not being mapped to the GraphQLSchema. How could I solve this?

Here is my code:

/* This is my repository interface */
@Repository("MyRepository")
public interface MyRepository extends PagingAndSortingRepository<Entity, Integer>, MyRepositoryExtension {
    public Page<Entity> query1(); // This query is being mapped correctly 
    public Page<Entity> query2(); // This query is also being mapped correctly 
}

/* This is my second repository where I have my additional custom queries */
public interface MyRepositoryExtension {
	public List<Entity> customQuery(); // This is not being mapped
}

Nevermind. This issue was for a different project (had too many tabs open)

I tried configuring the plugin like this:

           <plugin>
                <groupId>com.distelli.graphql</groupId>
                <artifactId>graphql-apigen</artifactId>
                <version>1.1.0</version>
                <executions>
                    <execution>
                        <id>generate-grapqhl-interfaces</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>apigen</goal>
                        </goals>
                        <configuration>
                            <sourceDirectory>${project.basedir}/src/main/graphql</sourceDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

but when I execute I get an error that it can't find the parent pom:

[ERROR] Plugin com.distelli.graphql:graphql-apigen:1.1.0 or one of its dependencies could not be resolved: Failed to read artifact descriptor for com.distelli.graphql:graphql-apigen:jar:1.1.0: Could not find artifact com.distelli.graphql:graphql-apigen-pom:pom:1.1.0

For what it's worth, I haven't been able to find it when I've looked for it manually on maven central either

Can't access my superclass fields

Hello,

I have parent class A and child class B.
I have GraphQLQuery that returns List<B>, but I don't have access to A class fields.

I've tried to implement InterfaceMappingStrategy from here #21, but schema generation fails on ObjectsImplementInterfaces rule checking with ObjectDoesNotImplementItsInterfaces error.

What I am doing wrong ?
Is there any opportunity to access A class fields without redefining them in class B ?

Thanks a lot)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.