graphql.schema.diffing.SchemaGraphFactory Maven / Gradle / Ivy
package graphql.schema.diffing;
import graphql.GraphQLContext;
import graphql.Internal;
import graphql.execution.ValuesResolver;
import graphql.introspection.Introspection;
import graphql.language.AstPrinter;
import graphql.schema.*;
import graphql.schema.idl.DirectiveInfo;
import graphql.schema.idl.ScalarInfo;
import graphql.util.TraversalControl;
import graphql.util.Traverser;
import graphql.util.TraverserContext;
import graphql.util.TraverserVisitor;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static graphql.Assert.assertNotNull;
@Internal
public class SchemaGraphFactory {
private int counter = 1;
private final String debugPrefix;
public SchemaGraphFactory(String debugPrefix) {
this.debugPrefix = debugPrefix;
}
public SchemaGraphFactory() {
this.debugPrefix = "";
}
public SchemaGraph createGraph(GraphQLSchema schema) {
Set roots = new LinkedHashSet<>();
roots.add(schema.getQueryType());
if (schema.isSupportingMutations()) {
roots.add(schema.getMutationType());
}
if (schema.isSupportingSubscriptions()) {
roots.add(schema.getSubscriptionType());
}
roots.addAll(schema.getAdditionalTypes());
roots.addAll(schema.getDirectives());
roots.addAll(schema.getSchemaDirectives());
roots.add(schema.getIntrospectionSchemaType());
Traverser traverser = Traverser.depthFirst(GraphQLSchemaElement::getChildren);
SchemaGraph schemaGraph = new SchemaGraph();
class IntrospectionNode {
}
traverser.traverse(roots, new TraverserVisitor() {
@Override
public TraversalControl enter(TraverserContext context) {
boolean isIntrospectionNode = false;
if (context.thisNode() instanceof GraphQLNamedType) {
if (Introspection.isIntrospectionTypes((GraphQLNamedType) context.thisNode())) {
isIntrospectionNode = true;
context.setVar(IntrospectionNode.class, new IntrospectionNode());
}
} else {
isIntrospectionNode = context.getVarFromParents(IntrospectionNode.class) != null;
}
if (context.thisNode() instanceof GraphQLObjectType) {
newObject((GraphQLObjectType) context.thisNode(), schemaGraph, isIntrospectionNode);
}
if (context.thisNode() instanceof GraphQLInterfaceType) {
newInterface((GraphQLInterfaceType) context.thisNode(), schemaGraph, isIntrospectionNode);
}
if (context.thisNode() instanceof GraphQLUnionType) {
newUnion((GraphQLUnionType) context.thisNode(), schemaGraph, isIntrospectionNode);
}
if (context.thisNode() instanceof GraphQLScalarType) {
newScalar((GraphQLScalarType) context.thisNode(), schemaGraph, isIntrospectionNode);
}
if (context.thisNode() instanceof GraphQLInputObjectType) {
newInputObject((GraphQLInputObjectType) context.thisNode(), schemaGraph, isIntrospectionNode);
}
if (context.thisNode() instanceof GraphQLEnumType) {
newEnum((GraphQLEnumType) context.thisNode(), schemaGraph, isIntrospectionNode);
}
if (context.thisNode() instanceof GraphQLDirective) {
// only continue if not applied directive
if (context.getParentNode() == null) {
newDirective((GraphQLDirective) context.thisNode(), schemaGraph);
}
}
return TraversalControl.CONTINUE;
}
@Override
public TraversalControl leave(TraverserContext context) {
return TraversalControl.CONTINUE;
}
});
addSchemaVertex(schemaGraph, schema);
ArrayList copyOfVertices = new ArrayList<>(schemaGraph.getVertices());
for (Vertex vertex : copyOfVertices) {
if (SchemaGraph.OBJECT.equals(vertex.getType())) {
handleObjectVertex(vertex, schemaGraph, schema);
}
if (SchemaGraph.INTERFACE.equals(vertex.getType())) {
handleInterfaceVertex(vertex, schemaGraph, schema);
}
if (SchemaGraph.UNION.equals(vertex.getType())) {
handleUnion(vertex, schemaGraph, schema);
}
if (SchemaGraph.INPUT_OBJECT.equals(vertex.getType())) {
handleInputObject(vertex, schemaGraph, schema);
}
if (SchemaGraph.DIRECTIVE.equals(vertex.getType())) {
handleDirective(vertex, schemaGraph, schema);
}
}
return schemaGraph;
}
private void addSchemaVertex(SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLObjectType queryType = graphQLSchema.getQueryType();
GraphQLObjectType mutationType = graphQLSchema.getMutationType();
GraphQLObjectType subscriptionType = graphQLSchema.getSubscriptionType();
Vertex schemaVertex = new Vertex(SchemaGraph.SCHEMA, "schema");
schemaVertex.add("name", SchemaGraph.SCHEMA);
schemaGraph.addVertex(schemaVertex);
Vertex queryVertex = schemaGraph.getType(queryType.getName());
schemaGraph.addEdge(new Edge(schemaVertex, queryVertex, "query"));
if (mutationType != null) {
Vertex mutationVertex = schemaGraph.getType(mutationType.getName());
schemaGraph.addEdge(new Edge(schemaVertex, mutationVertex, "mutation"));
}
if (subscriptionType != null) {
Vertex subscriptionVertex = schemaGraph.getType(subscriptionType.getName());
schemaGraph.addEdge(new Edge(schemaVertex, subscriptionVertex, "subscription"));
}
createAppliedDirectives(schemaVertex, graphQLSchema.getSchemaDirectives(), schemaGraph);
}
private void handleInputObject(Vertex inputObject, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) graphQLSchema.getType(inputObject.get("name"));
List inputFields = inputObjectType.getFields();
for (GraphQLInputObjectField inputField : inputFields) {
Vertex inputFieldVertex = schemaGraph.findTargetVertex(inputObject, vertex -> vertex.getType().equals("InputField") &&
vertex.get("name").equals(inputField.getName())).get();
handleInputField(inputFieldVertex, inputField, schemaGraph, graphQLSchema);
}
}
private void handleInputField(Vertex inputFieldVertex, GraphQLInputObjectField inputField, SchemaGraph
schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLInputType type = inputField.getType();
GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type);
Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName()));
Edge typeEdge = new Edge(inputFieldVertex, typeVertex);
String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type) + ";defaultValue=";
if (inputField.hasSetDefaultValue()) {
typeEdgeLabel += AstPrinter.printAst(ValuesResolver.valueToLiteral(inputField.getInputFieldDefaultValue(), inputField.getType(), GraphQLContext.getDefault(), Locale.getDefault()));
}
typeEdge.setLabel(typeEdgeLabel);
schemaGraph.addEdge(typeEdge);
}
private void handleUnion(Vertex unionVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLUnionType unionType = (GraphQLUnionType) graphQLSchema.getType(unionVertex.get("name"));
List types = unionType.getTypes();
for (GraphQLNamedOutputType unionMemberType : types) {
Vertex unionMemberVertex = assertNotNull(schemaGraph.getType(unionMemberType.getName()));
schemaGraph.addEdge(new Edge(unionVertex, unionMemberVertex));
}
}
private void handleInterfaceVertex(Vertex interfaceVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLInterfaceType interfaceType = (GraphQLInterfaceType) graphQLSchema.getType(interfaceVertex.get("name"));
for (GraphQLNamedOutputType implementsInterface : interfaceType.getInterfaces()) {
Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName()));
schemaGraph.addEdge(new Edge(interfaceVertex, implementsInterfaceVertex, "implements " + implementsInterface.getName()));
}
List fieldDefinitions = interfaceType.getFieldDefinitions();
for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) {
Vertex fieldVertex = schemaGraph.findTargetVertex(interfaceVertex, vertex -> vertex.getType().equals("Field") &&
vertex.get("name").equals(fieldDefinition.getName())).get();
handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema);
}
}
private void handleObjectVertex(Vertex objectVertex, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLObjectType objectType = graphQLSchema.getObjectType(objectVertex.get("name"));
for (GraphQLNamedOutputType implementsInterface : objectType.getInterfaces()) {
Vertex implementsInterfaceVertex = assertNotNull(schemaGraph.getType(implementsInterface.getName()));
schemaGraph.addEdge(new Edge(objectVertex, implementsInterfaceVertex, "implements " + implementsInterface.getName()));
}
List fieldDefinitions = objectType.getFieldDefinitions();
for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) {
Vertex fieldVertex = schemaGraph.findTargetVertex(objectVertex, vertex -> vertex.getType().equals("Field") &&
vertex.get("name").equals(fieldDefinition.getName())).get();
handleField(fieldVertex, fieldDefinition, schemaGraph, graphQLSchema);
}
}
private void handleField(Vertex fieldVertex, GraphQLFieldDefinition fieldDefinition, SchemaGraph
schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLOutputType type = fieldDefinition.getType();
GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type);
Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName()));
Edge typeEdge = new Edge(fieldVertex, typeVertex);
typeEdge.setLabel("type=" + GraphQLTypeUtil.simplePrint(type) + ";");
schemaGraph.addEdge(typeEdge);
for (GraphQLArgument graphQLArgument : fieldDefinition.getArguments()) {
Vertex argumentVertex = schemaGraph.findTargetVertex(fieldVertex, vertex -> vertex.getType().equals("Argument") &&
vertex.get("name").equals(graphQLArgument.getName())).get();
handleArgument(argumentVertex, graphQLArgument, schemaGraph);
}
}
private void handleDirective(Vertex directive, SchemaGraph schemaGraph, GraphQLSchema graphQLSchema) {
GraphQLDirective graphQLDirective = graphQLSchema.getDirective(directive.getName());
for (GraphQLArgument graphQLArgument : graphQLDirective.getArguments()) {
Vertex argumentVertex = schemaGraph.findTargetVertex(directive, vertex -> vertex.isOfType(SchemaGraph.ARGUMENT) &&
vertex.getName().equals(graphQLArgument.getName())).get();
handleArgument(argumentVertex, graphQLArgument, schemaGraph);
}
}
private void handleArgument(Vertex argumentVertex, GraphQLArgument graphQLArgument, SchemaGraph schemaGraph) {
GraphQLInputType type = graphQLArgument.getType();
GraphQLUnmodifiedType graphQLUnmodifiedType = GraphQLTypeUtil.unwrapAll(type);
Vertex typeVertex = assertNotNull(schemaGraph.getType(graphQLUnmodifiedType.getName()));
Edge typeEdge = new Edge(argumentVertex, typeVertex);
String typeEdgeLabel = "type=" + GraphQLTypeUtil.simplePrint(type) + ";defaultValue=";
if (graphQLArgument.hasSetDefaultValue()) {
typeEdgeLabel += AstPrinter.printAst(ValuesResolver.valueToLiteral(graphQLArgument.getArgumentDefaultValue(), graphQLArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault()));
}
typeEdge.setLabel(typeEdgeLabel);
schemaGraph.addEdge(typeEdge);
}
private void newObject(GraphQLObjectType graphQLObjectType, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex objectVertex = new Vertex(SchemaGraph.OBJECT, debugPrefix + String.valueOf(counter++));
objectVertex.setBuiltInType(isIntrospectionNode);
objectVertex.add("name", graphQLObjectType.getName());
objectVertex.add("description", desc(graphQLObjectType.getDescription()));
for (GraphQLFieldDefinition fieldDefinition : graphQLObjectType.getFieldDefinitions()) {
Vertex newFieldVertex = newField(fieldDefinition, schemaGraph, isIntrospectionNode);
schemaGraph.addVertex(newFieldVertex);
schemaGraph.addEdge(new Edge(objectVertex, newFieldVertex));
}
schemaGraph.addVertex(objectVertex);
schemaGraph.addType(graphQLObjectType.getName(), objectVertex);
createAppliedDirectives(objectVertex, graphQLObjectType.getDirectives(), schemaGraph);
}
private Vertex newField(GraphQLFieldDefinition graphQLFieldDefinition, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex fieldVertex = new Vertex(SchemaGraph.FIELD, debugPrefix + String.valueOf(counter++));
fieldVertex.setBuiltInType(isIntrospectionNode);
fieldVertex.add("name", graphQLFieldDefinition.getName());
fieldVertex.add("description", desc(graphQLFieldDefinition.getDescription()));
for (GraphQLArgument argument : graphQLFieldDefinition.getArguments()) {
Vertex argumentVertex = newArgument(argument, schemaGraph, isIntrospectionNode);
schemaGraph.addVertex(argumentVertex);
schemaGraph.addEdge(new Edge(fieldVertex, argumentVertex));
}
createAppliedDirectives(fieldVertex, graphQLFieldDefinition.getDirectives(), schemaGraph);
return fieldVertex;
}
private Vertex newArgument(GraphQLArgument graphQLArgument, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex vertex = new Vertex(SchemaGraph.ARGUMENT, debugPrefix + String.valueOf(counter++));
vertex.setBuiltInType(isIntrospectionNode);
vertex.add("name", graphQLArgument.getName());
vertex.add("description", desc(graphQLArgument.getDescription()));
createAppliedDirectives(vertex, graphQLArgument.getDirectives(), schemaGraph);
return vertex;
}
private void newScalar(GraphQLScalarType scalarType, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex scalarVertex = new Vertex(SchemaGraph.SCALAR, debugPrefix + String.valueOf(counter++));
scalarVertex.setBuiltInType(isIntrospectionNode);
if (ScalarInfo.isGraphqlSpecifiedScalar(scalarType.getName())) {
scalarVertex.setBuiltInType(true);
}
scalarVertex.add("name", scalarType.getName());
scalarVertex.add("description", desc(scalarType.getDescription()));
scalarVertex.add("specifiedByUrl", scalarType.getSpecifiedByUrl());
schemaGraph.addVertex(scalarVertex);
schemaGraph.addType(scalarType.getName(), scalarVertex);
createAppliedDirectives(scalarVertex, scalarType.getDirectives(), schemaGraph);
}
private void newInterface(GraphQLInterfaceType interfaceType, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex interfaceVertex = new Vertex(SchemaGraph.INTERFACE, debugPrefix + String.valueOf(counter++));
interfaceVertex.setBuiltInType(isIntrospectionNode);
interfaceVertex.add("name", interfaceType.getName());
interfaceVertex.add("description", desc(interfaceType.getDescription()));
for (GraphQLFieldDefinition fieldDefinition : interfaceType.getFieldDefinitions()) {
Vertex newFieldVertex = newField(fieldDefinition, schemaGraph, isIntrospectionNode);
schemaGraph.addVertex(newFieldVertex);
schemaGraph.addEdge(new Edge(interfaceVertex, newFieldVertex));
}
schemaGraph.addVertex(interfaceVertex);
schemaGraph.addType(interfaceType.getName(), interfaceVertex);
createAppliedDirectives(interfaceVertex, interfaceType.getDirectives(), schemaGraph);
}
private void newEnum(GraphQLEnumType enumType, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex enumVertex = new Vertex(SchemaGraph.ENUM, debugPrefix + String.valueOf(counter++));
enumVertex.setBuiltInType(isIntrospectionNode);
enumVertex.add("name", enumType.getName());
enumVertex.add("description", desc(enumType.getDescription()));
for (GraphQLEnumValueDefinition enumValue : enumType.getValues()) {
Vertex enumValueVertex = new Vertex(SchemaGraph.ENUM_VALUE, debugPrefix + String.valueOf(counter++));
enumValueVertex.setBuiltInType(isIntrospectionNode);
enumValueVertex.add("name", enumValue.getName());
schemaGraph.addVertex(enumValueVertex);
schemaGraph.addEdge(new Edge(enumVertex, enumValueVertex));
createAppliedDirectives(enumValueVertex, enumValue.getDirectives(), schemaGraph);
}
schemaGraph.addVertex(enumVertex);
schemaGraph.addType(enumType.getName(), enumVertex);
createAppliedDirectives(enumVertex, enumType.getDirectives(), schemaGraph);
}
private void newUnion(GraphQLUnionType unionType, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex unionVertex = new Vertex(SchemaGraph.UNION, debugPrefix + String.valueOf(counter++));
unionVertex.setBuiltInType(isIntrospectionNode);
unionVertex.add("name", unionType.getName());
unionVertex.add("description", desc(unionType.getDescription()));
schemaGraph.addVertex(unionVertex);
schemaGraph.addType(unionType.getName(), unionVertex);
createAppliedDirectives(unionVertex, unionType.getDirectives(), schemaGraph);
}
private void newInputObject(GraphQLInputObjectType inputObject, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex inputObjectVertex = new Vertex(SchemaGraph.INPUT_OBJECT, debugPrefix + String.valueOf(counter++));
inputObjectVertex.setBuiltInType(isIntrospectionNode);
inputObjectVertex.add("name", inputObject.getName());
inputObjectVertex.add("description", desc(inputObject.getDescription()));
for (GraphQLInputObjectField inputObjectField : inputObject.getFieldDefinitions()) {
Vertex newInputField = newInputField(inputObjectField, schemaGraph, isIntrospectionNode);
Edge newEdge = new Edge(inputObjectVertex, newInputField);
schemaGraph.addEdge(newEdge);
}
schemaGraph.addVertex(inputObjectVertex);
schemaGraph.addType(inputObject.getName(), inputObjectVertex);
createAppliedDirectives(inputObjectVertex, inputObject.getDirectives(), schemaGraph);
}
private void createAppliedDirectives(Vertex from,
List appliedDirectives,
SchemaGraph schemaGraph) {
Map countByName = new LinkedHashMap<>();
for (GraphQLDirective appliedDirective : appliedDirectives) {
Vertex appliedDirectiveVertex = new Vertex(SchemaGraph.APPLIED_DIRECTIVE, debugPrefix + String.valueOf(counter++));
appliedDirectiveVertex.add("name", appliedDirective.getName());
for (GraphQLArgument appliedArgument : appliedDirective.getArguments()) {
if (appliedArgument.hasSetValue()) {
Vertex appliedArgumentVertex = new Vertex(SchemaGraph.APPLIED_ARGUMENT, debugPrefix + String.valueOf(counter++));
appliedArgumentVertex.add("name", appliedArgument.getName());
appliedArgumentVertex.add("value", AstPrinter.printAst(ValuesResolver.valueToLiteral(appliedArgument.getArgumentValue(), appliedArgument.getType(), GraphQLContext.getDefault(), Locale.getDefault())));
schemaGraph.addVertex(appliedArgumentVertex);
schemaGraph.addEdge(new Edge(appliedDirectiveVertex, appliedArgumentVertex));
}
}
schemaGraph.addVertex(appliedDirectiveVertex);
// repeatable directives means we can have multiple directives with the same name
// the edge label indicates the applied directive index
Integer count = countByName.getOrDefault(appliedDirective.getName(), 0);
schemaGraph.addEdge(new Edge(from, appliedDirectiveVertex, String.valueOf(count)));
countByName.put(appliedDirective.getName(), count + 1);
}
}
private void newDirective(GraphQLDirective directive, SchemaGraph schemaGraph) {
Vertex directiveVertex = new Vertex(SchemaGraph.DIRECTIVE, debugPrefix + String.valueOf(counter++));
directiveVertex.add("name", directive.getName());
directiveVertex.add("repeatable", directive.isRepeatable());
directiveVertex.add("locations", directive.validLocations());
boolean graphqlSpecified = DirectiveInfo.isGraphqlSpecifiedDirective(directive.getName());
directiveVertex.setBuiltInType(graphqlSpecified);
directiveVertex.add("description", desc(directive.getDescription()));
for (GraphQLArgument argument : directive.getArguments()) {
Vertex argumentVertex = newArgument(argument, schemaGraph, graphqlSpecified);
schemaGraph.addVertex(argumentVertex);
schemaGraph.addEdge(new Edge(directiveVertex, argumentVertex));
}
schemaGraph.addDirective(directive.getName(), directiveVertex);
schemaGraph.addVertex(directiveVertex);
}
private Vertex newInputField(GraphQLInputObjectField inputField, SchemaGraph schemaGraph, boolean isIntrospectionNode) {
Vertex vertex = new Vertex(SchemaGraph.INPUT_FIELD, debugPrefix + String.valueOf(counter++));
schemaGraph.addVertex(vertex);
vertex.setBuiltInType(isIntrospectionNode);
vertex.add("name", inputField.getName());
vertex.add("description", desc(inputField.getDescription()));
createAppliedDirectives(vertex, inputField.getDirectives(), schemaGraph);
return vertex;
}
private String desc(String desc) {
return desc;
// return desc != null ? desc.replace("\n", "\\n") : null;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy