Skip to content

Commit 56b0b16

Browse files
committed
Update SchemaMappingInspector to support Connection types
Closes gh-656
1 parent dedebf4 commit 56b0b16

File tree

2 files changed

+61
-8
lines changed

2 files changed

+61
-8
lines changed

spring-graphql/src/main/java/org/springframework/graphql/execution/SchemaMappingInspector.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import graphql.schema.idl.RuntimeWiring;
3434
import org.apache.commons.logging.Log;
3535
import org.apache.commons.logging.LogFactory;
36+
import org.jetbrains.annotations.NotNull;
3637

3738
import org.springframework.beans.BeanUtils;
3839
import org.springframework.beans.BeansException;
@@ -56,6 +57,7 @@
5657
* even if a common interface is declared by a {@link SelfDescribingDataFetcher}.
5758
*
5859
* @author Brian Clozel
60+
* @author Rossen Stoyanchev
5961
* @since 1.2.0
6062
*/
6163
class SchemaMappingInspector {
@@ -120,6 +122,10 @@ private void inspectObjectType(GraphQLObjectType objectType, ResolvableType decl
120122
if (isTypeAlreadyInspected(objectType)) {
121123
return;
122124
}
125+
if (isConnectionType(objectType)) {
126+
objectType = getEdgeNodeType(objectType);
127+
declaredType = declaredType.getNested(2);
128+
}
123129
Map<String, DataFetcher> typeDataFetcher = this.runtimeWiring.getDataFetcherForType(objectType.getName());
124130
Class<?> declaredClass = unwrapPublisherTypes(declaredType);
125131
for (GraphQLFieldDefinition field : objectType.getFieldDefinitions()) {
@@ -142,6 +148,22 @@ private void inspectObjectType(GraphQLObjectType objectType, ResolvableType decl
142148
}
143149
}
144150

151+
private boolean isConnectionType(GraphQLObjectType objectType) {
152+
return (objectType.getName().endsWith("Connection") &&
153+
objectType.getField("edges") != null && objectType.getField("pageInfo") != null);
154+
}
155+
156+
@NotNull
157+
private GraphQLObjectType getEdgeNodeType(GraphQLObjectType objectType) {
158+
GraphQLList edgesListType = (GraphQLList) unwrapNonNull(objectType.getField("edges").getType());
159+
GraphQLObjectType edgeType = (GraphQLObjectType) edgesListType.getWrappedType();
160+
return (GraphQLObjectType) unwrapNonNull(edgeType.getField("node").getType());
161+
}
162+
163+
private <T extends GraphQLType> GraphQLType unwrapNonNull(GraphQLType outputType) {
164+
return (outputType instanceof GraphQLNonNull wrapper ? wrapper.getWrappedType() : outputType);
165+
}
166+
145167
@Nullable
146168
private Class<?> unwrapPublisherTypes(ResolvableType declaredType) {
147169
Class<?> rawClass = declaredType.getRawClass();

spring-graphql/src/test/java/org/springframework/graphql/execution/SchemaMappingInspectorTests.java

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@
2222

2323
import graphql.schema.GraphQLSchema;
2424
import graphql.schema.idl.RuntimeWiring;
25-
import graphql.schema.idl.SchemaGenerator;
2625
import org.assertj.core.api.AbstractAssert;
2726
import org.junit.jupiter.api.Nested;
2827
import org.junit.jupiter.api.Test;
2928
import reactor.core.publisher.Mono;
3029

3130
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
31+
import org.springframework.data.domain.OffsetScrollPosition;
32+
import org.springframework.data.domain.Window;
3233
import org.springframework.graphql.Author;
3334
import org.springframework.graphql.Book;
35+
import org.springframework.graphql.GraphQlSetup;
3436
import org.springframework.graphql.data.method.annotation.Argument;
3537
import org.springframework.graphql.data.method.annotation.MutationMapping;
3638
import org.springframework.graphql.data.method.annotation.QueryMapping;
@@ -45,6 +47,7 @@
4547
* Tests for {@link SchemaMappingInspector}.
4648
*
4749
* @author Brian Clozel
50+
* @author Rossen Stoyanchev
4851
*/
4952
class SchemaMappingInspectorTests {
5053

@@ -91,6 +94,23 @@ void inspectTypeForCollections() {
9194
assertThatReport(report).hasSize(1).missesFields("Book", "missing");
9295
}
9396

97+
@Test
98+
void inspectTypeForConnection() {
99+
String schema = """
100+
type Query {
101+
paginatedBooks: BookConnection
102+
}
103+
104+
type Book {
105+
id: ID
106+
name: String
107+
missing: Boolean
108+
}
109+
""";
110+
SchemaMappingInspector.Report report = inspectSchema(schema, BookController.class);
111+
assertThatReport(report).hasSize(1).missesFields("Book", "missing");
112+
}
113+
94114
@Test
95115
void inspectExtensionTypesForQueries() {
96116
String schema = """
@@ -413,6 +433,11 @@ public List<Book> allBooks() {
413433
return List.of(new Book());
414434
}
415435

436+
@QueryMapping
437+
public Window<Book> paginatedBooks() {
438+
return Window.from(List.of(new Book()), OffsetScrollPosition::of);
439+
}
440+
416441
@SchemaMapping
417442
public String fetcher(Book book) {
418443
return "custom fetcher";
@@ -457,12 +482,20 @@ record TeamMember(String name, Team team) {
457482
}
458483

459484
SchemaMappingInspector.Report inspectSchema(String schemaContent, Class<?>... controllers) {
460-
GraphQLSchema schema = SchemaGenerator.createdMockedSchema(schemaContent);
461-
RuntimeWiring.Builder builder = createRuntimeWiring(controllers);
462-
return new SchemaMappingInspector().inspectSchemaMappings(schema, builder.build());
485+
RuntimeWiringConfigurer wiringConfigurer = createRuntimeWiring(controllers);
486+
RuntimeWiring.Builder wiringBuilder = RuntimeWiring.newRuntimeWiring();
487+
wiringConfigurer.configure(wiringBuilder);
488+
489+
GraphQLSchema schema = GraphQlSetup.schemaContent(schemaContent)
490+
.runtimeWiring(wiringConfigurer)
491+
.typeDefinitionConfigurer(new ConnectionTypeDefinitionConfigurer())
492+
.toGraphQlSource()
493+
.schema();
494+
495+
return new SchemaMappingInspector().inspectSchemaMappings(schema, wiringBuilder.build());
463496
}
464497

465-
RuntimeWiring.Builder createRuntimeWiring(Class<?>... handlerTypes) {
498+
private RuntimeWiringConfigurer createRuntimeWiring(Class<?>... handlerTypes) {
466499
AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext();
467500
for (Class<?> handlerType : handlerTypes) {
468501
appContext.registerBean(handlerType);
@@ -473,9 +506,7 @@ RuntimeWiring.Builder createRuntimeWiring(Class<?>... handlerTypes) {
473506
configurer.setApplicationContext(appContext);
474507
configurer.afterPropertiesSet();
475508

476-
RuntimeWiring.Builder wiringBuilder = RuntimeWiring.newRuntimeWiring();
477-
configurer.configure(wiringBuilder);
478-
return wiringBuilder;
509+
return configurer;
479510
}
480511

481512
static SchemaInspectionReportAssert assertThatReport(SchemaMappingInspector.Report actual) {

0 commit comments

Comments
 (0)