graphql-java-annotations
graphql-java-annotations copied to clipboard
AnnotationsSchemaCreator doesn't allow Relay.pageInfoType to be overriden
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();