Git Product home page Git Product logo

graphql-java-annotations's Introduction

logo

GraphQL-Java Annotations

build Maven Central

GraphQL-Java is a great library, but its syntax is a little bit verbose. This library offers an annotations-based syntax for GraphQL schema definition.

If you would like to use a tool that creates a graphql spring boot server using graphql-java-annotations, you can view the graphql-spring-annotations library.

Table Of Contents

Getting Started

(Gradle syntax)

dependencies {
  compile "io.github.graphql-java:graphql-java-annotations:21.2"
}

(Maven syntax)

<dependency>
    <groupId>io.github.graphql-java</groupId>
    <artifactId>graphql-java-annotations</artifactId>
    <version>21.2</version>
</dependency>

The graphql-java-annotations library is able to create GraphQLType objects out of your Java classes. These GraphQLType objects can be later injected into the graphql-java schema.

graphql-java-annotations also allows you to wire your objects with data fetchers and type resolvers while annotating your fields/types. The result of this process will be a GraphQLCodeRegistry.Builder object that can be later built and injected to the graphql-java schema.

GraphQLAnnotations class

You can create an instance of the GraphQLAnnotations class in order to create the GraphQL types.

GraphQLAnnotations graphqlAnnotations = new GraphQLAnnotations();

Using this object, you will be able to create the GraphQL types. There are few types that can be generated - a GraphQLObjectType, a GraphQLInterfaceType and a GraphQLDirective.

GraphQLObjectType query = graphqlAnnotations.object(Query.class);
GraphQLDirective upperDirective = graphqlAnnotations.directive(UpperDirective.class);
GraphQLInterfaceType myInterface = graphqlAnnotations.generateInterface(MyInterface.class); 

Then you can use these types in order to create a graphql-java schema. But, in order to create a graphql-java schema, you need also the GraphQLCodeRegistry, which contains all the data fetchers mapped to their fields (and also type resolvers).

You can obtain the code registry this way:

graphqlAnnotations.getContainer().getCodeRegistryBuilder().build();

Annotations Schema Creator

Using the GraphQLAnnotations processor object can be a little bit confusing if you wish to use it to create a GraphQL schema. So we created a util class to help you create your desired GraphQL schema, in a syntax similiar to the graphql-java syntax.

In order to do so you can use the AnnotationsSchemaCreator.Builder in the following way:

    GraphQLSchema schema = AnnotationsSchemaCreator.newAnnotationsSchema()
        .query(Query.class) // to create you query object
        .mutation(Mutation.class) // to create your mutation object
        .subscription(Subscription.class) // to create your subscription object
        .directive(UpperDirective.class) // to create a directive
        .additionalType(AdditionalType.class) // to create some additional type and add it to the schema
        .typeFunction(CustomType.class) // to add a typefunction
        .setAlwaysPrettify(true) // to set the global prettifier of field names (removes get/set/is prefixes from names)
        .setRelay(customRelay) // to add a custom relay object
        .build();  

Of course you can use this builder with only some of the properties, but the query class must be provided. note - The GraphQLSchema is a graphql-java type.

Continue reading in order to understand how your java classes should look in order to be provided to the annotations schema creator.

Defining Objects

Any regular Java class can be converted to a GraphQL object type. Fields can be defined with a @GraphQLField (see more on fields below) annotation:

public class SomeObject {
  @GraphQLField
  public String field;
}

// ...
GraphQLAnnotations graphQLAnnotations = new GraphQLAnnotations();
GraphQLObjectType object = graphQLAnnotations.object(SomeObject.class);

Defining Interfaces

This is very similar to defining objects, with the addition of type resolver :

@GraphQLTypeResolver(MyTypeResolver.class)
public interface SomeInterface {
  @GraphQLField
  String field();
}

public class MyTypeResolver implements TypeResolver {
  GraphQLObjectType getType(TypeResolutionEnvironment env) { ... }
}

// ...
GraphQLAnnotations graphQLAnnotations = new GraphQLAnnotations();
GraphQLInterfaceType object = graphQLAnnotations.generateInterface(SomeInterface.class);

An instance of the type resolver will be created from the specified class. If a getInstance method is present on the class, it will be used instead of the default constructor.

Defining Unions

To have a union, you must annotate an interface with @GraphQLUnion. In the annotation, you must declare all the possible types of the union, and a type resolver. If no type resolver is specified, UnionTypeResolver is used. It follows this algorithm: The resolver assumes the the DB entity's name is the same as the API entity's name. If so, it takes the result from the dataFetcher and decides to which API entity it should be mapped (according to the name). Example: If you have a Pet union type, and the dataFetcher returns Dog, the typeResolver will check for each API entity if its name is equal to Dog, and returns if it finds something

@GraphQLUnion(possibleTypes={Dog.class, Cat.class})
public interface Pet {}

and an example with custom TypeResovler:

@GraphQLUnion(possibleTypes={DogApi.class, Cat.class}, typeResolver = PetTypeResolver.class)
public interface Pet {}


public class PetTypeResolver implements TypeResolver {
    @Override
    GraphQLObjectType getType(TypeResolutionEnvironment env) {
        Object obj = env.getObject();
        if(obj instanceof DogDB) {
            return (GraphQLObjectType) env.getSchema().getType("DogApi");
        }
        else {
            return (GraphQLObjectType) env.getSchema().getType("Cat");
        }
      
    }
}

NOTE: you can have (but not mandatory) a type resolver with constructor that has Class<?>[] as the first parameter and ProcessingElementsContainer as the second. the Class<?>[] parameter contains the possibleTypes class and ProcessingElementsContainer has all sorts of utils (you can check UnionTypeResolver to see how we use it there)

Fields

In addition to specifying a field over a Java class field, a field can be defined over a method:

public class SomeObject {
  @GraphQLField
  public String field() {
    return "field";
  }
}

Or a method with arguments:

public class SomeObject {
  @GraphQLField
  public String field(String value) {
    return value;
  }
}

Note: You need to use -parameters javac option to compile, which makes argument name as the default GraphQL name. Otherwise, you will need to add the @GraphQLName("value") annotation to specify one.

You can also inject DataFetchingEnvironment as an argument, at any position:

public class SomeObject {
  @GraphQLField
  public String field(DataFetchingEnvironment env, String value) {
    return value;
  }
}

Additionally, @GraphQLName can be used to override field name. You can use @GraphQLDescription to set a description.

These can also be used for field parameters:

public String field(@GraphQLName("val") String value) {
  return value;
}

In addition, @GraphQLDefaultValue can be used to set a default value to a parameter. Due to limitations of annotations, the default value has to be provided by a class that implements Supplier<Object>:

public static class DefaultValue implements Supplier<Object> {
  @Override
  public Object get() {
    return "default";
  }
}

@GraphQLField
public String field(@GraphQLDefaultValue(DefaultValue.class) String value) {
  return value;
}

