diff --git a/src/main/java/graphql/schema/GraphQLArgument.java b/src/main/java/graphql/schema/GraphQLArgument.java index 95495fb8d7..87c017d20d 100644 --- a/src/main/java/graphql/schema/GraphQLArgument.java +++ b/src/main/java/graphql/schema/GraphQLArgument.java @@ -4,6 +4,8 @@ import graphql.PublicApi; import graphql.language.InputValueDefinition; import graphql.util.FpKit; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.Collections; @@ -71,8 +73,8 @@ private GraphQLArgument(String name, String description, GraphQLInputType type, } - void replaceTypeReferences(Map typeMap) { - type = (GraphQLInputType) new SchemaUtil().resolveTypeReference(type, typeMap); + void replaceType(GraphQLInputType type) { + this.type = type; } @Override @@ -140,6 +142,16 @@ public static Builder newArgument(GraphQLArgument existing) { return new Builder(existing); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLArgument(this, context); + } + + @Override + public List getChildren() { + return Collections.singletonList(type); + } + public static class Builder { private String name; diff --git a/src/main/java/graphql/schema/GraphQLEnumType.java b/src/main/java/graphql/schema/GraphQLEnumType.java index 8277740fef..587d73b019 100644 --- a/src/main/java/graphql/schema/GraphQLEnumType.java +++ b/src/main/java/graphql/schema/GraphQLEnumType.java @@ -6,6 +6,8 @@ import graphql.PublicApi; import graphql.language.EnumTypeDefinition; import graphql.language.EnumValue; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -175,6 +177,16 @@ public GraphQLEnumType transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLEnumType(this, context); + } + + @Override + public List getChildren() { + return new ArrayList<>(valueDefinitionMap.values()); + } + public static Builder newEnum() { return new Builder(); } diff --git a/src/main/java/graphql/schema/GraphQLEnumValueDefinition.java b/src/main/java/graphql/schema/GraphQLEnumValueDefinition.java index 322d3af809..fd561f0633 100644 --- a/src/main/java/graphql/schema/GraphQLEnumValueDefinition.java +++ b/src/main/java/graphql/schema/GraphQLEnumValueDefinition.java @@ -4,6 +4,8 @@ import graphql.DirectivesUtil; import graphql.Internal; import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -105,6 +107,12 @@ public GraphQLEnumValueDefinition transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLEnumValueDefinition(this, context); + } + + public static Builder newEnumValueDefinition() { return new Builder(); } diff --git a/src/main/java/graphql/schema/GraphQLFieldDefinition.java b/src/main/java/graphql/schema/GraphQLFieldDefinition.java index d01eafca67..bd025ba5ab 100644 --- a/src/main/java/graphql/schema/GraphQLFieldDefinition.java +++ b/src/main/java/graphql/schema/GraphQLFieldDefinition.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.FieldDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.Collections; @@ -64,9 +66,8 @@ public GraphQLFieldDefinition(String name, String description, GraphQLOutputType this.definition = definition; } - - void replaceTypeReferences(Map typeMap) { - this.type = (GraphQLOutputType) new SchemaUtil().resolveTypeReference(this.type, typeMap); + void replaceType(GraphQLOutputType type) { + this.type = type; } @Override @@ -144,6 +145,19 @@ public GraphQLFieldDefinition transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLFieldDefinition(this, context); + } + + @Override + public List getChildren() { + List children = new ArrayList<>(); + children.add(type); + children.addAll(arguments); + return children; + } + public static Builder newFieldDefinition(GraphQLFieldDefinition existing) { return new Builder(existing); } diff --git a/src/main/java/graphql/schema/GraphQLInputObjectField.java b/src/main/java/graphql/schema/GraphQLInputObjectField.java index a25c7b1de8..ad1dec7843 100644 --- a/src/main/java/graphql/schema/GraphQLInputObjectField.java +++ b/src/main/java/graphql/schema/GraphQLInputObjectField.java @@ -4,8 +4,11 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.InputValueDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -59,8 +62,8 @@ public GraphQLInputObjectField(String name, String description, GraphQLInputType this.definition = definition; } - void replaceTypeReferences(Map typeMap) { - type = (GraphQLInputType) new SchemaUtil().resolveTypeReference(type, typeMap); + void replaceType(GraphQLInputType type) { + this.type = type; } @Override @@ -103,6 +106,16 @@ public GraphQLInputObjectField transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLInputObjectField(this, context); + } + + @Override + public List getChildren() { + return Collections.singletonList(type); + } + public static Builder newInputObjectField(GraphQLInputObjectField existing) { return new Builder(existing); } diff --git a/src/main/java/graphql/schema/GraphQLInputObjectType.java b/src/main/java/graphql/schema/GraphQLInputObjectType.java index e5abbaf340..344fcf86d3 100644 --- a/src/main/java/graphql/schema/GraphQLInputObjectType.java +++ b/src/main/java/graphql/schema/GraphQLInputObjectType.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.InputObjectTypeDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -110,6 +112,16 @@ public GraphQLInputObjectType transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLInputObjectType(this, context); + } + + @Override + public List getChildren() { + return new ArrayList<>(fieldMap.values()); + } + public static Builder newInputObject(GraphQLInputObjectType existing) { return new Builder(existing); } diff --git a/src/main/java/graphql/schema/GraphQLInterfaceType.java b/src/main/java/graphql/schema/GraphQLInterfaceType.java index b800ce15d2..c30f70ccfc 100644 --- a/src/main/java/graphql/schema/GraphQLInterfaceType.java +++ b/src/main/java/graphql/schema/GraphQLInterfaceType.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.InterfaceTypeDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.Collections; @@ -125,6 +127,16 @@ public GraphQLInterfaceType transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLInterfaceType(this, context); + } + + @Override + public List getChildren() { + return new ArrayList<>(fieldDefinitionsByName.values()); + } + public static Builder newInterface() { return new Builder(); } diff --git a/src/main/java/graphql/schema/GraphQLList.java b/src/main/java/graphql/schema/GraphQLList.java index 12dd5de2cc..f2615f0e3b 100644 --- a/src/main/java/graphql/schema/GraphQLList.java +++ b/src/main/java/graphql/schema/GraphQLList.java @@ -2,7 +2,11 @@ import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; +import java.util.Collections; +import java.util.List; import java.util.Map; import static graphql.Assert.assertNotNull; @@ -41,8 +45,8 @@ public GraphQLType getWrappedType() { return wrappedType; } - void replaceTypeReferences(Map typeMap) { - wrappedType = new SchemaUtil().resolveTypeReference(wrappedType, typeMap); + void replaceType(GraphQLType type) { + wrappedType = type; } @Override @@ -65,4 +69,14 @@ public int hashCode() { public String getName() { return null; } + + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLList(this, context); + } + + @Override + public List getChildren() { + return Collections.singletonList(wrappedType); + } } diff --git a/src/main/java/graphql/schema/GraphQLNonNull.java b/src/main/java/graphql/schema/GraphQLNonNull.java index 1a7d0193a7..4a0e2cab1a 100644 --- a/src/main/java/graphql/schema/GraphQLNonNull.java +++ b/src/main/java/graphql/schema/GraphQLNonNull.java @@ -2,7 +2,11 @@ import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; +import java.util.Collections; +import java.util.List; import java.util.Map; import static graphql.Assert.assertNotNull; @@ -41,8 +45,9 @@ public GraphQLType getWrappedType() { return wrappedType; } - void replaceTypeReferences(Map typeMap) { - wrappedType = new SchemaUtil().resolveTypeReference(wrappedType, typeMap); + + void replaceType(GraphQLType type) { + wrappedType = type; } @Override @@ -72,4 +77,14 @@ public String toString() { public String getName() { return null; } + + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLNonNull(this, context); + } + + @Override + public List getChildren() { + return Collections.singletonList(wrappedType); + } } diff --git a/src/main/java/graphql/schema/GraphQLObjectType.java b/src/main/java/graphql/schema/GraphQLObjectType.java index 62f9ff9453..cfee27cbb9 100644 --- a/src/main/java/graphql/schema/GraphQLObjectType.java +++ b/src/main/java/graphql/schema/GraphQLObjectType.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.ObjectTypeDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -60,10 +62,8 @@ public GraphQLObjectType(String name, String description, List typeMap) { - this.interfaces = this.interfaces.stream() - .map(type -> (GraphQLOutputType) new SchemaUtil().resolveTypeReference(type, typeMap)) - .collect(Collectors.toList()); + void replaceInterfaces(List interfaces) { + this.interfaces = interfaces; } private void buildDefinitionMap(List fieldDefinitions) { @@ -138,6 +138,18 @@ public GraphQLObjectType transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLObjectType(this, context); + } + + @Override + public List getChildren() { + List children = new ArrayList<>(fieldDefinitionsByName.values()); + children.addAll(interfaces); + return children; + } + public static Builder newObject() { return new Builder(); } diff --git a/src/main/java/graphql/schema/GraphQLScalarType.java b/src/main/java/graphql/schema/GraphQLScalarType.java index ee1a160806..d9ec6cf911 100644 --- a/src/main/java/graphql/schema/GraphQLScalarType.java +++ b/src/main/java/graphql/schema/GraphQLScalarType.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.ScalarTypeDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -105,6 +107,11 @@ public GraphQLScalarType transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLScalarType(this, context); + } + public static Builder newScalar() { return new Builder(); } diff --git a/src/main/java/graphql/schema/GraphQLType.java b/src/main/java/graphql/schema/GraphQLType.java index 0b3f3a67cd..b1e5af81fb 100644 --- a/src/main/java/graphql/schema/GraphQLType.java +++ b/src/main/java/graphql/schema/GraphQLType.java @@ -2,6 +2,11 @@ import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import java.util.Collections; +import java.util.List; /** * All types in graphql have a name @@ -12,4 +17,24 @@ public interface GraphQLType { * @return the name of the type which MUST fit within the regular expression {@code [_A-Za-z][_0-9A-Za-z]*} */ String getName(); + + /** + * @return returns all types directly associated with this node + */ + default List getChildren() { return Collections.emptyList(); } + + /** + * Double-dispatch entry point. + * + * It allows to travers a given non-trivial graphQL type and move from root to nested or enclosed types. + * + * This implements similar pattern as {@link graphql.language.Node}, see accept(...) for more details about the pattern. + * + * @param context TraverserContext bound to this graphQL type object + * @param visitor Visitor instance that performs actual processing on the types(s) + * + * @return Result of Visitor's operation. + * Note! Visitor's operation might return special results to control traversal process. + */ + TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor); } diff --git a/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java b/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java new file mode 100644 index 0000000000..d436c0d588 --- /dev/null +++ b/src/main/java/graphql/schema/GraphQLTypeCollectingVisitor.java @@ -0,0 +1,118 @@ +package graphql.schema; + +import graphql.AssertException; +import graphql.Internal; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import java.util.HashMap; +import java.util.Map; + +import static java.lang.String.format; + +@Internal +public class GraphQLTypeCollectingVisitor extends GraphQLTypeVisitorStub { + + private final Map result = new HashMap<>(); + + public GraphQLTypeCollectingVisitor() { + } + + @Override + public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext context) { + assertTypeUniqueness(node, result); + save(node.getName(), node); + return super.visitGraphQLEnumType(node, context); + } + + @Override + public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + assertTypeUniqueness(node, result); + save(node.getName(), node); + return super.visitGraphQLScalarType(node, context); + } + + @Override + public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { + if (isTypeReference(node.getName())) { + assertTypeUniqueness(node, result); + } else { + save(node.getName(), node); + } + return super.visitGraphQLObjectType(node, context); + } + + @Override + public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context) { + if (isTypeReference(node.getName())) { + assertTypeUniqueness(node, result); + } else { + save(node.getName(), node); + } + return super.visitGraphQLInputObjectType(node, context); + } + + @Override + public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext context) { + if (isTypeReference(node.getName())) { + assertTypeUniqueness(node, result); + } else { + save(node.getName(), node); + } + + return super.visitGraphQLInterfaceType(node, context); + } + + @Override + public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context) { + assertTypeUniqueness(node, result); + save(node.getName(), node); + return super.visitGraphQLUnionType(node, context); + } + + @Override + public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + + return super.visitGraphQLFieldDefinition(node, context); + } + + // TODO: eh? Isn't it similar to assertTypeUniqueness? + private boolean isTypeReference(String name) { + return result.containsKey(name) && !(result.get(name) instanceof GraphQLTypeReference); + } + + private void save(String name, GraphQLType type) { + result.put(name, type); + } + + + /* +From http://facebook.github.io/graphql/#sec-Type-System + + All types within a GraphQL schema must have unique names. No two provided types may have the same name. + No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types). + +Enforcing this helps avoid problems later down the track fo example https://github.com/graphql-java/graphql-java/issues/373 +*/ + private void assertTypeUniqueness(GraphQLType type, Map result) { + GraphQLType existingType = result.get(type.getName()); + // do we have an existing definition + if (existingType != null) { + // type references are ok + if (!(existingType instanceof GraphQLTypeReference || type instanceof GraphQLTypeReference)) + // object comparison here is deliberate + if (existingType != type) { + throw new AssertException(format("All types within a GraphQL schema must have unique names. No two provided types may have the same name.\n" + + "No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types).\n" + + "You have redefined the type '%s' from being a '%s' to a '%s'", + type.getName(), existingType.getClass().getSimpleName(), type.getClass().getSimpleName())); + } + } + } + + public Map getResult() { + return result; + } + + +} diff --git a/src/main/java/graphql/schema/GraphQLTypeReference.java b/src/main/java/graphql/schema/GraphQLTypeReference.java index c97a215ae2..e1540be99a 100644 --- a/src/main/java/graphql/schema/GraphQLTypeReference.java +++ b/src/main/java/graphql/schema/GraphQLTypeReference.java @@ -3,6 +3,9 @@ import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + import static graphql.Assert.assertValidName; /** @@ -36,4 +39,9 @@ public GraphQLTypeReference(String name) { public String getName() { return name; } + + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLTypeReference(this, context); + } } diff --git a/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java b/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java new file mode 100644 index 0000000000..8e48961783 --- /dev/null +++ b/src/main/java/graphql/schema/GraphQLTypeResolvingVisitor.java @@ -0,0 +1,85 @@ +package graphql.schema; + +import graphql.Internal; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import java.util.Map; +import java.util.stream.Collectors; + +import static graphql.Assert.assertNotNull; + +@Internal +public class GraphQLTypeResolvingVisitor extends GraphQLTypeVisitorStub { + protected final Map typeMap; + + public GraphQLTypeResolvingVisitor(Map typeMap) { + this.typeMap = typeMap; + } + + @Override + public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { + + node.replaceInterfaces(node.getInterfaces().stream() + .map(type -> (GraphQLOutputType) typeMap.get(type.getName())) + .collect(Collectors.toList())); + return super.visitGraphQLObjectType(node, context); + } + + @Override + public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context) { + + node.replaceTypes(node.getTypes().stream() + .map(type -> (GraphQLOutputType) typeMap.get(type.getName())) + .collect(Collectors.toList())); + return super.visitGraphQLUnionType(node, context); + } + + @Override + public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context) { + + final GraphQLType resolvedType = typeMap.get(node.getName()); + assertNotNull(resolvedType, "type %s not found in schema", node.getName()); + context.getParentContext().thisNode().accept(context, new TypeRefResolvingVisitor(resolvedType)); + return super.visitGraphQLTypeReference(node, context); + } + + + private class TypeRefResolvingVisitor extends GraphQLTypeVisitorStub { + protected final GraphQLType resolvedType; + + TypeRefResolvingVisitor(GraphQLType resolvedType) { + this.resolvedType = resolvedType; + } + + @Override + public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + node.replaceType((GraphQLOutputType) resolvedType); + return super.visitGraphQLFieldDefinition(node, context); + } + + @Override + public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext context) { + node.replaceType((GraphQLInputType) resolvedType); + return super.visitGraphQLArgument(node, context); + } + + @Override + public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context) { + node.replaceType((GraphQLInputType) resolvedType); + return super.visitGraphQLInputObjectField(node, context); + } + + @Override + public TraversalControl visitGraphQLList(GraphQLList node, TraverserContext context) { + node.replaceType(resolvedType); + return super.visitGraphQLList(node, context); + } + + @Override + public TraversalControl visitGraphQLNonNull(GraphQLNonNull node, TraverserContext context) { + node.replaceType(resolvedType); + return super.visitGraphQLNonNull(node, context); + } + } +} diff --git a/src/main/java/graphql/schema/GraphQLTypeVisitor.java b/src/main/java/graphql/schema/GraphQLTypeVisitor.java new file mode 100644 index 0000000000..49e5975711 --- /dev/null +++ b/src/main/java/graphql/schema/GraphQLTypeVisitor.java @@ -0,0 +1,73 @@ +package graphql.schema; + +import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +@PublicApi +public interface GraphQLTypeVisitor { + TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext context); + + TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext context); + + TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext context); + + TraversalControl visitGraphQLEnumValueDefinition(GraphQLEnumValueDefinition node, TraverserContext context); + + TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context); + + TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context); + + TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context); + + TraversalControl visitGraphQLList(GraphQLList node, TraverserContext context); + + TraversalControl visitGraphQLNonNull(GraphQLNonNull node, TraverserContext context); + + TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context); + + TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context); + + TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context); + + TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context); + + // Marker interfaces + default TraversalControl visitGraphQLModifiedType(GraphQLModifiedType node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLCompositeType(GraphQLCompositeType node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLDirectiveContainer(GraphQLDirectiveContainer node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLFieldsContainer(GraphQLFieldsContainer node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLInputFieldsContainer(GraphQLInputFieldsContainer node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLInputType(GraphQLInputType node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLNullableType(GraphQLNullableType node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLOutputType(GraphQLOutputType node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + default TraversalControl visitGraphQLUnmodifiedType(GraphQLUnmodifiedType node, TraverserContext context) { + throw new UnsupportedOperationException(); + } + + +} diff --git a/src/main/java/graphql/schema/GraphQLTypeVisitorStub.java b/src/main/java/graphql/schema/GraphQLTypeVisitorStub.java new file mode 100644 index 0000000000..54b569727d --- /dev/null +++ b/src/main/java/graphql/schema/GraphQLTypeVisitorStub.java @@ -0,0 +1,83 @@ +package graphql.schema; + +import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; + +import static graphql.util.TraversalControl.CONTINUE; + +/** + * Base implementation of {@link GraphQLTypeVisitor} for convenience. + * Overwrite only required methods and/or {@link #visitGraphQLType(GraphQLType, TraverserContext)} as default fallback. + */ +@PublicApi +public class GraphQLTypeVisitorStub implements GraphQLTypeVisitor { + @Override + public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLEnumValueDefinition(GraphQLEnumValueDefinition node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLList(GraphQLList node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLNonNull(GraphQLNonNull node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + @Override + public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context) { + return visitGraphQLType(node, context); + } + + protected TraversalControl visitGraphQLType(GraphQLType node, TraverserContext context) { + return CONTINUE; + } +} diff --git a/src/main/java/graphql/schema/GraphQLUnionType.java b/src/main/java/graphql/schema/GraphQLUnionType.java index a8a86cb10b..2d2629fc39 100644 --- a/src/main/java/graphql/schema/GraphQLUnionType.java +++ b/src/main/java/graphql/schema/GraphQLUnionType.java @@ -4,6 +4,8 @@ import graphql.Internal; import graphql.PublicApi; import graphql.language.UnionTypeDefinition; +import graphql.util.TraversalControl; +import graphql.util.TraverserContext; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -61,10 +63,8 @@ public GraphQLUnionType(String name, String description, List this.directives = directives; } - void replaceTypeReferences(Map typeMap) { - this.types = this.types.stream() - .map(type -> (GraphQLOutputType) new SchemaUtil().resolveTypeReference(type, typeMap)) - .collect(Collectors.toList()); + void replaceTypes(List types) { + this.types = types; } /** @@ -112,6 +112,16 @@ public GraphQLUnionType transform(Consumer builderConsumer) { return builder.build(); } + @Override + public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) { + return visitor.visitGraphQLUnionType(this, context); + } + + @Override + public List getChildren() { + return new ArrayList<>(types); + } + public static Builder newUnionType() { return new Builder(); } diff --git a/src/main/java/graphql/schema/SchemaUtil.java b/src/main/java/graphql/schema/SchemaUtil.java index 98faf2a7bc..516d452e74 100644 --- a/src/main/java/graphql/schema/SchemaUtil.java +++ b/src/main/java/graphql/schema/SchemaUtil.java @@ -1,161 +1,53 @@ package graphql.schema; -import graphql.Assert; -import graphql.AssertException; import graphql.Internal; import graphql.introspection.Introspection; import java.util.ArrayList; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import static graphql.schema.GraphQLTypeUtil.isList; -import static graphql.schema.GraphQLTypeUtil.isNonNull; -import static graphql.schema.GraphQLTypeUtil.unwrapOne; -import static java.lang.String.format; - @Internal public class SchemaUtil { - @SuppressWarnings("StatementWithEmptyBody") - private void collectTypes(GraphQLType root, Map result) { - if (isNonNull(root)) { - collectTypes(unwrapOne(root), result); - } else if (isList(root)) { - collectTypes(unwrapOne(root), result); - } else if (root instanceof GraphQLEnumType) { - assertTypeUniqueness(root, result); - result.put(root.getName(), root); - } else if (root instanceof GraphQLScalarType) { - assertTypeUniqueness(root, result); - result.put(root.getName(), root); - } else if (root instanceof GraphQLObjectType) { - collectTypesForObjects((GraphQLObjectType) root, result); - } else if (root instanceof GraphQLInterfaceType) { - collectTypesForInterfaces((GraphQLInterfaceType) root, result); - } else if (root instanceof GraphQLUnionType) { - collectTypesForUnions((GraphQLUnionType) root, result); - } else if (root instanceof GraphQLInputObjectType) { - collectTypesForInputObjects((GraphQLInputObjectType) root, result); - } else if (root instanceof GraphQLTypeReference) { - // nothing to do - } else { - Assert.assertShouldNeverHappen("Unknown type %s", root); - } - } - - /* - From http://facebook.github.io/graphql/#sec-Type-System - - All types within a GraphQL schema must have unique names. No two provided types may have the same name. - No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types). - - Enforcing this helps avoid problems later down the track fo example https://github.com/graphql-java/graphql-java/issues/373 - */ - private void assertTypeUniqueness(GraphQLType type, Map result) { - GraphQLType existingType = result.get(type.getName()); - // do we have an existing definition - if (existingType != null) { - // type references are ok - if (!(existingType instanceof GraphQLTypeReference || type instanceof GraphQLTypeReference)) - // object comparison here is deliberate - if (existingType != type) { - throw new AssertException(format("All types within a GraphQL schema must have unique names. No two provided types may have the same name.\n" + - "No provided type may have a name which conflicts with any built in types (including Scalar and Introspection types).\n" + - "You have redefined the type '%s' from being a '%s' to a '%s'", - type.getName(), existingType.getClass().getSimpleName(), type.getClass().getSimpleName())); - } - } - } - - private void collectTypesForUnions(GraphQLUnionType unionType, Map result) { - assertTypeUniqueness(unionType, result); - - result.put(unionType.getName(), unionType); - for (GraphQLType type : unionType.getTypes()) { - collectTypes(type, result); - } + private static final TypeTraverser TRAVERSER = new TypeTraverser(); - } - private void collectTypesForInterfaces(GraphQLInterfaceType interfaceType, Map result) { - if (result.containsKey(interfaceType.getName()) && !(result.get(interfaceType.getName()) instanceof GraphQLTypeReference)) { - assertTypeUniqueness(interfaceType, result); - return; - } - result.put(interfaceType.getName(), interfaceType); + Map allTypes(final GraphQLSchema schema, final Set additionalTypes) { + List roots = new ArrayList() {{ + add(schema.getQueryType()); - // this deliberately has open field visibility as its collecting on the whole schema - for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) { - collectTypes(fieldDefinition.getType(), result); - for (GraphQLArgument fieldArgument : fieldDefinition.getArguments()) { - collectTypes(fieldArgument.getType(), result); + if (schema.isSupportingMutations()) { + add(schema.getMutationType()); } - } - } - - private void collectTypesForObjects(GraphQLObjectType objectType, Map result) { - if (result.containsKey(objectType.getName()) && !(result.get(objectType.getName()) instanceof GraphQLTypeReference)) { - assertTypeUniqueness(objectType, result); - return; - } - result.put(objectType.getName(), objectType); + if (schema.isSupportingSubscriptions()) { + add(schema.getSubscriptionType()); + } - // this deliberately has open field visibility as its collecting on the whole schema - for (GraphQLFieldDefinition fieldDefinition : objectType.getFieldDefinitions()) { - collectTypes(fieldDefinition.getType(), result); - for (GraphQLArgument fieldArgument : fieldDefinition.getArguments()) { - collectTypes(fieldArgument.getType(), result); + if (additionalTypes != null) { + addAll(additionalTypes); } - } - for (GraphQLOutputType interfaceType : objectType.getInterfaces()) { - collectTypes(interfaceType, result); - } - } - private void collectTypesForInputObjects(GraphQLInputObjectType objectType, Map result) { - if (result.containsKey(objectType.getName()) && !(result.get(objectType.getName()) instanceof GraphQLTypeReference)) { - assertTypeUniqueness(objectType, result); - return; - } - result.put(objectType.getName(), objectType); + add(Introspection.__Schema); + }}; - for (GraphQLInputObjectField fieldDefinition : objectType.getFields()) { - collectTypes(fieldDefinition.getType(), result); - } + GraphQLTypeCollectingVisitor visitor = new GraphQLTypeCollectingVisitor(); + TRAVERSER.depthFirst(visitor, roots); + return visitor.getResult(); } - Map allTypes(GraphQLSchema schema, Set additionalTypes) { - Map typesByName = new LinkedHashMap<>(); - collectTypes(schema.getQueryType(), typesByName); - if (schema.isSupportingMutations()) { - collectTypes(schema.getMutationType(), typesByName); - } - if (schema.isSupportingSubscriptions()) { - collectTypes(schema.getSubscriptionType(), typesByName); - } - if (additionalTypes != null) { - for (GraphQLType type : additionalTypes) { - collectTypes(type, typesByName); - } - } - collectTypes(Introspection.__Schema, typesByName); - return typesByName; - } - /* * Indexes GraphQLObject types registered with the provided schema by implemented GraphQLInterface name * * This helps in accelerates/simplifies collecting types that implement a certain interface * * Provided to replace {@link #findImplementations(graphql.schema.GraphQLSchema, graphql.schema.GraphQLInterfaceType)} - * + * */ Map> groupImplementations(GraphQLSchema schema) { Map> result = new HashMap<>(); @@ -204,52 +96,8 @@ public List findImplementations(GraphQLSchema schema, GraphQL return result; } - void replaceTypeReferences(GraphQLSchema schema) { - Map typeMap = schema.getTypeMap(); - for (GraphQLType type : typeMap.values()) { - if (type instanceof GraphQLFieldsContainer) { - resolveTypeReferencesForFieldsContainer((GraphQLFieldsContainer) type, typeMap); - } - if (type instanceof GraphQLInputFieldsContainer) { - resolveTypeReferencesForInputFieldsContainer((GraphQLInputFieldsContainer) type, typeMap); - } - if (type instanceof GraphQLObjectType) { - ((GraphQLObjectType) type).replaceTypeReferences(typeMap); - } - if (type instanceof GraphQLUnionType) { - ((GraphQLUnionType) type).replaceTypeReferences(typeMap); - } - } - } - - private void resolveTypeReferencesForFieldsContainer(GraphQLFieldsContainer fieldsContainer, Map typeMap) { - for (GraphQLFieldDefinition fieldDefinition : fieldsContainer.getFieldDefinitions()) { - fieldDefinition.replaceTypeReferences(typeMap); - for (GraphQLArgument argument : fieldDefinition.getArguments()) { - argument.replaceTypeReferences(typeMap); - } - } - } - - private void resolveTypeReferencesForInputFieldsContainer(GraphQLInputFieldsContainer fieldsContainer, Map typeMap) { - for (GraphQLInputObjectField fieldDefinition : fieldsContainer.getFieldDefinitions()) { - fieldDefinition.replaceTypeReferences(typeMap); - } - } - - GraphQLType resolveTypeReference(GraphQLType type, Map typeMap) { - if (type instanceof GraphQLTypeReference || typeMap.containsKey(type.getName())) { - GraphQLType resolvedType = typeMap.get(type.getName()); - Assert.assertTrue(resolvedType != null, "type %s not found in schema", type.getName()); - return resolvedType; - } - if (isList(type)) { - ((GraphQLList) type).replaceTypeReferences(typeMap); - } - if (isNonNull(type)) { - ((GraphQLNonNull) type).replaceTypeReferences(typeMap); - } - return type; + final Map typeMap = schema.getTypeMap(); + TRAVERSER.depthFirst(new GraphQLTypeResolvingVisitor(typeMap), typeMap.values()); } } diff --git a/src/main/java/graphql/schema/TypeTraverser.java b/src/main/java/graphql/schema/TypeTraverser.java new file mode 100644 index 0000000000..0bb151a41a --- /dev/null +++ b/src/main/java/graphql/schema/TypeTraverser.java @@ -0,0 +1,82 @@ +package graphql.schema; + + +import graphql.Internal; +import graphql.PublicApi; +import graphql.util.TraversalControl; +import graphql.util.Traverser; +import graphql.util.TraverserContext; +import graphql.util.TraverserResult; +import graphql.util.TraverserVisitor; + + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import static graphql.util.TraversalControl.CONTINUE; + +@PublicApi +public class TypeTraverser { + + + private final Function> getChildren; + + public TypeTraverser(Function> getChildren) { + this.getChildren = getChildren; + } + + public TypeTraverser() { + this(GraphQLType::getChildren); + } + + public TraverserResult depthFirst(GraphQLTypeVisitor graphQLTypeVisitor, GraphQLType root) { + return depthFirst(graphQLTypeVisitor, Collections.singletonList(root)); + } + + public TraverserResult depthFirst(final GraphQLTypeVisitor graphQLTypeVisitor, Collection roots) { + return depthFirst(initTraverser(), new TraverserDelegateVisitor(graphQLTypeVisitor), roots); + } + + public TraverserResult depthFirst(final GraphQLTypeVisitor graphQLTypeVisitor, + Collection roots, + Map types) { + return depthFirst(initTraverser().rootVar(TypeTraverser.class, types), new TraverserDelegateVisitor(graphQLTypeVisitor), roots); + } + + public TraverserResult depthFirst(final Traverser traverser, + final TraverserDelegateVisitor traverserDelegateVisitor, + Collection roots) { + return doTraverse(traverser, roots, traverserDelegateVisitor); + } + + private Traverser initTraverser() { + return Traverser.depthFirst(getChildren); + } + + private TraverserResult doTraverse(Traverser traverser, Collection roots, TraverserDelegateVisitor traverserDelegateVisitor) { + return traverser.traverse(roots,traverserDelegateVisitor); + } + + private static class TraverserDelegateVisitor implements TraverserVisitor { + private final GraphQLTypeVisitor before; + + TraverserDelegateVisitor(GraphQLTypeVisitor delegate) { + this.before = delegate; + + } + + @Override + public TraversalControl enter(TraverserContext context) { + return context.thisNode().accept(context, before); + } + + @Override + public TraversalControl leave(TraverserContext context) { + return CONTINUE; + } + } + +} diff --git a/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy b/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy index 3550665626..b34e3aa5dd 100644 --- a/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy +++ b/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy @@ -19,21 +19,22 @@ class StarWarsIntrospectionTests extends Specification { """ def expected = [ __schema: [types: - [[name: 'QueryType'], + [[name: 'Human'], + [name: '__TypeKind'], + [name: '__Field'], [name: 'Character'], - [name: 'String'], - [name: 'Episode'], - [name: 'Human'], - [name: 'Droid'], [name: '__Schema'], [name: '__Type'], - [name: '__TypeKind'], - [name: '__Field'], + [name: '__EnumValue'], + [name: '__DirectiveLocation'], + [name: 'String'], + [name: 'Droid'], + [name: 'Episode'], [name: '__InputValue'], [name: 'Boolean'], - [name: '__EnumValue'], - [name: '__Directive'], - [name: '__DirectiveLocation']] + [name: 'QueryType'], + [name: '__Directive'] + ] ] ] diff --git a/src/test/groovy/graphql/UnionTest.groovy b/src/test/groovy/graphql/UnionTest.groovy index bf6b757047..63745e2f41 100644 --- a/src/test/groovy/graphql/UnionTest.groovy +++ b/src/test/groovy/graphql/UnionTest.groovy @@ -37,8 +37,8 @@ class UnionTest extends Specification { ], interfaces : null, possibleTypes: [ - [name: 'Person'], [name: 'Cat'], + [name: 'Person'], [name: 'Dog'] ], enumValues : null, diff --git a/src/test/groovy/graphql/schema/GraphQLTypeVisitorStubTest.groovy b/src/test/groovy/graphql/schema/GraphQLTypeVisitorStubTest.groovy new file mode 100644 index 0000000000..22ec38ba09 --- /dev/null +++ b/src/test/groovy/graphql/schema/GraphQLTypeVisitorStubTest.groovy @@ -0,0 +1,40 @@ +package graphql.schema + +import graphql.Scalars +import graphql.util.TraversalControl +import graphql.util.TraverserContext +import spock.lang.Specification +import spock.lang.Unroll + +class GraphQLTypeVisitorStubTest extends Specification { + + + @Unroll + def "#visitMethod scalar type"() { + given: + GraphQLTypeVisitorStub typeVisitorStub = Spy(GraphQLTypeVisitorStub, constructorArgs: []) + TraverserContext context = Mock(TraverserContext) + + when: + def control = typeVisitorStub."$visitMethod"(node, context) + then: + typeVisitorStub.visitGraphQLType(node, context) >> TraversalControl.CONTINUE + control == TraversalControl.CONTINUE + + where: + node | visitMethod + Mock(GraphQLScalarType) | 'visitGraphQLScalarType' + Mock(GraphQLArgument) | 'visitGraphQLArgument' + Mock(GraphQLInterfaceType) | 'visitGraphQLInterfaceType' + Mock(GraphQLEnumType) | 'visitGraphQLEnumType' + Mock(GraphQLEnumValueDefinition) | 'visitGraphQLEnumValueDefinition' + Mock(GraphQLFieldDefinition) | 'visitGraphQLFieldDefinition' + Mock(GraphQLInputObjectField) | 'visitGraphQLInputObjectField' + Mock(GraphQLInputObjectType) | 'visitGraphQLInputObjectType' + Mock(GraphQLList) | 'visitGraphQLList' + Mock(GraphQLNonNull) | 'visitGraphQLNonNull' + Mock(GraphQLObjectType) | 'visitGraphQLObjectType' + Mock(GraphQLTypeReference) | 'visitGraphQLTypeReference' + Mock(GraphQLUnionType) | 'visitGraphQLUnionType' + } +} diff --git a/src/test/groovy/graphql/schema/TypeTraverserTest.groovy b/src/test/groovy/graphql/schema/TypeTraverserTest.groovy new file mode 100644 index 0000000000..03d3a94bb0 --- /dev/null +++ b/src/test/groovy/graphql/schema/TypeTraverserTest.groovy @@ -0,0 +1,290 @@ +package graphql.schema + +import graphql.Scalars +import graphql.TypeResolutionEnvironment +import graphql.util.TraversalControl +import graphql.util.TraverserContext +import spock.lang.Specification + +class TypeTraverserTest extends Specification { + + + def "reachable scalar type"() { + + when: + + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, Scalars.GraphQLString) + + then: + + visitor.getStack() == ["scalar: String", "fallback: String"] + + + } + + def "reachable string argument type"() { + when: + def visitor = new GraphQLTestingVisitor() + + new TypeTraverser().depthFirst(visitor, GraphQLArgument.newArgument() + .name("Test") + .type(Scalars.GraphQLString) + .build()) + then: + visitor.getStack() == ["argument: Test", "fallback: Test", "scalar: String", "fallback: String"] + } + + def "reachable number argument type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLArgument.newArgument() + .name("Test") + .type(Scalars.GraphQLInt) + .build()) + then: + visitor.getStack() == ["argument: Test", "fallback: Test", "scalar: Int", "fallback: Int"] + + } + + def "reachable enum type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLEnumType + .newEnum() + .name("foo") + .value("bar") + .value(GraphQLEnumValueDefinition.newEnumValueDefinition().name("abc").value(123).build()) + .build()) + then: + visitor.getStack() == ["enum: foo", "fallback: foo", + "enum value: bar", "fallback: bar", + "enum value: abc", "fallback: abc"] + + } + + def "reachable field definition type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLFieldDefinition.newFieldDefinition() + .name("foo") + .type(Scalars.GraphQLString) + .build()) + then: + visitor.getStack() == ["field: foo", "fallback: foo", "scalar: String", "fallback: String"] + + } + + def "reachable input object field type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLInputObjectField.newInputObjectField() + .name("bar") + .type(Scalars.GraphQLString) + .build()) + then: + visitor.getStack() == ["input object field: bar", "fallback: bar", "scalar: String", "fallback: String"] + } + + def "reachable input object type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLInputObjectType.newInputObject() + .name("foo") + .field(GraphQLInputObjectField.newInputObjectField() + .name("bar") + .type(Scalars.GraphQLString) + .build()) + .build()) + then: + visitor.getStack() == ["input object: foo", "fallback: foo", + "input object field: bar", "fallback: bar", + "scalar: String", "fallback: String"] + } + + + def "reachable interface type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLInterfaceType.newInterface() + .name("foo") + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("bar") + .type(Scalars.GraphQLString) + .build()) + .typeResolver(NOOP_RESOLVER) + .build()) + then: + visitor.getStack() == ["interface: foo", "fallback: foo", + "field: bar", "fallback: bar", + "scalar: String", "fallback: String"] + } + + def "reachable list type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLList.list(Scalars.GraphQLString)) + then: + visitor.getStack() == ["list: String", "fallback: null", + "scalar: String", "fallback: String"] + } + + + def "reachable nonNull type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLNonNull.nonNull(Scalars.GraphQLString)) + then: + visitor.getStack() == ["nonNull: String", "fallback: null", + "scalar: String", "fallback: String"] + } + + def "reachable object type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLObjectType.newObject() + .name("myObject") + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("foo") + .type(Scalars.GraphQLString) + .build()) + .withInterface(GraphQLInterfaceType.newInterface() + .name("bar") + .typeResolver(NOOP_RESOLVER) + .build()) + .build()) + then: + visitor.getStack() == ["object: myObject", "fallback: myObject", + "field: foo", "fallback: foo", + "scalar: String", "fallback: String", + "interface: bar", "fallback: bar"] + } + + + def "reachable reference type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLTypeReference.typeRef("something")) + then: + visitor.getStack() == ["reference: something", "fallback: something"] + } + + def "reachable union type"() { + when: + def visitor = new GraphQLTestingVisitor() + new TypeTraverser().depthFirst(visitor, GraphQLUnionType.newUnionType() + .name("foo") + .possibleType(GraphQLObjectType.newObject().name("dummy").build()) + .possibleType(GraphQLTypeReference.typeRef("dummyRef")) + .typeResolver(NOOP_RESOLVER) + .build()) + then: + visitor.getStack() == ["union: foo", "fallback: foo", + "object: dummy", "fallback: dummy", + "reference: dummyRef", "fallback: dummyRef"] + } + + + def NOOP_RESOLVER = new TypeResolver() { + @Override + GraphQLObjectType getType(TypeResolutionEnvironment env) { + return null + } + } + + + class GraphQLTestingVisitor extends GraphQLTypeVisitorStub { + + def stack = [] + + @Override + TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext context) { + stack.add("argument: ${node.getName()}") + return super.visitGraphQLArgument(node, context) + } + + @Override + TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext context) { + stack.add("scalar: ${node.getName()}") + return super.visitGraphQLScalarType(node, context) + } + + @Override + protected TraversalControl visitGraphQLType(GraphQLType node, TraverserContext context) { + stack.add("fallback: ${node.getName()}") + return super.visitGraphQLType(node, context) + } + + @Override + TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext context) { + stack.add("enum: ${node.getName()}") + return super.visitGraphQLEnumType(node, context) + } + + @Override + TraversalControl visitGraphQLEnumValueDefinition(GraphQLEnumValueDefinition node, TraverserContext context) { + stack.add("enum value: ${node.getName()}") + return super.visitGraphQLEnumValueDefinition(node, context) + } + + @Override + TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + stack.add("field: ${node.getName()}") + return super.visitGraphQLFieldDefinition(node, context) + } + + @Override + TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context) { + stack.add("input object field: ${node.getName()}") + return super.visitGraphQLInputObjectField(node, context) + } + + @Override + TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext context) { + stack.add("input object: ${node.getName()}") + return super.visitGraphQLInputObjectType(node, context) + } + + @Override + TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext context) { + stack.add("interface: ${node.getName()}") + return super.visitGraphQLInterfaceType(node, context) + } + + @Override + TraversalControl visitGraphQLList(GraphQLList node, TraverserContext context) { + stack.add("list: ${node.getWrappedType().getName()}") + return super.visitGraphQLList(node, context) + } + + @Override + TraversalControl visitGraphQLNonNull(GraphQLNonNull node, TraverserContext context) { + stack.add("nonNull: ${node.getWrappedType().getName()}") + return super.visitGraphQLNonNull(node, context) + } + + @Override + TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext context) { + stack.add("object: ${node.getName()}") + return super.visitGraphQLObjectType(node, context) + } + + @Override + TraversalControl visitGraphQLTypeReference(GraphQLTypeReference node, TraverserContext context) { + stack.add("reference: ${node.getName()}") + return super.visitGraphQLTypeReference(node, context) + } + + @Override + TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext context) { + stack.add("union: ${node.getName()}") + return super.visitGraphQLUnionType(node, context) + } + + def getStack() { + return stack + } + } + + +}