From 54d061b218abff0b45a493c564c09eb289439075 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Tue, 19 Apr 2022 14:51:36 +0100 Subject: [PATCH] Extract base GraphQlSource builder Separate more clearly the SDL builder options from other common options independent of how GraphQLSchema is created. See gh-312 --- .../src/docs/asciidoc/index.adoc | 6 +- .../AbstractGraphQlSourceBuilder.java | 147 +++++++++++ .../DefaultGraphQlSourceBuilder.java | 242 ------------------ ...ultSchemaResourceGraphQlSourceBuilder.java | 152 +++++++++++ .../ExternalSchemaGraphQlSourceBuilder.java | 49 ++++ .../graphql/execution/GraphQlSource.java | 164 ++++++------ ...emaResourceGraphQlSourceBuilderTests.java} | 4 +- .../springframework/graphql/GraphQlSetup.java | 4 +- 8 files changed, 442 insertions(+), 326 deletions(-) create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/execution/AbstractGraphQlSourceBuilder.java delete mode 100644 spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilder.java create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java create mode 100644 spring-graphql/src/main/java/org/springframework/graphql/execution/ExternalSchemaGraphQlSourceBuilder.java rename spring-graphql/src/test/java/org/springframework/graphql/execution/{DefaultGraphQlSourceBuilderTests.java => DefaultSchemaResourceGraphQlSourceBuilderTests.java} (97%) diff --git a/spring-graphql-docs/src/docs/asciidoc/index.adoc b/spring-graphql-docs/src/docs/asciidoc/index.adoc index f75f322f..fe309312 100644 --- a/spring-graphql-docs/src/docs/asciidoc/index.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/index.adoc @@ -243,9 +243,9 @@ The main implementation, `DefaultExecutionGraphQlService`, is configured with a `graphql.GraphQL` instance to use for request execution. It provides a builder API to initialize GraphQL Java and build a `GraphQlSource`. -The default `GraphQlSource` builder, accessible via `GraphQlSource.builder()`, enables -support for <>, <>, and -<>. +The default `GraphQlSource` builder, accessible via +`GraphQlSource.schemaResourceBuilder()`, enables support for +<>, <>, and <>. The Spring Boot {spring-boot-ref-docs}/web.html#web.graphql[starter] initializes a `GraphQlSource` instance through the default `GraphQlSource.Builder` and also enables diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/AbstractGraphQlSourceBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/AbstractGraphQlSourceBuilder.java new file mode 100644 index 00000000..ce0684b9 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/AbstractGraphQlSourceBuilder.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.execution; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import graphql.GraphQL; +import graphql.execution.instrumentation.ChainedInstrumentation; +import graphql.execution.instrumentation.Instrumentation; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLSchema; +import graphql.schema.GraphQLTypeVisitor; +import graphql.schema.SchemaTraverser; + + +/** + * Implementation of {@link GraphQlSource.Builder} that leaves it to subclasses + * to initialize {@link GraphQLSchema}. + * + * @author Rossen Stoyanchev + * @author Brian Clozel + * @since 1.0.0 + */ +abstract class AbstractGraphQlSourceBuilder> implements GraphQlSource.Builder { + + private final List exceptionResolvers = new ArrayList<>(); + + private final List typeVisitors = new ArrayList<>(); + + private final List instrumentations = new ArrayList<>(); + + private Consumer graphQlConfigurers = (builder) -> { + }; + + + @Override + public B exceptionResolvers(List resolvers) { + this.exceptionResolvers.addAll(resolvers); + return self(); + } + + @Override + public B typeVisitors(List typeVisitors) { + this.typeVisitors.addAll(typeVisitors); + return self(); + } + + @Override + public B instrumentation(List instrumentations) { + this.instrumentations.addAll(instrumentations); + return self(); + } + + @Override + public B configureGraphQl(Consumer configurer) { + this.graphQlConfigurers = this.graphQlConfigurers.andThen(configurer); + return self(); + } + + @SuppressWarnings("unchecked") + private T self() { + return (T) this; + } + + @Override + public GraphQlSource build() { + GraphQLSchema schema = initGraphQlSchema(); + + schema = applyTypeVisitors(schema); + + GraphQL.Builder builder = GraphQL.newGraphQL(schema); + builder.defaultDataFetcherExceptionHandler(new ExceptionResolversExceptionHandler(this.exceptionResolvers)); + + if (!this.instrumentations.isEmpty()) { + builder = builder.instrumentation(new ChainedInstrumentation(this.instrumentations)); + } + + this.graphQlConfigurers.accept(builder); + + return new FixedGraphQlSource(builder.build(), schema); + } + + /** + * Subclasses must implement this method to provide the + * {@link GraphQLSchema} instance. + */ + protected abstract GraphQLSchema initGraphQlSchema(); + + private GraphQLSchema applyTypeVisitors(GraphQLSchema schema) { + List visitors = new ArrayList<>(this.typeVisitors); + visitors.add(ContextDataFetcherDecorator.TYPE_VISITOR); + + GraphQLCodeRegistry.Builder codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.getCodeRegistry()); + Map, Object> vars = Collections.singletonMap(GraphQLCodeRegistry.Builder.class, codeRegistry); + + SchemaTraverser traverser = new SchemaTraverser(); + traverser.depthFirstFullSchema(visitors, schema, vars); + + return schema.transformWithoutTypes(builder -> builder.codeRegistry(codeRegistry)); + } + + + /** + * {@link GraphQlSource} with fixed {@link GraphQL} and {@link GraphQLSchema} instances. + */ + private static class FixedGraphQlSource implements GraphQlSource { + + private final GraphQL graphQl; + + private final GraphQLSchema schema; + + FixedGraphQlSource(GraphQL graphQl, GraphQLSchema schema) { + this.graphQl = graphQl; + this.schema = schema; + } + + @Override + public GraphQL graphQl() { + return this.graphQl; + } + + @Override + public GraphQLSchema schema() { + return this.schema; + } + + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilder.java deleted file mode 100644 index 62dc4884..00000000 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilder.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.graphql.execution; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -import graphql.GraphQL; -import graphql.execution.instrumentation.ChainedInstrumentation; -import graphql.execution.instrumentation.Instrumentation; -import graphql.language.InterfaceTypeDefinition; -import graphql.language.UnionTypeDefinition; -import graphql.schema.GraphQLCodeRegistry; -import graphql.schema.GraphQLSchema; -import graphql.schema.GraphQLTypeVisitor; -import graphql.schema.SchemaTraverser; -import graphql.schema.TypeResolver; -import graphql.schema.idl.CombinedWiringFactory; -import graphql.schema.idl.NoopWiringFactory; -import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; -import graphql.schema.idl.SchemaParser; -import graphql.schema.idl.TypeDefinitionRegistry; -import graphql.schema.idl.WiringFactory; - -import org.springframework.core.io.Resource; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * Default implementation of {@link GraphQlSource.Builder} that initializes a - * {@link GraphQL} instance and wraps it with a {@link GraphQlSource} that returns it. - * - * @author Rossen Stoyanchev - * @author Brian Clozel - */ -class DefaultGraphQlSourceBuilder implements GraphQlSource.Builder { - - private final Set schemaResources = new LinkedHashSet<>(); - - private final List runtimeWiringConfigurers = new ArrayList<>(); - - @Nullable - private TypeResolver defaultTypeResolver; - - private final List exceptionResolvers = new ArrayList<>(); - - private final List typeVisitors = new ArrayList<>(); - - private final List instrumentations = new ArrayList<>(); - - @Nullable - private BiFunction schemaFactory; - - private Consumer graphQlConfigurers = (builder) -> { - }; - - - @Override - public GraphQlSource.Builder schemaResources(Resource... resources) { - this.schemaResources.addAll(Arrays.asList(resources)); - return this; - } - - @Override - public GraphQlSource.Builder configureRuntimeWiring(RuntimeWiringConfigurer configurer) { - this.runtimeWiringConfigurers.add(configurer); - return this; - } - - @Override - public GraphQlSource.Builder defaultTypeResolver(TypeResolver typeResolver) { - this.defaultTypeResolver = typeResolver; - return this; - } - - @Override - public GraphQlSource.Builder exceptionResolvers(List resolvers) { - this.exceptionResolvers.addAll(resolvers); - return this; - } - - @Override - public GraphQlSource.Builder typeVisitors(List typeVisitors) { - this.typeVisitors.addAll(typeVisitors); - return this; - } - - @Override - public GraphQlSource.Builder instrumentation(List instrumentations) { - this.instrumentations.addAll(instrumentations); - return this; - } - - @Override - public GraphQlSource.Builder schemaFactory( - BiFunction schemaFactory) { - - this.schemaFactory = schemaFactory; - return this; - } - - @Override - public GraphQlSource.Builder configureGraphQl(Consumer configurer) { - this.graphQlConfigurers = this.graphQlConfigurers.andThen(configurer); - return this; - } - - @Override - public GraphQlSource build() { - TypeDefinitionRegistry registry = this.schemaResources.stream() - .map(this::parseSchemaResource).reduce(TypeDefinitionRegistry::merge) - .orElseThrow(MissingSchemaException::new); - - RuntimeWiring runtimeWiring = initRuntimeWiring(); - - registerDefaultTypeResolver(registry, runtimeWiring); - - GraphQLSchema schema = (this.schemaFactory != null ? - this.schemaFactory.apply(registry, runtimeWiring) : - new SchemaGenerator().makeExecutableSchema(registry, runtimeWiring)); - - schema = applyTypeVisitors(schema); - - GraphQL.Builder builder = GraphQL.newGraphQL(schema); - builder.defaultDataFetcherExceptionHandler(new ExceptionResolversExceptionHandler(this.exceptionResolvers)); - if (!this.instrumentations.isEmpty()) { - builder = builder.instrumentation(new ChainedInstrumentation(this.instrumentations)); - } - - this.graphQlConfigurers.accept(builder); - GraphQL graphQl = builder.build(); - - return new CachedGraphQlSource(graphQl, schema); - } - - private RuntimeWiring initRuntimeWiring() { - RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring(); - this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(builder)); - - List factories = new ArrayList<>(); - WiringFactory factory = builder.build().getWiringFactory(); - if (!factory.getClass().equals(NoopWiringFactory.class)) { - factories.add(factory); - } - this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(builder, factories)); - if (!factories.isEmpty()) { - builder.wiringFactory(new CombinedWiringFactory(factories)); - } - - return builder.build(); - } - - private void registerDefaultTypeResolver(TypeDefinitionRegistry registry, RuntimeWiring runtimeWiring) { - TypeResolver typeResolver = - (this.defaultTypeResolver != null ? this.defaultTypeResolver : new ClassNameTypeResolver()); - registry.types().values().stream() - .filter(def -> def instanceof UnionTypeDefinition || def instanceof InterfaceTypeDefinition) - .forEach(def -> runtimeWiring.getTypeResolvers().putIfAbsent(def.getName(), typeResolver)); - } - - private TypeDefinitionRegistry parseSchemaResource(Resource schemaResource) { - Assert.notNull(schemaResource, "'schemaResource' not provided"); - Assert.isTrue(schemaResource.exists(), "'schemaResource' must exist: " + schemaResource); - try { - try (InputStream inputStream = schemaResource.getInputStream()) { - return new SchemaParser().parse(inputStream); - } - } - catch (IOException ex) { - throw new IllegalArgumentException("Failed to load schema resource: " + schemaResource); - } - catch (Exception ex) { - throw new IllegalStateException("Failed to parse schema resource: " + schemaResource, ex); - } - } - - private GraphQLSchema applyTypeVisitors(GraphQLSchema schema) { - List visitors = new ArrayList<>(this.typeVisitors); - visitors.add(ContextDataFetcherDecorator.TYPE_VISITOR); - - GraphQLCodeRegistry.Builder codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.getCodeRegistry()); - Map, Object> vars = Collections.singletonMap(GraphQLCodeRegistry.Builder.class, codeRegistry); - - SchemaTraverser traverser = new SchemaTraverser(); - traverser.depthFirstFullSchema(visitors, schema, vars); - - return schema.transformWithoutTypes(builder -> builder.codeRegistry(codeRegistry)); - } - - - - /** - * GraphQlSource that returns the built GraphQL instance and its schema. - */ - private static class CachedGraphQlSource implements GraphQlSource { - - private final GraphQL graphQl; - - private final GraphQLSchema schema; - - CachedGraphQlSource(GraphQL graphQl, GraphQLSchema schema) { - this.graphQl = graphQl; - this.schema = schema; - } - - @Override - public GraphQL graphQl() { - return this.graphQl; - } - - @Override - public GraphQLSchema schema() { - return this.schema; - } - - } - -} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java new file mode 100644 index 00000000..1a722f79 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilder.java @@ -0,0 +1,152 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.execution; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; + +import graphql.language.InterfaceTypeDefinition; +import graphql.language.UnionTypeDefinition; +import graphql.schema.GraphQLSchema; +import graphql.schema.TypeResolver; +import graphql.schema.idl.CombinedWiringFactory; +import graphql.schema.idl.NoopWiringFactory; +import graphql.schema.idl.RuntimeWiring; +import graphql.schema.idl.SchemaGenerator; +import graphql.schema.idl.SchemaParser; +import graphql.schema.idl.TypeDefinitionRegistry; +import graphql.schema.idl.WiringFactory; + +import org.springframework.core.io.Resource; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + + +/** + * Implementation of {@link GraphQlSource.SchemaResourceBuilder}. + * + * @author Rossen Stoyanchev + * @author Brian Clozel + * @since 1.0.0 + */ +final class DefaultSchemaResourceGraphQlSourceBuilder + extends AbstractGraphQlSourceBuilder + implements GraphQlSource.SchemaResourceBuilder { + + private final Set schemaResources = new LinkedHashSet<>(); + + private final List runtimeWiringConfigurers = new ArrayList<>(); + + @Nullable + private TypeResolver typeResolver; + + @Nullable + private BiFunction schemaFactory; + + + @Override + public DefaultSchemaResourceGraphQlSourceBuilder schemaResources(Resource... resources) { + this.schemaResources.addAll(Arrays.asList(resources)); + return this; + } + + @Override + public DefaultSchemaResourceGraphQlSourceBuilder configureRuntimeWiring(RuntimeWiringConfigurer configurer) { + this.runtimeWiringConfigurers.add(configurer); + return this; + } + + @Override + public DefaultSchemaResourceGraphQlSourceBuilder defaultTypeResolver(TypeResolver typeResolver) { + this.typeResolver = typeResolver; + return this; + } + + @Override + public DefaultSchemaResourceGraphQlSourceBuilder schemaFactory( + BiFunction schemaFactory) { + + this.schemaFactory = schemaFactory; + return this; + } + + @Override + protected GraphQLSchema initGraphQlSchema() { + + TypeDefinitionRegistry registry = this.schemaResources.stream() + .map(this::parse) + .reduce(TypeDefinitionRegistry::merge) + .orElseThrow(MissingSchemaException::new); + + RuntimeWiring runtimeWiring = initRuntimeWiring(); + + TypeResolver typeResolver = initTypeResolver(); + registry.types().values().forEach(def -> { + if (def instanceof UnionTypeDefinition || def instanceof InterfaceTypeDefinition) { + runtimeWiring.getTypeResolvers().putIfAbsent(def.getName(), typeResolver); + } + }); + + return (this.schemaFactory != null ? + this.schemaFactory.apply(registry, runtimeWiring) : + new SchemaGenerator().makeExecutableSchema(registry, runtimeWiring)); + } + + private TypeDefinitionRegistry parse(Resource schemaResource) { + Assert.notNull(schemaResource, "'schemaResource' not provided"); + Assert.isTrue(schemaResource.exists(), "'schemaResource' must exist: " + schemaResource); + try { + try (InputStream inputStream = schemaResource.getInputStream()) { + return new SchemaParser().parse(inputStream); + } + } + catch (IOException ex) { + throw new IllegalArgumentException("Failed to load schema resource: " + schemaResource); + } + catch (Exception ex) { + throw new IllegalStateException("Failed to parse schema resource: " + schemaResource, ex); + } + } + + private RuntimeWiring initRuntimeWiring() { + RuntimeWiring.Builder builder = RuntimeWiring.newRuntimeWiring(); + this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(builder)); + + List factories = new ArrayList<>(); + WiringFactory factory = builder.build().getWiringFactory(); + if (!factory.getClass().equals(NoopWiringFactory.class)) { + factories.add(factory); + } + this.runtimeWiringConfigurers.forEach(configurer -> configurer.configure(builder, factories)); + if (!factories.isEmpty()) { + builder.wiringFactory(new CombinedWiringFactory(factories)); + } + + return builder.build(); + } + + private TypeResolver initTypeResolver() { + return (this.typeResolver != null ? this.typeResolver : new ClassNameTypeResolver()); + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/ExternalSchemaGraphQlSourceBuilder.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/ExternalSchemaGraphQlSourceBuilder.java new file mode 100644 index 00000000..d1eefcdf --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/ExternalSchemaGraphQlSourceBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.graphql.execution; + + +import graphql.schema.GraphQLSchema; + +import org.springframework.util.Assert; + + +/** + * {@link GraphQlSource.Builder} that uses an externally prepared + * {@link GraphQLSchema}. + * + * @author Rossen Stoyanchev + * @since 1.0.0 + */ +final class ExternalSchemaGraphQlSourceBuilder extends AbstractGraphQlSourceBuilder + implements GraphQlSource.Builder { + + private final GraphQLSchema schema; + + + public ExternalSchemaGraphQlSourceBuilder(GraphQLSchema schema) { + Assert.notNull(schema, "GraphQLSchema is required"); + this.schema = schema; + } + + + @Override + protected GraphQLSchema initGraphQlSchema() { + return this.schema; + } + +} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/GraphQlSource.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/GraphQlSource.java index 29bbcf77..6a7311e4 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/GraphQlSource.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/GraphQlSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package org.springframework.graphql.execution; -import java.io.File; +import java.io.InputStream; import java.util.List; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -31,8 +31,9 @@ import graphql.schema.idl.TypeDefinitionRegistry; import org.springframework.core.io.Resource; + /** - * Strategy to resolve the {@link GraphQL} instance to use. + * Strategy to resolve a {@link GraphQL} and a {@link GraphQLSchema}. * *

