-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Smarter schema visitor #3087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Smarter schema visitor #3087
Changes from all commits
f9ce2b3
c92b102
b869dce
07def39
33cea4a
18bf953
792f1cb
9f00d25
1e28acc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -144,14 +144,14 @@ private Object transformImpl(final GraphQLSchema schema, GraphQLSchemaElement sc | |
| final Map<String, GraphQLTypeReference> typeReferences = new LinkedHashMap<>(); | ||
|
|
||
| // first pass - general transformation | ||
| boolean schemaChanged = traverseAndTransform(dummyRoot, changedTypes, typeReferences, visitor, codeRegistry); | ||
| boolean schemaChanged = traverseAndTransform(dummyRoot, changedTypes, typeReferences, visitor, codeRegistry, schema); | ||
|
|
||
| // if we have changed any named elements AND we have type references referring to them then | ||
| // we need to make a second pass to replace these type references to the new names | ||
| if (!changedTypes.isEmpty()) { | ||
| boolean hasTypeRefsForChangedTypes = changedTypes.keySet().stream().anyMatch(typeReferences::containsKey); | ||
| if (hasTypeRefsForChangedTypes) { | ||
| replaceTypeReferences(dummyRoot, codeRegistry, changedTypes); | ||
| replaceTypeReferences(dummyRoot, schema, codeRegistry, changedTypes); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -170,7 +170,7 @@ private Object transformImpl(final GraphQLSchema schema, GraphQLSchemaElement sc | |
| } | ||
| } | ||
|
|
||
| private void replaceTypeReferences(DummyRoot dummyRoot, GraphQLCodeRegistry.Builder codeRegistry, Map<String, GraphQLNamedType> changedTypes) { | ||
| private void replaceTypeReferences(DummyRoot dummyRoot, GraphQLSchema schema, GraphQLCodeRegistry.Builder codeRegistry, Map<String, GraphQLNamedType> changedTypes) { | ||
| GraphQLTypeVisitor typeRefVisitor = new GraphQLTypeVisitorStub() { | ||
| @Override | ||
| public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference typeRef, TraverserContext<GraphQLSchemaElement> context) { | ||
|
|
@@ -182,10 +182,10 @@ public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference typeRef, | |
| return CONTINUE; | ||
| } | ||
| }; | ||
| traverseAndTransform(dummyRoot, new HashMap<>(), new HashMap<>(), typeRefVisitor, codeRegistry); | ||
| traverseAndTransform(dummyRoot, new HashMap<>(), new HashMap<>(), typeRefVisitor, codeRegistry, schema); | ||
| } | ||
|
|
||
| private boolean traverseAndTransform(DummyRoot dummyRoot, Map<String, GraphQLNamedType> changedTypes, Map<String, GraphQLTypeReference> typeReferences, GraphQLTypeVisitor visitor, GraphQLCodeRegistry.Builder codeRegistry) { | ||
| private boolean traverseAndTransform(DummyRoot dummyRoot, Map<String, GraphQLNamedType> changedTypes, Map<String, GraphQLTypeReference> typeReferences, GraphQLTypeVisitor visitor, GraphQLCodeRegistry.Builder codeRegistry, GraphQLSchema schema) { | ||
| List<NodeZipper<GraphQLSchemaElement>> zippers = new LinkedList<>(); | ||
| Map<GraphQLSchemaElement, NodeZipper<GraphQLSchemaElement>> zipperByNodeAfterTraversing = new LinkedHashMap<>(); | ||
| Map<GraphQLSchemaElement, NodeZipper<GraphQLSchemaElement>> zipperByOriginalNode = new LinkedHashMap<>(); | ||
|
|
@@ -195,7 +195,7 @@ private boolean traverseAndTransform(DummyRoot dummyRoot, Map<String, GraphQLNam | |
| Map<GraphQLSchemaElement, List<GraphQLSchemaElement>> reverseDependencies = new LinkedHashMap<>(); | ||
| Map<String, List<GraphQLSchemaElement>> typeRefReverseDependencies = new LinkedHashMap<>(); | ||
|
|
||
| TraverserVisitor<GraphQLSchemaElement> nodeTraverserVisitor = new TraverserVisitor<GraphQLSchemaElement>() { | ||
| TraverserVisitor<GraphQLSchemaElement> nodeTraverserVisitor = new TraverserVisitor<>() { | ||
| @Override | ||
| public TraversalControl enter(TraverserContext<GraphQLSchemaElement> context) { | ||
| GraphQLSchemaElement currentSchemaElement = context.thisNode(); | ||
|
|
@@ -271,6 +271,9 @@ public TraversalControl backRef(TraverserContext<GraphQLSchemaElement> context) | |
| if (codeRegistry != null) { | ||
| traverser.rootVar(GraphQLCodeRegistry.Builder.class, codeRegistry); | ||
| } | ||
| if (schema != null) { | ||
| traverser.rootVar(GraphQLSchema.class, schema); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We now add the source schema as a root variable - which visitors can now access |
||
|
|
||
| traverser.traverse(dummyRoot, nodeTraverserVisitor); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,9 +66,11 @@ public TraverserResult depthFirstFullSchema(List<GraphQLTypeVisitor> typeVisitor | |
| roots.addAll(schema.getAdditionalTypes()); | ||
| roots.addAll(schema.getDirectives()); | ||
| roots.addAll(schema.getSchemaDirectives()); | ||
| roots.addAll(schema.getSchemaAppliedDirectives()); | ||
| roots.add(schema.getIntrospectionSchemaType()); | ||
| TraverserDelegateListVisitor traverserDelegateListVisitor = new TraverserDelegateListVisitor(typeVisitors); | ||
| return initTraverser().rootVars(rootVars).traverse(roots, traverserDelegateListVisitor); | ||
| Traverser<GraphQLSchemaElement> traverser = initTraverser().rootVars(rootVars).rootVar(GraphQLSchema.class, schema); | ||
| return traverser.traverse(roots, traverserDelegateListVisitor); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here on SchemaTraverser - the schema being traversed is now a root variable |
||
| } | ||
|
|
||
| public TraverserResult depthFirst(GraphQLTypeVisitor graphQLTypeVisitor, GraphQLSchemaElement root) { | ||
|
|
@@ -131,7 +133,10 @@ private static class TraverserDelegateListVisitor implements TraverserVisitor<Gr | |
| @Override | ||
| public TraversalControl enter(TraverserContext<GraphQLSchemaElement> context) { | ||
| for (GraphQLTypeVisitor graphQLTypeVisitor : typeVisitors) { | ||
| context.thisNode().accept(context, graphQLTypeVisitor); | ||
| TraversalControl control = context.thisNode().accept(context, graphQLTypeVisitor); | ||
| if (control != CONTINUE) { | ||
| return control; | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was broken. I fixed it and now tests broke So I wrote a new test for this case - when NOT CONTINUE is returned |
||
| } | ||
| return CONTINUE; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package graphql.schema.visitor; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a different "control" that SchemaTraversalControl - so we can have better methods and "hide" the The only way to get a GraphQLSchemaTraversalControl (which is package level on creation) is by the helper methods and hence this simplifies the call API for consumers |
||
|
|
||
| import graphql.PublicApi; | ||
| import graphql.schema.GraphQLSchemaElement; | ||
| import graphql.util.TraversalControl; | ||
| import graphql.util.TraverserContext; | ||
| import graphql.util.TreeTransformerUtil; | ||
|
|
||
| /** | ||
| * This indicates what traversal control to apply during the visitation | ||
| * and can be created via calls to methods like {@link GraphQLSchemaVisitorEnvironment#ok()} | ||
| * or {@link GraphQLSchemaVisitorEnvironment#changeNode(GraphQLSchemaElement)} say | ||
| */ | ||
| @PublicApi | ||
| public class GraphQLSchemaTraversalControl { | ||
| private final GraphQLSchemaElement element; | ||
| private final Control control; | ||
|
|
||
| enum Control { | ||
| CONTINUE(TraversalControl.CONTINUE), | ||
| QUIT(TraversalControl.QUIT), | ||
| CHANGE(TraversalControl.CONTINUE), | ||
| DELETE(TraversalControl.CONTINUE), | ||
| INSERT_BEFORE(TraversalControl.CONTINUE), | ||
| INSERT_AFTER(TraversalControl.CONTINUE); | ||
|
|
||
| private final TraversalControl traversalControl; | ||
|
|
||
| Control(TraversalControl traversalControl) { | ||
| this.traversalControl = traversalControl; | ||
| } | ||
|
|
||
| public TraversalControl toTraversalControl() { | ||
| return traversalControl; | ||
| } | ||
| } | ||
|
|
||
| static final GraphQLSchemaTraversalControl CONTINUE = new GraphQLSchemaTraversalControl(Control.CONTINUE, null); | ||
| static final GraphQLSchemaTraversalControl QUIT = new GraphQLSchemaTraversalControl(Control.QUIT, null); | ||
| static final GraphQLSchemaTraversalControl DELETE = new GraphQLSchemaTraversalControl(Control.DELETE, null); | ||
|
|
||
| GraphQLSchemaTraversalControl(Control control, GraphQLSchemaElement element) { | ||
| this.element = element; | ||
| this.control = control; | ||
| } | ||
|
|
||
| GraphQLSchemaElement getElement() { | ||
| return element; | ||
| } | ||
|
|
||
| Control getControl() { | ||
| return control; | ||
| } | ||
|
|
||
| boolean isAbortive() { | ||
| return control == Control.QUIT; | ||
| } | ||
|
|
||
| boolean isMutative() { | ||
| return control == Control.DELETE || control == Control.CHANGE || control == Control.INSERT_AFTER || control == Control.INSERT_BEFORE; | ||
| } | ||
|
|
||
| TraversalControl toTraversalControl(TraverserContext<GraphQLSchemaElement> context) { | ||
| if (control == Control.CONTINUE || control == Control.QUIT) { | ||
| return control.toTraversalControl(); | ||
| } | ||
| if (control == Control.DELETE) { | ||
| TreeTransformerUtil.deleteNode(context); | ||
| } | ||
| if (control == Control.CHANGE) { | ||
| TreeTransformerUtil.changeNode(context, element); | ||
| } | ||
| if (control == Control.INSERT_AFTER) { | ||
| TreeTransformerUtil.insertAfter(context, element); | ||
| } | ||
| if (control == Control.INSERT_BEFORE) { | ||
| TreeTransformerUtil.insertAfter(context, element); | ||
| } | ||
| return TraversalControl.CONTINUE; | ||
| } | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See how this hodes the TreeTransformerUtil malarky AND also we can detect intention from the GraphQLSchemaTraversalControl as a value and not a side effect |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ordered all the methods here in alphabetic order so I can know what's what