graphql.schema.idl.SchemaGeneratorHelper Maven / Gradle / Ivy
package graphql.schema.idl;
import graphql.Assert;
import graphql.AssertException;
import graphql.Internal;
import graphql.introspection.Introspection.DirectiveLocation;
import graphql.language.Argument;
import graphql.language.Comment;
import graphql.language.Description;
import graphql.language.Directive;
import graphql.language.DirectiveDefinition;
import graphql.language.EnumTypeDefinition;
import graphql.language.EnumTypeExtensionDefinition;
import graphql.language.EnumValueDefinition;
import graphql.language.FieldDefinition;
import graphql.language.InputObjectTypeDefinition;
import graphql.language.InputObjectTypeExtensionDefinition;
import graphql.language.InputValueDefinition;
import graphql.language.InterfaceTypeDefinition;
import graphql.language.InterfaceTypeExtensionDefinition;
import graphql.language.Node;
import graphql.language.ObjectTypeDefinition;
import graphql.language.ObjectTypeExtensionDefinition;
import graphql.language.OperationTypeDefinition;
import graphql.language.ScalarTypeDefinition;
import graphql.language.ScalarTypeExtensionDefinition;
import graphql.language.StringValue;
import graphql.language.Type;
import graphql.language.TypeDefinition;
import graphql.language.TypeName;
import graphql.language.UnionTypeDefinition;
import graphql.language.UnionTypeExtensionDefinition;
import graphql.language.Value;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetcherFactories;
import graphql.schema.DataFetcherFactory;
import graphql.schema.FieldCoordinates;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLDirective;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLEnumValueDefinition;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLNamedInputType;
import graphql.schema.GraphQLNamedOutputType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.GraphQLUnionType;
import graphql.schema.GraphqlTypeComparatorRegistry;
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.util.FpKit;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import static graphql.Assert.assertNotNull;
import static graphql.Directives.DEPRECATED_DIRECTIVE_DEFINITION;
import static graphql.Directives.SPECIFIED_BY_DIRECTIVE_DEFINITION;
import static graphql.Directives.SpecifiedByDirective;
import static graphql.collect.ImmutableKit.map;
import static graphql.introspection.Introspection.DirectiveLocation.ARGUMENT_DEFINITION;
import static graphql.introspection.Introspection.DirectiveLocation.ENUM;
import static graphql.introspection.Introspection.DirectiveLocation.ENUM_VALUE;
import static graphql.introspection.Introspection.DirectiveLocation.FIELD_DEFINITION;
import static graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION;
import static graphql.introspection.Introspection.DirectiveLocation.INPUT_OBJECT;
import static graphql.introspection.Introspection.DirectiveLocation.OBJECT;
import static graphql.introspection.Introspection.DirectiveLocation.SCALAR;
import static graphql.introspection.Introspection.DirectiveLocation.UNION;
import static graphql.schema.GraphQLEnumValueDefinition.newEnumValueDefinition;
import static graphql.schema.GraphQLTypeReference.typeRef;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
@Internal
public class SchemaGeneratorHelper {
/**
* We pass this around so we know what we have defined in a stack like manner plus
* it gives us helper functions
*/
static class BuildContext {
private final TypeDefinitionRegistry typeRegistry;
private final RuntimeWiring wiring;
private final Deque typeStack = new ArrayDeque<>();
private final Map outputGTypes = new LinkedHashMap<>();
private final Map inputGTypes = new LinkedHashMap<>();
private final Map directiveBehaviourContext = new LinkedHashMap<>();
private final Set directives = new LinkedHashSet<>();
private final GraphQLCodeRegistry.Builder codeRegistry;
public final Map operationTypeDefs;
BuildContext(TypeDefinitionRegistry typeRegistry, RuntimeWiring wiring, Map operationTypeDefinitions) {
this.typeRegistry = typeRegistry;
this.wiring = wiring;
this.codeRegistry = GraphQLCodeRegistry.newCodeRegistry(wiring.getCodeRegistry());
this.operationTypeDefs = operationTypeDefinitions;
}
public TypeDefinitionRegistry getTypeRegistry() {
return typeRegistry;
}
TypeDefinition getTypeDefinition(Type type) {
Optional optionalTypeDefinition = typeRegistry.getType(type);
return optionalTypeDefinition.orElseThrow(
() -> new AssertException(format(" type definition for type '%s' not found", type))
);
}
boolean stackContains(TypeInfo typeInfo) {
return typeStack.contains(typeInfo.getName());
}
void push(TypeInfo typeInfo) {
typeStack.push(typeInfo.getName());
}
void pop() {
typeStack.pop();
}
GraphQLOutputType hasOutputType(TypeDefinition typeDefinition) {
return outputGTypes.get(typeDefinition.getName());
}
GraphQLInputType hasInputType(TypeDefinition typeDefinition) {
return inputGTypes.get(typeDefinition.getName());
}
void putOutputType(GraphQLNamedOutputType outputType) {
outputGTypes.put(outputType.getName(), outputType);
// certain types can be both input and output types, for example enums and scalars
if (outputType instanceof GraphQLInputType) {
inputGTypes.put(outputType.getName(), (GraphQLInputType) outputType);
}
}
void putInputType(GraphQLNamedInputType inputType) {
inputGTypes.put(inputType.getName(), inputType);
// certain types can be both input and output types, for example enums and scalars
if (inputType instanceof GraphQLOutputType) {
outputGTypes.put(inputType.getName(), (GraphQLOutputType) inputType);
}
}
RuntimeWiring getWiring() {
return wiring;
}
GraphqlTypeComparatorRegistry getComparatorRegistry() {
return wiring.getComparatorRegistry();
}
public GraphQLCodeRegistry.Builder getCodeRegistry() {
return codeRegistry;
}
public void addDirectiveDefinition(GraphQLDirective directive) {
this.directives.add(directive);
}
public void addDirectives(Set directives) {
this.directives.addAll(directives);
}
public Set getDirectives() {
return directives;
}
}
static final String NO_LONGER_SUPPORTED = "No longer supported";
private static Description createDescription(String s) {
return new Description(s, null, false);
}
String buildDescription(Node> node, Description description) {
if (description != null) {
return description.getContent();
}
List comments = node.getComments();
List lines = new ArrayList<>();
for (Comment comment : comments) {
String commentLine = comment.getContent();
if (commentLine.trim().isEmpty()) {
lines.clear();
} else {
lines.add(commentLine);
}
}
if (lines.size() == 0) {
return null;
}
return lines.stream().collect(joining("\n"));
}
String buildDeprecationReason(List directives) {
directives = Optional.ofNullable(directives).orElse(emptyList());
Optional directive = directives.stream().filter(d -> "deprecated".equals(d.getName())).findFirst();
if (directive.isPresent()) {
Map args = directive.get().getArguments().stream().collect(toMap(
Argument::getName, arg -> ((StringValue) arg.getValue()).getValue()
));
if (args.isEmpty()) {
return NO_LONGER_SUPPORTED; // default value from spec
} else {
// pre flight checks have ensured its valid
return args.get("reason");
}
}
return null;
}
private GraphQLDirective buildAppliedDirective(BuildContext buildCtx, Directive directive, DirectiveLocation directiveLocation, Set runtimeDirectives, GraphqlTypeComparatorRegistry comparatorRegistry, Set previousNames) {
GraphQLDirective gqlDirective = buildAppliedDirective(buildCtx, directive, runtimeDirectives, directiveLocation, comparatorRegistry);
if (previousNames.contains(directive.getName())) {
// other parts of the code protect against duplicate non repeatable directives
Assert.assertTrue(gqlDirective.isRepeatable(), () -> String.format("The directive '%s' MUST be defined as a repeatable directive if its repeated on an SDL element", directive.getName()));
}
previousNames.add(gqlDirective.getName());
return gqlDirective;
}
// builds directives from a type and its extensions
GraphQLDirective buildAppliedDirective(BuildContext buildCtx,
Directive directive,
Set directiveDefinitions,
DirectiveLocation directiveLocation,
GraphqlTypeComparatorRegistry comparatorRegistry) {
GraphQLDirective.Builder builder = GraphQLDirective.newDirective()
.name(directive.getName())
.description(buildDescription(directive, null))
.comparatorRegistry(comparatorRegistry)
.validLocations(directiveLocation);
Optional directiveDefOpt = FpKit.findOne(directiveDefinitions, dd -> dd.getName().equals(directive.getName()));
GraphQLDirective graphQLDirective = directiveDefOpt.orElseGet(() -> {
Function inputTypeFactory = inputType -> buildInputType(buildCtx, inputType);
return buildDirectiveDefinitionFromAst(buildCtx, buildCtx.getTypeRegistry().getDirectiveDefinition(directive.getName()).get(), inputTypeFactory);
});
builder.repeatable(graphQLDirective.isRepeatable());
List appliedArguments = map(directive.getArguments(), arg -> buildAppliedDArgument(buildCtx, arg, graphQLDirective));
appliedArguments = transferMissingArguments(appliedArguments, graphQLDirective);
appliedArguments.forEach(builder::argument);
return builder.build();
}
private GraphQLArgument buildAppliedDArgument(BuildContext buildCtx, Argument arg, GraphQLDirective directiveDefinition) {
GraphQLArgument directiveDefArgument = directiveDefinition.getArgument(arg.getName());
GraphQLArgument.Builder builder = GraphQLArgument.newArgument();
builder.name(arg.getName());
GraphQLInputType inputType = directiveDefArgument.getType();
builder.type(inputType);
// we know it is a literal because it was created by SchemaGenerator
if (directiveDefArgument.getArgumentDefaultValue().isSet()) {
builder.defaultValueLiteral((Value) directiveDefArgument.getArgumentDefaultValue().getValue());
}
// Object value = buildCtx, arg.getValue(), inputType);
// we put the default value in if the specified is null
if (arg.getValue() != null) {
//TODO: maybe validation of it
builder.valueLiteral(arg.getValue());
}
return builder.build();
}
private List transferMissingArguments(List arguments, GraphQLDirective directiveDefinition) {
Map declaredArgs = FpKit.getByName(arguments, GraphQLArgument::getName, FpKit.mergeFirst());
List argumentsOut = new ArrayList<>(arguments);
for (GraphQLArgument directiveDefArg : directiveDefinition.getArguments()) {
if (!declaredArgs.containsKey(directiveDefArg.getName())) {
GraphQLArgument.Builder missingArg = GraphQLArgument.newArgument()
.name(directiveDefArg.getName())
.description(directiveDefArg.getDescription())
.definition(directiveDefArg.getDefinition())
.type(directiveDefArg.getType());
if (directiveDefArg.hasSetDefaultValue()) {
missingArg.defaultValueLiteral((Value) directiveDefArg.getArgumentDefaultValue().getValue());
}
if (directiveDefArg.hasSetValue()) {
missingArg.valueLiteral((Value) directiveDefArg.getArgumentValue().getValue());
}
argumentsOut.add(missingArg.build());
}
}
return argumentsOut;
}
GraphQLDirective buildDirectiveDefinitionFromAst(BuildContext buildCtx, DirectiveDefinition directiveDefinition, Function inputTypeFactory) {
GraphQLDirective.Builder builder = GraphQLDirective.newDirective()
.name(directiveDefinition.getName())
.definition(directiveDefinition)
.repeatable(directiveDefinition.isRepeatable())
.description(buildDescription(directiveDefinition, directiveDefinition.getDescription()));
List locations = buildLocations(directiveDefinition);
locations.forEach(builder::validLocations);
List arguments = map(directiveDefinition.getInputValueDefinitions(),
arg -> buildDirectiveArgumentDefinitionFromAst(buildCtx, arg, inputTypeFactory));
arguments.forEach(builder::argument);
return builder.build();
}
private List buildLocations(DirectiveDefinition directiveDefinition) {
return map(directiveDefinition.getDirectiveLocations(),
dl -> DirectiveLocation.valueOf(dl.getName().toUpperCase()));
}
private GraphQLArgument buildDirectiveArgumentDefinitionFromAst(BuildContext buildCtx, InputValueDefinition arg, Function inputTypeFactory) {
GraphQLArgument.Builder builder = GraphQLArgument.newArgument()
.name(arg.getName())
.definition(arg);
GraphQLInputType inputType = inputTypeFactory.apply(arg.getType());
builder.type(inputType);
if (arg.getDefaultValue() != null) {
builder.valueLiteral(arg.getDefaultValue());
builder.defaultValueLiteral(arg.getDefaultValue());
}
builder.description(buildDescription(arg, arg.getDescription()));
return builder.build();
}
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(typeRef(typeInfo.getName()));
}
buildCtx.push(typeInfo);
if (typeDefinition instanceof InputObjectTypeDefinition) {
inputType = buildInputObjectType(buildCtx, (InputObjectTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof EnumTypeDefinition) {
inputType = buildEnumType(buildCtx, (EnumTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof ScalarTypeDefinition) {
inputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
} else {
// typeDefinition is not a valid InputType
throw new NotAnInputTypeError(rawType, typeDefinition);
}
buildCtx.putInputType((GraphQLNamedInputType) inputType);
buildCtx.pop();
return typeInfo.decorate(inputType);
}
GraphQLInputObjectType buildInputObjectType(BuildContext buildCtx, InputObjectTypeDefinition typeDefinition) {
GraphQLInputObjectType.Builder builder = GraphQLInputObjectType.newInputObject();
builder.definition(typeDefinition);
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition, typeDefinition.getDescription()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
List extensions = inputObjectTypeExtensions(typeDefinition, buildCtx);
builder.extensionDefinitions(extensions);
builder.withDirectives(
buildAppliedDirectives(buildCtx,
typeDefinition.getDirectives(),
directivesOf(extensions),
INPUT_OBJECT,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
typeDefinition.getInputValueDefinitions().forEach(inputValue ->
builder.field(buildInputField(buildCtx, inputValue)));
extensions.forEach(extension -> extension.getInputValueDefinitions().forEach(inputValueDefinition -> {
GraphQLInputObjectField inputField = buildInputField(buildCtx, inputValueDefinition);
if (!builder.hasField(inputField.getName())) {
builder.field(inputField);
}
}));
return builder.build();
}
private GraphQLInputObjectField buildInputField(BuildContext buildCtx, InputValueDefinition fieldDef) {
GraphQLInputObjectField.Builder fieldBuilder = GraphQLInputObjectField.newInputObjectField();
fieldBuilder.definition(fieldDef);
fieldBuilder.name(fieldDef.getName());
fieldBuilder.description(buildDescription(fieldDef, fieldDef.getDescription()));
fieldBuilder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));
fieldBuilder.comparatorRegistry(buildCtx.getComparatorRegistry());
// currently the spec doesnt allow deprecations on InputValueDefinitions but it should!
//fieldBuilder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));
GraphQLInputType inputType = buildInputType(buildCtx, fieldDef.getType());
fieldBuilder.type(inputType);
Value defaultValue = fieldDef.getDefaultValue();
if (defaultValue != null) {
fieldBuilder.defaultValueLiteral(defaultValue);
}
fieldBuilder.withDirectives(
buildAppliedDirectives(buildCtx,
fieldDef.getDirectives(),
emptyList(),
INPUT_FIELD_DEFINITION,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
return fieldBuilder.build();
}
GraphQLEnumType buildEnumType(BuildContext buildCtx, EnumTypeDefinition typeDefinition) {
GraphQLEnumType.Builder builder = GraphQLEnumType.newEnum();
builder.definition(typeDefinition);
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition, typeDefinition.getDescription()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
List extensions = enumTypeExtensions(typeDefinition, buildCtx);
builder.extensionDefinitions(extensions);
EnumValuesProvider enumValuesProvider = buildCtx.getWiring().getEnumValuesProviders().get(typeDefinition.getName());
typeDefinition.getEnumValueDefinitions().forEach(evd -> {
GraphQLEnumValueDefinition enumValueDefinition = buildEnumValue(buildCtx, typeDefinition, enumValuesProvider, evd);
builder.value(enumValueDefinition);
});
extensions.forEach(extension -> extension.getEnumValueDefinitions().forEach(evd -> {
GraphQLEnumValueDefinition enumValueDefinition = buildEnumValue(buildCtx, typeDefinition, enumValuesProvider, evd);
if (!builder.hasValue(enumValueDefinition.getName())) {
builder.value(enumValueDefinition);
}
}));
builder.withDirectives(
buildAppliedDirectives(buildCtx,
typeDefinition.getDirectives(),
directivesOf(extensions),
ENUM,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
return builder.build();
}
private GraphQLEnumValueDefinition buildEnumValue(BuildContext buildCtx,
EnumTypeDefinition typeDefinition,
EnumValuesProvider enumValuesProvider,
EnumValueDefinition evd) {
String description = buildDescription(evd, evd.getDescription());
String deprecation = buildDeprecationReason(evd.getDirectives());
Object value;
if (enumValuesProvider != null) {
value = enumValuesProvider.getValue(evd.getName());
assertNotNull(value,
() -> format("EnumValuesProvider for %s returned null for %s", typeDefinition.getName(), evd.getName()));
} else {
value = evd.getName();
}
return newEnumValueDefinition()
.name(evd.getName())
.value(value)
.description(description)
.deprecationReason(deprecation)
.definition(evd)
.comparatorRegistry(buildCtx.getComparatorRegistry())
.withDirectives(
buildAppliedDirectives(buildCtx,
evd.getDirectives(),
emptyList(),
ENUM_VALUE,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
)
.build();
}
GraphQLScalarType buildScalar(BuildContext buildCtx, ScalarTypeDefinition typeDefinition) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring runtimeWiring = buildCtx.getWiring();
WiringFactory wiringFactory = runtimeWiring.getWiringFactory();
List extensions = scalarTypeExtensions(typeDefinition, buildCtx);
ScalarWiringEnvironment environment = new ScalarWiringEnvironment(typeRegistry, typeDefinition, extensions);
GraphQLScalarType scalar;
if (wiringFactory.providesScalar(environment)) {
scalar = wiringFactory.getScalar(environment);
} else {
scalar = buildCtx.getWiring().getScalars().get(typeDefinition.getName());
}
if (!ScalarInfo.isGraphqlSpecifiedScalar(scalar)) {
String description = getScalarDesc(scalar,typeDefinition);
scalar = scalar.transform(builder -> builder
.description(description)
.definition(typeDefinition)
.comparatorRegistry(buildCtx.getComparatorRegistry())
.specifiedByUrl(getSpecifiedByUrl(typeDefinition, extensions))
.withDirectives(buildAppliedDirectives(
buildCtx,
typeDefinition.getDirectives(),
directivesOf(extensions),
SCALAR,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
));
}
return scalar;
}
private String getScalarDesc(GraphQLScalarType scalar, ScalarTypeDefinition typeDefinition) {
if (scalar.getDescription() != null ) {
if (!scalar.getDescription().trim().isEmpty()) {
return scalar.getDescription();
}
}
if (typeDefinition.getDescription() != null) {
return typeDefinition.getDescription().getContent();
}
return "";
}
String getSpecifiedByUrl(ScalarTypeDefinition scalarTypeDefinition, List extensions) {
List allDirectives = new ArrayList<>(scalarTypeDefinition.getDirectives());
extensions.forEach(extension -> allDirectives.addAll(extension.getDirectives()));
Optional specifiedByDirective = FpKit.findOne(allDirectives,
directiveDefinition -> directiveDefinition.getName().equals(SpecifiedByDirective.getName()));
if (!specifiedByDirective.isPresent()) {
return null;
}
Argument urlArgument = specifiedByDirective.get().getArgument("url");
StringValue url = (StringValue) urlArgument.getValue();
return url.getValue();
}
private TypeResolver getTypeResolverForInterface(BuildContext buildCtx, InterfaceTypeDefinition interfaceType) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring wiring = buildCtx.getWiring();
WiringFactory wiringFactory = wiring.getWiringFactory();
TypeResolver typeResolver;
InterfaceWiringEnvironment environment = new InterfaceWiringEnvironment(typeRegistry, interfaceType);
if (wiringFactory.providesTypeResolver(environment)) {
typeResolver = wiringFactory.getTypeResolver(environment);
assertNotNull(typeResolver, () -> "The WiringFactory indicated it provides a interface 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 TypeResolver getTypeResolverForUnion(BuildContext buildCtx, UnionTypeDefinition unionType) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring wiring = buildCtx.getWiring();
WiringFactory wiringFactory = wiring.getWiringFactory();
TypeResolver typeResolver;
UnionWiringEnvironment environment = new UnionWiringEnvironment(typeRegistry, unionType);
if (wiringFactory.providesTypeResolver(environment)) {
typeResolver = wiringFactory.getTypeResolver(environment);
assertNotNull(typeResolver, () -> "The WiringFactory indicated it union 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;
}
GraphQLDirective[] buildAppliedDirectives(BuildContext buildCtx,
List directives,
List extensionDirectives,
DirectiveLocation directiveLocation,
Set runtimeDirectives,
GraphqlTypeComparatorRegistry comparatorRegistry) {
directives = Optional.ofNullable(directives).orElse(emptyList());
extensionDirectives = Optional.ofNullable(extensionDirectives).orElse(emptyList());
Set previousNames = new LinkedHashSet<>();
List output = new ArrayList<>();
for (Directive directive : directives) {
GraphQLDirective gqlDirective = buildAppliedDirective(buildCtx, directive, directiveLocation, runtimeDirectives, comparatorRegistry, previousNames);
output.add(gqlDirective);
}
for (Directive directive : extensionDirectives) {
GraphQLDirective gqlDirective = buildAppliedDirective(buildCtx, directive, directiveLocation, runtimeDirectives, comparatorRegistry, previousNames);
output.add(gqlDirective);
}
return output.toArray(new GraphQLDirective[0]);
}
private void buildInterfaceTypeInterfaces(BuildContext buildCtx,
InterfaceTypeDefinition typeDefinition,
GraphQLInterfaceType.Builder builder,
List extensions) {
Map interfaces = new LinkedHashMap<>();
typeDefinition.getImplements().forEach(type -> {
GraphQLNamedOutputType newInterfaceType = buildOutputType(buildCtx, type);
interfaces.put(newInterfaceType.getName(), newInterfaceType);
});
extensions.forEach(extension -> extension.getImplements().forEach(type -> {
GraphQLInterfaceType interfaceType = buildOutputType(buildCtx, type);
if (!interfaces.containsKey(interfaceType.getName())) {
interfaces.put(interfaceType.getName(), interfaceType);
}
}));
interfaces.values().forEach(interfaze -> {
if (interfaze instanceof GraphQLInterfaceType) {
builder.withInterface((GraphQLInterfaceType) interfaze);
return;
}
if (interfaze instanceof GraphQLTypeReference) {
builder.withInterface((GraphQLTypeReference) interfaze);
}
});
}
private GraphQLObjectType buildOperation(BuildContext buildCtx, OperationTypeDefinition operation) {
Type type = operation.getTypeName();
return buildOutputType(buildCtx, type);
}
GraphQLInterfaceType buildInterfaceType(BuildContext buildCtx, InterfaceTypeDefinition typeDefinition) {
GraphQLInterfaceType.Builder builder = GraphQLInterfaceType.newInterface();
builder.definition(typeDefinition);
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition, typeDefinition.getDescription()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
List extensions = interfaceTypeExtensions(typeDefinition, buildCtx);
builder.extensionDefinitions(extensions);
builder.withDirectives(
buildAppliedDirectives(buildCtx,
typeDefinition.getDirectives(),
directivesOf(extensions),
OBJECT,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
typeDefinition.getFieldDefinitions().forEach(fieldDef -> {
GraphQLFieldDefinition fieldDefinition = buildField(buildCtx, typeDefinition, fieldDef);
builder.field(fieldDefinition);
});
extensions.forEach(extension -> extension.getFieldDefinitions().forEach(fieldDef -> {
GraphQLFieldDefinition fieldDefinition = buildField(buildCtx, typeDefinition, fieldDef);
if (!builder.hasField(fieldDefinition.getName())) {
builder.field(fieldDefinition);
}
}));
buildInterfaceTypeInterfaces(buildCtx, typeDefinition, builder, extensions);
GraphQLInterfaceType interfaceType = builder.build();
if (!buildCtx.getCodeRegistry().hasTypeResolver(interfaceType.getName())) {
TypeResolver typeResolver = getTypeResolverForInterface(buildCtx, typeDefinition);
buildCtx.getCodeRegistry().typeResolver(interfaceType, typeResolver);
}
return interfaceType;
}
GraphQLObjectType buildObjectType(BuildContext buildCtx, ObjectTypeDefinition typeDefinition) {
GraphQLObjectType.Builder builder = GraphQLObjectType.newObject();
builder.definition(typeDefinition);
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition, typeDefinition.getDescription()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
List extensions = objectTypeExtensions(typeDefinition, buildCtx);
builder.extensionDefinitions(extensions);
builder.withDirectives(
buildAppliedDirectives(buildCtx,
typeDefinition.getDirectives(),
directivesOf(extensions),
OBJECT,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
typeDefinition.getFieldDefinitions().forEach(fieldDef -> {
GraphQLFieldDefinition fieldDefinition = buildField(buildCtx, typeDefinition, fieldDef);
builder.field(fieldDefinition);
});
extensions.forEach(extension -> extension.getFieldDefinitions().forEach(fieldDef -> {
GraphQLFieldDefinition fieldDefinition = buildField(buildCtx, typeDefinition, fieldDef);
if (!builder.hasField(fieldDefinition.getName())) {
builder.field(fieldDefinition);
}
}));
buildObjectTypeInterfaces(buildCtx, typeDefinition, builder, extensions);
return builder.build();
}
private void buildObjectTypeInterfaces(BuildContext buildCtx,
ObjectTypeDefinition typeDefinition,
GraphQLObjectType.Builder builder,
List extensions) {
Map interfaces = new LinkedHashMap<>();
typeDefinition.getImplements().forEach(type -> {
GraphQLNamedOutputType newInterfaceType = buildOutputType(buildCtx, type);
interfaces.put(newInterfaceType.getName(), newInterfaceType);
});
extensions.forEach(extension -> extension.getImplements().forEach(type -> {
GraphQLInterfaceType interfaceType = buildOutputType(buildCtx, type);
if (!interfaces.containsKey(interfaceType.getName())) {
interfaces.put(interfaceType.getName(), interfaceType);
}
}));
interfaces.values().forEach(interfaze -> {
if (interfaze instanceof GraphQLInterfaceType) {
builder.withInterface((GraphQLInterfaceType) interfaze);
return;
}
if (interfaze instanceof GraphQLTypeReference) {
builder.withInterface((GraphQLTypeReference) interfaze);
}
});
}
GraphQLUnionType buildUnionType(BuildContext buildCtx, UnionTypeDefinition typeDefinition) {
GraphQLUnionType.Builder builder = GraphQLUnionType.newUnionType();
builder.definition(typeDefinition);
builder.name(typeDefinition.getName());
builder.description(buildDescription(typeDefinition, typeDefinition.getDescription()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
List extensions = unionTypeExtensions(typeDefinition, buildCtx);
builder.extensionDefinitions(extensions);
typeDefinition.getMemberTypes().forEach(mt -> {
GraphQLOutputType outputType = buildOutputType(buildCtx, mt);
if (outputType instanceof GraphQLTypeReference) {
builder.possibleType((GraphQLTypeReference) outputType);
} else {
builder.possibleType((GraphQLObjectType) outputType);
}
});
builder.withDirectives(
buildAppliedDirectives(buildCtx,
typeDefinition.getDirectives(),
directivesOf(extensions),
UNION,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
extensions.forEach(extension -> extension.getMemberTypes().forEach(mt -> {
GraphQLNamedOutputType outputType = buildOutputType(buildCtx, mt);
if (!builder.containType(outputType.getName())) {
if (outputType instanceof GraphQLTypeReference) {
builder.possibleType((GraphQLTypeReference) outputType);
} else {
builder.possibleType((GraphQLObjectType) outputType);
}
}
}
));
GraphQLUnionType unionType = builder.build();
if (!buildCtx.getCodeRegistry().hasTypeResolver(unionType.getName())) {
TypeResolver typeResolver = getTypeResolverForUnion(buildCtx, typeDefinition);
buildCtx.getCodeRegistry().typeResolver(unionType, typeResolver);
}
return unionType;
}
/**
* 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", "TypeParameterUnusedInFormals"})
private 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(typeRef(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(buildCtx, (EnumTypeDefinition) typeDefinition);
} else if (typeDefinition instanceof ScalarTypeDefinition) {
outputType = buildScalar(buildCtx, (ScalarTypeDefinition) typeDefinition);
} else {
// typeDefinition is not a valid output type
throw new NotAnOutputTypeError(rawType, typeDefinition);
}
buildCtx.putOutputType((GraphQLNamedOutputType) outputType);
buildCtx.pop();
return (T) typeInfo.decorate(outputType);
}
GraphQLFieldDefinition buildField(BuildContext buildCtx, TypeDefinition parentType, FieldDefinition fieldDef) {
GraphQLFieldDefinition.Builder builder = GraphQLFieldDefinition.newFieldDefinition();
builder.definition(fieldDef);
builder.name(fieldDef.getName());
builder.description(buildDescription(fieldDef, fieldDef.getDescription()));
builder.deprecate(buildDeprecationReason(fieldDef.getDirectives()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
GraphQLDirective[] directives = buildAppliedDirectives(buildCtx,
fieldDef.getDirectives(),
emptyList(), FIELD_DEFINITION,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry());
builder.withDirectives(
directives
);
fieldDef.getInputValueDefinitions().forEach(inputValueDefinition ->
builder.argument(buildArgument(buildCtx, inputValueDefinition)));
GraphQLOutputType fieldType = buildOutputType(buildCtx, fieldDef.getType());
builder.type(fieldType);
GraphQLFieldDefinition fieldDefinition = builder.build();
// if they have already wired in a fetcher - then leave it alone
FieldCoordinates coordinates = FieldCoordinates.coordinates(parentType.getName(), fieldDefinition.getName());
if (!buildCtx.getCodeRegistry().hasDataFetcher(coordinates)) {
DataFetcherFactory dataFetcherFactory = buildDataFetcherFactory(buildCtx, parentType, fieldDef, fieldType, Arrays.asList(directives));
buildCtx.getCodeRegistry().dataFetcher(coordinates, dataFetcherFactory);
}
return fieldDefinition;
}
private DataFetcherFactory buildDataFetcherFactory(BuildContext buildCtx,
TypeDefinition parentType,
FieldDefinition fieldDef,
GraphQLOutputType fieldType,
List directives) {
String fieldName = fieldDef.getName();
String parentTypeName = parentType.getName();
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
RuntimeWiring runtimeWiring = buildCtx.getWiring();
WiringFactory wiringFactory = runtimeWiring.getWiringFactory();
FieldWiringEnvironment wiringEnvironment = new FieldWiringEnvironment(typeRegistry, parentType, fieldDef, fieldType, directives);
DataFetcherFactory> dataFetcherFactory;
if (wiringFactory.providesDataFetcherFactory(wiringEnvironment)) {
dataFetcherFactory = wiringFactory.getDataFetcherFactory(wiringEnvironment);
assertNotNull(dataFetcherFactory, () -> "The WiringFactory indicated it provides a data fetcher factory but then returned null");
} else {
//
// ok they provide a data fetcher directly
DataFetcher> dataFetcher;
if (wiringFactory.providesDataFetcher(wiringEnvironment)) {
dataFetcher = wiringFactory.getDataFetcher(wiringEnvironment);
assertNotNull(dataFetcher, () -> "The WiringFactory indicated it provides a data fetcher but then returned null");
} else {
dataFetcher = runtimeWiring.getDataFetcherForType(parentTypeName).get(fieldName);
if (dataFetcher == null) {
dataFetcher = runtimeWiring.getDefaultDataFetcherForType(parentTypeName);
if (dataFetcher == null) {
dataFetcher = wiringFactory.getDefaultDataFetcher(wiringEnvironment);
if (dataFetcher == null) {
dataFetcher = dataFetcherOfLastResort(wiringEnvironment);
}
}
}
}
dataFetcherFactory = DataFetcherFactories.useDataFetcher(dataFetcher);
}
return dataFetcherFactory;
}
GraphQLArgument buildArgument(BuildContext buildCtx, InputValueDefinition valueDefinition) {
GraphQLArgument.Builder builder = GraphQLArgument.newArgument();
builder.definition(valueDefinition);
builder.name(valueDefinition.getName());
builder.description(buildDescription(valueDefinition, valueDefinition.getDescription()));
builder.deprecate(buildDeprecationReason(valueDefinition.getDirectives()));
builder.comparatorRegistry(buildCtx.getComparatorRegistry());
GraphQLInputType inputType = buildInputType(buildCtx, valueDefinition.getType());
builder.type(inputType);
Value defaultValue = valueDefinition.getDefaultValue();
if (defaultValue != null) {
builder.defaultValueLiteral(defaultValue);
}
builder.withDirectives(
buildAppliedDirectives(buildCtx,
valueDefinition.getDirectives(),
emptyList(),
ARGUMENT_DEFINITION,
buildCtx.getDirectives(),
buildCtx.getComparatorRegistry())
);
return builder.build();
}
void buildOperations(BuildContext buildCtx, GraphQLSchema.Builder schemaBuilder) {
//
// Schema can be missing if the type is called 'Query'. Pre flight checks have checked that!
//
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
Map operationTypeDefs = buildCtx.operationTypeDefs;
GraphQLObjectType query;
GraphQLObjectType mutation;
GraphQLObjectType subscription;
Optional queryOperation = getOperationNamed("query", operationTypeDefs);
if (!queryOperation.isPresent()) {
@SuppressWarnings({"OptionalGetWithoutIsPresent", "ConstantConditions"})
TypeDefinition queryTypeDef = typeRegistry.getType("Query").get();
query = buildOutputType(buildCtx, TypeName.newTypeName().name(queryTypeDef.getName()).build());
} else {
query = buildOperation(buildCtx, queryOperation.get());
}
schemaBuilder.query(query);
Optional mutationOperation = getOperationNamed("mutation", operationTypeDefs);
if (!mutationOperation.isPresent()) {
Optional mutationTypeDef = typeRegistry.getType("Mutation");
if (mutationTypeDef.isPresent()) {
mutation = buildOutputType(buildCtx, TypeName.newTypeName().name(mutationTypeDef.get().getName()).build());
schemaBuilder.mutation(mutation);
}
} else {
mutation = buildOperation(buildCtx, mutationOperation.get());
schemaBuilder.mutation(mutation);
}
Optional subscriptionOperation = getOperationNamed("subscription", operationTypeDefs);
if (!subscriptionOperation.isPresent()) {
Optional subscriptionTypeDef = typeRegistry.getType("Subscription");
if (subscriptionTypeDef.isPresent()) {
subscription = buildOutputType(buildCtx, TypeName.newTypeName().name(subscriptionTypeDef.get().getName()).build());
schemaBuilder.subscription(subscription);
}
} else {
subscription = buildOperation(buildCtx, subscriptionOperation.get());
schemaBuilder.subscription(subscription);
}
}
void buildSchemaDirectivesAndExtensions(BuildContext buildCtx, GraphQLSchema.Builder schemaBuilder) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
List schemaDirectiveList = SchemaExtensionsChecker.gatherSchemaDirectives(typeRegistry);
Set runtimeDirectives = buildCtx.getDirectives();
schemaBuilder.withSchemaDirectives(
buildAppliedDirectives(buildCtx, schemaDirectiveList, emptyList(), DirectiveLocation.SCHEMA, runtimeDirectives, buildCtx.getComparatorRegistry())
);
schemaBuilder.definition(typeRegistry.schemaDefinition().orElse(null));
schemaBuilder.extensionDefinitions(typeRegistry.getSchemaExtensionDefinitions());
}
List inputObjectTypeExtensions(InputObjectTypeDefinition typeDefinition, BuildContext buildCtx) {
return buildCtx.getTypeRegistry().inputObjectTypeExtensions().getOrDefault(typeDefinition.getName(), emptyList());
}
List enumTypeExtensions(EnumTypeDefinition typeDefinition, BuildContext buildCtx) {
return buildCtx.getTypeRegistry().enumTypeExtensions().getOrDefault(typeDefinition.getName(), emptyList());
}
List scalarTypeExtensions(ScalarTypeDefinition typeDefinition, BuildContext buildCtx) {
return buildCtx.getTypeRegistry().scalarTypeExtensions().getOrDefault(typeDefinition.getName(), emptyList());
}
List interfaceTypeExtensions(InterfaceTypeDefinition typeDefinition, BuildContext buildCtx) {
return buildCtx.getTypeRegistry().interfaceTypeExtensions().getOrDefault(typeDefinition.getName(), emptyList());
}
List objectTypeExtensions(ObjectTypeDefinition typeDefinition, BuildContext buildCtx) {
return buildCtx.getTypeRegistry().objectTypeExtensions().getOrDefault(typeDefinition.getName(), emptyList());
}
List unionTypeExtensions(UnionTypeDefinition typeDefinition, BuildContext buildCtx) {
return buildCtx.getTypeRegistry().unionTypeExtensions().getOrDefault(typeDefinition.getName(), emptyList());
}
/**
* We build the query / mutation / subscription path as a tree of referenced types
* but then we build the rest of the types specified and put them in as additional types
*
* @param buildCtx the context we need to work out what we are doing
*
* @return the additional types not referenced from the top level operations
*/
Set buildAdditionalTypes(BuildContext buildCtx) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
Set detachedTypeNames = getDetachedTypeNames(buildCtx);
Set additionalTypes = new LinkedHashSet<>();
// recursively record detached types on the ctx and add them to the additionalTypes set
typeRegistry.types().values().stream()
.filter(typeDefinition -> detachedTypeNames.contains(typeDefinition.getName()))
.forEach(typeDefinition -> {
TypeName typeName = TypeName.newTypeName().name(typeDefinition.getName()).build();
if (typeDefinition instanceof InputObjectTypeDefinition) {
if (buildCtx.hasInputType(typeDefinition) == null) {
buildCtx.putInputType((GraphQLNamedInputType) buildInputType(buildCtx, typeName));
}
additionalTypes.add(buildCtx.inputGTypes.get(typeDefinition.getName()));
} else {
if (buildCtx.hasOutputType(typeDefinition) == null) {
buildCtx.putOutputType(buildOutputType(buildCtx, typeName));
}
additionalTypes.add(buildCtx.outputGTypes.get(typeDefinition.getName()));
}
});
typeRegistry.scalars().values().stream()
.filter(typeDefinition -> detachedTypeNames.contains(typeDefinition.getName()))
.forEach(scalarTypeDefinition -> {
if (ScalarInfo.isGraphqlSpecifiedScalar(scalarTypeDefinition.getName())) {
return;
}
if (buildCtx.hasInputType(scalarTypeDefinition) == null && buildCtx.hasOutputType(scalarTypeDefinition) == null) {
buildCtx.putOutputType(buildScalar(buildCtx, scalarTypeDefinition));
}
if (buildCtx.hasInputType(scalarTypeDefinition) != null) {
additionalTypes.add(buildCtx.inputGTypes.get(scalarTypeDefinition.getName()));
} else if (buildCtx.hasOutputType(scalarTypeDefinition) != null) {
additionalTypes.add(buildCtx.outputGTypes.get(scalarTypeDefinition.getName()));
}
});
return additionalTypes;
}
/**
* Detached types (or additional types) are all types that
* are not connected to the root operations types.
*
* @param buildCtx buildCtx
*
* @return detached type names
*/
private Set getDetachedTypeNames(BuildContext buildCtx) {
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
// connected types are all types that have a path that connects them back to the root operation types.
Set connectedTypes = new HashSet<>(buildCtx.inputGTypes.keySet());
connectedTypes.addAll(buildCtx.outputGTypes.keySet());
Set allTypeNames = new HashSet<>(typeRegistry.types().keySet());
Set scalars = new HashSet<>(typeRegistry.scalars().keySet());
allTypeNames.addAll(scalars);
// detached types are all types minus the connected types.
Set detachedTypeNames = new HashSet<>(allTypeNames);
detachedTypeNames.removeAll(connectedTypes);
return detachedTypeNames;
}
Set buildAdditionalDirectiveDefinitions(BuildContext buildCtx) {
Set additionalDirectives = new LinkedHashSet<>();
TypeDefinitionRegistry typeRegistry = buildCtx.getTypeRegistry();
for (DirectiveDefinition directiveDefinition : typeRegistry.getDirectiveDefinitions().values()) {
Function inputTypeFactory = inputType -> buildInputType(buildCtx, inputType);
GraphQLDirective directive = buildDirectiveDefinitionFromAst(buildCtx, directiveDefinition, inputTypeFactory);
buildCtx.addDirectiveDefinition(directive);
additionalDirectives.add(directive);
}
return additionalDirectives;
}
void addDirectivesIncludedByDefault(TypeDefinitionRegistry typeRegistry) {
// we synthesize this into the type registry - no need for them to add it
typeRegistry.add(DEPRECATED_DIRECTIVE_DEFINITION);
typeRegistry.add(SPECIFIED_BY_DIRECTIVE_DEFINITION);
}
private Optional getOperationNamed(String name, Map operationTypeDefs) {
return Optional.ofNullable(operationTypeDefs.get(name));
}
private DataFetcher> dataFetcherOfLastResort(FieldWiringEnvironment environment) {
String fieldName = environment.getFieldDefinition().getName();
return new PropertyDataFetcher(fieldName);
}
private List directivesOf(List extends TypeDefinition> typeDefinitions) {
return typeDefinitions.stream()
.map(TypeDefinition::getDirectives).filter(Objects::nonNull)
.flatMap(List::stream).collect(Collectors.toList());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy