Skip to content

Commit fb86ee6

Browse files
bbakermanandimarek
andauthored
post processing on schema directive wiring afterr schema is build (#2082)
* Starting PR for post processing on schema directive wiring * use correct changeNode method * consider GraphQLTypeReference too * consider schema description and schema directives when transforming schema * Schema directive wiring now happens in the post processing step - not as each element is built * We cant shortcut this * Imports not used * Reordered the schema callbacks to reflect the old days behavior * Proper clean up Co-authored-by: Andreas Marek <andimarek@fastmail.fm>
1 parent 17f0e27 commit fb86ee6

11 files changed

Lines changed: 326 additions & 80 deletions

src/main/java/graphql/schema/GraphQLObjectType.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.google.common.collect.ImmutableList;
44
import com.google.common.collect.ImmutableMap;
5+
import graphql.Assert;
6+
import graphql.AssertException;
57
import graphql.Internal;
68
import graphql.PublicApi;
79
import graphql.language.ObjectTypeDefinition;
@@ -363,11 +365,15 @@ public Builder withInterface(GraphQLInterfaceType interfaceType) {
363365
return this;
364366
}
365367

366-
public Builder replaceInterfaces(List<GraphQLInterfaceType> interfaces) {
368+
public Builder replaceInterfaces(List<? extends GraphQLNamedOutputType> interfaces) {
367369
assertNotNull(interfaces, () -> "interfaces can't be null");
368370
this.interfaces.clear();
369-
for (GraphQLInterfaceType interfaceType : interfaces) {
370-
this.interfaces.put(interfaceType.getName(), interfaceType);
371+
for (GraphQLNamedOutputType schemaElement : interfaces) {
372+
if (schemaElement instanceof GraphQLInterfaceType || schemaElement instanceof GraphQLTypeReference) {
373+
this.interfaces.put(schemaElement.getName(), schemaElement);
374+
} else {
375+
Assert.assertShouldNeverHappen("Unexpected type " + (schemaElement != null ? schemaElement.getClass() : "null"));
376+
}
371377
}
372378
return this;
373379
}

src/main/java/graphql/schema/GraphQLSchema.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,13 @@ public Builder withSchemaDirectives(GraphQLDirective... directives) {
482482
return this;
483483
}
484484

485+
public Builder withSchemaDirectives(Collection<? extends GraphQLDirective> directives) {
486+
for (GraphQLDirective directive : directives) {
487+
withSchemaDirective(directive);
488+
}
489+
return this;
490+
}
491+
485492
public Builder withSchemaDirective(GraphQLDirective directive) {
486493
assertNotNull(directive, () -> "directive can't be null");
487494
schemaDirectives.put(directive.getName(), directive);

src/main/java/graphql/schema/GraphQLUnionType.java

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

33

4+
import graphql.Assert;
45
import com.google.common.collect.ImmutableList;
56
import graphql.Internal;
67
import graphql.PublicApi;
@@ -292,10 +293,16 @@ public Builder possibleTypes(GraphQLObjectType... type) {
292293
return this;
293294
}
294295

295-
public Builder replacePossibleTypes(List<GraphQLObjectType> types) {
296+
public Builder replacePossibleTypes(List<? extends GraphQLNamedOutputType> types) {
296297
this.types.clear();
297-
for (GraphQLObjectType graphQLType : types) {
298-
possibleType(graphQLType);
298+
for (GraphQLSchemaElement schemaElement : types) {
299+
if (schemaElement instanceof GraphQLTypeReference) {
300+
possibleType((GraphQLTypeReference) schemaElement);
301+
} else if (schemaElement instanceof GraphQLObjectType) {
302+
possibleType((GraphQLObjectType) schemaElement);
303+
} else {
304+
Assert.assertShouldNeverHappen("Unexpected type " + (schemaElement != null ? schemaElement.getClass() : "null"));
305+
}
299306
}
300307
return this;
301308
}

src/main/java/graphql/schema/SchemaTransformer.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Set;
2525

2626
import static graphql.Assert.assertNotEmpty;
27+
import static graphql.Assert.assertNotNull;
2728
import static graphql.Assert.assertShouldNeverHappen;
2829
import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER;
2930
import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer;
@@ -44,6 +45,7 @@ private static class DummyRoot implements GraphQLSchemaElement {
4445
static final String SUBSCRIPTION = "subscription";
4546
static final String ADD_TYPES = "addTypes";
4647
static final String DIRECTIVES = "directives";
48+
static final String SCHEMA_DIRECTIVES = "schemaDirectives";
4749
static final String INTROSPECTION = "introspection";
4850
GraphQLSchema schema;
4951

@@ -52,13 +54,15 @@ private static class DummyRoot implements GraphQLSchemaElement {
5254
GraphQLObjectType subscription;
5355
Set<GraphQLType> additionalTypes;
5456
Set<GraphQLDirective> directives;
57+
Set<GraphQLDirective> schemaDirectives;
5558

5659
DummyRoot(GraphQLSchema schema) {
5760
this.schema = schema;
5861
query = schema.getQueryType();
5962
mutation = schema.isSupportingMutations() ? schema.getMutationType() : null;
6063
subscription = schema.isSupportingSubscriptions() ? schema.getSubscriptionType() : null;
6164
additionalTypes = schema.getAdditionalTypes();
65+
schemaDirectives = new LinkedHashSet<>(schema.getSchemaDirectives());
6266
directives = new LinkedHashSet<>(schema.getDirectives());
6367
}
6468

@@ -80,6 +84,7 @@ public SchemaElementChildrenContainer getChildrenWithTypeReferences() {
8084
}
8185
builder.children(ADD_TYPES, additionalTypes);
8286
builder.children(DIRECTIVES, directives);
87+
builder.children(SCHEMA_DIRECTIVES, schemaDirectives);
8388
builder.child(INTROSPECTION, Introspection.__Schema);
8489
return builder.build();
8590
}
@@ -92,6 +97,7 @@ public GraphQLSchemaElement withNewChildren(SchemaElementChildrenContainer newCh
9297
subscription = newChildren.getChildOrNull(SUBSCRIPTION);
9398
additionalTypes = new LinkedHashSet<>(newChildren.getChildren(ADD_TYPES));
9499
directives = new LinkedHashSet<>(newChildren.getChildren(DIRECTIVES));
100+
schemaDirectives = new LinkedHashSet<>(newChildren.getChildren(SCHEMA_DIRECTIVES));
95101
return this;
96102
}
97103

@@ -140,7 +146,7 @@ public TraversalControl enter(TraverserContext<GraphQLSchemaElement> context) {
140146

141147
int zippersBefore = zippers.size();
142148
TraversalControl result = context.thisNode().accept(context, visitor);
143-
// detection if the node was changed: TODO make it better: doesn't work for parallel
149+
// detection if the node was changed
144150
if (zippersBefore + 1 == zippers.size()) {
145151
nodeZipper = zippers.get(zippers.size() - 1);
146152
}
@@ -171,7 +177,9 @@ public TraversalControl backRef(TraverserContext<GraphQLSchemaElement> context)
171177
NodeZipper<GraphQLSchemaElement> zipper = zipperByOriginalNode.get(context.thisNode());
172178
breadcrumbsByZipper.get(zipper).add(context.getBreadcrumbs());
173179
visitor.visitBackRef(context);
174-
reverseDependencies.get(zipper.getCurNode()).add(context.getParentNode());
180+
List<GraphQLSchemaElement> reverseDependenciesForCurNode = reverseDependencies.get(zipper.getCurNode());
181+
assertNotNull(reverseDependenciesForCurNode);
182+
reverseDependenciesForCurNode.add(context.getParentNode());
175183
return TraversalControl.CONTINUE;
176184
}
177185
};
@@ -193,7 +201,9 @@ public TraversalControl backRef(TraverserContext<GraphQLSchemaElement> context)
193201
.subscription(dummyRoot.subscription)
194202
.additionalTypes(dummyRoot.additionalTypes)
195203
.additionalDirectives(dummyRoot.directives)
204+
.withSchemaDirectives(dummyRoot.schemaDirectives)
196205
.codeRegistry(builder.build())
206+
.description(schema.getDescription())
197207
.buildImpl(true);
198208
return newSchema;
199209
}
@@ -288,13 +298,13 @@ private void zipUpToDummyRoot(List<NodeZipper<GraphQLSchemaElement>> zippers,
288298

289299
// update curZippers
290300
NodeZipper<GraphQLSchemaElement> curZipperForElement = nodeToZipper.get(element);
291-
Assert.assertNotNull(curZipperForElement, () -> format("curZipperForElement is null for parentNode %s", element));
301+
assertNotNull(curZipperForElement, () -> format("curZipperForElement is null for parentNode %s", element));
292302
curZippers.remove(curZipperForElement);
293303
curZippers.add(newZipper);
294304

295305
// update breadcrumbsByZipper to use the newZipper
296306
List<List<Breadcrumb<GraphQLSchemaElement>>> breadcrumbsForOriginalParent = breadcrumbsByZipper.get(curZipperForElement);
297-
Assert.assertNotNull(breadcrumbsForOriginalParent, () -> format("No breadcrumbs found for zipper %s", curZipperForElement));
307+
assertNotNull(breadcrumbsForOriginalParent, () -> format("No breadcrumbs found for zipper %s", curZipperForElement));
298308
breadcrumbsByZipper.remove(curZipperForElement);
299309
breadcrumbsByZipper.put(newZipper, breadcrumbsForOriginalParent);
300310

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public interface SchemaDirectiveWiringEnvironment<T extends GraphQLDirectiveCont
7171
*
7272
* @return hierarchical graphql language node information
7373
*/
74-
NodeParentTree<NamedNode> getNodeParentTree();
74+
NodeParentTree<NamedNode<?>> getNodeParentTree();
7575

7676
/**
7777
* The type hierarchy depends on the element in question. For example {@link graphql.schema.GraphQLObjectType} elements
@@ -114,7 +114,7 @@ public interface SchemaDirectiveWiringEnvironment<T extends GraphQLDirectiveCont
114114
*
115115
* @throws graphql.AssertException if there is not field in context at the time of the directive wiring callback
116116
*/
117-
DataFetcher getFieldDataFetcher();
117+
DataFetcher<?> getFieldDataFetcher();
118118

119119
/**
120120
* This is a shortcut method to set a new data fetcher in the underlying {@link graphql.schema.GraphQLCodeRegistry}
@@ -129,6 +129,6 @@ public interface SchemaDirectiveWiringEnvironment<T extends GraphQLDirectiveCont
129129
*
130130
* @throws graphql.AssertException if there is not field in context at the time of the directive wiring callback
131131
*/
132-
GraphQLFieldDefinition setFieldDataFetcher(DataFetcher newDataFetcher);
132+
GraphQLFieldDefinition setFieldDataFetcher(DataFetcher<?> newDataFetcher);
133133

134134
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class SchemaDirectiveWiringEnvironmentImpl<T extends GraphQLDirectiveCont
2424

2525
private final T element;
2626
private final Map<String, GraphQLDirective> directives;
27-
private final NodeParentTree<NamedNode> nodeParentTree;
27+
private final NodeParentTree<NamedNode<?>> nodeParentTree;
2828
private final TypeDefinitionRegistry typeDefinitionRegistry;
2929
private final Map<String, Object> context;
3030
private final GraphQLCodeRegistry.Builder codeRegistry;
@@ -72,7 +72,7 @@ public boolean containsDirective(String directiveName) {
7272
}
7373

7474
@Override
75-
public NodeParentTree<NamedNode> getNodeParentTree() {
75+
public NodeParentTree<NamedNode<?>> getNodeParentTree() {
7676
return nodeParentTree;
7777
}
7878

@@ -107,14 +107,14 @@ public GraphQLFieldDefinition getFieldDefinition() {
107107
}
108108

109109
@Override
110-
public DataFetcher getFieldDataFetcher() {
110+
public DataFetcher<?> getFieldDataFetcher() {
111111
assertNotNull(fieldDefinition, () -> "An output field must be in context to call this method");
112112
assertNotNull(fieldsContainer, () -> "An output field container must be in context to call this method");
113113
return codeRegistry.getDataFetcher(fieldsContainer, fieldDefinition);
114114
}
115115

116116
@Override
117-
public GraphQLFieldDefinition setFieldDataFetcher(DataFetcher newDataFetcher) {
117+
public GraphQLFieldDefinition setFieldDataFetcher(DataFetcher<?> newDataFetcher) {
118118
assertNotNull(fieldDefinition, () -> "An output field must be in context to call this method");
119119
assertNotNull(fieldsContainer, () -> "An output field container must be in context to call this method");
120120

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package graphql.schema.idl;
2+
3+
import graphql.Internal;
4+
import graphql.language.NamedNode;
5+
import graphql.schema.GraphQLCodeRegistry;
6+
import graphql.schema.GraphQLEnumType;
7+
import graphql.schema.GraphQLInputObjectType;
8+
import graphql.schema.GraphQLInterfaceType;
9+
import graphql.schema.GraphQLNamedType;
10+
import graphql.schema.GraphQLObjectType;
11+
import graphql.schema.GraphQLScalarType;
12+
import graphql.schema.GraphQLSchema;
13+
import graphql.schema.GraphQLSchemaElement;
14+
import graphql.schema.GraphQLTypeVisitorStub;
15+
import graphql.schema.GraphQLUnionType;
16+
import graphql.schema.SchemaTransformer;
17+
import graphql.util.TraversalControl;
18+
import graphql.util.TraverserContext;
19+
import graphql.util.TreeTransformerUtil;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import java.util.function.Function;
24+
25+
import static graphql.util.TraversalControl.CONTINUE;
26+
27+
@Internal
28+
class SchemaDirectiveWiringSchemaGeneratorPostProcessing implements SchemaGeneratorPostProcessing {
29+
30+
private final SchemaGeneratorDirectiveHelper generatorDirectiveHelper = new SchemaGeneratorDirectiveHelper();
31+
private final TypeDefinitionRegistry typeRegistry;
32+
private final RuntimeWiring runtimeWiring;
33+
private final GraphQLCodeRegistry.Builder codeRegistryBuilder;
34+
private final Map<String, Object> directiveBehaviourContext = new HashMap<>();
35+
36+
37+
public SchemaDirectiveWiringSchemaGeneratorPostProcessing(TypeDefinitionRegistry typeRegistry, RuntimeWiring runtimeWiring, GraphQLCodeRegistry.Builder codeRegistryBuilder) {
38+
this.typeRegistry = typeRegistry;
39+
this.runtimeWiring = runtimeWiring;
40+
this.codeRegistryBuilder = codeRegistryBuilder;
41+
}
42+
43+
44+
@Override
45+
public GraphQLSchema process(GraphQLSchema originalSchema) {
46+
GraphQLSchema newSchema = SchemaTransformer.transformSchema(originalSchema, new Visitor());
47+
return newSchema.transform(builder -> {
48+
// they could have changed the code registry so rebuild it
49+
GraphQLCodeRegistry codeRegistry = this.codeRegistryBuilder.build();
50+
builder.codeRegistry(codeRegistry);
51+
});
52+
}
53+
54+
public class Visitor extends GraphQLTypeVisitorStub {
55+
56+
private SchemaGeneratorDirectiveHelper.Parameters mkBehaviourParams() {
57+
return new SchemaGeneratorDirectiveHelper.Parameters(typeRegistry, runtimeWiring, directiveBehaviourContext, codeRegistryBuilder);
58+
}
59+
60+
private TraversalControl changOrContinue(GraphQLSchemaElement node, GraphQLSchemaElement newNode, TraverserContext<GraphQLSchemaElement> context) {
61+
if (node != newNode) {
62+
TreeTransformerUtil.changeNode(context, newNode);
63+
}
64+
return CONTINUE;
65+
}
66+
67+
private boolean isIntrospectionType(GraphQLNamedType type) {
68+
return type.getName().startsWith("__");
69+
}
70+
71+
private <T extends GraphQLNamedType> boolean notSuitable(T node, Function<T, NamedNode<?>> suitableFunc) {
72+
if (isIntrospectionType(node)) {
73+
return true;
74+
}
75+
NamedNode<?> definition = suitableFunc.apply(node);
76+
return definition == null;
77+
}
78+
79+
@Override
80+
public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext<GraphQLSchemaElement> context) {
81+
if (notSuitable(node, GraphQLObjectType::getDefinition)) {
82+
return CONTINUE;
83+
}
84+
GraphQLSchemaElement newNode = generatorDirectiveHelper.onObject(node, mkBehaviourParams());
85+
return changOrContinue(node, newNode, context);
86+
}
87+
88+
@Override
89+
public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext<GraphQLSchemaElement> context) {
90+
if (notSuitable(node, GraphQLInterfaceType::getDefinition)) {
91+
return CONTINUE;
92+
}
93+
GraphQLSchemaElement newNode = generatorDirectiveHelper.onInterface(node, mkBehaviourParams());
94+
return changOrContinue(node, newNode, context);
95+
}
96+
97+
@Override
98+
public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext<GraphQLSchemaElement> context) {
99+
if (notSuitable(node, GraphQLEnumType::getDefinition)) {
100+
return CONTINUE;
101+
}
102+
GraphQLSchemaElement newNode = generatorDirectiveHelper.onEnum(node, mkBehaviourParams());
103+
return changOrContinue(node, newNode, context);
104+
}
105+
106+
@Override
107+
public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext<GraphQLSchemaElement> context) {
108+
if (notSuitable(node, GraphQLInputObjectType::getDefinition)) {
109+
return CONTINUE;
110+
}
111+
GraphQLSchemaElement newNode = generatorDirectiveHelper.onInputObjectType(node, mkBehaviourParams());
112+
return changOrContinue(node, newNode, context);
113+
}
114+
115+
@Override
116+
public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext<GraphQLSchemaElement> context) {
117+
if (notSuitable(node, GraphQLScalarType::getDefinition)) {
118+
return CONTINUE;
119+
}
120+
GraphQLSchemaElement newNode = generatorDirectiveHelper.onScalar(node, mkBehaviourParams());
121+
return changOrContinue(node, newNode, context);
122+
}
123+
124+
@Override
125+
public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext<GraphQLSchemaElement> context) {
126+
if (notSuitable(node, GraphQLUnionType::getDefinition)) {
127+
return CONTINUE;
128+
}
129+
GraphQLSchemaElement newNode = generatorDirectiveHelper.onUnion(node, mkBehaviourParams());
130+
return changOrContinue(node, newNode, context);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)