graphql.schema.idl.RuntimeWiring Maven / Gradle / Ivy
package graphql.schema.idl;
import graphql.PublicApi;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphqlTypeComparatorRegistry;
import graphql.schema.TypeResolver;
import graphql.schema.idl.errors.StrictModeWiringException;
import graphql.schema.visibility.GraphqlFieldVisibility;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import static graphql.Assert.assertNotNull;
import static graphql.schema.visibility.DefaultGraphqlFieldVisibility.DEFAULT_FIELD_VISIBILITY;
import static java.lang.String.format;
/**
* A runtime wiring is a specification of data fetchers, type resolvers and custom scalars that are needed
* to wire together a functional {@link GraphQLSchema}
*/
@PublicApi
public class RuntimeWiring {
private final Map> dataFetchers;
private final Map defaultDataFetchers;
private final Map scalars;
private final Map typeResolvers;
private final Map registeredDirectiveWiring;
private final List directiveWiring;
private final WiringFactory wiringFactory;
private final Map enumValuesProviders;
private final GraphqlFieldVisibility fieldVisibility;
private final GraphQLCodeRegistry codeRegistry;
private final GraphqlTypeComparatorRegistry comparatorRegistry;
/**
* This is a Runtime wiring which provides mocked types resolver
* and scalars. Useful for testing only.
*/
public static final RuntimeWiring MOCKED_WIRING = RuntimeWiring
.newRuntimeWiring()
.wiringFactory(new MockedWiringFactory()).build();
private RuntimeWiring(Builder builder) {
this.dataFetchers = builder.dataFetchers;
this.defaultDataFetchers = builder.defaultDataFetchers;
this.scalars = builder.scalars;
this.typeResolvers = builder.typeResolvers;
this.registeredDirectiveWiring = builder.registeredDirectiveWiring;
this.directiveWiring = builder.directiveWiring;
this.wiringFactory = builder.wiringFactory;
this.enumValuesProviders = builder.enumValuesProviders;
this.fieldVisibility = builder.fieldVisibility;
this.codeRegistry = builder.codeRegistry;
this.comparatorRegistry = builder.comparatorRegistry;
}
/**
* @return a builder of Runtime Wiring
*/
public static Builder newRuntimeWiring() {
return new Builder();
}
/**
* @param originalRuntimeWiring the runtime wiring to start from
*
* @return a builder of Runtime Wiring based on the provided one
*/
public static Builder newRuntimeWiring(RuntimeWiring originalRuntimeWiring) {
Builder builder = new Builder();
builder.dataFetchers.putAll(originalRuntimeWiring.dataFetchers);
builder.defaultDataFetchers.putAll(originalRuntimeWiring.defaultDataFetchers);
builder.scalars.putAll(originalRuntimeWiring.scalars);
builder.typeResolvers.putAll(originalRuntimeWiring.typeResolvers);
builder.registeredDirectiveWiring.putAll(originalRuntimeWiring.registeredDirectiveWiring);
builder.directiveWiring.addAll(originalRuntimeWiring.directiveWiring);
builder.wiringFactory = originalRuntimeWiring.wiringFactory;
builder.enumValuesProviders.putAll(originalRuntimeWiring.enumValuesProviders);
builder.fieldVisibility = originalRuntimeWiring.fieldVisibility;
builder.codeRegistry = originalRuntimeWiring.codeRegistry;
builder.comparatorRegistry = originalRuntimeWiring.comparatorRegistry;
return builder;
}
/**
* This helps you transform the current RuntimeWiring object into another one by starting a builder with all
* the current values and allows you to transform it how you want.
*
* @param builderConsumer the consumer code that will be given a builder to transform
*
* @return a new RuntimeWiring object based on calling build on that builder
*/
public RuntimeWiring transform(Consumer builderConsumer) {
Builder builder = newRuntimeWiring(this);
builderConsumer.accept(builder);
return builder.build();
}
public GraphQLCodeRegistry getCodeRegistry() {
return codeRegistry;
}
public Map getScalars() {
return new LinkedHashMap<>(scalars);
}
public Map> getDataFetchers() {
return dataFetchers;
}
/**
* This is deprecated because the name has the wrong plural case.
*
* @param typeName the type for fetch a map of per field data fetchers for
*
* @return a map of field data fetchers for a type
*
* @deprecated See {@link #getDataFetchersForType(String)}
*/
@Deprecated(since = "2024-04-28")
public Map getDataFetcherForType(String typeName) {
return dataFetchers.computeIfAbsent(typeName, k -> new LinkedHashMap<>());
}
/**
* This returns a map of the data fetchers per field on that named type.
*
* @param typeName the type for fetch a map of per field data fetchers for
*
* @return a map of field data fetchers for a type
*/
public Map getDataFetchersForType(String typeName) {
return dataFetchers.computeIfAbsent(typeName, k -> new LinkedHashMap<>());
}
public DataFetcher getDefaultDataFetcherForType(String typeName) {
return defaultDataFetchers.get(typeName);
}
public Map getTypeResolvers() {
return typeResolvers;
}
public Map getEnumValuesProviders() {
return this.enumValuesProviders;
}
public WiringFactory getWiringFactory() {
return wiringFactory;
}
public GraphqlFieldVisibility getFieldVisibility() {
return fieldVisibility;
}
public Map getRegisteredDirectiveWiring() {
return registeredDirectiveWiring;
}
public List getDirectiveWiring() {
return directiveWiring;
}
public GraphqlTypeComparatorRegistry getComparatorRegistry() {
return comparatorRegistry;
}
@PublicApi
public static class Builder {
private final Map> dataFetchers = new LinkedHashMap<>();
private final Map defaultDataFetchers = new LinkedHashMap<>();
private final Map scalars = new LinkedHashMap<>();
private final Map typeResolvers = new LinkedHashMap<>();
private final Map enumValuesProviders = new LinkedHashMap<>();
private final Map registeredDirectiveWiring = new LinkedHashMap<>();
private final List directiveWiring = new ArrayList<>();
private WiringFactory wiringFactory = new NoopWiringFactory();
private boolean strictMode = false;
private GraphqlFieldVisibility fieldVisibility = DEFAULT_FIELD_VISIBILITY;
private GraphQLCodeRegistry codeRegistry = GraphQLCodeRegistry.newCodeRegistry().build();
private GraphqlTypeComparatorRegistry comparatorRegistry = GraphqlTypeComparatorRegistry.AS_IS_REGISTRY;
private Builder() {
ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS.forEach(this::scalar);
}
/**
* This puts the builder into strict mode, so if things get defined twice, for example, it will throw a {@link StrictModeWiringException}.
*
* @return this builder
*/
public Builder strictMode() {
this.strictMode = true;
return this;
}
/**
* Adds a wiring factory into the runtime wiring
*
* @param wiringFactory the wiring factory to add
*
* @return this outer builder
*/
public Builder wiringFactory(WiringFactory wiringFactory) {
assertNotNull(wiringFactory, () -> "You must provide a wiring factory");
this.wiringFactory = wiringFactory;
return this;
}
/**
* This allows you to seed in your own {@link graphql.schema.GraphQLCodeRegistry} instance
*
* @param codeRegistry the code registry to use
*
* @return this outer builder
*/
public Builder codeRegistry(GraphQLCodeRegistry codeRegistry) {
this.codeRegistry = assertNotNull(codeRegistry);
return this;
}
/**
* This allows you to seed in your own {@link graphql.schema.GraphQLCodeRegistry} instance
*
* @param codeRegistry the code registry to use
*
* @return this outer builder
*/
public Builder codeRegistry(GraphQLCodeRegistry.Builder codeRegistry) {
this.codeRegistry = assertNotNull(codeRegistry).build();
return this;
}
/**
* This allows you to add in new custom Scalar implementations beyond the standard set.
*
* @param scalarType the new scalar implementation
*
* @return the runtime wiring builder
*/
public Builder scalar(GraphQLScalarType scalarType) {
if (strictMode && scalars.containsKey(scalarType.getName())) {
throw new StrictModeWiringException(format("The scalar %s is already defined", scalarType.getName()));
}
scalars.put(scalarType.getName(), scalarType);
return this;
}
/**
* This allows you to add a field visibility that will be associated with the schema
*
* @param fieldVisibility the new field visibility
*
* @return the runtime wiring builder
*/
public Builder fieldVisibility(GraphqlFieldVisibility fieldVisibility) {
this.fieldVisibility = assertNotNull(fieldVisibility);
return this;
}
/**
* This allows you to add a new type wiring via a builder
*
* @param builder the type wiring builder to use
*
* @return this outer builder
*/
public Builder type(TypeRuntimeWiring.Builder builder) {
return type(builder.build());
}
/**
* This form allows a lambda to be used as the builder of a type wiring
*
* @param typeName the name of the type to wire
* @param builderFunction a function that will be given the builder to use
*
* @return the runtime wiring builder
*/
public Builder type(String typeName, UnaryOperator builderFunction) {
TypeRuntimeWiring.Builder builder = builderFunction.apply(TypeRuntimeWiring.newTypeWiring(typeName));
return type(builder.build());
}
/**
* This adds a type wiring
*
* @param typeRuntimeWiring the new type wiring
*
* @return the runtime wiring builder
*/
public Builder type(TypeRuntimeWiring typeRuntimeWiring) {
String typeName = typeRuntimeWiring.getTypeName();
Map typeDataFetchers = dataFetchers.computeIfAbsent(typeName, k -> new LinkedHashMap<>());
if (strictMode && !typeDataFetchers.isEmpty()) {
throw new StrictModeWiringException(format("The type %s has already been defined", typeName));
}
typeDataFetchers.putAll(typeRuntimeWiring.getFieldDataFetchers());
DataFetcher> defaultDataFetcher = typeRuntimeWiring.getDefaultDataFetcher();
if (defaultDataFetcher != null) {
defaultDataFetchers.put(typeName, defaultDataFetcher);
}
TypeResolver typeResolver = typeRuntimeWiring.getTypeResolver();
if (typeResolver != null) {
if (strictMode && this.typeResolvers.containsKey(typeName)) {
throw new StrictModeWiringException(format("The type %s already has a type resolver defined", typeName));
}
this.typeResolvers.put(typeName, typeResolver);
}
EnumValuesProvider enumValuesProvider = typeRuntimeWiring.getEnumValuesProvider();
if (enumValuesProvider != null) {
if (strictMode && this.enumValuesProviders.containsKey(typeName)) {
throw new StrictModeWiringException(format("The type %s already has a enum provider defined", typeName));
}
this.enumValuesProviders.put(typeName, enumValuesProvider);
}
return this;
}
/**
* This provides the wiring code for a named directive.
*
* Note: The provided directive wiring will ONLY be called back if an element has a directive
* with the specified name.
*
* To be called back for every directive the use {@link #directiveWiring(SchemaDirectiveWiring)} or
* use {@link graphql.schema.idl.WiringFactory#providesSchemaDirectiveWiring(SchemaDirectiveWiringEnvironment)}
* instead.
*
* @param directiveName the name of the directive to wire
* @param schemaDirectiveWiring the runtime behaviour of this wiring
*
* @return the runtime wiring builder
*
* @see #directiveWiring(SchemaDirectiveWiring)
* @see graphql.schema.idl.SchemaDirectiveWiring
* @see graphql.schema.idl.WiringFactory#providesSchemaDirectiveWiring(SchemaDirectiveWiringEnvironment)
*/
public Builder directive(String directiveName, SchemaDirectiveWiring schemaDirectiveWiring) {
registeredDirectiveWiring.put(directiveName, schemaDirectiveWiring);
return this;
}
/**
* This adds a directive wiring that will be called for all directives.
*
* Note : Unlike {@link #directive(String, SchemaDirectiveWiring)} which is only called back if a named
* directives is present, this directive wiring will be called back for every element
* in the schema even if it has zero directives.
*
* @param schemaDirectiveWiring the runtime behaviour of this wiring
*
* @return the runtime wiring builder
*
* @see #directive(String, SchemaDirectiveWiring)
* @see graphql.schema.idl.SchemaDirectiveWiring
* @see graphql.schema.idl.WiringFactory#providesSchemaDirectiveWiring(SchemaDirectiveWiringEnvironment)
*/
public Builder directiveWiring(SchemaDirectiveWiring schemaDirectiveWiring) {
directiveWiring.add(schemaDirectiveWiring);
return this;
}
/**
* You can specify your own sort order of graphql types via {@link graphql.schema.GraphqlTypeComparatorRegistry}
* which will tell you what type of objects you are to sort when
* it asks for a comparator.
*
* @param comparatorRegistry your own comparator registry
*
* @return the runtime wiring builder
*/
public Builder comparatorRegistry(GraphqlTypeComparatorRegistry comparatorRegistry) {
this.comparatorRegistry = comparatorRegistry;
return this;
}
/**
* @return the built runtime wiring
*/
public RuntimeWiring build() {
return new RuntimeWiring(this);
}
}
}