-
Notifications
You must be signed in to change notification settings - Fork 6
/
SimpleGraphQLBuilder.java
281 lines (249 loc) · 10.2 KB
/
SimpleGraphQLBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package com.newrelic.graphql.schema;
import com.newrelic.graphql.schema.scalars.PredefinedScalars;
import graphql.GraphQL;
import graphql.Scalars;
import graphql.execution.AsyncExecutionStrategy;
import graphql.execution.AsyncSerialExecutionStrategy;
import graphql.execution.DataFetcherExceptionHandler;
import graphql.execution.SimpleDataFetcherExceptionHandler;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.TypeDefinition;
import graphql.language.UnionTypeDefinition;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.TypeResolver;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
/**
* This builder provides a simple fluent interface for wiring up your schema for runtime execution.
* It also provides defaults for a few common scenarios to streamline getting started:
*
* <ul>
* <li>Default registration for scalar types as Strings until overridden otherwise
* <li>Default type resolving for interfaces/unions, so if your Java class names match your
* GraphQL type names no further customization is required
* <li>Simple builder methods for common operations (i.e. providing the {@code fetcher} for a
* field)
* </ul>
*
* <p>If you need access to the lower level {@code RuntimeWiring.Builder}, use the {@code configure}
* function to provide a callback. If you need access to the {@code GraphQL.Builder}, then call
* {@code builder()} instead of {@code build()} to get the in-flight builder instance back.
*
* <pre>
* Reader schemaReader = //... Read your GraphQL SDL from somewhere
* GraphQL graphQL = new SimpleGraphQLBuilder(schemaReader)
* .fetcher("Query", "myField", new QueryMyFieldFetcher())
* .build()
* </pre>
*/
public class SimpleGraphQLBuilder {
private static final SchemaParser schemaParser = new SchemaParser();
private static final DefaultTypeResolver defaultTypeResolver = new DefaultTypeResolver();
private final Reader schemaReader;
private DataFetcherExceptionHandler exceptionHandler;
private HashMap<String, DataFetcher> fetchers;
private HashMap<String, GraphQLScalarType> scalars;
private HashMap<String, TypeResolver> typeResolvers;
private IConfigureSimpleGraphQLBuilder configurator;
private boolean usePredefinedScalars;
/** @param schema Reader containing your GraphQL SDL definition */
public SimpleGraphQLBuilder(Reader schema) {
this.schemaReader = schema;
this.exceptionHandler = new SimpleDataFetcherExceptionHandler();
this.fetchers = new HashMap<>();
this.scalars = new HashMap<>();
this.typeResolvers = new HashMap<>();
this.usePredefinedScalars = true;
}
/**
* @return Constructs an instance of the GraphQL execution object with your builder configuration.
*/
public GraphQL build() {
return builder().build();
}
/** @return Returns intermediate builder object to allow additional configuration. */
public GraphQL.Builder builder() {
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaReader);
RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring();
wireUpDataFetchers(runtimeWiringBuilder, fetchers);
wireUpScalars(runtimeWiringBuilder, scalars, typeRegistry);
wireUpTypeResolvers(runtimeWiringBuilder, typeResolvers, typeRegistry);
if (configurator != null) {
configurator.configure(typeRegistry, runtimeWiringBuilder);
}
SchemaGenerator schemaGenerator = new SchemaGenerator();
SchemaGenerator.Options options = SchemaGenerator.Options.defaultOptions();
GraphQLSchema schema =
schemaGenerator.makeExecutableSchema(options, typeRegistry, runtimeWiringBuilder.build());
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(new AsyncExecutionStrategy(exceptionHandler))
.mutationExecutionStrategy(new AsyncSerialExecutionStrategy(exceptionHandler));
}
/**
* @param typeName GraphQL type name that this data fetcher applies to
* @param fieldName Name of field on the GraphQL type that the data fetcher applies to
* @param fetcher Instance of DataFetcher to apply to the provided type and field name
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder fetcher(String typeName, String fieldName, DataFetcher fetcher) {
this.fetchers.put(String.format("%s.%s", typeName, fieldName), fetcher);
return this;
}
/**
* @param incoming Map from field identifying strings to DataFetcher instances. Strings must be in
* the format "Type.field"
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder fetchers(Map<String, DataFetcher> incoming) {
this.fetchers.putAll(incoming);
return this;
}
/**
* @param incoming Map from scalar names to scalar definition types
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder scalars(Map<String, GraphQLScalarType> incoming) {
this.scalars.putAll(incoming);
return this;
}
/**
* @param name Scalar name
* @param type Scalar type definition
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder scalar(String name, GraphQLScalarType type) {
this.scalars.put(name, type);
return this;
}
/**
* @param incoming Map from type name to a custom type resolver
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder typeResolvers(Map<String, TypeResolver> incoming) {
this.typeResolvers.putAll(incoming);
return this;
}
/**
* @param name Type name
* @param resolver Type resolver instance
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder typeResolver(String name, TypeResolver resolver) {
this.typeResolvers.put(name, resolver);
return this;
}
/**
* @param exceptionHandler Exception handler to pass to the GraphQL execution strategy
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder exceptionHandler(DataFetcherExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
return this;
}
/**
* @param usePredefinedScalars Controls whether the predefined scalars from this library should be
* automatically registered when building the GraphQL execution instance.
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder usePredefinedScalars(boolean usePredefinedScalars) {
this.usePredefinedScalars = usePredefinedScalars;
return this;
}
/** Interface for providing a custom configuration callback to the SimpleGraphQLBuilder. */
public interface IConfigureSimpleGraphQLBuilder {
/**
* @param registry Type registry loaded with your schema
* @param runtimeWiringBuilder Runtime wiring object being used to create the GraphQL execution
* instance.
*/
void configure(TypeDefinitionRegistry registry, RuntimeWiring.Builder runtimeWiringBuilder);
}
/**
* @param configurator Custom callback to further configure your GraphQL execution instance.
* @return Fluent builder instance
*/
public SimpleGraphQLBuilder configure(IConfigureSimpleGraphQLBuilder configurator) {
this.configurator = configurator;
return this;
}
private static void wireUpDataFetchers(
RuntimeWiring.Builder builder, Map<String, DataFetcher> fetchers) {
fetchers.forEach(
(key, fetcher) -> {
String[] parts = key.split("[.]");
if (parts.length == 2) {
builder.type(parts[0], t -> t.dataFetcher(parts[1], fetcher));
}
});
}
private void wireUpScalars(
RuntimeWiring.Builder builder,
Map<String, GraphQLScalarType> scalars,
TypeDefinitionRegistry typeRegistry) {
typeRegistry
.scalars()
.forEach(
(name, typeDefinition) -> {
if (!PredefinedScalars.isBuiltin(name)) {
// User defined scalar
GraphQLScalarType scalarType = scalars.get(name);
// Predefined scalar from our library
if (scalarType == null && usePredefinedScalars) {
scalarType = PredefinedScalars.get(name);
}
// Fall back to default scalar definition
if (scalarType == null) {
scalarType = defaultScalarType(name);
}
// We may have a description on
// 1) the explicit type registered with us in code or
// 2) the type definition itself (i.e. from the GraphQL SDL file)
//
// Either way, apply that before building the runtime type here
String description = scalarType.getDescription();
if (description == null && typeDefinition.getDescription() != null) {
description = typeDefinition.getDescription().content;
}
if (description != null) {
scalarType =
GraphQLScalarType.newScalar(scalarType).description(description).build();
}
builder.scalar(scalarType);
}
});
}
private static void wireUpTypeResolvers(
RuntimeWiring.Builder builder,
Map<String, TypeResolver> typeResolvers,
TypeDefinitionRegistry typeRegistry) {
typeRegistry
.getTypes(InterfaceTypeDefinition.class)
.forEach(type -> resolverForType(builder, typeResolvers, type));
typeRegistry
.getTypes(UnionTypeDefinition.class)
.forEach(type -> resolverForType(builder, typeResolvers, type));
}
private static void resolverForType(
RuntimeWiring.Builder builder, Map<String, TypeResolver> typeResolvers, TypeDefinition type) {
String typeName = type.getName();
TypeResolver resolver = typeResolvers.getOrDefault(typeName, defaultTypeResolver);
builder.type(typeName, t -> t.typeResolver(resolver));
}
private static GraphQLScalarType defaultScalarType(String name) {
return GraphQLScalarType.newScalar()
.name(name)
.coercing(Scalars.GraphQLString.getCoercing())
.build();
}
}