* This contract also includes a {@link GraphQlSource} builder encapsulating the @@ -44,78 +45,51 @@ import org.springframework.core.io.Resource; */ public interface GraphQlSource { + /** - * Return the {@link GraphQL} to use. This can be a cached instance or a different one - * from time to time (e.g. based on a reloaded schema). - * @return the GraphQL instance to use + * Return the {@link GraphQL} to use. This can be a cached instance or a + * different one from time to time (e.g. based on a reloaded schema). */ GraphQL graphQl(); /** * Return the {@link GraphQLSchema} used by the current {@link GraphQL}. - * @return the current GraphQL schema */ GraphQLSchema schema(); + /** - * Return a builder for a {@link GraphQlSource} given input for the initialization of - * {@link GraphQL} and {@link graphql.schema.GraphQLSchema}. - * @return a builder for a GraphQlSource + * Return a {@link GraphQlSource} builder that parses GraphQL Schema + * resources and uses {@link RuntimeWiring} to create the + * {@link graphql.schema.GraphQLSchema}. */ - static Builder builder() { - return new DefaultGraphQlSourceBuilder(); + static SchemaResourceBuilder schemaResourceBuilder() { + return new DefaultSchemaResourceGraphQlSourceBuilder(); } /** - * Builder for a {@link GraphQlSource}. + * Return a {@link GraphQlSource} builder that uses an externally prepared + * {@link GraphQLSchema}. */ - interface Builder { + static Builder builder(GraphQLSchema schema) { + return new ExternalSchemaGraphQlSourceBuilder(schema); + } + + + + /** + * Common configuration options for all {@link GraphQlSource} builders, + * independent of how {@link GraphQLSchema} is created. + */ + interface Builder> { /** - * Add {@literal ".graphqls"} schema resources to be - * {@link TypeDefinitionRegistry#merge(TypeDefinitionRegistry) merged} into the type registry. - * @param resources resources for the GraphQL schema - * @return the current builder - * @see graphql.schema.idl.SchemaParser#parse(File) - */ - Builder schemaResources(Resource... resources); - - /** - * Add a component that is given access to the {@link RuntimeWiring.Builder} - * used to register {@link graphql.schema.DataFetcher}s, custom scalar - * types, type resolvers, and more. - * @param configurer the configurer to apply - * @return the current builder - * @see graphql.schema.idl.SchemaGenerator#makeExecutableSchema(TypeDefinitionRegistry, RuntimeWiring) - */ - Builder configureRuntimeWiring(RuntimeWiringConfigurer configurer); - - /** - * Configure the default {@link TypeResolver} to use for GraphQL Interface - * and Union types that don't already have such a registration after all - * {@link #configureRuntimeWiring(RuntimeWiringConfigurer) RuntimeWiringConfigurer's} - * have been applied. - *

A GraphQL {@code TypeResolver} is used to determine the GraphQL Object - * type of values returned from DataFetcher's of GraphQL Interface or - * Union fields. - *

By default this is set to {@link ClassNameTypeResolver}, which - * tries to match the simple class name of the Object value to a GraphQL - * Object type, and it also tries the same for supertypes (base classes - * and interfaces). See the Javadoc of {@code ClassNameTypeResolver} for - * further ways to customize matching a Java class to a GraphQL Object type. - * @param typeResolver the {@code TypeResolver} to use - * @return the current builder - * @see ClassNameTypeResolver - */ - Builder defaultTypeResolver(TypeResolver typeResolver); - - /** - * Add {@link DataFetcherExceptionResolver}'s to use for resolving exceptions from - * {@link graphql.schema.DataFetcher}'s. + * Add {@link DataFetcherExceptionResolver}s for resolving exceptions + * from {@link graphql.schema.DataFetcher}s. * @param resolvers the resolvers to add * @return the current builder */ - Builder exceptionResolvers(List resolvers); + B exceptionResolvers(List resolvers); /** * Add {@link GraphQLTypeVisitor}s to visit all element of the created @@ -124,45 +98,81 @@ public interface GraphQlSource { * {@link graphql.schema.SchemaTraverser} and cannot change the schema. * @param typeVisitors the type visitors * @return the current builder - * @see graphql.schema.SchemaTransformer#transformSchema(GraphQLSchema, - * GraphQLTypeVisitor) + * @see graphql.schema.SchemaTransformer#transformSchema(GraphQLSchema, GraphQLTypeVisitor) */ - Builder typeVisitors(List typeVisitors); + B typeVisitors(List typeVisitors); /** - * Provide {@link Instrumentation} components to instrument the execution of - * GraphQL queries. + * Provide {@link Instrumentation} components to instrument the + * execution of GraphQL queries. * @param instrumentations the instrumentation components * @return the current builder * @see graphql.GraphQL.Builder#instrumentation(Instrumentation) */ - Builder instrumentation(List instrumentations); + B instrumentation(List instrumentations); /** - * Configure a function to create the {@link GraphQLSchema} instance from the - * given {@link TypeDefinitionRegistry} and {@link RuntimeWiring}. This may - * be useful for federation to create a combined schema. - *

By default, the schema is created with - * {@link graphql.schema.idl.SchemaGenerator#makeExecutableSchema}. - * @param schemaFactory the function to create the schema - * @return the current builder - */ - Builder schemaFactory(BiFunction schemaFactory); - - /** - * Configure consumers to be given access to the {@link GraphQL.Builder} used to - * build {@link GraphQL}. + * Configure consumers to be given access to the {@link GraphQL.Builder} + * used to build {@link GraphQL}. * @param configurer the configurer * @return the current builder */ - Builder configureGraphQl(Consumer configurer); + B configureGraphQl(Consumer configurer); /** - * Build the {@link GraphQlSource}. - * @return the built GraphQlSource + * Build the {@link GraphQlSource} instance. */ GraphQlSource build(); } + + /** + * {@link GraphQlSource} builder that relies on parsing schema definition + * files and uses a {@link RuntimeWiring} to create the underlying + * {@link GraphQLSchema}. + */ + interface SchemaResourceBuilder extends Builder { + + /** + * Add schema definition resources, typically {@literal ".graphqls"} files, to be + * {@link graphql.schema.idl.SchemaParser#parse(InputStream) parsed} and + * {@link TypeDefinitionRegistry#merge(TypeDefinitionRegistry) merged}. + * @param resources resources with GraphQL schema definitions + * @return the current builder + */ + SchemaResourceBuilder schemaResources(Resource... resources); + + /** + * Configure the underlying {@link RuntimeWiring.Builder} to register + * data fetchers, custom scalar types, type resolvers, and more. + * @param configurer the configurer to apply + * @return the current builder + */ + SchemaResourceBuilder configureRuntimeWiring(RuntimeWiringConfigurer configurer); + + /** + * Configure the default {@link TypeResolver} to use for GraphQL interface + * and union types that don't have such a registration after + * {@link #configureRuntimeWiring(RuntimeWiringConfigurer) applying} + * {@code RuntimeWiringConfigurer}s. + *

By default this is set to {@link ClassNameTypeResolver}. + * @param typeResolver the {@code TypeResolver} to use + * @return the current builder + */ + SchemaResourceBuilder defaultTypeResolver(TypeResolver typeResolver); + + /** + * Configure a function to create the {@link GraphQLSchema} from the + * given {@link TypeDefinitionRegistry} and {@link RuntimeWiring}. + * This may be used for federation to create a combined schema. + *

By default, the schema is created with + * {@link graphql.schema.idl.SchemaGenerator#makeExecutableSchema}. + * @param schemaFactory the function to create the schema + * @return the current builder + */ + SchemaResourceBuilder schemaFactory(BiFunction schemaFactory); + + } + } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilderTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilderTests.java similarity index 97% rename from spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilderTests.java rename to spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilderTests.java index d10c0a3a..20c332f2 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultGraphQlSourceBuilderTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultSchemaResourceGraphQlSourceBuilderTests.java @@ -33,11 +33,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; /** - * Unit tests for {@link DefaultGraphQlSourceBuilder}. + * Unit tests for {@link DefaultSchemaResourceGraphQlSourceBuilder}. * * @author Rossen Stoyanchev */ -public class DefaultGraphQlSourceBuilderTests { +public class DefaultSchemaResourceGraphQlSourceBuilderTests { @Test // gh-230 void duplicateResourcesAreIgnored() { diff --git a/spring-graphql/src/testFixtures/java/org/springframework/graphql/GraphQlSetup.java b/spring-graphql/src/testFixtures/java/org/springframework/graphql/GraphQlSetup.java index df712b7b..1db0fbb0 100644 --- a/spring-graphql/src/testFixtures/java/org/springframework/graphql/GraphQlSetup.java +++ b/spring-graphql/src/testFixtures/java/org/springframework/graphql/GraphQlSetup.java @@ -51,7 +51,7 @@ import org.springframework.lang.Nullable; @SuppressWarnings("unused") public class GraphQlSetup implements GraphQlServiceSetup { - private final GraphQlSource.Builder graphQlSourceBuilder; + private final GraphQlSource.SchemaResourceBuilder graphQlSourceBuilder; private final List dataLoaderRegistrars = new ArrayList<>(); @@ -61,7 +61,7 @@ public class GraphQlSetup implements GraphQlServiceSetup { private GraphQlSetup(Resource... schemaResources) { - this.graphQlSourceBuilder = GraphQlSource.builder().schemaResources(schemaResources); + this.graphQlSourceBuilder = GraphQlSource.schemaResourceBuilder().schemaResources(schemaResources); }