Skip to content

Commit 289a711

Browse files
authored
Merge pull request #109 from Jahia/custom-relay
Allows to set a custom Relay implementation
2 parents 617665b + 1fa465a commit 289a711

File tree

4 files changed

+123
-13
lines changed

4 files changed

+123
-13
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,19 @@ GraphQLAnnotations.register(new UUIDTypeFunction())
220220

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

223-
## Relay Mutations
223+
## Relay support
224+
225+
### Mutations
224226

225227
You can use `@GraphQLRelayMutation` annotation to make mutation adhere to
226228
Relay [specification for mutations](https://facebook.github.io/relay/graphql/mutations.htm)
227229

228-
## Relay Connection
230+
### Connection
229231

230232
You can use `@GraphQLConnection` annotation to make a field iterable in adherence to Relay [Connection specification](https://facebook.github.io/relay/graphql/connections.htm).
233+
234+
### Customizing Relay schema
235+
236+
By default, GraphQLAnnotations will use the `graphql.relay.Relay` class to create the Relay specific schema types (Mutations, Connections, Edges, PageInfo, ...).
237+
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
238+
can redefine methods that create Relay types.

src/main/java/graphql/annotations/GraphQLAnnotations.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@
4848
@Component
4949
public class GraphQLAnnotations implements GraphQLAnnotationsProcessor {
5050

51-
private static final Relay RELAY_TYPES = new Relay();
52-
5351
private static final List<Class> TYPES_FOR_CONNECTION = Arrays.asList(GraphQLObjectType.class, GraphQLInterfaceType.class, GraphQLUnionType.class, GraphQLTypeReference.class);
5452

5553
private Map<String, graphql.schema.GraphQLType> typeRegistry = new HashMap<>();
5654
private Map<Class<?>, Set<Class<?>>> extensionsTypeRegistry = new HashMap<>();
5755
private final Stack<String> processing = new Stack<>();
56+
private Relay relay = new Relay();
5857

5958
public GraphQLAnnotations() {
6059
this(new DefaultTypeFunction());
@@ -71,6 +70,10 @@ public static GraphQLAnnotations getInstance() {
7170
return instance;
7271
}
7372

73+
public void setRelay(Relay relay) {
74+
this.relay = relay;
75+
}
76+
7477
@Override
7578
public graphql.schema.GraphQLType getInterface(Class<?> iface) throws GraphQLAnnotationsException {
7679
String typeName = getTypeName(iface);
@@ -518,9 +521,9 @@ private GraphQLOutputType getGraphQLConnection(boolean isConnection, AccessibleO
518521
assert wrappedType instanceof GraphQLObjectType;
519522
String annValue = field.getAnnotation(GraphQLConnection.class).name();
520523
String connectionName = annValue.isEmpty() ? wrappedType.getName() : annValue;
521-
GraphQLObjectType edgeType = RELAY_TYPES.edgeType(connectionName, (GraphQLOutputType) wrappedType, null, Collections.<GraphQLFieldDefinition>emptyList());
522-
outputType = RELAY_TYPES.connectionType(connectionName, edgeType, Collections.emptyList());
523-
builder.argument(RELAY_TYPES.getConnectionFieldArguments());
524+
GraphQLObjectType edgeType = relay.edgeType(connectionName, (GraphQLOutputType) wrappedType, null, Collections.<GraphQLFieldDefinition>emptyList());
525+
outputType = relay.connectionType(connectionName, edgeType, Collections.emptyList());
526+
builder.argument(relay.getConnectionFieldArguments());
524527
}
525528
}
526529
return outputType;
@@ -576,7 +579,7 @@ protected GraphQLFieldDefinition getField(Method method) throws GraphQLAnnotatio
576579
return getArgument(parameter, graphQLType);
577580
}).collect(Collectors.toList());
578581

579-
GraphQLFieldDefinition relay = null;
582+
GraphQLFieldDefinition relayFieldDefinition = null;
580583
if (method.isAnnotationPresent(GraphQLRelayMutation.class)) {
581584
if (!(outputType instanceof GraphQLObjectType || outputType instanceof GraphQLInterfaceType)) {
582585
throw new RuntimeException("outputType should be an object or an interface");
@@ -587,12 +590,12 @@ protected GraphQLFieldDefinition getField(Method method) throws GraphQLAnnotatio
587590
List<GraphQLFieldDefinition> fieldDefinitions = outputType instanceof GraphQLObjectType ?
588591
((GraphQLObjectType) outputType).getFieldDefinitions() :
589592
((GraphQLInterfaceType) outputType).getFieldDefinitions();
590-
relay = RELAY_TYPES.mutationWithClientMutationId(title, method.getName(),
593+
relayFieldDefinition = relay.mutationWithClientMutationId(title, method.getName(),
591594
args.stream().
592595
map(t -> newInputObjectField().name(t.getName()).type(t.getType()).description(t.getDescription()).build()).
593596
collect(Collectors.toList()), fieldDefinitions, null);
594-
builder.argument(relay.getArguments());
595-
builder.type(relay.getType());
597+
builder.argument(relayFieldDefinition.getArguments());
598+
builder.type(relayFieldDefinition.getType());
596599
} else {
597600
builder.argument(args);
598601
}
@@ -620,8 +623,8 @@ protected GraphQLFieldDefinition getField(Method method) throws GraphQLAnnotatio
620623
actualDataFetcher = constructDataFetcher(method.getName(), dataFetcher);
621624
}
622625

623-
if (method.isAnnotationPresent(GraphQLRelayMutation.class) && relay != null) {
624-
actualDataFetcher = new RelayMutationMethodDataFetcher(method, args, relay.getArgument("input").getType(), relay.getType());
626+
if (method.isAnnotationPresent(GraphQLRelayMutation.class) && relayFieldDefinition != null) {
627+
actualDataFetcher = new RelayMutationMethodDataFetcher(method, args, relayFieldDefinition.getArgument("input").getType(), relayFieldDefinition.getType());
625628
}
626629

627630
if (isConnection) {

src/test/java/graphql/annotations/GraphQLConnectionTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
import graphql.ExecutionResult;
1818
import graphql.GraphQL;
19+
import graphql.annotations.util.CustomRelay;
20+
import graphql.relay.Relay;
1921
import graphql.schema.DataFetchingEnvironment;
22+
import graphql.schema.GraphQLList;
2023
import graphql.schema.GraphQLObjectType;
2124
import graphql.schema.GraphQLSchema;
2225
import org.testng.annotations.Test;
@@ -113,6 +116,25 @@ public void methodList() {
113116

114117
}
115118

119+
@Test
120+
public void customRelayMethodList() {
121+
try {
122+
GraphQLAnnotations.getInstance().setRelay(new CustomRelay());
123+
GraphQLObjectType object = GraphQLAnnotations.object(TestConnections.class);
124+
GraphQLSchema schema = newSchema().query(object).build();
125+
126+
graphql.schema.GraphQLObjectType f = (GraphQLObjectType) schema.getType("ObjConnection");
127+
assertTrue(f.getFieldDefinitions().size() == 4);
128+
assertTrue(f.getFieldDefinition("nodes").getType() instanceof GraphQLList);
129+
assertEquals(((GraphQLList)f.getFieldDefinition("nodes").getType()).getWrappedType().getName(), "Obj");
130+
131+
GraphQLObjectType pageInfo = (GraphQLObjectType) schema.getType("PageInfo");
132+
assertTrue(pageInfo.getFieldDefinition("additionalInfo") != null);
133+
} finally {
134+
GraphQLAnnotations.getInstance().setRelay(new Relay());
135+
}
136+
}
137+
116138
public void testResult(String name, ExecutionResult result) {
117139
Map<String, Map<String, List<Map<String, Map<String, Object>>>>> data = result.getData();
118140
List<Map<String, Map<String, Object>>> edges = data.get(name).get("edges");
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.util;
16+
17+
import graphql.relay.Relay;
18+
import graphql.schema.*;
19+
20+
import java.util.List;
21+
22+
import static graphql.Scalars.GraphQLBoolean;
23+
import static graphql.Scalars.GraphQLInt;
24+
import static graphql.Scalars.GraphQLString;
25+
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
26+
import static graphql.schema.GraphQLObjectType.newObject;
27+
28+
public class CustomRelay extends Relay {
29+
30+
private GraphQLObjectType pageInfoType = newObject()
31+
.name("PageInfo")
32+
.description("Information about pagination in a connection.")
33+
.field(newFieldDefinition()
34+
.name("hasNextPage")
35+
.type(new GraphQLNonNull(GraphQLBoolean))
36+
.description("When paginating forwards, are there more items?"))
37+
.field(newFieldDefinition()
38+
.name("hasPreviousPage")
39+
.type(new GraphQLNonNull(GraphQLBoolean))
40+
.description("When paginating backwards, are there more items?"))
41+
.field(newFieldDefinition()
42+
.name("startCursor")
43+
.type(GraphQLString)
44+
.description("When paginating backwards, the cursor to continue."))
45+
.field(newFieldDefinition()
46+
.name("endCursor")
47+
.type(GraphQLString)
48+
.description("When paginating forwards, the cursor to continue."))
49+
.field(newFieldDefinition()
50+
.name("additionalInfo")
51+
.type(GraphQLString))
52+
.build();
53+
54+
@Override
55+
public GraphQLObjectType connectionType(String name, GraphQLObjectType edgeType, List<GraphQLFieldDefinition> connectionFields) {
56+
return newObject()
57+
.name(name + "Connection")
58+
.description("A connection to a list of items.")
59+
.field(newFieldDefinition()
60+
.name("edges")
61+
.description("a list of edges")
62+
.type(new GraphQLList(edgeType)))
63+
.field(newFieldDefinition()
64+
.name("nodes")
65+
.description("a list of nodes")
66+
.type(new GraphQLList(edgeType.getFieldDefinition("node").getType())))
67+
.field(newFieldDefinition()
68+
.name("pageInfo")
69+
.description("details about this specific page")
70+
.type(new GraphQLNonNull(pageInfoType)))
71+
.field(newFieldDefinition()
72+
.name("totalCount")
73+
.description("number of nodes in connection")
74+
.type(GraphQLInt))
75+
.fields(connectionFields)
76+
.build();
77+
}}

0 commit comments

Comments
 (0)