graphql-java-annotations icon indicating copy to clipboard operation
graphql-java-annotations copied to clipboard

AnnotationsSchemaCreator doesn't allow Relay.pageInfoType to be overriden

Open blongstreth opened this issue 3 years ago • 0 comments

Hello:

I noticed that AnnotationsSchemaCreator adds Relay.pageInfoType during the build phase and doesn't allow it to be overridden. I have a use case where I want to federate into a schema which already has scalar Cursor defined for the Relay specification. This means all types like PageInfo, Edge, etc... use Cursor instead of String in the schema. I managed to work around the issue by hacking the GraphQLSchema.Builder and preventing it from setting Relay.pageInfoType. I realize I could have not used AnnotationsSchemaCreator but I want to use this handy utility. Anyway, the code listed below is an example of one one way to workaround the issue. If anyone would consider fixing the issue along with adding direct support for GraphQL-java version 15.0 would be greatly appreciated.

Regards,

Bradley

Example Classes:

public class CustomRelay extends Relay
{
    public static final GraphQLScalarType GraphQLCursor = GraphQLScalarType.newScalar().name("Cursor")
          .description("Relay Cursor").coercing(new Coercing<String, String>()
          {
              @Override
              public String serialize(Object input)
              {
                  return input.toString();
              }

              @Override
              public String parseValue(Object input)
              {
                  return serialize(input);
              }

              @Override
              public String parseLiteral(Object input)
              {
                  if (!(input instanceof StringValue))
                  {
                      throw new CoercingParseLiteralException(
                            "Expected AST type 'StringValue' but was '" + input + "'.");
                  }
                  return ((StringValue) input).getValue();
              }
          }).build();

    public static GraphQLObjectType newPageInfoType = newObject()
            .name("PageInfo")
            .description("Information about pagination in a connection.")
            .field(newFieldDefinition()
                    .name("hasNextPage")
                    .type(new GraphQLNonNull(GraphQLBoolean))
                    .description("When paginating forwards, are there more items?"))
            .field(newFieldDefinition()
                    .name("hasPreviousPage")
                    .type(new GraphQLNonNull(GraphQLBoolean))
                    .description("When paginating backwards, are there more items?"))
            .field(newFieldDefinition()
                    .name("startCursor")
                    .type(GraphQLCursor)
                    .description("When paginating backwards, the cursor to continue."))
            .field(newFieldDefinition()
                    .name("endCursor")
                    .type(GraphQLCursor)
                    .description("When paginating forwards, the cursor to continue."))
            .field(newFieldDefinition()
                    .name("additionalInfo")
                    .type(GraphQLString))
            .build();

    public List<GraphQLArgument> getConnectionFieldArguments() {
        List<GraphQLArgument> args = new ArrayList<>();
        args.add(newArgument()
              .name("before")
              .description("fetching only nodes before this node (exclusive)")
              .type(GraphQLCursor)
              .build());
        args.add(newArgument()
              .name("after")
              .description("fetching only nodes after this node (exclusive)")
              .type(GraphQLCursor)
              .build());
        args.add(newArgument()
              .name("first")
              .description("fetching only the first certain number of nodes")
              .type(GraphQLInt)
              .build());
        args.add(newArgument()
              .name("last")
              .description("fetching only the last certain number of nodes")
              .type(GraphQLInt)
              .build());
        return args;
    }

    public List<GraphQLArgument> getBackwardPaginationConnectionFieldArguments() {
        List<GraphQLArgument> args = new ArrayList<>();
        args.add(newArgument()
              .name("before")
              .description("fetching only nodes before this node (exclusive)")
              .type(GraphQLCursor)
              .build());
        args.add(newArgument()
              .name("last")
              .description("fetching only the last certain number of nodes")
              .type(GraphQLInt)
              .build());
        return args;
    }

    public List<GraphQLArgument> getForwardPaginationConnectionFieldArguments() {
        List<GraphQLArgument> args = new ArrayList<>();
        args.add(newArgument()
              .name("after")
              .description("fetching only nodes after this node (exclusive)")
              .type(GraphQLCursor)
              .build());
        args.add(newArgument()
              .name("first")
              .description("fetching only the first certain number of nodes")
              .type(GraphQLInt)
              .build());
        return args;
    }

    public GraphQLObjectType edgeType(String name, GraphQLOutputType nodeType, GraphQLInterfaceType nodeInterface, List<GraphQLFieldDefinition> edgeFields) {
        return newObject()
              .name(name + "Edge")
              .description("An edge in a connection")
              .field(newFieldDefinition()
                    .name("node")
                    .type(nodeType)
                    .description("The item at the end of the edge"))
              .field(newFieldDefinition()
                    .name("cursor")
                    .type(nonNull(GraphQLCursor))
                    .description("cursor marks a unique position or index into the connection"))
              .fields(edgeFields)
              .build();
    }

    @Override
    public GraphQLObjectType connectionType(String name, GraphQLObjectType edgeType, List<GraphQLFieldDefinition> connectionFields) {
        return newObject()
                .name(name + "Connection")
                .description("A connection to a list of items.")
                .field(newFieldDefinition()
                        .name("edges")
                        .description("a list of edges")
                        .type(new GraphQLList(edgeType)))
                .field(newFieldDefinition()
                        .name("nodes")
                        .description("a list of nodes")
                        .type(new GraphQLList(edgeType.getFieldDefinition("node").getType())))
                .field(newFieldDefinition()
                        .name("pageInfo")
                        .description("details about this specific page")
                        .type(new GraphQLNonNull(newPageInfoType)))
                .field(newFieldDefinition()
                        .name("totalCount")
                        .description("number of nodes in connection")
                        .type(GraphQLInt))
                .fields(connectionFields)
                .build();
    }
public class HackedGraphQLSchemaBuilder extends GraphQLSchema.Builder
    {
        public HackedGraphQLSchemaBuilder()
        {
        }

        @Override
        public GraphQLSchema.Builder additionalType(GraphQLType additionalType)
        {
            if (!Relay.pageInfoType.equals(additionalType))
            {
                return super.additionalType(additionalType);
            }
            return this;
        }

        @Override
        public GraphQLSchema.Builder additionalTypes(Set<GraphQLType> additionalTypes)
        {
            return super.additionalTypes(additionalTypes.stream().filter(additionalType -> !Relay.pageInfoType.equals(additionalType)).collect(
                  Collectors.toSet()));
        }
    }

Example Usage:

        final GraphQLAnnotations graphqlAnnotations = new GraphQLAnnotations();
        graphqlAnnotations.setRelay(new CustomRelay());

        final GraphQLSchema.Builder schemaBuilder = new HackedGraphQLSchemaBuilder()
              .additionalType(CustomRelay.newPageInfoType);

        final GraphQLSchema schema = AnnotationsSchemaCreator.newAnnotationsSchema()
              .setAnnotationsProcessor(graphqlAnnotations)
              .setGraphQLSchemaBuilder(schemaBuilder)
              .query(Query.class) // to create you query object
              .mutation(Mutation.class) // to create your mutation object
              .setAlwaysPrettify(true) // to set the global prettifier of field names (removes get/set/is prefixes from names)
              .setRelay(new CustomRelay());
              .build();

blongstreth avatar Aug 11 '20 05:08 blongstreth