The DefaultValue class can define a getInstance method that will be called instead of the default constructor.

@GraphQLDeprecate and Java's @Deprecated can be used to specify a deprecated field or method.

Custom data fetcher

You can specify a custom data fetcher for a field with @GraphQLDataFetcher. The annotation will reference a class name, which will be used as data fetcher.

An instance of the data fetcher will be created. The args attribute on the annotation can be used to specify a list of String arguments to pass to the constructor, allowing to reuse the same class on different fields, with different parameter. The firstArgIsTargetName attribute can also be set on @GraphQLDataFetcher to pass the field name as a single parameter of the constructor.

Assuming you are using @GraphQLDataFetcher this way:

@GraphQLField
@GraphQLDataFetcher(value = HelloWorldDataFetcher.class, args = { "arg1", "arg2" })
public String getHelloWorld(){
    return null;
}

Then the class that extends from DataFetcher.class will get this args to two supported constructors
Or to a constructor that expecting String array that's way (String[] args or String... args) or for a constructor that expecting the same number of args that you send with in the annotation.
You get to choose which implementation you want.

public class HelloWorldDataFetcher implements DataFetcher<String> {

    public HelloWorldDataFetcher(String[] args){
        // Do something with your args
    }

    // Note that you need to expect the same number of args as you send with in the annotation args
    public HelloWorldDataFetcher(String arg1, String arg2){
        // Do something with your args
    }

    @Override
    public String get(DataFetchingEnvironment environment) {
        return "something";
    }
}

If no argument is needed and a getInstance method is present, this method will be called instead of the constructor.

Type extensions

Having one single class declaring all fields in a graphQL object type is not always possible, or can lead to huge classes. Modularizing the schema by defining fields in different classes allows you to split it in smaller chunks of codes. In IDL, this is usually written by using the extend keyword on top of a type definition. So you have a type defined like this :

type Human {
    id: ID!
    name: String!
}

It would be possible to extend it later on by using the following syntax :

extend type Human {
    homePlanet: String
}

Defining extensions in annotations

This is possible when using annotations by registering "extensions" classes, corresponding to extend clauses, before creating the objects with the GraphQLAnnotationsProcessor. Extension classes are simple classes, using the same annotations, with an additional @GraphQLTypeExtension on the class itself. The annotation value is required and will be the class that it actually extends.

So the previous schema could be defined by the following classes :

@GraphQLName("Human")
public class Human {
    @GraphQLField
    public String name() { }
}
@GraphQLTypeExtension(Human.class)
public class HumanExtension {
    @GraphQLField
    public String homePlanet() { }
}

Classes marked as "extensions" will actually not define a new type, but rather set new fields on the class it extends when it will be created. All GraphQL annotations can be used on extension classes.

Extensions are registered in GraphQLAnnotations object by using registerTypeExtension. Note that extensions must be registered before the type itself is requested with getObject() :

// Register extensions
graphqlAnnotations.registerTypeExtension(HumanExtension.class);

// Create type
GraphQLObjectType type = processor.getObject(Human.class);

Data fetching with extensions

As opposed to standard annotated classes mapped to GraphQL types, no instance of the extensions are created by default. In DataFetcher, the source object will still be an instance of the extended class. It is however possible to provide a constructor taking the extended class as parameter. This constructor will be used to create an instance of the extension class when a field with the default DataFetcher (without @DataFetcher) will be queried. If no such constructor is provided, the field must either be declared as static or marked as @GraphQLInvokeDetached. Original source object can be found in the DataFetchingEnvironment.

@GraphQLTypeExtension(Human.class)
public class HumanExtension {
    
    public HumanExtension(Human human) {
        this.human = human;   
    }
    
    @GraphQLField
    public String homePlanet() { 
        // get value somehow from human object
    }
}

Type Inference

By default, standard GraphQL types (String, Integer, Long, Float, Boolean, Enum, List) will be inferred from Java types. Also, it will respect @GraphQLNonNull with respect to value's nullability

Stream type is also supported and treated as a list.

If you want to register an additional type (for example, UUID), you have to create a new class implementing TypeFunction for it:

public class UUIDTypeFunction implements TypeFunction {
    ...
}

And register it with GraphQLAnnotations:

graphqlAnnotations.registerType(new UUIDTypeFunction())

// or if not using a static version of GraphQLAnnotations:
// new GraphQLAnnotations().registerType(new UUIDTypeFunction())

You can also specify custom type function for any field with @GraphQLType annotation.

Directives

In GraphQL, you can add directives to your schema. Directive is a way of adding some logic to your schema or changing your schema. For example, we can create a @upper directive, that if we add it to string fields in our schema, they will be transformed to upper cases (its an example, you need to implement it).

Declaring a GraphQLDirective

There are multiple ways to declare a directive in your schema using graphql-java-annotations.

Using a Java Annotation (recommended)

This is the most recommended way of creating a directive, because it is very easy to use later in your schema. In order to declare a directive using a java annotation, you first have to create the java annotation, and annotate it with special annotations.

For example, we wish to create a directive that adds suffix to graphql fields.

@GraphQLName("suffix")
@GraphQLDescription("this directive adds suffix to a string type")
@GraphQLDirectiveDefinition(wiring = SuffixWiring.class)
@DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
@Retention(RetentionPolicy.RUNTIME)
@interface Suffix {
    @GraphQLName("suffixToAdd")
    @GraphQLDescription("the suffix to add to your type")
    boolean suffixToAdd() default true;
}
  • must be annotated with @GraphQLDirectiveDefinition and to supply a wiring class to it (will be explained later)
  • the name of the directive will be taken from the class name (Suffix) or if annotated with @GraphQLName - from its value
  • the description is taken from the @GraphQLDescription annotation
  • must be annotated with @Retention with a RUNTIME policy
  • must be annotated with @DirectiveLocations in order to specify where we can put this directive on (for example - field definition, interface)

You can see that we also defined a sufixToAdd argument for the directive. We can also use @GraphQLName and @GraphQLDescription annotations in there.

In order to define a default value for the argument, use the default keyword like in the example.

After you created the class, you will be able to create the GraphQLDirective object using the following code:

GraphQLDirective directive = graphqlAnnotations.directive(Suffix.class);

Using a method declaration

You can also declare an annotation via a method declaration inside some class. For example, we will create a class of directive declarations:

class DirectiveDeclarations{
    @GraphQLName("upper")
    @GraphQLDescription("upper directive")
    @GraphQLDirectiveDefinition(wiring = UpperWiring.class)
    @DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
    public void upperDirective(@GraphQLName("isActive") @GraphQLDescription("is active") boolean isActive) {
    }
    
