package graphql.schema.idl;
import graphql.GraphQLError;
import graphql.language.ArrayValue;
import graphql.language.BooleanValue;
import graphql.language.Comment;
import graphql.language.EnumTypeDefinition;
import graphql.language.EnumValue;
import graphql.language.FieldDefinition;
import graphql.language.FloatValue;
import graphql.language.InputObjectTypeDefinition;
import graphql.language.InputValueDefinition;
import graphql.language.IntValue;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.Node;
import graphql.language.ObjectTypeDefinition;
import graphql.language.ObjectValue;
import graphql.language.OperationTypeDefinition;
import graphql.language.ScalarTypeDefinition;
import graphql.language.SchemaDefinition;
import graphql.language.StringValue;
import graphql.language.Type;
import graphql.language.TypeDefinition;
import graphql.language.TypeExtensionDefinition;
import graphql.language.UnionTypeDefinition;
import graphql.language.Value;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.GraphQLUnionType;
import graphql.schema.PropertyDataFetcher;
import graphql.schema.TypeResolver;
import graphql.schema.TypeResolverProxy;
import graphql.schema.idl.errors.NotAnInputTypeError;
import graphql.schema.idl.errors.NotAnOutputTypeError;
import graphql.schema.idl.errors.SchemaProblem;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Stack;
import static graphql.Assert.assertNotNull;
/**
* This can generate a working runtime schema from a type registry and runtime wiring
*/
public class SchemaGenerator {
/**
* We pass this around so we know what we have defined in a stack like manner plus
* it gives us helper functions
*/
class BuildContext {
private final TypeDefinitionRegistry typeRegistry;
private final RuntimeWiring wiring;
private final Stack<String> definitionStack = new Stack<>();
private final Map<String, GraphQLOutputType> outputGTypes = new HashMap<>();
private final Map<String, GraphQLInputType> inputGTypes = new HashMap<>();
BuildContext(TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) {
this.typeRegistry = typeRegistry;
this.wiring = wiring;
}
public TypeDefinitionRegistry getTypeRegistry() {
return typeRegistry;
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
TypeDefinition getTypeDefinition(Type type) {
return typeRegistry.getType(type).get();
}
boolean stackContains(TypeInfo typeInfo) {
return definitionStack.contains(typeInfo.getName());
}
void push(TypeInfo typeInfo) {
definitionStack.push(typeInfo.getName());
}
String pop() {
return definitionStack.pop();
}
GraphQLOutputType hasOutputType(TypeDefinition typeDefinition) {
return outputGTypes.get(typeDefinition.getName());
}
GraphQLInputType hasInputType(TypeDefinition typeDefinition) {
return inputGTypes.get(typeDefinition.getName());
}
void put(GraphQLOutputType outputType) {
outputGTypes.put(outputType.getName(), outputType);
// certain types can be both input and output types, for example enums
if (outputType instanceof GraphQLInputType) {
inputGTypes.put(outputType.getName(), (GraphQLInputType) outputType);
}
}
void put(GraphQLInputType inputType) {
inputGTypes.put(inputType.getName(), inputType);
// certain types can be both input and output types, for example enums
if (inputType instanceof GraphQLOutputType) {
outputGTypes.put(inputType.getName(), (GraphQLOutputType) inputType);
}
}
RuntimeWiring getWiring() {
return wiring;
}
@SuppressWarnings("OptionalGetWithoutIsPresent")
public SchemaDefinition getSchemaDefinition() {
return typeRegistry.schemaDefinition().get();
}
}
private final SchemaTypeChecker typeChecker = new SchemaTypeChecker();
public SchemaGenerator() {
}
/**
* This will take a {@link TypeDefinitionRegistry} and a {@link RuntimeWiring} and put them together to create a executable schema
*
* @param typeRegistry this can be obtained via {@link SchemaParser#parse(String)}
* @param wiring this can be built using {@link RuntimeWiring#newRuntimeWiring()}
* @return an executable schema
* @throws SchemaProblem if there are problems in assembling a schema such as missing type resolvers or no operations defined
*/
public GraphQLSchema makeExecutableSchema(TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring) throws SchemaProblem {
List<GraphQLError> errors = typeChecker.checkTypeRegistry(typeRegistry, wiring);
if (!errors.isEmpty()) {
throw new SchemaProblem(errors);
}
BuildContext buildCtx = new BuildContext(typeRegistry, wiring);
return makeExecutableSchemaImpl(buildCtx);
}
private GraphQLSchema makeExecutableSchemaImpl(BuildContext buildCtx) {
SchemaDefinition schemaDefinition = buildCtx.getSchemaDefinition();
List<OperationTypeDefinition> operationTypes = schemaDefinition.getOperationTypeDefinitions();
// pre-flight checked via checker
@SuppressWarnings("OptionalGetWithoutIsPresent")
OperationTypeDefinition queryOp = operationTypes.stream().filter(op -> "query".equals(op.getName())).findFirst().get();
Optional<OperationTypeDefinition> mutationOp = operationTypes.stream().filter(op -> "mutation".equals(op.getName())).findFirst();
Optional<OperationTypeDefinition> subscriptionOp = operationTypes.stream().filter(op -> "subscription".equals(op.getName())).findFirst();
GraphQLObjectType query = buildOperation(buildCtx, queryOp);
GraphQLObjectType mutation;
GraphQLObjectType subscription;
GraphQLSchema.Builder schemaBuilder = GraphQLSchema
.newSchema()
.query(query);
if (mutationOp.isPresent()) {
mutation = buildOperation(buildCtx, mutationOp.get());
schemaBuilder.mutation(mutation);
}
if (subscriptionOp.isPresent()) {
subscription = buildOperation(buildCtx, subscriptionOp.get());
schemaBuilder.subscription(subscription);
}
return schemaBuilder.build();
}
private GraphQLObjectType buildOperation(BuildContext buildCtx, OperationTypeDefinition operation) {
Type type = operation.getType();
return buildOutputType(buildCtx, type);
}
/**
* This is the main recursive spot that builds out the various forms of Output types
*
* @param buildCtx the context we need to work out what we are doing
* @param rawType the type to be built
* @return an output type
*/
@SuppressWarnings("unchecked")
private <T extends GraphQLOutputType> T buildOutputType(BuildContext buildCtx, Type rawType) {
TypeDefinition typeDefinition = buildCtx.getTypeDefinition(rawType);
TypeInfo typeInfo = TypeInfo.typeInfo(rawType);
GraphQLOutputType outputType = buildCtx.hasOutputType(typeDefinition);
if (outputType != null) {
return typeInfo.decorate(outputType);
}
if (buildCtx.stackContains(typeInfo)) {
// we have circled around so put in a type reference and fix it up later
// otherwise we will go into an infinite loop
return typeInfo.decorate(new GraphQLTypeReference(typeInfo.getName()));
}
buildCtx.push(typeInfo);
if (typeDefinition instanceof ObjectTypeDefinition) {
outputType = buildObjectType(buildCtx, (ObjectTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof InterfaceTypeDefinition) {
outputType = buildInterfaceType(buildCtx, (InterfaceTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof UnionTypeDefinition) {
outputType = buildUnionType(buildCtx, (UnionTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof EnumTypeDefinition) {
outputType = buildEnumType((EnumTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof ScalarTypeDefinition) {
outputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
} else {
// typeDefinition is not a valid output type
throw new NotAnOutputTypeError(typeDefinition);
}
buildCtx.put(outputType);
buildCtx.pop();
return (T) typeInfo.decorate(outputType);
}
private GraphQLInputType buildInputType(BuildContext buildCtx, Type rawType) {
TypeDefinition typeDefinition = buildCtx.getTypeDefinition(rawType);
TypeInfo typeInfo = TypeInfo.typeInfo(rawType);
GraphQLInputType inputType = buildCtx.hasInputType(typeDefinition);
if (inputType != null) {
return typeInfo.decorate(inputType);
}
if (buildCtx.stackContains(typeInfo)) {
// we have circled around so put in a type reference and fix it later
return typeInfo.decorate(new GraphQLTypeReference(typeInfo.getName()));
}
buildCtx.push(typeInfo);
if (typeDefinition instanceof InputObjectTypeDefinition) {
inputType = buildInputObjectType(buildCtx, (InputObjectTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof EnumTypeDefinition) {
inputType = buildEnumType((EnumTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof ScalarTypeDefinition) {
inputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
} else {
// typeDefinition is not a valid InputType
throw new NotAnInputTypeError(typeDefinition);
}
buildCtx.put(inputType);
buildCtx.pop();
return typeInfo.decorate(inputType);
}
private GraphQLObjectType buildObjectType(BuildContext buildCtx, ObjectTypeDefinition typeDefinition) {
GraphQLObjectType.Builder builder = GraphQLObjectType.newObject();
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition));
List<TypeExtensionDefinition> typeExtensions = getTypeExtensionsOf(typeDefinition, buildCtx);
buildObjectTypeFields(buildCtx, typeDefinition, builder, typeExtensions);
buildObjectTypeInterfaces(buildCtx, typeDefinition, builder, typeExtensions);
return builder.build();
}
private void buildObjectTypeFields(BuildContext buildCtx, ObjectTypeDefinition typeDefinition, GraphQLObjectType.Builder builder, List<TypeExtensionDefinition> typeExtensions) {
Map<String, GraphQLFieldDefinition> fieldDefinitions = new LinkedHashMap<>();
typeDefinition.getFieldDefinitions().forEach(fieldDef -> {
GraphQLFieldDefinition newFieldDefinition = buildField(buildCtx, typeDefinition, fieldDef);
fieldDefinitions.put(newFieldDefinition.getName(), newFieldDefinition);
});
// an object consists of the fields it gets from its definition AND its type extensions
typeExtensions.forEach(typeExt -> typeExt.getFieldDefinitions().forEach(fieldDef -> {
GraphQLFieldDefinition newFieldDefinition = buildField(buildCtx, typeDefinition, fieldDef);
//
// de-dupe here - pre-flight checks ensure all dupes are of the same type
if (!fieldDefinitions.containsKey(newFieldDefinition.getName())) {
fieldDefinitions.put(newFieldDefinition.getName(), newFieldDefinition);
}
}));
fieldDefinitions.values().forEach(builder::field);
}
private void buildObjectTypeInterfaces(BuildContext buildCtx, ObjectTypeDefinition typeDefinition, GraphQLObjectType.Builder builder, List<TypeExtensionDefinition> typeExtensions) {
Map<String, GraphQLInterfaceType> interfaces = new LinkedHashMap<>();
typeDefinition.getImplements().forEach(type -> {
GraphQLInterfaceType newInterfaceType = buildOutputType(buildCtx, type);
interfaces.put(newInterfaceType.getName(), newInterfaceType);
});
// an object consists of the interfaces it gets from its definition AND its type extensions
typeExtensions.forEach(typeExt -> typeExt.getImplements().forEach(type -> {
GraphQLInterfaceType interfaceType = buildOutputType(buildCtx, type);
//
// de-dupe here - pre-flight checks ensure all dupes are of the same type
if (!interfaces.containsKey(interfaceType.getName())) {
interfaces.put(interfaceType.getName(), interfaceType);
}
}));
interfaces.values().forEach(builder::withInterface);
}
private List<TypeExtensionDefinition> getTypeExtensionsOf(ObjectTypeDefinition objectTypeDefinition, BuildContext buildCtx) {
List<TypeExtensionDefinition> typeExtensionDefinitions = buildCtx.typeRegistry.typeExtensions().get(objectTypeDefinition.getName());
return typeExtensionDefinitions == null ? Collections.emptyList() : typeExtensionDefinitions;
}
private GraphQLInterfaceType buildInterfaceType(BuildContext buildCtx, InterfaceTypeDefinition typeDefinition) {
GraphQLInterfaceType.Builder builder = GraphQLInterfaceType.newInterface();
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition));
builder.typeResolver(getTypeResolverForInterface(buildCtx, typeDefinition));
typeDefinition.getFieldDefinitions().forEach(fieldDef ->
builder.field(buildField(buildCtx, typeDefinition, fieldDef)));
return builder.build();
}
private GraphQLUnionType buildUnionType(BuildContext buildCtx, UnionTypeDefinition typeDefinition) {
GraphQLUnionType.Builder builder = GraphQLUnionType.newUnionType();
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition));
builder.typeResolver(getTypeResolverForUnion(buildCtx, typeDefinition));
typeDefinition.getMemberTypes().forEach(mt -> {
GraphQLOutputType outputType = buildOutputType(buildCtx, mt);
if (outputType instanceof GraphQLTypeReference) {
builder.possibleType((GraphQLTypeReference) outputType);
} else {
builder.possibleType((GraphQLObjectType) outputType);
}
});
return builder.build();
}
private GraphQLEnumType buildEnumType(EnumTypeDefinition typeDefinition) {
GraphQLEnumType.Builder builder = GraphQLEnumType.newEnum();
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition));
typeDefinition.getEnumValueDefinitions().forEach(evd -> builder.value(evd.getName()));
return builder.build();
}
private GraphQLScalarType buildScalar(BuildContext buildCtx, ScalarTypeDefinition typeDefinition) {
return buildCtx.getWiring().getScalars().get(typeDefinition.getName());
}
private GraphQLFieldDefinition buildField(BuildContext buildCtx, TypeDefinition parentType, FieldDefinition fieldDef) {
GraphQLFieldDefinition.Builder builder = GraphQLFieldDefinition.newFieldDefinition();
builder.name(fieldDef.getName());
builder.description(buildDescription(fieldDef));
builder.dataFetcher(buildDataFetcher(buildCtx, parentType, fieldDef));
fieldDef.getInputValueDefinitions().forEach(inputValueDefinition ->
builder.argument(buildArgument(buildCtx, inputValueDefinition)));
GraphQLOutputType outputType = buildOutputType(buildCtx, fieldDef.getType());
builder.type(outputType);
return builder.build();
}
private DataFetcher buildDataFetcher(BuildContext buildCtx, TypeDefinition parentType, FieldDefinition fieldDef) {
String fieldName = fieldDef.getName();
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring wiring = buildCtx.getWiring();
WiringFactory wiringFactory = wiring.getWiringFactory();
DataFetcher dataFetcher;
if (wiringFactory.providesDataFetcher(typeRegistry, fieldDef)) {
dataFetcher = wiringFactory.getDataFetcher(typeRegistry, fieldDef);
assertNotNull(dataFetcher, "The WiringFactory indicated it provides a data fetcher but then returned null");
} else {
dataFetcher = wiring.getDataFetcherForType(parentType.getName()).get(fieldName);
if (dataFetcher == null) {
//
// in the future we could support FieldDateFetcher but we would need a way to indicate that in the schema spec
// perhaps by a directive
dataFetcher = new PropertyDataFetcher(fieldName);
}
}
return dataFetcher;
}
private GraphQLInputObjectType buildInputObjectType(BuildContext buildCtx, InputObjectTypeDefinition typeDefinition) {
GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject();
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition));
typeDefinition.getInputValueDefinitions().forEach(fieldDef ->
builder.field(buildInputField(buildCtx, fieldDef)));
return builder.build();
}
private GraphQLInputObjectField buildInputField(BuildContext buildCtx, InputValueDefinition fieldDef) {
GraphQLInputObjectField.Builder fieldBuilder = GraphQLInputObjectField.newInputObjectField();
fieldBuilder.name(fieldDef.getName());
fieldBuilder.description(buildDescription(fieldDef));
fieldBuilder.type(buildInputType(buildCtx, fieldDef.getType()));
fieldBuilder.defaultValue(buildValue(fieldDef.getDefaultValue()));
return fieldBuilder.build();
}
private GraphQLArgument buildArgument(BuildContext buildCtx, InputValueDefinition valueDefinition) {
GraphQLArgument.Builder builder = GraphQLArgument.newArgument();
builder.name(valueDefinition.getName());
builder.description(buildDescription(valueDefinition));
builder.type(buildInputType(buildCtx, valueDefinition.getType()));
builder.defaultValue(buildValue(valueDefinition.getDefaultValue()));
return builder.build();
}
private Object buildValue(Value value) {
Object result = null;
if (value instanceof IntValue) {
result = ((IntValue) value).getValue();
} else if (value instanceof FloatValue) {
result = ((FloatValue) value).getValue();
} else if (value instanceof StringValue) {
result = ((StringValue) value).getValue();
} else if (value instanceof EnumValue) {
result = ((EnumValue) value).getName();
} else if (value instanceof BooleanValue) {
result = ((BooleanValue) value).isValue();
} else if (value instanceof ArrayValue) {
ArrayValue arrayValue = (ArrayValue) value;
result = arrayValue.getValues().stream().map(this::buildValue).toArray();
} else if (value instanceof ObjectValue) {
result = buildObjectValue((ObjectValue) value);
}
return result;
}
private Object buildObjectValue(ObjectValue defaultValue) {
HashMap<String, Object> map = new LinkedHashMap<>();
defaultValue.getObjectFields().forEach(of -> map.put(of.getName(), buildValue(of.getValue())));
return map;
}
private TypeResolver getTypeResolverForUnion(BuildContext buildCtx, UnionTypeDefinition unionType) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring wiring = buildCtx.getWiring();
WiringFactory wiringFactory = wiring.getWiringFactory();
TypeResolver typeResolver;
if (wiringFactory.providesTypeResolver(typeRegistry, unionType)) {
typeResolver = wiringFactory.getTypeResolver(typeRegistry, unionType);
assertNotNull(typeResolver, "The WiringFactory indicated it provides a type resolver but then returned null");
} else {
typeResolver = wiring.getTypeResolvers().get(unionType.getName());
if (typeResolver == null) {
// this really should be checked earlier via a pre-flight check
typeResolver = new TypeResolverProxy();
}
}
return typeResolver;
}
private TypeResolver getTypeResolverForInterface(BuildContext buildCtx, InterfaceTypeDefinition interfaceType) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring wiring = buildCtx.getWiring();
WiringFactory wiringFactory = wiring.getWiringFactory();
TypeResolver typeResolver;
if (wiringFactory.providesTypeResolver(typeRegistry, interfaceType)) {
typeResolver = wiringFactory.getTypeResolver(typeRegistry, interfaceType);
assertNotNull(typeResolver, "The WiringFactory indicated it provides a type resolver but then returned null");
} else {
typeResolver = wiring.getTypeResolvers().get(interfaceType.getName());
if (typeResolver == null) {
// this really should be checked earlier via a pre-flight check
typeResolver = new TypeResolverProxy();
}
}
return typeResolver;
}
private String buildDescription(Node node) {
StringBuilder sb = new StringBuilder();
List<Comment> comments = node.getComments();
for (int i = 0; i < comments.size(); i++) {
if (i > 0) {
sb.append("\n");
}
sb.append(comments.get(i).getContent().trim());
}
return sb.toString();
}
}