Extract base GraphQlSource builder
Separate more clearly the SDL builder options from other common options independent of how GraphQLSchema is created. See gh-312
This commit is contained in:
@@ -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 <<execution-reactive-datafetcher>>, <<execution-context>>, and
|
||||
<<execution-exceptions>>.
|
||||
The default `GraphQlSource` builder, accessible via
|
||||
`GraphQlSource.schemaResourceBuilder()`, enables support for
|
||||
<<execution-reactive-datafetcher>>, <<execution-context>>, and <<execution-exceptions>>.
|
||||
|
||||
The Spring Boot {spring-boot-ref-docs}/web.html#web.graphql[starter] initializes a
|
||||
`GraphQlSource` instance through the default `GraphQlSource.Builder` and also enables
|
||||
|
||||
@@ -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<B extends GraphQlSource.Builder<B>> implements GraphQlSource.Builder<B> {
|
||||
|
||||
private final List<DataFetcherExceptionResolver> exceptionResolvers = new ArrayList<>();
|
||||
|
||||
private final List<GraphQLTypeVisitor> typeVisitors = new ArrayList<>();
|
||||
|
||||
private final List<Instrumentation> instrumentations = new ArrayList<>();
|
||||
|
||||
private Consumer<GraphQL.Builder> graphQlConfigurers = (builder) -> {
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
public B exceptionResolvers(List<DataFetcherExceptionResolver> resolvers) {
|
||||
this.exceptionResolvers.addAll(resolvers);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B typeVisitors(List<GraphQLTypeVisitor> typeVisitors) {
|
||||
this.typeVisitors.addAll(typeVisitors);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B instrumentation(List<Instrumentation> instrumentations) {
|
||||
this.instrumentations.addAll(instrumentations);
|
||||
return self();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B configureGraphQl(Consumer<GraphQL.Builder> configurer) {
|
||||
this.graphQlConfigurers = this.graphQlConfigurers.andThen(configurer);
|
||||
return self();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends B> 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<GraphQLTypeVisitor> visitors = new ArrayList<>(this.typeVisitors);
|
||||
visitors.add(ContextDataFetcherDecorator.TYPE_VISITOR);
|
||||
|
||||
GraphQLCodeRegistry.Builder codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.getCodeRegistry());
|
||||
Map<Class<?>, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Resource> schemaResources = new LinkedHashSet<>();
|
||||
|
||||
private final List<RuntimeWiringConfigurer> runtimeWiringConfigurers = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private TypeResolver defaultTypeResolver;
|
||||
|
||||
private final List<DataFetcherExceptionResolver> exceptionResolvers = new ArrayList<>();
|
||||
|
||||
private final List<GraphQLTypeVisitor> typeVisitors = new ArrayList<>();
|
||||
|
||||
private final List<Instrumentation> instrumentations = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private BiFunction<TypeDefinitionRegistry, RuntimeWiring, GraphQLSchema> schemaFactory;
|
||||
|
||||
private Consumer<GraphQL.Builder> 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<DataFetcherExceptionResolver> resolvers) {
|
||||
this.exceptionResolvers.addAll(resolvers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlSource.Builder typeVisitors(List<GraphQLTypeVisitor> typeVisitors) {
|
||||
this.typeVisitors.addAll(typeVisitors);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlSource.Builder instrumentation(List<Instrumentation> instrumentations) {
|
||||
this.instrumentations.addAll(instrumentations);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlSource.Builder schemaFactory(
|
||||
BiFunction<TypeDefinitionRegistry, RuntimeWiring, GraphQLSchema> schemaFactory) {
|
||||
|
||||
this.schemaFactory = schemaFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlSource.Builder configureGraphQl(Consumer<GraphQL.Builder> 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<WiringFactory> 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<GraphQLTypeVisitor> visitors = new ArrayList<>(this.typeVisitors);
|
||||
visitors.add(ContextDataFetcherDecorator.TYPE_VISITOR);
|
||||
|
||||
GraphQLCodeRegistry.Builder codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.getCodeRegistry());
|
||||
Map<Class<?>, 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<GraphQlSource.SchemaResourceBuilder>
|
||||
implements GraphQlSource.SchemaResourceBuilder {
|
||||
|
||||
private final Set<Resource> schemaResources = new LinkedHashSet<>();
|
||||
|
||||
private final List<RuntimeWiringConfigurer> runtimeWiringConfigurers = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private TypeResolver typeResolver;
|
||||
|
||||
@Nullable
|
||||
private BiFunction<TypeDefinitionRegistry, RuntimeWiring, GraphQLSchema> 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<TypeDefinitionRegistry, RuntimeWiring, GraphQLSchema> 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<WiringFactory> 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ExternalSchemaGraphQlSourceBuilder>
|
||||
implements GraphQlSource.Builder<ExternalSchemaGraphQlSourceBuilder> {
|
||||
|
||||
private final GraphQLSchema schema;
|
||||
|
||||
|
||||
public ExternalSchemaGraphQlSourceBuilder(GraphQLSchema schema) {
|
||||
Assert.notNull(schema, "GraphQLSchema is required");
|
||||
this.schema = schema;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected GraphQLSchema initGraphQlSchema() {
|
||||
return this.schema;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>
|
||||
* 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<B extends Builder<B>> {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>A GraphQL {@code TypeResolver} is used to determine the GraphQL Object
|
||||
* type of values returned from DataFetcher's of GraphQL Interface or
|
||||
* Union fields.
|
||||
* <p>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<DataFetcherExceptionResolver> resolvers);
|
||||
B exceptionResolvers(List<DataFetcherExceptionResolver> 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<GraphQLTypeVisitor> typeVisitors);
|
||||
B typeVisitors(List<GraphQLTypeVisitor> 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<Instrumentation> instrumentations);
|
||||
B instrumentation(List<Instrumentation> 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.
|
||||
* <p>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<TypeDefinitionRegistry, RuntimeWiring, GraphQLSchema> 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<GraphQL.Builder> configurer);
|
||||
B configureGraphQl(Consumer<GraphQL.Builder> 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<SchemaResourceBuilder> {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>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.
|
||||
* <p>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<TypeDefinitionRegistry, RuntimeWiring, GraphQLSchema> schemaFactory);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
@@ -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<DataLoaderRegistrar> 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user