    @GraphQLName("suffix")
    @GraphQLDescription("suffix directive")
    @GraphQLDirectiveDefinition(wiring = SuffixWiring.class)
    @DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
    public void suffixDirective(@GraphQLName("suffix") @GraphQLDescription("the suffix") String suffix) {
    }
}
  • The methods has to be annotated with the @GraphQLDirectiveDefinition annotation, and to be supplied with a wiring class
  • The methods has to be annotated with the @DirectiveLocations annotation
  • Can be used: @GraphQLName and @GraphQLDescription - also inside method parameters (that will be transformed into arguments of the directive)

Notice that method params cannot have default values - so the directive arguments will not have default values.

In order to create the directives, you need to write:

Set<GraphQLDirective> set = graphqlAnnotations.directives(DirectiveDeclarations.class);

Using a class declaration

Another way is to declare the directive using a class.

For example:

@GraphQLName("upper")
@GraphQLDescription("upper")
@DirectiveLocations({Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.INTERFACE})
@GraphQLDirectiveDefinition(wiring = UpperWiring.class)
public static class UpperDirective {
    @GraphQLName("isActive")
    private boolean isActive = true;
}

The name of the directive will be taken from the @GraphQLName annotation (if not specified, the name will be the class's name). The description of the directive will be taken from the @GraphQLDescription annotation's value. The valid locations of the directive (locations which the directive can be applied on) will be taken from @DirectiveLocations. The arguments of the directive will be taken from the fields defined in the class - notice that you can only use primitive types as arguments of a directive. For example, we defined an isActive field - which is boolean, and its default value is true. That's how the argument of the directive will be defined. You can also use @GraphQLName and @GraphQLDescription annotations on the field.

After you created the class, you will be able to create the GraphQLDirective object using the following code:

GraphQLDirective directive = graphqlAnnotations.directive(UpperDirective.class);

Wiring with the directives

In order to define the wiring logic (what will be executed on top of the graphql type annotated with the directive) we have to create wiring class.

In order to define a wiring functionality, you have to create a Wiring class matching one of your directives. For example:

public class UpperWiring implements AnnotationsDirectiveWiring {
        @Override
        public GraphQLFieldDefinition onField(AnnotationsWiringEnvironment environment) {
            GraphQLFieldDefinition field = (GraphQLFieldDefinition) environment.getElement();
            boolean isActive = (boolean) environment.getDirective().getArgument("isActive").getValue();
            CodeRegistryUtil.wrapDataFetcher(field, environment, (((dataFetchingEnvironment, value) -> {
                if (value instanceof String && isActive) {
                    return ((String) value).toUpperCase();
                }
                return value; 
            })));            
            return field;
        }
    }

In this example we wrap the data fetcher of the field in order to make the resolved value upper case.

You can also use the field.transform method in order to change some of the field's properties.

This class turns your string field to upper case if the directive argument "isActive" is set to true.

Put this class inside the @GraphQLDirectiveDefinition(wiring = UpperWiring.class) annotation where you declare your directive (see directive declaration section above).

Using the directives

There are 2 ways of using the directives in your graphql types.

Using the directive java annotation (RECOMMENDED)

This way only works if you declared your directive as a java annotation. In the example above, we created the @Suffix annotation as a directive. So now we can put it on top of our graphql field.

For example:

@GraphQLField
@Suffix(suffixToAdd = " is cool")
public String name(){
    return "yarin";
}

Now every time the field will be executed, the suffix " is cool" will be added to it. You can also use directive on field arguments, interfaces, etc.

Using @GraphQLDirectives annotation

This way works in the 3 methods of declaring directives, but is less recommended because its more complicated and not so nice. You can annotate your graphql field with the @GraphQLDirectives annotation and supply it with the directives to use and the arguments values you want to supply.

For example:

@GraphQLField
@GraphQLDirectives(@Directive(name = "upperCase", argumentsValues = {"true"}))
public String name() {
    return "yarin";
}

We now wired the field "name" - so it will turn upper case when calling the field. The Directive annotations requires the name of the directive, the wiring class (the UpperWiring class defined earlier), and the values of the arguments. If an argument has a default value, you don't have to supply a value in the arguments values.

Notice that in any way, the directives are sequential, so the first annotated directive will happen before the second one. If put both java annotation directive and @GraphQLDirectives annotation directives, the java annotation directive will be applied first.

Relay support

Mutations

You can use @GraphQLRelayMutation annotation to make mutation adhere to Relay specification for mutations

Connection

You can use @GraphQLConnection annotation to make a field iterable in adherence to Relay Connection specification.
If a field is annotated with the annotation, the associated dataFetcher must return an instance of PaginatedData.
The PaginatedData class holds the result of the connection:

  1. The data of the page
  2. Whether or not there is a next page and a previous page
  3. A method that returns for each entity the encoded cursor of the entity (it returns string)

For you convenience, there is AbstractPaginatedData that can be extended.

If you want to use you own implementation of connection, that's fine, just give a value to connection().
Please note that if you do so, you also have to specify your own connection validator that implements ConnectionValidator
(and should throw @GraphQLConnectionException if something is wrong)

NOTE: because PropertyDataFetcher and FieldDataFetcher can't handle connection, this annotation cant be used on a field that doesn't have a dataFetcher

Customizing Relay schema

By default, GraphQLAnnotations will use the graphql.relay.Relay class to create the Relay specific schema types (Mutations, Connections, Edges, PageInfo, ...). It is possible to set a custom implementation of the Relay class with graphqlAnnotations.setRelay method. The class should inherit from graphql.relay.Relay and can redefine methods that create Relay types.

It is also possible to specify for every connection which relay do you want to use, by giving a value to the annotation: @GraphQLConnection(connectionType = customRelay.class). If you do that, please also give values to connectionFetcher and validator.

There is also a support for simple paging, without "Nodes" and "Edges". To use it, annotate you connection like that: @GraphQLConnection(connectionFetcher = SimplePaginatedDataConnectionFetcher.class, connectionType = SimpleRelay.class, validator = SimplePaginatedDataConnectionTypeValidator.class) and the return type must be of type SimplePaginatedData. It has 2 methods:

  1. getTotalCount - how many elements are there in total
  2. getData - get the data

For you convenience, there are two classes that you can use: AbstractSimplePaginatedData and SimplePaginatedDataImpl For examples, look at the tests

graphql-java-annotations's People

Contributors

alexkrebiehlkr avatar anatol-sialitski avatar apottere avatar asinbow avatar azure-pipelines[bot] avatar bbakerman avatar ceronman avatar chenshoo avatar cvolzke avatar danieltaub96 avatar eugenmayer avatar geichelberger avatar guy120494 avatar itaykl avatar javiergamarra avatar jplock avatar manikandan-ravikumar avatar nirpet avatar nmn avatar osher-sade avatar richharms avatar sebasjm avatar sergehuber avatar setchy avatar tkfftk avatar tklovett avatar vinayknl avatar vincent-pang avatar yarinvak avatar yrashk 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

graphql-java-annotations's Issues

How to get JSON from ExecutionResult?

Hi All,

Thank you for this project. It looks promising. I am trying to launch an exemplary spring boot jpa application and add graphql-java-annotations to it. If someone knows how to connect them in a simple way, I would be grateful for an example.

Meanwhile, when I was tinkering with the project I noticed that ExecutionResult returns something like:
{defaultUser={name=Test Name}}

It is taken from your tests. Could you tell me why it is not like JSON? What I should do to receive JSON as a result?

Too tight coupled interfaces

I started to use your library, and it's really interesting. But I got a problem : I need to provide loose coupled interfaces. My domain model is provided as set of interfaces, with different implementations in different services. So domain interfaces can't be aware of their implementation.
Or is it actually impossible to generate interfaces without a GraphQLTypeResolver interface, and so a known TypeResolver. So with the actual architecture, it's impossible to generate loose coupled interfaces.

A solution could be to use ServiceLoader system, so TypeResolver could be provided by the implementation jar. Actually , the default comportement for GraphQLAnnotations#getInterface if ther's no GraphQLTypeResolver on the interface is to fallback to an object (there's an error in the javadoc by the way). Fallback to a TypeResolvers class which use ServiceLoader solve the tight coupling problem. But it's breaking change... Another solution is to handle the absence of the annotation only in getIfaceBuilder, at the end the method. It does not break the contract, but I think it's a bit strange, as it's contradictory with the comportement of getInterface.

If one of the solution looks good for you, I'll submit a PR. If not, I'll use my own fork for my project...

New version release?

Hi, first I wanna thank you all for the library, it's quite cool and makes working with GraphQL a way better experience.
I've noticed that though there was quite a decent amount of activity and commits during the last months, the last version is from Nov 2016. Is there any reason you didn't decide to release a new version just yet?
I am particularly interested in latest changes that add args to the DataFetcher annotation. And though I could potentially use latest commits in order to gain that support, given our current configuration could be kind of a hassle (and maybe even impossible to not be attached to a release - which kind of makes sense sometimes - )

Thanks!

Using an object as argument causes IllegalArgumentException

When using an object as field argument, an IllegalArgumentException will be thrown at execution.

Example:

public class GraphQLTest {

  @GraphQLName("TestObjectInputArgument")
  public static class TestObjectInputArgument {
    @GraphQLField public String a;
    @GraphQLField public int b;
  }

  public static class TestObjectInput {
    @GraphQLField
    public TestObjectInputArgument test(@GraphQLName("args") TestObjectInputArgument args) {
      return args;
    }
  }

  public static void main(String[] args) throws Exception {
    GraphQLSchema schema =
        GraphQLSchema.newSchema().query(GraphQLAnnotations.object(TestObjectInput.class)).build();
    GraphQL graphql2 = new GraphQL(schema);
    ExecutionResult result2 =
        graphql2.execute("{ test(args:{ a:\"x\", b:1 }) { a } }", new TestObjectInput());
  }
}

The reason is that MethodDataFetcher will send a HashMap instead of a TestObjectInputArgument as the first argument of test.

One workaround for this to use @GraphQLDataFetcher and provide a custom data fetcher. However, I think that to make the API more terse, MethodDataFetcher should be able to handle this. One way to do it is to have a convention of making input object provide a constructor that could take a HashMap as argument and it's able to create the proper argument for the method.

If you think this is a good idea I could provide a PR for this.

Method data fetcher swallowing exceptions

Hi yrashk,

I'm using your MethodDataFetcher to get my data from a service. My service throws exceptions on failures, but the MethodDataFetcher is consuming anything I throw. The result is I have no data, but executionResult.getErrors() is empty.

Is there a reason that the exceptions are not rethrown?

Thanks,
Alex

Schema field order is non-deterministic

Hi there,

First, thanks for this wonderful library!

I noticed this while generating schema dumps for Relay. The ordering of methods and fields returned by Class.getMethods and Class.getFields is undefined, which ends up causing GraphQL Annotations to generate schemas with arbitrary field orderings.

This unfortunately ends up making schema dumps look like they were modified after each execution. I was wondering if perhaps it would be preferable to sort the methods by name / signature prior to generating graphql fields so that the ordering remains stable. Thoughts?

Consider removing Lombok from implementation

Could this project consider NOT use Lombok in the implementation of the library

Good libraries tend to have a limited set of dependencies.

Lombok is one of the most contentious dependencies out there because of its "magic" nature.

We have an informal ban on it inside my area in Atlassian because of debugging problems at runtime and the byte code weaving side of it at compile time.

I would ask that this otherwise excellent library not use Lombok in its implementation. I think this will increase its uptake, not just in my company but with others.

I am in the process of creating a PR to see what the impact of removing Lombok would be.

GraphQL ID support ?

It might be a dumb question but it is not clear to me whether GraphQLID is supported or not.
I see it defined in graphql-java here I'm not sure how it use since I use only annotations ;-)

Does it make sense to have a GraphQLID annotation ? Something that I could use it like:

public class LogBatch {

	private final String id;

	public LogBatch(final String id) {
		this.id = id;
	}

	@GraphQLID
	public String getId() {
		return this.id;
	}

}

Add support for batched data fetchers

When using a BatchedExecutionStrategy, graphql-java requires the get method of the data fetcher to be decorated with the @Batched annotation.

Currently the only way of using batched fetchers with graphql-java-annotations is to use a custom @GraphQLDataFetcher. I think this can be solved by adding a new annotation called GraphQLBatched that can be added to methods and then a special subclass of MethodDataFetcher should internally use the @Batched annotation.

If you agree with this approach I can provide a PR.

Recursive types not resolved

I'm trying to have a model return a field of the same type. Ex:

public class Model {

    @GraphQLField
    public List<Model> similarModels() {
        return ...
    }

}

When I inspect the resulting schema, I see that the similarModels() field is left a reference type and is never resolved to a model. When I try to make a query on this field, I get the following error:

graphql.schema.GraphQLTypeReference cannot be cast to graphql.schema.GraphQLUnmodifiedType

I have tried passing in types that I reference to schema.build() without success. At what point should this reference type be resolved to a concrete GraphQLObjectType?

Thanks,
Alex

Specify possibleTypes on an interface

Hi, I'm wondering how to set the possible types that implement an interface. Similar to @GraphQLUnion(possibleTypes = TestObject1.class) on Union Types.

Trying to run an inline fragment query to fetch implementation specific fields, but getting an error like this: "Fragment cannot be spread here as objects of type GraphQLInterfaceType can never be of type GraphQLObjectType"

The query doesn't pass validation, probably because the interface definition has empty "possibleTypes": []

Introspection query result snippet:

    {
      "kind": "INTERFACE",
      "name": "Character",
      "description": null,
      "fields": [.........],
      "inputFields": null,
      "interfaces": null,
      "enumValues": null,
      "possibleTypes": []
    }

I must be missing something.... if anyone has gotten this to work, please let me know!

Any support for Map

Currently, the schema supports int String... Any plan for the support of Map?

@GraphQLConnection vs @GraphQLDataFetcher

I have a field which is a List of chicks. This last is annotated with @GraphQLConnection.java:

@GraphQLConnection
@GraphQLDataFetcher(CatDataFetcher.class)
@GraphQLField
public List<Cat> cats;

My usecase is to fetch available cats from a database. For this purpose, I have created a CatDataFetcher class that builds and executes a query against my database. It is working but this basic approach fetch all cats in memory before filtering them based on criteria passed with the connection (e.g. first=10, last=3, etc.).

Since I have a lot of entries, I would prefer to apply pagination at the database level directly. By default, filtering for a Connection is performed by SimpleListConnection after the execution of a GraphQLDataFetcher. I have seen it is possible to specify a DispatchingConnection with the @GraphQLConnection annotation.

What would be the recommended way to proceed with my use case? should I move the code that builds the query and execute it in a dedicated DispatchingConnection and simply remove the GraphQLDataFetcher?

Should @GraphQLDataFetcher never be used when @GraphQLConnection is already set on a field?

In the case I use a custom DispatchingConnection with the @GraphQLConnection annotation, can I assume that additional arguments that will be defined on the field will be accessible from my custom DispatchingConnection (once #33 is fixed)?

Couldn't get private field in object

Hello, I try to use graphql-java-annotations in my project.
But @GraphQLField does not work in private field.
Do you have a plan support this feature?
My graphql-java-annotations's version is 0.11.0

Execution of the object doesn't run the method

I'm trying to port over some code written in vanilla graphql-java to start using annotations.

I've got code that looks like this:

    @GraphQLName("VersionType")
    @GraphQLDescription("VersionType")
    private static class Version {
        @GraphQLField
        public @NotNull String version() {
            return "1.0.0";
        }
    }

    @SneakyThrows
    private static GraphQLObjectType getVersionType() {
        return GraphQLAnnotations.object(Version.class);
    }

    private static GraphQLObjectType queryType = newObject()
        .name("Query")
        .field(newFieldDefinition()
            .name("Version")
            .type(GraphQLString)
            .staticValue("1.0.0")
            .build()
        )
        ...
        .build()


    protected static GraphQLSchema schema = newSchema()
        .query(queryType)
        .mutation(mutationType)
        .build();

    public static GraphQL graphQL = new GraphQL(schema);

Now, the object builds correctly and the schema for the object type shows up. But when I try to do any query, I always get null back. The String version() method isn't even called.

I'm using the latest version of GraphQL-java (2.0.0) and everything is working fine in the other parts of the scheme written without annotations.

Thanks for the help in advance.

Query with fragment fails

A graphql query which uses fragments fails:
Example: ({items { ... on MyObject {a, my {b}} ... on MyObject2 {a, b} }}

The return type of getItems is an interface.
MyObject and MyObject2 are two different implementations of that interface.

I've created two demo classes (plain, annotations) which demonstrates the problem:
https://github.com/mattesja/graphql-helloworld

HelloWorldPlain.java shows how the fragment query works in plain graphql-java.
HelloWorldAnnotation.java shows how the fragment query fails with graphql-java-annotations.

Hints

  • I discovered, that the GraphQLInterfaceType objects are instantiated multiple times. I guess this is the cause, that makes PossibleFragmentSpreads.doTypesOverlap() fail.
  • The GraphQLInterfaceType is not found in the schema dictionary:
    Class: PossibleFragmentSpreads.doTypesOverlap()
    Line: new SchemaUtil().findImplementations(getValidationContext().getSchema(), (GraphQLInterfaceType) parent);

The underlying TypeFunction is not available to registered extension TypeFunctions

Lets say I want to write an extension "container" TypeFunction

Imagine its like the build in List one where it detects the "outer type" and then wants to recurse into the inner types and run them back thought the default type processor again.

For example this is the current List type function

        @Override
    public GraphQLType apply(Class<?> aClass, AnnotatedType annotatedType) {
        if (!(annotatedType instanceof AnnotatedParameterizedType)) {
            throw new IllegalArgumentException("List type parameter should be specified");
        }
        AnnotatedParameterizedType parameterizedType = (AnnotatedParameterizedType) annotatedType;
        AnnotatedType arg = parameterizedType.getAnnotatedActualTypeArguments()[0];
        Class<?> klass;
        if (arg.getType() instanceof ParameterizedType) {
            klass = (Class<?>) ((ParameterizedType) (arg.getType())).getRawType();
        } else {
            klass = (Class<?>) arg.getType();
        }
        return new GraphQLList(DefaultTypeFunction.this.apply(klass, arg));
    }

Notice how after unwrapping the "container" it can call back into sub types via

DefaultTypeFunction.this.apply(klass, arg)

How if you write your own TypeFunction you cant get a hold of the default TypeFunction

Its defined as

    protected TypeFunction defaultTypeFunction;

So you can make a sub class and hence extract it out but I think it should be public and hence available for re-use for "container" types.

Cannot build graphql-java-annotations with graphql-java master branch

When I build annotations with graphql-java master branch, the following error occur.

GraphQLAnnotations.java:82: error: incompatible types: invalid method reference
        }).forEach(builder::possibleType);
                   ^
    incompatible types: GraphQLType cannot be converted to GraphQLObjectType

I believe that it is related to this change in graphql-java
graphql-java/graphql-java#129
The input parameter of function GraphQLUnionType -> Builder -> possibleType is changed.

-        public Builder possibleType(GraphQLType type) {
+        public Builder possibleType(GraphQLObjectType type) {

Updating graphql-java

Is there a reason why graphql-java is still using version 2.2.0 ?
Why not using 2.3.0 ?

Support for input type having a list of complex input type ?

Hi,

I would like to know if the annotations supports introspecting a List of complex input type.

Here's (simplified) example of what I'm trying to achieve which leads to a Introspection must provide input type for arguments error in chrome GraphiQL client .

I wonder if I'm doing something wrong or if there is no support of list of complex input types through annotations.

The SpaceInstanceInput below is what I'm passing as a parameter of this mutation

@GraphQLField
public MutationResult updateSpaceInstance(
	  @GraphQLName("spaceInstanceInput") final SpaceInstanceInput spaceInstanceInput) {

where SpaceInstanceInput data structure looks like:

public class SpaceInstanceInput {

    private final List<LogSubscriptionInput> logSubscriptions = null;
    private LogSubscriptionInput firstLogSubscriptions;

    // WORKS FINE
    @GraphQLField
    public LogSubscriptionInput getFirstLogSubscription() {
        return this.firstLogSubscriptions;
    }

    // DOES NOT WORK
    @GraphQLField
    public List<LogSubscriptionInput> getLogSubscriptions() {
        return this.logSubscriptions;
    }

}

public class LogSubscriptionInput {

    private LogTypeInput logTypeInput;
    private String subscriptionId;

    @GraphQLField
    public LogTypeInput getLogTypeInput() {
        return this.logTypeInput;
    }

    @GraphQLField
    public String getSubscriptionId() {
        return this.subscriptionId;
    }
}

public class LogTypeInput {

    private final String format;
    private final String type;

    @GraphQLField
    public String getFormat() {
        return this.format;
    }

    @GraphQLField
    public String getType() {
        return this.type;
    }
}

GraphQLConnection not preserved on both sides of mutually recursive associations

I have a simple schema with two object types: Task, and User. A Task references a list of Users and a User references a list of Tasks. Both these associations are marked with @GraphQLConnection. When the schema is built at runtime, only one of these ends up as a connection, and the other ends up as a GraphQLList.

When I construct the schema manually (not using annotations) with GraphQLTypeReference as both edge types, the result is correct.

I've attached a simple test case and my schema code as an example.

Archive.zip

Stateful data fetchers

I'm a little bit confused about how you're meant to implement a stateful data fetcher. For example, suppose I declare a data fetcher method that requires interrogating a Spring JPA repository as part of its implementation. How would I go about fetching the repository, apart from exposing it in a public global accessor? Is it possible to "wire in" non GraphQL parameters on the data fetcher method that I can specify at schema creation time?

For example:

...
  @GraphQLField
  public String field(DataFetchingEnvironment env, MyJpaRepository repo, String value) {
    return repo.getByField(value);
  }
...

Support for Iterable versus List

if I have a bean with

@GraphQlField
public List<Foo> getFoos() ...

it works as expected and creates GraphqlList(Foo)

But what about anything Iterable

@GraphQlField
public Collection<Foo> getFoos() ...

@GraphQlField
public Set<Foo> getFoos() ...

@GraphQlField
public Iterable<Foo> getFoos() ...

They can ALL be mapped to GraphqlList no? Am I missing something?

Currently its defined as

        @Override public Collection<Class<?>> getAcceptedTypes() {
        return Arrays.asList(List.class, AbstractList.class);
    }

But I think anything that can be iterated can be represented by a List in graphql no?

Extract EnhancedExecutionStrategy into separate module (or project)

EnhancedExecutionStrategy (really Relay.js execution strategy) doesn't really seem to belong in the same module as the rest of the annotation processing, since it can be used separately.

I'd like to include it as a dependency in graphql-java-servlet without requiring the use of annotations - would it be possible to create a new module or project (graphql-java-executioncontexts? :P ) for it that deploys a separate jarfile?

Specifying a data fetcher for a mutation does not get called during execution

Hello,

Thanks for your great work! Using this library has definitely made working with GraphQL in Java a pleasure.

I have the following mutation:

    @GraphQLField
    @GraphQLName("createPart")
    @GraphQLRelayMutation
    @GraphQLDataFetcher(CreatePartDataFetcher.class)
    public static CreatePartPayload createPart(@GraphQLName("name")
                                               @GraphQLNonNull String name,
                                               @GraphQLName("description")
                                               @GraphQLNonNull String description) {
        return null;
    }

I have the following data fetcher:

@Service
@Log4j2
public class CreatePartDataFetcher extends BaseDataFetcher {
    @Override
    public CreatePartPayload get(final DataFetchingEnvironment environment) {
        log.debug("Creating part");
        final Object input = environment.getArgument("input");
        final Part part = mapper.map(input, Part.class);
        final PartService partService = applicationContext.getBean(PartService.class);
        partService.save(part);
        return new CreatePartPayload(mapper.map(part, PartType.class));
    }
}

The data fetcher is not called....

I have used the same implementation for queries and the data fetcher is successfully called. What am I doing wrong?

EDIT: It would be nice to have an annotation called @GraphQLDataIngester for this purpose.
EDIT: Looking at this code it appears it is not possible to specify a custom data fetcher. :( I use spring, so I can't inject a service into the type as normally would. I have to use this data fetcher work around to get access to the spring environment:

public abstract class BaseDataFetcher implements DataFetcher, ApplicationContextAware {

    protected static Mapper mapper = new DozerBeanMapper();
    protected static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        applicationContext = context;
    }
}

Support of inherited interfaces

Only direct implemented interfaces are supported when building a GraphQLObjectType.

class MyObject implements MyInterface {} => OK

class MyObject2 extends MyObject {} => No interface in ObjectType

ClassUtils.getAllInterfaces(object) should be used instead of object.getInterfaces()

Would you accept such a pull request adding such a dependency on apache-commons-lang?

Method data fetcher on root query

I am trying to leverage your package to make a schema builder. To do this, I scan my package for a certain class annotation, and construct a GraphQLObjectType from any of those classes found.

I'm having a problem with the MethodDataFetcher on methods using this approach (the GraphQLDataFetcher annotation with a separate data fetching class works). When the MethodDataFetcher tries to resolve the field, environment.getSource() is null and the fetcher returns null. This only appears to be a problem for fields defined on the QueryRoot.

A root fields node may be defined like this:

class ModelRootQueries {
  public Model modelById(@GraphQLName("id") String id) {
    // never invoked
    return ModelService.get(id);
  }
}

I am constructing my QueryRoot like so:

GraphQLObjectType node = GraphQLAnnotations.newObject(ModelRootQueries.class);
root.fields(node.getFieldDefinitions();
...
GraphQLSchema schema = newSchema.query(root).build();

In this case, the body of the method is never called, and data ends up being null.

I'd appreciate some insight into whether I'm wiring it incorrectly, or if there's a problem with the MethodDataFetcher's implementation.

Thanks,
Alex

Unions?

How would you define a Union type?

Arguments for a field annotated with @GraphQLConnection

I have a query type with a field named jobs:

@GraphQLField
@GraphQLConnection
public List<Job> jobs(JobInput jobInput) {
    ....
}

This last field defines an InputType in order to pass some arguments.

@GraphQLType
@Getter
public class JobInput {

    @GraphQLName("id")
    private long id;

    @GraphQLName("test")
    private String name;

}

Unfortunately, when settings the JobInput type, any GraphQL query that is submitted leads to a NoSuchElementException in MethodDataFetcher#invocationArgs. The problem seems due to the environment that no longer contains the JobInput argument that is defined with the field. This argument is removed from the environment when GraphQLAnnotations.ConnectionDataFetcher#get is invoked. There is even an explicit comment saying Exclude arguments. I am not sure to understand if this removal is a bug (i.e. only arguments related to relay pagination should be removed) or a voluntary action.

Is there a standard way to support arguments with a GraphQLConnection?

Random "graphql.schema.GraphQLTypeReference cannot be cast to graphql.schema.GraphQLUnmodifiedType"

Hello.

I'm not completely sure if this is an issue of graphql-java-annotations or graphql-java. I am cross posting the issue just in case. I apologize if this causes inconveniences.

I'm having the error described in the title randomly with this simple recursive schema:

public class Building {
        private String name;

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

        @GraphQLField
        @GraphQLName("name")
        public String getName() {
            return name;
        }

        @GraphQLField
        @GraphQLName("apartments")
        public List<Apartment> getApartments() {
            List<Apartment> apartments = new ArrayList();
            apartments.add(new Apartment(this, "one", 10));
            apartments.add(new Apartment(this, "two", 20));
            apartments.add(new Apartment(this, "three", 30));
            return apartments;
        }
}

public class Apartment {
    private String name;
    private Integer size;
    private Building building;

    public Apartment(Building building, String name, Integer size) {
        this.building = building;
        this.name = name;
        this.size = size;
        this.building = building;
    }

    @GraphQLField
    @GraphQLName("building")
    public Building getBuilding() {
        return building;
    }

    @GraphQLField
    @GraphQLName("size")
    public Integer getSize() {
        return size;
    }

    @GraphQLField
    @GraphQLName("name")
    public String getName() {
        return name;
    }
}

public class Query {
    @GraphQLField
    public static Building building() {
        return new Building("Building");
    }

    @GraphQLField
    public static Apartment apartment() {
        return new Apartment(new Building("New building"), "New apartment", 20);
    }
}

I'm running it in this way:

public static void main(String[] args) throws Exception {
        GraphQLObjectType annotationsType = GraphQLAnnotations.object(Query.class);
        GraphQLSchema schema = GraphQLSchema.newSchema()
                .query(annotationsType)
                .build();
        Object result = new GraphQL(schema).execute("{ building { apartments { name, building { name } } } }").getData();

        System.out.println(result);
    }

The problem I have is that the test here runs okay 9 out of 10 times aprox. However, every once in a while, I get the following exception:

Exception in thread "main" java.lang.ClassCastException: graphql.schema.GraphQLTypeReference cannot be cast to graphql.schema.GraphQLUnmodifiedType
    at graphql.schema.SchemaUtil.getUnmodifiedType(SchemaUtil.java:30)
    at graphql.schema.SchemaUtil.isLeafType(SchemaUtil.java:12)
    at graphql.validation.rules.ScalarLeafs.checkField(ScalarLeafs.java:21)
    at graphql.validation.RulesVisitor.checkField(RulesVisitor.java:92)
    at graphql.validation.RulesVisitor.enter(RulesVisitor.java:51)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:19)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverseImpl(LanguageTraversal.java:22)
    at graphql.validation.LanguageTraversal.traverse(LanguageTraversal.java:14)
    at graphql.validation.Validator.validateDocument(Validator.java:20)
    at graphql.GraphQL.execute(GraphQL.java:73)
    at graphql.GraphQL.execute(GraphQL.java:55)
    at graphql.GraphQL.execute(GraphQL.java:47)
    at graphql.GraphQL.execute(GraphQL.java:43)
    at GraphQLTest.main(GraphQLTest.java:63)

I believe this has to do with the order in which graphql-java-annotations processes the annotations of the Query class. When I print schema.getAllTypesAsList() I noticed that every time the error happens, the Apartment type appears before the Building type.

I am using the following versions:

        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java</artifactId>
            <version>2016-08-21T01-09-05</version>
        </dependency>
        <dependency>
            <groupId>com.graphql-java</groupId>
            <artifactId>graphql-java-annotations</artifactId>
            <version>0.9.0</version>
        </dependency>

Sorry for not having a concrete test case, but as I said before, this is something that happens randomly. Please let me know if you need more details.

Using observables with method data fetchers

Is it possible to use observables/futures/etc with method data fetchers? I have an execution strategy that supports observables, but I'm having trouble wiring it up when I'm returning data from a method.

For example: I have a service that returns an Observable<Model>. I can wire it up using a field builder like so:

newFieldDefinition().
    .type(Model)
    .dataFetcher(env -> service.getModelById(1234)) // returns an Observable<Model>
    .build()

However, if I want to define my field with a MethodDataFetcher, I don't see a clean way to define my return type as Model, but really return an Observable<Model>:

// Doesn't work... doesn't know what to do with a return type of observable...?
@GraphQLField
public Observable<Model> getModel(int id) {
    return service.getModelById(1234);
}
// Doesn't work... incompatible return types
@GraphQLField
public Model getModel(int id) {
    return service.getModelById(1234);
}

Is this something I can add with a TypeResolver? (i.e., if the return type is Observable<T>, then the return type should map to T). Or does it have to be baked into the library? As always, thanks for your time.

Alex

Jdk version

This lib is awesome, make GraphQL less verbose to use in Java, thanks!

But it looks like that this lib depends on jdk 1.8, is there any particular reason for this? our production code is still using jdk1.6, but want to use this lib.

The GraphQLAnnotations methods throw unecessary exceptions

The reflection exceptions are re-thrown in the API to create Graph objects

    try {
        GraphQLObjectType IssueFieldQL =  GraphQLAnnotations.object(IssueField.class);
    } catch (IllegalAccessException | NoSuchMethodException | InstantiationException e) {
        throw new RuntimeException(e);
    }

This clutters the usage of the annotations code for no great gain. Even your own tests use the @SneakyThrows to get around the annoying nature of this signature.

I suggest it would better to have GraphQLAnnotationsReflectionException as a runtime exception in the case where consumers provide a bad class

Need for an example

Hi,
This is not really an issue but a request.
Could be possible to have an example of a complete schema using graphql-java-annotations?
I have tried to look for examples but I haven't found anything I could be based on.

Thanks in advance.

Ane

Some of test cases cannot pass in my project

"C:\Program Files\Java\jdk1.8.0_25\bin\java" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:49887,suspend=y,server=n -ea -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 2016.3.1\lib\idea_rt.jar;C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 2016.3.1\plugins\testng\lib\testng-plugin.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_25\jre\lib\rt.jar;C:\shishuwu\Saturn\jason\graphql\graphqljavaannotationsample\target\test-classes;C:\shishuwu\Saturn\jason\graphql\graphqljavaannotationsample\target\classes;C:\Users\shishu.AUTH.m2\repository\com\graphql-java\graphql-java-annotations\0.13.2\graphql-java-annotations-0.13.2.jar;C:\Users\shishu.AUTH.m2\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;C:\Users\shishu.AUTH.m2\repository\com\graphql-java\graphql-java\2.2.0\graphql-java-2.2.0.jar;C:\Users\shishu.AUTH.m2\repository\org\antlr\antlr4-runtime\4.5.1\antlr4-runtime-4.5.1.jar;C:\Users\shishu.AUTH.m2\repository\org\slf4j\slf4j-api\1.7.12\slf4j-api-1.7.12.jar;C:\Users\shishu.AUTH.m2\repository\org\antlr\antlr4\4.5.1\antlr4-4.5.1.jar;C:\Users\shishu.AUTH.m2\repository\org\testng\testng\6.9.10\testng-6.9.10.jar;C:\Users\shishu.AUTH.m2\repository\com\beust\jcommander\1.48\jcommander-1.48.jar;C:\Users\shishu.AUTH.m2\repository\org\beanshell\bsh\2.0b4\bsh-2.0b4.jar;C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 2016.3.1\plugins\testng\lib\jcommander.jar" org.testng.RemoteTestNGStarter -usedefaultlisteners false -socket49886 @w@C:\Users\shishu.AUTH\AppData\Local\Temp\idea_working_dirs_testng.tmp -temp C:\Users\shishu.AUTH\AppData\Local\Temp\idea_testng.tmp
Connected to the target VM, address: '127.0.0.1:49887', transport: 'socket'
[TestNG] Running:
C:\Users\shishu.AUTH.IdeaIC2016.3\system\temp-testng-customsuite.xml
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

java.lang.AssertionError: expected [true] but found [false]
Expected :true
Actual :false

at org.testng.Assert.fail(Assert.java:94)
at org.testng.Assert.failNotEquals(Assert.java:513)
at org.testng.Assert.assertTrue(Assert.java:42)
at org.testng.Assert.assertTrue(Assert.java:52)
at graphql.annotations.GraphQLObjectTest.defaultArg(GraphQLObjectTest.java:474)
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:483)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:86)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:643)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:820)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1128)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:782)
at org.testng.TestRunner.run(TestRunner.java:632)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
at org.testng.SuiteRunner.run(SuiteRunner.java:268)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1244)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
at org.testng.TestNG.run(TestNG.java:1064)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)

