Skip to content

Commit 1394cf2

Browse files
bbakermanandimarek
authored andcommitted
graphql-java#419 - dynamic runtime wiring factory support
1 parent 5d2571f commit 1394cf2

14 files changed

+562
-34
lines changed

src/main/java/graphql/language/InterfaceTypeDefinition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import java.util.ArrayList;
55
import java.util.List;
66

7-
public class InterfaceTypeDefinition extends AbstractNode implements TypeDefinition {
7+
public class InterfaceTypeDefinition extends AbstractNode implements TypeDefinition, ResolvedTypeDefinition {
88
private String name;
99
private List<FieldDefinition> definitions = new ArrayList<>();
1010
private List<Directive> directives = new ArrayList<>();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package graphql.language;
2+
3+
4+
/**
5+
* A type that will need a type resolution to be run to work out what it really is at runtime
6+
*/
7+
public interface ResolvedTypeDefinition extends TypeDefinition {
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package graphql.language;
22

33

4+
import java.util.List;
5+
46
public interface TypeDefinition extends Node, Definition {
57
/**
68
* @return the name of the type being defined.
79
*/
810
String getName();
11+
12+
/**
13+
* @return the directives of this type being defined
14+
*/
15+
List<Directive> getDirectives();
16+
917
}

src/main/java/graphql/language/UnionTypeDefinition.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import java.util.ArrayList;
55
import java.util.List;
66

7-
public class UnionTypeDefinition extends AbstractNode implements TypeDefinition {
7+
public class UnionTypeDefinition extends AbstractNode implements TypeDefinition, ResolvedTypeDefinition {
88
private String name;
99
private List<Directive> directives = new ArrayList<>();
1010
private List<Type> memberTypes = new ArrayList<>();
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package graphql.schema.idl;
2+
3+
import graphql.language.FieldDefinition;
4+
import graphql.language.ResolvedTypeDefinition;
5+
import graphql.schema.DataFetcher;
6+
import graphql.schema.TypeResolver;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
import static graphql.Assert.assertNotNull;
12+
13+
/**
14+
* This combines a number of {@link WiringFactory}s together to act as one. It asks each one
15+
* whether it handles a type and delegates to the first one to answer yes.
16+
*/
17+
public class CombinedWiringFactory implements WiringFactory {
18+
private List<WiringFactory> factories;
19+
20+
public CombinedWiringFactory(List<WiringFactory> factories) {
21+
assertNotNull(factories, "You must provide a list of wiring factories");
22+
this.factories = new ArrayList<>(factories);
23+
}
24+
25+
@Override
26+
public boolean providesTypeResolver(TypeDefinitionRegistry registry, ResolvedTypeDefinition definition) {
27+
for (WiringFactory factory : factories) {
28+
if (factory.providesTypeResolver(registry, definition)) {
29+
return true;
30+
}
31+
}
32+
return false;
33+
}
34+
35+
@Override
36+
public TypeResolver getTypeResolver(TypeDefinitionRegistry registry, ResolvedTypeDefinition definition) {
37+
for (WiringFactory factory : factories) {
38+
if (factory.providesTypeResolver(registry, definition)) {
39+
return factory.getTypeResolver(registry, definition);
40+
}
41+
}
42+
return null;
43+
}
44+
45+
@Override
46+
public boolean providesDataFetcher(TypeDefinitionRegistry registry, FieldDefinition definition) {
47+
for (WiringFactory factory : factories) {
48+
if (factory.providesDataFetcher(registry, definition)) {
49+
return true;
50+
}
51+
}
52+
return false;
53+
}
54+
55+
@Override
56+
public DataFetcher getDataFetcher(TypeDefinitionRegistry registry, FieldDefinition definition) {
57+
for (WiringFactory factory : factories) {
58+
if (factory.providesDataFetcher(registry, definition)) {
59+
return factory.getDataFetcher(registry, definition);
60+
}
61+
}
62+
return null;
63+
}
64+
}

src/main/java/graphql/schema/idl/RuntimeWiring.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package graphql.schema.idl;
22

3+
import graphql.Assert;
34
import graphql.PublicApi;
5+
import graphql.language.FieldDefinition;
6+
import graphql.language.ResolvedTypeDefinition;
47
import graphql.schema.DataFetcher;
58
import graphql.schema.GraphQLScalarType;
69
import graphql.schema.GraphQLSchema;
@@ -20,11 +23,13 @@ public class RuntimeWiring {
2023
private final Map<String, Map<String, DataFetcher>> dataFetchers;
2124
private final Map<String, GraphQLScalarType> scalars;
2225
private final Map<String, TypeResolver> typeResolvers;
26+
private final WiringFactory wiringFactory;
2327

24-
private RuntimeWiring(Map<String, Map<String, DataFetcher>> dataFetchers, Map<String, GraphQLScalarType> scalars, Map<String, TypeResolver> typeResolvers) {
28+
private RuntimeWiring(Map<String, Map<String, DataFetcher>> dataFetchers, Map<String, GraphQLScalarType> scalars, Map<String, TypeResolver> typeResolvers, WiringFactory wiringFactory) {
2529
this.dataFetchers = dataFetchers;
2630
this.scalars = scalars;
2731
this.typeResolvers = typeResolvers;
32+
this.wiringFactory = wiringFactory;
2833
}
2934

3035
public Map<String, GraphQLScalarType> getScalars() {
@@ -43,6 +48,10 @@ public Map<String, TypeResolver> getTypeResolvers() {
4348
return typeResolvers;
4449
}
4550

51+
public WiringFactory getWiringFactory() {
52+
return wiringFactory;
53+
}
54+
4655
/**
4756
* @return a builder of Runtime Wiring
4857
*/
@@ -55,11 +64,25 @@ public static class Builder {
5564
private final Map<String, Map<String, DataFetcher>> dataFetchers = new LinkedHashMap<>();
5665
private final Map<String, GraphQLScalarType> scalars = new LinkedHashMap<>();
5766
private final Map<String, TypeResolver> typeResolvers = new LinkedHashMap<>();
67+
private WiringFactory wiringFactory = WiringFactory.NOOP_WIRING_FACTORY;
5868

5969
private Builder() {
6070
ScalarInfo.STANDARD_SCALARS.forEach(this::scalar);
6171
}
6272

73+
/**
74+
* Adds a wiring factory into the runtime wiring
75+
*
76+
* @param wiringFactory the wiring factory to add
77+
*
78+
* @return this outer builder
79+
*/
80+
public Builder wiringFactory(WiringFactory wiringFactory) {
81+
Assert.assertNotNull(wiringFactory, "You must provide a wiring factory");
82+
this.wiringFactory = wiringFactory;
83+
return this;
84+
}
85+
6386
/**
6487
* This allows you to add in new custom Scalar implementations beyond the standard set.
6588
*
@@ -86,7 +109,7 @@ public Builder type(TypeRuntimeWiring.Builder builder) {
86109
/**
87110
* This form allows a lambda to be used as the builder of a type wiring
88111
*
89-
* @param typeName the name of the type to wire
112+
* @param typeName the name of the type to wire
90113
* @param builderFunction a function that will be given the builder to use
91114
*
92115
* @return the runtime wiring builder
@@ -119,7 +142,7 @@ public Builder type(TypeRuntimeWiring typeRuntimeWiring) {
119142
* @return the built runtime wiring
120143
*/
121144
public RuntimeWiring build() {
122-
return new RuntimeWiring(dataFetchers, scalars, typeResolvers);
145+
return new RuntimeWiring(dataFetchers, scalars, typeResolvers, wiringFactory);
123146
}
124147

125148
}

src/main/java/graphql/schema/idl/SchemaGenerator.java

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import graphql.language.ObjectTypeDefinition;
1717
import graphql.language.ObjectValue;
1818
import graphql.language.OperationTypeDefinition;
19+
import graphql.language.ResolvedTypeDefinition;
1920
import graphql.language.ScalarTypeDefinition;
2021
import graphql.language.SchemaDefinition;
2122
import graphql.language.StringValue;
@@ -53,6 +54,8 @@
5354
import java.util.Optional;
5455
import java.util.Stack;
5556

57+
import static graphql.Assert.assertNotNull;
58+
5659
/**
5760
* This can generate a working runtime schema from a type registry and runtime wiring
5861
*/
@@ -75,6 +78,10 @@ class BuildContext {
7578
this.wiring = wiring;
7679
}
7780

81+
public TypeDefinitionRegistry getTypeRegistry() {
82+
return typeRegistry;
83+
}
84+
7885
@SuppressWarnings("OptionalGetWithoutIsPresent")
7986
TypeDefinition getTypeDefinition(Type type) {
8087
return typeRegistry.getType(type).get();
@@ -220,7 +227,7 @@ private <T extends GraphQLOutputType> T buildOutputType(BuildContext buildCtx, T
220227
outputType = buildUnionType(buildCtx, (UnionTypeDefinition) typeDefinition);
221228
} else if (typeDefinition instanceof EnumTypeDefinition) {
222229
outputType = buildEnumType((EnumTypeDefinition) typeDefinition);
223-
} else if (typeDefinition instanceof ScalarTypeDefinition){
230+
} else if (typeDefinition instanceof ScalarTypeDefinition) {
224231
outputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
225232
} else {
226233
// typeDefinition is not a valid output type
@@ -253,7 +260,7 @@ private GraphQLInputType buildInputType(BuildContext buildCtx, Type rawType) {
253260
inputType = buildInputObjectType(buildCtx, (InputObjectTypeDefinition) typeDefinition);
254261
} else if (typeDefinition instanceof EnumTypeDefinition) {
255262
inputType = buildEnumType((EnumTypeDefinition) typeDefinition);
256-
} else if (typeDefinition instanceof ScalarTypeDefinition){
263+
} else if (typeDefinition instanceof ScalarTypeDefinition) {
257264
inputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
258265
} else {
259266
// typeDefinition is not a valid InputType
@@ -331,7 +338,7 @@ private GraphQLInterfaceType buildInterfaceType(BuildContext buildCtx, Interface
331338
builder.name(typeDefinition.getName());
332339
builder.description(buildDescription(typeDefinition));
333340

334-
builder.typeResolver(getTypeResolver(buildCtx, typeDefinition.getName()));
341+
builder.typeResolver(getTypeResolver(buildCtx, typeDefinition));
335342

336343
typeDefinition.getFieldDefinitions().forEach(fieldDef ->
337344
builder.field(buildField(buildCtx, typeDefinition, fieldDef)));
@@ -342,11 +349,15 @@ private GraphQLUnionType buildUnionType(BuildContext buildCtx, UnionTypeDefiniti
342349
GraphQLUnionType.Builder builder = GraphQLUnionType.newUnionType();
343350
builder.name(typeDefinition.getName());
344351
builder.description(buildDescription(typeDefinition));
345-
builder.typeResolver(getTypeResolver(buildCtx, typeDefinition.getName()));
352+
builder.typeResolver(getTypeResolver(buildCtx, typeDefinition));
346353

347354
typeDefinition.getMemberTypes().forEach(mt -> {
348-
GraphQLObjectType objectType = buildOutputType(buildCtx, mt);
349-
builder.possibleType(objectType);
355+
GraphQLOutputType outputType = buildOutputType(buildCtx, mt);
356+
if (outputType instanceof GraphQLTypeReference) {
357+
builder.possibleType((GraphQLTypeReference) outputType);
358+
} else {
359+
builder.possibleType((GraphQLObjectType) outputType);
360+
}
350361
});
351362
return builder.build();
352363
}
@@ -381,14 +392,23 @@ private GraphQLFieldDefinition buildField(BuildContext buildCtx, TypeDefinition
381392
}
382393

383394
private DataFetcher buildDataFetcher(BuildContext buildCtx, TypeDefinition parentType, FieldDefinition fieldDef) {
384-
RuntimeWiring wiring = buildCtx.getWiring();
385395
String fieldName = fieldDef.getName();
386-
DataFetcher dataFetcher = wiring.getDataFetcherForType(parentType.getName()).get(fieldName);
387-
if (dataFetcher == null) {
388-
//
389-
// in the future we could support FieldDateFetcher but we would need a way to indicate that in the schema spec
390-
// perhaps by a directive
391-
dataFetcher = new PropertyDataFetcher(fieldName);
396+
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
397+
RuntimeWiring wiring = buildCtx.getWiring();
398+
WiringFactory wiringFactory = wiring.getWiringFactory();
399+
400+
DataFetcher dataFetcher;
401+
if (wiringFactory.providesDataFetcher(typeRegistry, fieldDef)) {
402+
dataFetcher = wiringFactory.getDataFetcher(typeRegistry, fieldDef);
403+
assertNotNull(dataFetcher, "The WiringFactory indicated it provides a data fetcher but then returned null");
404+
} else {
405+
dataFetcher = wiring.getDataFetcherForType(parentType.getName()).get(fieldName);
406+
if (dataFetcher == null) {
407+
//
408+
// in the future we could support FieldDateFetcher but we would need a way to indicate that in the schema spec
409+
// perhaps by a directive
410+
dataFetcher = new PropertyDataFetcher(fieldName);
411+
}
392412
}
393413
return dataFetcher;
394414
}
@@ -454,12 +474,24 @@ private Object buildObjectValue(ObjectValue defaultValue) {
454474
return map;
455475
}
456476

457-
private TypeResolver getTypeResolver(BuildContext buildCtx, String name) {
458-
TypeResolver typeResolver = buildCtx.getWiring().getTypeResolvers().get(name);
459-
if (typeResolver == null) {
460-
// this really should be checked earlier via a pre-flight check
461-
typeResolver = new TypeResolverProxy();
477+
private TypeResolver getTypeResolver(BuildContext buildCtx, ResolvedTypeDefinition typeDefinition) {
478+
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
479+
RuntimeWiring wiring = buildCtx.getWiring();
480+
WiringFactory wiringFactory = wiring.getWiringFactory();
481+
482+
TypeResolver typeResolver;
483+
if (wiringFactory.providesTypeResolver(typeRegistry, typeDefinition)) {
484+
typeResolver = wiringFactory.getTypeResolver(typeRegistry, typeDefinition);
485+
assertNotNull(typeResolver, "The WiringFactory indicated it provides a type resolver but then returned null");
486+
487+
} else {
488+
typeResolver = wiring.getTypeResolvers().get(typeDefinition.getName());
489+
if (typeResolver == null) {
490+
// this really should be checked earlier via a pre-flight check
491+
typeResolver = new TypeResolverProxy();
492+
}
462493
}
494+
463495
return typeResolver;
464496
}
465497

src/main/java/graphql/schema/idl/SchemaTypeChecker.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import graphql.language.InterfaceTypeDefinition;
99
import graphql.language.ObjectTypeDefinition;
1010
import graphql.language.OperationTypeDefinition;
11+
import graphql.language.ResolvedTypeDefinition;
1112
import graphql.language.SchemaDefinition;
1213
import graphql.language.Type;
1314
import graphql.language.TypeDefinition;
@@ -153,14 +154,19 @@ private void checkScalarImplementationsArePresent(List<GraphQLError> errors, Typ
153154

154155
private void checkTypeResolversArePresent(List<GraphQLError> errors, TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) {
155156

156-
Consumer<TypeDefinition> checkForResolver = typeDef -> {
157-
if (!wiring.getTypeResolvers().containsKey(typeDef.getName())) {
157+
Consumer<ResolvedTypeDefinition> checkForResolver = typeDef -> {
158+
boolean hasTypeResolver = wiring.getWiringFactory().providesTypeResolver(typeRegistry,typeDef);
159+
if (! hasTypeResolver) {
160+
hasTypeResolver = wiring.getTypeResolvers().containsKey(typeDef.getName());
161+
}
162+
if (! hasTypeResolver) {
158163
errors.add(new MissingTypeResolverError(typeDef));
159164
}
160165
};
161166

162167
typeRegistry.types().values().stream()
163-
.filter(typeDef -> typeDef instanceof InterfaceTypeDefinition || typeDef instanceof UnionTypeDefinition)
168+
.filter(typeDef -> typeDef instanceof ResolvedTypeDefinition)
169+
.map(ResolvedTypeDefinition.class::cast)
164170
.forEach(checkForResolver);
165171

166172
}

0 commit comments

Comments
 (0)