Add ConnectionTypeConfigurer
See gh-620
This commit is contained in:
@@ -17,7 +17,6 @@ package org.springframework.graphql.execution;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.language.FieldDefinition;
|
||||
@@ -30,18 +29,20 @@ import graphql.language.TypeName;
|
||||
import graphql.schema.idl.TypeDefinitionRegistry;
|
||||
|
||||
/**
|
||||
* Exposes the {@link #generateConnectionTypes(TypeDefinitionRegistry)
|
||||
* generateConnectionTypes method} for adding boilerplate type definitions to a
|
||||
* {@link TypeDefinitionRegistry}, for pagination based on the Relay
|
||||
* <a href="https://relay.dev/graphql/connections.htm">GraphQL Cursor Connections Specification</a>.
|
||||
* {@link TypeDefinitionConfigurer} that generates "Connection" types by looking
|
||||
* for fields whose type definition name ends in "Connection", considered by the
|
||||
* <a href="https://relay.dev/graphql/connections.htm">GraphQL Cursor Connections Specification</a>
|
||||
* to be a {@literal Connection Type}, and adding the required type definitions
|
||||
* if they don't already exist.
|
||||
*
|
||||
* <p>Use {@link GraphQlSource.SchemaResourceBuilder#configureTypeDefinitionRegistry(Function)}
|
||||
* to enable connection type generation.
|
||||
* <p>This is intended to be set on
|
||||
* {@link GraphQlSource.SchemaResourceBuilder#configureTypeDefinitions(TypeDefinitionConfigurer)
|
||||
* GraphQlSource.Builder}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.2
|
||||
*/
|
||||
public class ConnectionTypeGenerator {
|
||||
public class ConnectionTypeDefinitionConfigurer implements TypeDefinitionConfigurer {
|
||||
|
||||
private static final TypeName STRING_TYPE = new TypeName("String");
|
||||
|
||||
@@ -50,14 +51,8 @@ public class ConnectionTypeGenerator {
|
||||
private static final TypeName PAGE_INFO_TYPE = new TypeName("PageInfo");
|
||||
|
||||
|
||||
/**
|
||||
* Find fields whose type definition name ends in "Connection", considered
|
||||
* by the spec to be a {@literal Connection Type}, and add type definitions
|
||||
* for all such types, if they don't exist already.
|
||||
* @param registry the registry to check and add types to
|
||||
* @return the same registry instance with additional types added
|
||||
*/
|
||||
public TypeDefinitionRegistry generateConnectionTypes(TypeDefinitionRegistry registry) {
|
||||
@Override
|
||||
public void configure(TypeDefinitionRegistry registry) {
|
||||
|
||||
Set<String> typeNames = findConnectionTypeNames(registry);
|
||||
|
||||
@@ -89,8 +84,6 @@ public class ConnectionTypeGenerator {
|
||||
.build());
|
||||
});
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static Set<String> findConnectionTypeNames(TypeDefinitionRegistry registry) {
|
||||
@@ -24,7 +24,6 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import graphql.language.InterfaceTypeDefinition;
|
||||
@@ -61,8 +60,7 @@ final class DefaultSchemaResourceGraphQlSourceBuilder
|
||||
|
||||
private final Set<Resource> schemaResources = new LinkedHashSet<>();
|
||||
|
||||
@Nullable
|
||||
private Function<TypeDefinitionRegistry, TypeDefinitionRegistry> typeDefinitionRegistryConfigurer;
|
||||
private List<TypeDefinitionConfigurer> typeDefinitionConfigurers = new ArrayList<>();
|
||||
|
||||
private final List<RuntimeWiringConfigurer> runtimeWiringConfigurers = new ArrayList<>();
|
||||
|
||||
@@ -81,12 +79,8 @@ final class DefaultSchemaResourceGraphQlSourceBuilder
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphQlSource.SchemaResourceBuilder configureTypeDefinitionRegistry(
|
||||
Function<TypeDefinitionRegistry, TypeDefinitionRegistry> configurer) {
|
||||
|
||||
this.typeDefinitionRegistryConfigurer = (this.typeDefinitionRegistryConfigurer != null ?
|
||||
this.typeDefinitionRegistryConfigurer.andThen(configurer) : configurer);
|
||||
|
||||
public GraphQlSource.SchemaResourceBuilder configureTypeDefinitions(TypeDefinitionConfigurer configurer) {
|
||||
this.typeDefinitionConfigurers.add(configurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -118,8 +112,8 @@ final class DefaultSchemaResourceGraphQlSourceBuilder
|
||||
.reduce(TypeDefinitionRegistry::merge)
|
||||
.orElseThrow(MissingSchemaException::new);
|
||||
|
||||
if (this.typeDefinitionRegistryConfigurer != null) {
|
||||
registry = this.typeDefinitionRegistryConfigurer.apply(registry);
|
||||
for (TypeDefinitionConfigurer configurer : this.typeDefinitionConfigurers) {
|
||||
configurer.configure(registry);
|
||||
}
|
||||
|
||||
logger.info("Loaded " + this.schemaResources.size() + " resource(s) in the GraphQL schema.");
|
||||
|
||||
@@ -173,17 +173,14 @@ public interface GraphQlSource {
|
||||
SchemaResourceBuilder schemaResources(Resource... resources);
|
||||
|
||||
/**
|
||||
* Provide a function to customize the {@link TypeDefinitionRegistry}
|
||||
* created by parsing schema files. This allows adding or changing schema
|
||||
* type definitions before {@link GraphQLSchema} is created and validated.
|
||||
* @param configurer the function to apply accepting the current
|
||||
* {@link TypeDefinitionRegistry} and returning the one to use, likely
|
||||
* the same instance since {@link TypeDefinitionRegistry} is mutable.
|
||||
* Customize the {@link TypeDefinitionRegistry} created from parsed
|
||||
* schema files, adding or changing schema type definitions before the
|
||||
* {@link GraphQLSchema} is created and validated.
|
||||
* @param configurer the configurer to apply
|
||||
* @return the current builder
|
||||
* @sine 1.2
|
||||
* @since 1.2
|
||||
*/
|
||||
SchemaResourceBuilder configureTypeDefinitionRegistry(
|
||||
Function<TypeDefinitionRegistry, TypeDefinitionRegistry> configurer);
|
||||
SchemaResourceBuilder configureTypeDefinitions(TypeDefinitionConfigurer configurer);
|
||||
|
||||
/**
|
||||
* Configure the underlying {@link RuntimeWiring.Builder} to register
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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 graphql.schema.idl.TypeDefinitionRegistry;
|
||||
|
||||
/**
|
||||
* Callback that allows customizing the {@link TypeDefinitionRegistry} created
|
||||
* from parsed schema files. It allows adding schema types programmatically
|
||||
* before the registry is used to create {@link GraphQLSchema}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.2
|
||||
*/
|
||||
public interface TypeDefinitionConfigurer {
|
||||
|
||||
/**
|
||||
* Customize the given {@link TypeDefinitionRegistry}.
|
||||
* @param registry the registry to customize
|
||||
*/
|
||||
void configure(TypeDefinitionRegistry registry);
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import org.springframework.graphql.data.pagination.ConnectionFieldTypeVisitor;
|
||||
import org.springframework.graphql.data.query.ScrollPositionCursorStrategy;
|
||||
import org.springframework.graphql.data.query.ScrollSubrange;
|
||||
import org.springframework.graphql.data.query.WindowConnectionAdapter;
|
||||
import org.springframework.graphql.execution.ConnectionTypeGenerator;
|
||||
import org.springframework.graphql.execution.ConnectionTypeDefinitionConfigurer;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -83,8 +83,7 @@ public class SchemaMappingPaginationTests {
|
||||
|
||||
ExecutionGraphQlService graphQlService = graphQlService((configurer, setup) -> {
|
||||
|
||||
ConnectionTypeGenerator typeGenerator = new ConnectionTypeGenerator();
|
||||
setup.typeDefinitionRegistryConfigurer(typeGenerator::generateConnectionTypes);
|
||||
setup.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer());
|
||||
|
||||
ScrollPositionCursorStrategy cursorStrategy = new ScrollPositionCursorStrategy();
|
||||
WindowConnectionAdapter connectionAdapter = new WindowConnectionAdapter(cursorStrategy);
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.graphql.BookSource;
|
||||
import org.springframework.graphql.ExecutionGraphQlResponse;
|
||||
import org.springframework.graphql.GraphQlSetup;
|
||||
import org.springframework.graphql.TestExecutionRequest;
|
||||
import org.springframework.graphql.execution.ConnectionTypeGenerator;
|
||||
import org.springframework.graphql.execution.ConnectionTypeDefinitionConfigurer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ConnectionFieldTypeVisitorTests {
|
||||
|
||||
ExecutionGraphQlResponse response = GraphQlSetup.schemaContent(schemaContent)
|
||||
.dataFetcher("Query", "books", env -> BookSource.books())
|
||||
.typeDefinitionRegistryConfigurer(new ConnectionTypeGenerator()::generateConnectionTypes)
|
||||
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer())
|
||||
.typeVisitor(ConnectionFieldTypeVisitor.create(List.of(adapter)))
|
||||
.toGraphQlService()
|
||||
.execute(TestExecutionRequest.forDocument(document))
|
||||
|
||||
@@ -38,12 +38,12 @@ import org.springframework.graphql.TestExecutionRequest;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ConnectionTypeGenerator}.
|
||||
* Unit tests for {@link ConnectionTypeDefinitionConfigurer}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 1.2
|
||||
*/
|
||||
public class ConnectionTypeGeneratorTests {
|
||||
public class ConnectionTypeDefinitionConfigurerTests {
|
||||
|
||||
@Test
|
||||
void connectionTypeGeneration() throws Exception {
|
||||
@@ -108,8 +108,7 @@ public class ConnectionTypeGeneratorTests {
|
||||
}
|
||||
|
||||
private GraphQlSetup initGraphQlSetup(String schema) {
|
||||
ConnectionTypeGenerator generator = new ConnectionTypeGenerator();
|
||||
return GraphQlSetup.schemaContent(schema).typeDefinitionRegistryConfigurer(generator::generateConnectionTypes);
|
||||
return GraphQlSetup.schemaContent(schema).typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer());
|
||||
}
|
||||
|
||||
private static <N> Connection<N> createConnection(
|
||||
@@ -19,14 +19,12 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import graphql.GraphQL;
|
||||
import graphql.execution.instrumentation.Instrumentation;
|
||||
import graphql.schema.DataFetcher;
|
||||
import graphql.schema.GraphQLTypeVisitor;
|
||||
import graphql.schema.TypeResolver;
|
||||
import graphql.schema.idl.TypeDefinitionRegistry;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
@@ -38,6 +36,7 @@ import org.springframework.graphql.execution.DefaultExecutionGraphQlService;
|
||||
import org.springframework.graphql.execution.GraphQlSource;
|
||||
import org.springframework.graphql.execution.RuntimeWiringConfigurer;
|
||||
import org.springframework.graphql.execution.SubscriptionExceptionResolver;
|
||||
import org.springframework.graphql.execution.TypeDefinitionConfigurer;
|
||||
import org.springframework.graphql.server.WebGraphQlHandler;
|
||||
import org.springframework.graphql.server.WebGraphQlInterceptor;
|
||||
import org.springframework.graphql.server.WebGraphQlSetup;
|
||||
@@ -82,10 +81,8 @@ public class GraphQlSetup implements GraphQlServiceSetup {
|
||||
wiringBuilder.type(type, typeBuilder -> typeBuilder.dataFetcher(field, dataFetcher)));
|
||||
}
|
||||
|
||||
public GraphQlSetup typeDefinitionRegistryConfigurer(
|
||||
Function<TypeDefinitionRegistry, TypeDefinitionRegistry> configurer) {
|
||||
|
||||
this.graphQlSourceBuilder.configureTypeDefinitionRegistry(configurer);
|
||||
public GraphQlSetup typeDefinitionConfigurer(TypeDefinitionConfigurer configurer) {
|
||||
this.graphQlSourceBuilder.configureTypeDefinitions(configurer);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user