java.lang.AssertionError: expected [a] but found [arg0]
Expected :a
Actual :arg0

at org.testng.Assert.fail(Assert.java:94)
at org.testng.Assert.failNotEquals(Assert.java:513)
at org.testng.Assert.assertEqualsImpl(Assert.java:135)
at org.testng.Assert.assertEquals(Assert.java:116)
at org.testng.Assert.assertEquals(Assert.java:190)
at org.testng.Assert.assertEquals(Assert.java:200)
at graphql.annotations.GraphQLObjectTest.fields(GraphQLObjectTest.java:189)
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:483)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:86)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:643)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:820)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1128)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:782)
at org.testng.TestRunner.run(TestRunner.java:632)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
at org.testng.SuiteRunner.run(SuiteRunner.java:268)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1244)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
at org.testng.TestNG.run(TestNG.java:1064)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)

java.lang.NullPointerException
at graphql.annotations.GraphQLObjectTest.inputObjectArgument(GraphQLObjectTest.java:571)
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:483)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:86)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:643)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:820)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1128)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:782)
at org.testng.TestRunner.run(TestRunner.java:632)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
at org.testng.SuiteRunner.run(SuiteRunner.java:268)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1244)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
at org.testng.TestNG.run(TestNG.java:1064)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)

java.lang.AssertionError: expected [true] but found [false]
Expected :true
Actual :false

