Skip to content
Merged
12 changes: 9 additions & 3 deletions src/main/java/graphql/schema/GraphQLObjectType.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import graphql.Assert;
import graphql.AssertException;
import graphql.Internal;
import graphql.PublicApi;
import graphql.language.ObjectTypeDefinition;
Expand Down Expand Up @@ -363,11 +365,15 @@ public Builder withInterface(GraphQLInterfaceType interfaceType) {
return this;
}

public Builder replaceInterfaces(List<GraphQLInterfaceType> interfaces) {
public Builder replaceInterfaces(List<? extends GraphQLNamedOutputType> interfaces) {
assertNotNull(interfaces, () -> "interfaces can't be null");
this.interfaces.clear();
for (GraphQLInterfaceType interfaceType : interfaces) {
this.interfaces.put(interfaceType.getName(), interfaceType);
for (GraphQLNamedOutputType schemaElement : interfaces) {
if (schemaElement instanceof GraphQLInterfaceType || schemaElement instanceof GraphQLTypeReference) {
this.interfaces.put(schemaElement.getName(), schemaElement);
} else {
Assert.assertShouldNeverHappen("Unexpected type " + (schemaElement != null ? schemaElement.getClass() : "null"));
}
}
return this;
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/graphql/schema/GraphQLSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,13 @@ public Builder withSchemaDirectives(GraphQLDirective... directives) {
return this;
}

public Builder withSchemaDirectives(Collection<? extends GraphQLDirective> directives) {
for (GraphQLDirective directive : directives) {
withSchemaDirective(directive);
}
return this;
}

public Builder withSchemaDirective(GraphQLDirective directive) {
assertNotNull(directive, () -> "directive can't be null");
schemaDirectives.put(directive.getName(), directive);
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/graphql/schema/GraphQLUnionType.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package graphql.schema;


import graphql.Assert;
import com.google.common.collect.ImmutableList;
import graphql.Internal;
import graphql.PublicApi;
Expand Down Expand Up @@ -292,10 +293,16 @@ public Builder possibleTypes(GraphQLObjectType... type) {
return this;
}

public Builder replacePossibleTypes(List<GraphQLObjectType> types) {
public Builder replacePossibleTypes(List<? extends GraphQLNamedOutputType> types) {
this.types.clear();
for (GraphQLObjectType graphQLType : types) {
possibleType(graphQLType);
for (GraphQLSchemaElement schemaElement : types) {
if (schemaElement instanceof GraphQLTypeReference) {
possibleType((GraphQLTypeReference) schemaElement);
} else if (schemaElement instanceof GraphQLObjectType) {
possibleType((GraphQLObjectType) schemaElement);
} else {
Assert.assertShouldNeverHappen("Unexpected type " + (schemaElement != null ? schemaElement.getClass() : "null"));
}
}
return this;
}
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/graphql/schema/SchemaTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.Set;

import static graphql.Assert.assertNotEmpty;
import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertShouldNeverHappen;
import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER;
import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer;
Expand All @@ -44,6 +45,7 @@ private static class DummyRoot implements GraphQLSchemaElement {
static final String SUBSCRIPTION = "subscription";
static final String ADD_TYPES = "addTypes";
static final String DIRECTIVES = "directives";
static final String SCHEMA_DIRECTIVES = "schemaDirectives";
static final String INTROSPECTION = "introspection";
GraphQLSchema schema;

Expand All @@ -52,13 +54,15 @@ private static class DummyRoot implements GraphQLSchemaElement {
GraphQLObjectType subscription;
Set<GraphQLType> additionalTypes;
Set<GraphQLDirective> directives;
Set<GraphQLDirective> schemaDirectives;

DummyRoot(GraphQLSchema schema) {
this.schema = schema;
query = schema.getQueryType();
mutation = schema.isSupportingMutations() ? schema.getMutationType() : null;
subscription = schema.isSupportingSubscriptions() ? schema.getSubscriptionType() : null;
additionalTypes = schema.getAdditionalTypes();
schemaDirectives = new LinkedHashSet<>(schema.getSchemaDirectives());
directives = new LinkedHashSet<>(schema.getDirectives());
}

Expand All @@ -80,6 +84,7 @@ public SchemaElementChildrenContainer getChildrenWithTypeReferences() {
}
builder.children(ADD_TYPES, additionalTypes);
builder.children(DIRECTIVES, directives);
builder.children(SCHEMA_DIRECTIVES, schemaDirectives);
builder.child(INTROSPECTION, Introspection.__Schema);
return builder.build();
}
Expand All @@ -92,6 +97,7 @@ public GraphQLSchemaElement withNewChildren(SchemaElementChildrenContainer newCh
subscription = newChildren.getChildOrNull(SUBSCRIPTION);
additionalTypes = new LinkedHashSet<>(newChildren.getChildren(ADD_TYPES));
directives = new LinkedHashSet<>(newChildren.getChildren(DIRECTIVES));
schemaDirectives = new LinkedHashSet<>(newChildren.getChildren(SCHEMA_DIRECTIVES));
return this;
}

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

int zippersBefore = zippers.size();
TraversalControl result = context.thisNode().accept(context, visitor);
// detection if the node was changed: TODO make it better: doesn't work for parallel
// detection if the node was changed
if (zippersBefore + 1 == zippers.size()) {
nodeZipper = zippers.get(zippers.size() - 1);
}
Expand Down Expand Up @@ -171,7 +177,9 @@ public TraversalControl backRef(TraverserContext<GraphQLSchemaElement> context)
NodeZipper<GraphQLSchemaElement> zipper = zipperByOriginalNode.get(context.thisNode());
breadcrumbsByZipper.get(zipper).add(context.getBreadcrumbs());
visitor.visitBackRef(context);
reverseDependencies.get(zipper.getCurNode()).add(context.getParentNode());
List<GraphQLSchemaElement> reverseDependenciesForCurNode = reverseDependencies.get(zipper.getCurNode());
assertNotNull(reverseDependenciesForCurNode);
reverseDependenciesForCurNode.add(context.getParentNode());
return TraversalControl.CONTINUE;
}
};
Expand All @@ -193,7 +201,9 @@ public TraversalControl backRef(TraverserContext<GraphQLSchemaElement> context)
.subscription(dummyRoot.subscription)
.additionalTypes(dummyRoot.additionalTypes)
.additionalDirectives(dummyRoot.directives)
.withSchemaDirectives(dummyRoot.schemaDirectives)
.codeRegistry(builder.build())
.description(schema.getDescription())
.buildImpl(true);
return newSchema;
}
Expand Down Expand Up @@ -288,13 +298,13 @@ private void zipUpToDummyRoot(List<NodeZipper<GraphQLSchemaElement>> zippers,

// update curZippers
NodeZipper<GraphQLSchemaElement> curZipperForElement = nodeToZipper.get(element);
Assert.assertNotNull(curZipperForElement, () -> format("curZipperForElement is null for parentNode %s", element));
assertNotNull(curZipperForElement, () -> format("curZipperForElement is null for parentNode %s", element));
curZippers.remove(curZipperForElement);
curZippers.add(newZipper);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public interface SchemaDirectiveWiringEnvironment<T extends GraphQLDirectiveCont
*
* @return hierarchical graphql language node information
*/
NodeParentTree<NamedNode> getNodeParentTree();
NodeParentTree<NamedNode<?>> getNodeParentTree();

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

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

prevent IDEA warnings on generics


}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class SchemaDirectiveWiringEnvironmentImpl<T extends GraphQLDirectiveCont

private final T element;
private final Map<String, GraphQLDirective> directives;
private final NodeParentTree<NamedNode> nodeParentTree;
private final NodeParentTree<NamedNode<?>> nodeParentTree;
private final TypeDefinitionRegistry typeDefinitionRegistry;
private final Map<String, Object> context;
private final GraphQLCodeRegistry.Builder codeRegistry;
Expand Down Expand Up @@ -72,7 +72,7 @@ public boolean containsDirective(String directiveName) {
}

@Override
public NodeParentTree<NamedNode> getNodeParentTree() {
public NodeParentTree<NamedNode<?>> getNodeParentTree() {
return nodeParentTree;
}

Expand Down Expand Up @@ -107,14 +107,14 @@ public GraphQLFieldDefinition getFieldDefinition() {
}

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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package graphql.schema.idl;

import graphql.Internal;
import graphql.language.NamedNode;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLSchemaElement;
import graphql.schema.GraphQLTypeVisitorStub;
import graphql.schema.GraphQLUnionType;
import graphql.schema.SchemaTransformer;
import graphql.util.TraversalControl;
import graphql.util.TraverserContext;
import graphql.util.TreeTransformerUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import static graphql.util.TraversalControl.CONTINUE;

@Internal
class SchemaDirectiveWiringSchemaGeneratorPostProcessing implements SchemaGeneratorPostProcessing {

private final SchemaGeneratorDirectiveHelper generatorDirectiveHelper = new SchemaGeneratorDirectiveHelper();
private final TypeDefinitionRegistry typeRegistry;
private final RuntimeWiring runtimeWiring;
private final GraphQLCodeRegistry.Builder codeRegistryBuilder;
private final Map<String, Object> directiveBehaviourContext = new HashMap<>();


public SchemaDirectiveWiringSchemaGeneratorPostProcessing(TypeDefinitionRegistry typeRegistry, RuntimeWiring runtimeWiring, GraphQLCodeRegistry.Builder codeRegistryBuilder) {
this.typeRegistry = typeRegistry;
this.runtimeWiring = runtimeWiring;
this.codeRegistryBuilder = codeRegistryBuilder;
}


@Override
public GraphQLSchema process(GraphQLSchema originalSchema) {
GraphQLSchema newSchema = SchemaTransformer.transformSchema(originalSchema, new Visitor());
return newSchema.transform(builder -> {
// they could have changed the code registry so rebuild it
GraphQLCodeRegistry codeRegistry = this.codeRegistryBuilder.build();
builder.codeRegistry(codeRegistry);
});
}

public class Visitor extends GraphQLTypeVisitorStub {

private SchemaGeneratorDirectiveHelper.Parameters mkBehaviourParams() {
return new SchemaGeneratorDirectiveHelper.Parameters(typeRegistry, runtimeWiring, directiveBehaviourContext, codeRegistryBuilder);
}

private TraversalControl changOrContinue(GraphQLSchemaElement node, GraphQLSchemaElement newNode, TraverserContext<GraphQLSchemaElement> context) {
if (node != newNode) {
TreeTransformerUtil.changeNode(context, newNode);
}
return CONTINUE;
}

private boolean isIntrospectionType(GraphQLNamedType type) {
return type.getName().startsWith("__");
}

private <T extends GraphQLNamedType> boolean notSuitable(T node, Function<T, NamedNode<?>> suitableFunc) {
if (isIntrospectionType(node)) {
return true;
}
NamedNode<?> definition = suitableFunc.apply(node);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the schema directive wiring code makes an assumption we have a SDL AST definition behind it - so it's SDL only

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we need to restrict it? Is it bad that we restrict it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We don't want any one tweaking introspection types I think

return definition == null;
}

@Override
public TraversalControl visitGraphQLObjectType(GraphQLObjectType node, TraverserContext<GraphQLSchemaElement> context) {
if (notSuitable(node, GraphQLObjectType::getDefinition)) {
return CONTINUE;
}
GraphQLSchemaElement newNode = generatorDirectiveHelper.onObject(node, mkBehaviourParams());
return changOrContinue(node, newNode, context);
}

@Override
public TraversalControl visitGraphQLInterfaceType(GraphQLInterfaceType node, TraverserContext<GraphQLSchemaElement> context) {
if (notSuitable(node, GraphQLInterfaceType::getDefinition)) {
return CONTINUE;
}
GraphQLSchemaElement newNode = generatorDirectiveHelper.onInterface(node, mkBehaviourParams());
return changOrContinue(node, newNode, context);
}

@Override
public TraversalControl visitGraphQLEnumType(GraphQLEnumType node, TraverserContext<GraphQLSchemaElement> context) {
if (notSuitable(node, GraphQLEnumType::getDefinition)) {
return CONTINUE;
}
GraphQLSchemaElement newNode = generatorDirectiveHelper.onEnum(node, mkBehaviourParams());
return changOrContinue(node, newNode, context);
}

@Override
public TraversalControl visitGraphQLInputObjectType(GraphQLInputObjectType node, TraverserContext<GraphQLSchemaElement> context) {
if (notSuitable(node, GraphQLInputObjectType::getDefinition)) {
return CONTINUE;
}
GraphQLSchemaElement newNode = generatorDirectiveHelper.onInputObjectType(node, mkBehaviourParams());
return changOrContinue(node, newNode, context);
}

@Override
public TraversalControl visitGraphQLScalarType(GraphQLScalarType node, TraverserContext<GraphQLSchemaElement> context) {
if (notSuitable(node, GraphQLScalarType::getDefinition)) {
return CONTINUE;
}
GraphQLSchemaElement newNode = generatorDirectiveHelper.onScalar(node, mkBehaviourParams());
return changOrContinue(node, newNode, context);
}

@Override
public TraversalControl visitGraphQLUnionType(GraphQLUnionType node, TraverserContext<GraphQLSchemaElement> context) {
if (notSuitable(node, GraphQLUnionType::getDefinition)) {
return CONTINUE;
}
GraphQLSchemaElement newNode = generatorDirectiveHelper.onUnion(node, mkBehaviourParams());
return changOrContinue(node, newNode, context);
}
}
}
Loading