From 56b0b168b91b802cb1003ce0bfa50b883d8da826 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Thu, 13 Apr 2023 16:56:26 +0100 Subject: [PATCH] Update SchemaMappingInspector to support Connection types Closes gh-656 --- .../execution/SchemaMappingInspector.java | 22 +++++++++ .../SchemaMappingInspectorTests.java | 47 +++++++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java index a5e9a82a..6b89f4ab 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java @@ -33,6 +33,7 @@ import graphql.schema.GraphQLType; import graphql.schema.idl.RuntimeWiring; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -56,6 +57,7 @@ import org.springframework.util.MultiValueMap; * even if a common interface is declared by a {@link SelfDescribingDataFetcher}. * * @author Brian Clozel + * @author Rossen Stoyanchev * @since 1.2.0 */ class SchemaMappingInspector { @@ -120,6 +122,10 @@ class SchemaMappingInspector { if (isTypeAlreadyInspected(objectType)) { return; } + if (isConnectionType(objectType)) { + objectType = getEdgeNodeType(objectType); + declaredType = declaredType.getNested(2); + } Map typeDataFetcher = this.runtimeWiring.getDataFetcherForType(objectType.getName()); Class declaredClass = unwrapPublisherTypes(declaredType); for (GraphQLFieldDefinition field : objectType.getFieldDefinitions()) { @@ -142,6 +148,22 @@ class SchemaMappingInspector { } } + private boolean isConnectionType(GraphQLObjectType objectType) { + return (objectType.getName().endsWith("Connection") && + objectType.getField("edges") != null && objectType.getField("pageInfo") != null); + } + + @NotNull + private GraphQLObjectType getEdgeNodeType(GraphQLObjectType objectType) { + GraphQLList edgesListType = (GraphQLList) unwrapNonNull(objectType.getField("edges").getType()); + GraphQLObjectType edgeType = (GraphQLObjectType) edgesListType.getWrappedType(); + return (GraphQLObjectType) unwrapNonNull(edgeType.getField("node").getType()); + } + + private GraphQLType unwrapNonNull(GraphQLType outputType) { + return (outputType instanceof GraphQLNonNull wrapper ? wrapper.getWrappedType() : outputType); + } + @Nullable private Class unwrapPublisherTypes(ResolvableType declaredType) { Class rawClass = declaredType.getRawClass(); diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java index 5372fa87..25a47dc6 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java @@ -22,15 +22,17 @@ import java.util.List; import graphql.schema.GraphQLSchema; import graphql.schema.idl.RuntimeWiring; -import graphql.schema.idl.SchemaGenerator; import org.assertj.core.api.AbstractAssert; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.data.domain.OffsetScrollPosition; +import org.springframework.data.domain.Window; import org.springframework.graphql.Author; import org.springframework.graphql.Book; +import org.springframework.graphql.GraphQlSetup; import org.springframework.graphql.data.method.annotation.Argument; import org.springframework.graphql.data.method.annotation.MutationMapping; import org.springframework.graphql.data.method.annotation.QueryMapping; @@ -45,6 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link SchemaMappingInspector}. * * @author Brian Clozel + * @author Rossen Stoyanchev */ class SchemaMappingInspectorTests { @@ -91,6 +94,23 @@ class SchemaMappingInspectorTests { assertThatReport(report).hasSize(1).missesFields("Book", "missing"); } + @Test + void inspectTypeForConnection() { + String schema = """ + type Query { + paginatedBooks: BookConnection + } + + type Book { + id: ID + name: String + missing: Boolean + } + """; + SchemaMappingInspector.Report report = inspectSchema(schema, BookController.class); + assertThatReport(report).hasSize(1).missesFields("Book", "missing"); + } + @Test void inspectExtensionTypesForQueries() { String schema = """ @@ -413,6 +433,11 @@ class SchemaMappingInspectorTests { return List.of(new Book()); } + @QueryMapping + public Window paginatedBooks() { + return Window.from(List.of(new Book()), OffsetScrollPosition::of); + } + @SchemaMapping public String fetcher(Book book) { return "custom fetcher"; @@ -457,12 +482,20 @@ class SchemaMappingInspectorTests { } SchemaMappingInspector.Report inspectSchema(String schemaContent, Class... controllers) { - GraphQLSchema schema = SchemaGenerator.createdMockedSchema(schemaContent); - RuntimeWiring.Builder builder = createRuntimeWiring(controllers); - return new SchemaMappingInspector().inspectSchemaMappings(schema, builder.build()); + RuntimeWiringConfigurer wiringConfigurer = createRuntimeWiring(controllers); + RuntimeWiring.Builder wiringBuilder = RuntimeWiring.newRuntimeWiring(); + wiringConfigurer.configure(wiringBuilder); + + GraphQLSchema schema = GraphQlSetup.schemaContent(schemaContent) + .runtimeWiring(wiringConfigurer) + .typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer()) + .toGraphQlSource() + .schema(); + + return new SchemaMappingInspector().inspectSchemaMappings(schema, wiringBuilder.build()); } - RuntimeWiring.Builder createRuntimeWiring(Class... handlerTypes) { + private RuntimeWiringConfigurer createRuntimeWiring(Class... handlerTypes) { AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext(); for (Class handlerType : handlerTypes) { appContext.registerBean(handlerType); @@ -473,9 +506,7 @@ class SchemaMappingInspectorTests { configurer.setApplicationContext(appContext); configurer.afterPropertiesSet(); - RuntimeWiring.Builder wiringBuilder = RuntimeWiring.newRuntimeWiring(); - configurer.configure(wiringBuilder); - return wiringBuilder; + return configurer; } static SchemaInspectionReportAssert assertThatReport(SchemaMappingInspector.Report actual) {