at org.testng.Assert.fail(Assert.java:94)
at org.testng.Assert.failNotEquals(Assert.java:513)
at org.testng.Assert.assertTrue(Assert.java:42)
at org.testng.Assert.assertTrue(Assert.java:52)
at graphql.annotations.GraphQLObjectTest.query(GraphQLObjectTest.java:436)
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:483)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:86)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:643)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:820)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1128)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:782)
at org.testng.TestRunner.run(TestRunner.java:632)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
at org.testng.SuiteRunner.run(SuiteRunner.java:268)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1244)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
at org.testng.TestNG.run(TestNG.java:1064)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)

java.lang.AssertionError: expected object to not be null

at org.testng.Assert.fail(Assert.java:94)
at org.testng.Assert.assertNotNull(Assert.java:423)
at org.testng.Assert.assertNotNull(Assert.java:408)
at graphql.annotations.RelayTest.argMutation(RelayTest.java:174)
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:483)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:86)
at org.testng.internal.Invoker.invokeMethod(Invoker.java:643)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:820)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1128)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
at org.testng.TestRunner.privateRun(TestRunner.java:782)
at org.testng.TestRunner.run(TestRunner.java:632)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
at org.testng.SuiteRunner.run(SuiteRunner.java:268)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1244)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
at org.testng.TestNG.run(TestNG.java:1064)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)
java.lang.AssertionError: expected object to not be null

	at org.testng.Assert.fail(Assert.java:94)
	at org.testng.Assert.assertNotNull(Assert.java:423)
	at org.testng.Assert.assertNotNull(Assert.java:408)
	at graphql.annotations.RelayTest.argVariableMutation(RelayTest.java:212)
	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:483)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:86)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:643)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:820)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1128)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:129)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:112)
	at org.testng.TestRunner.privateRun(TestRunner.java:782)
	at org.testng.TestRunner.run(TestRunner.java:632)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:366)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:361)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:319)
	at org.testng.SuiteRunner.run(SuiteRunner.java:268)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1244)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
	at org.testng.TestNG.run(TestNG.java:1064)
	at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
	at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:127)

