Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions src/main/java/graphql/schema/GraphQLTypeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,12 +196,7 @@ public static <T extends GraphQLType> T unwrapOneAs(GraphQLType type) {
* @return the underlying type
*/
public static GraphQLUnmodifiedType unwrapAll(GraphQLType type) {
while (true) {
if (isNotWrapped(type)) {
return (GraphQLUnmodifiedType) type;
}
type = unwrapOne(type);
}
return unwrapAllAs(type);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was probably a mistake to have the well named unwrapAll return GraphQLUnmodifiedType which excludes GraphqlTypeReferences - but API is API so leave it as is

I did change how it works internally here

}

/**
Expand All @@ -215,7 +210,16 @@ public static GraphQLUnmodifiedType unwrapAll(GraphQLType type) {
*/
public static <T extends GraphQLType> T unwrapAllAs(GraphQLType type) {
//noinspection unchecked
return (T) unwrapAll(type);
return (T) unwrapAllImpl(type);
}

private static GraphQLType unwrapAllImpl(GraphQLType type) {
while (true) {
if (isNotWrapped(type)) {
return type;
}
type = unwrapOne(type);
}
}


Expand Down
93 changes: 76 additions & 17 deletions src/main/java/graphql/schema/impl/GraphQLTypeCollectingVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import com.google.common.collect.ImmutableMap;
import graphql.AssertException;
import graphql.Internal;
import graphql.schema.GraphQLAppliedDirectiveArgument;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchemaElement;
Expand All @@ -21,13 +26,17 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;

import static graphql.schema.GraphQLTypeUtil.unwrapAllAs;
import static graphql.util.TraversalControl.CONTINUE;
import static java.lang.String.format;

@Internal
public class GraphQLTypeCollectingVisitor extends GraphQLTypeVisitorStub {

private final Map<String, GraphQLNamedType> result = new LinkedHashMap<>();
private final Map<String, GraphQLNamedType> indirectStrongReferences = new LinkedHashMap<>();

public GraphQLTypeCollectingVisitor() {
}
Expand All @@ -36,14 +45,14 @@ public GraphQLTypeCollectingVisitor() {
public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext<GraphQLSchemaElement> context) {
assertTypeUniqueness(node, result);
save(node.getName(), node);
return super.visitGraphQLEnumType(node, context);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext<GraphQLSchemaElement> context) {
assertTypeUniqueness(node, result);
save(node.getName(), node);
return super.visitGraphQLScalarType(node, context);
return CONTINUE;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of this super code boiled down to CONTINUE!

}

@Override
Expand All @@ -53,7 +62,7 @@ public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, Traverser
} else {
save(node.getName(), node);
}
return super.visitGraphQLObjectType(node, context);
return CONTINUE;
}

@Override
Expand All @@ -63,7 +72,7 @@ public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node,
} else {
save(node.getName(), node);
}
return super.visitGraphQLInputObjectType(node, context);
return CONTINUE;
}

@Override
Expand All @@ -73,23 +82,48 @@ public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, Tra
} else {
save(node.getName(), node);
}

return super.visitGraphQLInterfaceType(node, context);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext<GraphQLSchemaElement> context) {
assertTypeUniqueness(node, result);
save(node.getName(), node);
return super.visitGraphQLUnionType(node, context);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
return CONTINUE;
}

return super.visitGraphQLFieldDefinition(node, context);
@Override
public TraversalControl visitGraphQLArgument(GraphQLArgument node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
return CONTINUE;
}

@Override
public TraversalControl visitGraphQLAppliedDirectiveArgument(GraphQLAppliedDirectiveArgument node, TraverserContext<GraphQLSchemaElement> context) {
saveIndirectStrongReference(node::getType);
return CONTINUE;
}

private <T> void saveIndirectStrongReference(Supplier<GraphQLType> typeSupplier) {
GraphQLNamedType type = unwrapAllAs(typeSupplier.get());
if (!(type instanceof GraphQLTypeReference)) {
indirectStrongReferences.put(type.getName(), type);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only puts in fields that unwrap to be an actual type

}


private boolean isNotTypeReference(String name) {
return result.containsKey(name) && !(result.get(name) instanceof GraphQLTypeReference);
}
Expand All @@ -112,18 +146,43 @@ private void assertTypeUniqueness(GraphQLNamedType type, Map<String, GraphQLName
// 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()));
}
if (!(existingType instanceof GraphQLTypeReference || type instanceof GraphQLTypeReference)) {
assertUniqueTypeObjects(type, existingType);
}
}
}

private void assertUniqueTypeObjects(GraphQLNamedType type, GraphQLType existingType) {
// 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()));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made a common method here

}
}

public ImmutableMap<String, GraphQLNamedType> getResult() {
return ImmutableMap.copyOf(new TreeMap<>(result));
Map<String, GraphQLNamedType> types = new TreeMap<>(fixDanglingReplacedTypes(result));
return ImmutableMap.copyOf(types);
}

/**
* It's possible for certain schema edits to create a situation where a field / arg / input field had a type reference, then
* it got replaced with an actual strong reference and then the schema gets edited such that the only reference
* to that type is the replaced strong reference. This edge case means that the replaced reference can be
* missed if it's the only way to get to that type because this visitor asks for the children as original type,
* e.g. the type reference and not the replaced reference.
*
* @param visitedTypes the types collected by this visitor
*
* @return a fixed up map where the only
*/
private Map<String, GraphQLNamedType> fixDanglingReplacedTypes(Map<String, GraphQLNamedType> visitedTypes) {
for (GraphQLNamedType indirectStrongReference : indirectStrongReferences.values()) {
String typeName = indirectStrongReference.getName();
visitedTypes.putIfAbsent(typeName, indirectStrongReference);
}
return visitedTypes;
}
}
Loading