Disconnected from the target VM, address: '127.0.0.1:49887', transport: 'socket'

Default Suite
Total tests run: 61, Failures: 6, Skips: 0

Process finished with exit code 0

Should allow for specialized constructor @GraphQLDataFetcher

I've noticed that GraphQL will, for fields starting with "is" (e.g. isAvailable), set the DataFetcher to FieldDataFetcher as opposed to PropertyDataFetcher.

Native boolean fields starting by "isXxxx" are allowed to have a getter method of the same name as defined in the Java Beans spec section 8.3.2:

   public boolean is<PropertyName>();

The same is not necessarily true for Boolean object types.

Although it is discussible if such fields should be using FieldDataFetcher by default, the graphql-java-annotations should allow graphql fields to be easily overwritten by another DataFetcher basic type.

The current case generates a FieldDataFetcher by default:

  @ApiModelProperty(value = "")
  @GraphQLField
  var isAvailable: Boolean? = null

But instead I would like it to use the PropertyDataFetcher:

  @ApiModelProperty(value = "")
  @GraphQLField
  @GraphQLDataFetcher(PropertyDataFetcher::class)
  var isAvailable: Boolean? = null

And this code fails to compile because PropertyDataFetcher annotation does not have a public default constructor. To get around it I had to specialize the PropertyDataFetcher for that specific field and it can become quite repetitive having to do the same for other similar fields.

Instead, it would be nice to allow to pass argument for a given specialized constructor as in:

  @GraphQLDataFetcher(value=PropertyDataFetcher::class, args="isAvailable")
  public Boolean isAvailable = null

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.