From 579bb649bdbd220fae6dda899fb5258f951c4dec Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Wed, 4 Sep 2024 17:08:14 +0100 Subject: [PATCH] Improve paginated type check in schema inspection Closes gh-1053 --- .../execution/SchemaMappingInspector.java | 36 ++++++++++++------- .../SchemaMappingInspectorTests.java | 32 +++++++++++++++-- 2 files changed, 53 insertions(+), 15 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 7c3b1587..e202cab2 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 @@ -684,8 +684,9 @@ public final class SchemaMappingInspector { // Remove GraphQL type wrappers, and nest within Java generic types GraphQLType outputType = unwrapIfNonNull(field.getType()); - if (isPaginatedType(outputType)) { - outputType = getPaginatedType((GraphQLObjectType) outputType, schema); + GraphQLType paginatedType = getPaginatedType(outputType); + if (paginatedType != null) { + outputType = paginatedType; resolvableType = nestForConnection(resolvableType); } else if (outputType instanceof GraphQLList listType) { @@ -702,17 +703,26 @@ public final class SchemaMappingInspector { return (type instanceof GraphQLNonNull graphQLNonNull) ? graphQLNonNull.getWrappedType() : type; } - private static boolean isPaginatedType(GraphQLType type) { - return (type instanceof GraphQLObjectType objectType && - objectType.getName().endsWith("Connection") && - objectType.getField("edges") != null && objectType.getField("pageInfo") != null); - } - - private static GraphQLType getPaginatedType(GraphQLObjectType type, GraphQLSchema schema) { - String name = type.getName().substring(0, type.getName().length() - 10); - GraphQLType nodeType = schema.getType(name); - Assert.state(nodeType != null, "No node type for '" + type.getName() + "'"); - return nodeType; + @Nullable + private static GraphQLType getPaginatedType(GraphQLType type) { + if (!(type instanceof GraphQLObjectType cot && cot.getName().endsWith("Connection"))) { + return null; + } + GraphQLFieldDefinition edges = cot.getField("edges"); + if (edges == null) { + return null; + } + if (!(unwrapIfNonNull(edges.getType()) instanceof GraphQLList lt)) { + return null; + } + if (!(lt.getWrappedType() instanceof GraphQLObjectType eot)) { + return null; + } + GraphQLFieldDefinition node = eot.getField("node"); + if (node == null) { + return null; + } + return unwrapIfNonNull(node.getType()); } private static ResolvableType nestForConnection(ResolvableType type) { 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 ba031df1..3ce3fdbc 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 @@ -113,7 +113,7 @@ class SchemaMappingInspectorTests extends SchemaMappingInspectorTestSupport { } @Test - void reportWorksForQueryWithConnection() { + void reportWorksForConnectionType() { String schema = """ type Query { paginatedBooks: BookConnection @@ -124,7 +124,7 @@ class SchemaMappingInspectorTests extends SchemaMappingInspectorTestSupport { } type BookEdge { cursor: String! - # ... + node: Book! } type PageInfo { startCursor: String @@ -140,6 +140,34 @@ class SchemaMappingInspectorTests extends SchemaMappingInspectorTestSupport { assertThatReport(report).hasUnmappedFieldCount(1).containsUnmappedFields("Book", "missing"); } + @Test // gh-1053 + void reportWorksForConnectionWithCustomNodeTypeName() { + String schema = """ + type Query { + paginatedBooks: BookConnection + } + type BookConnection { + edges: [BookEdge]! + pageInfo: PageInfo! + } + type BookEdge { + cursor: String! + node: MyBook! + } + type PageInfo { + startCursor: String + # ... + } + type MyBook { + id: ID + name: String + missing: Boolean + } + """; + SchemaReport report = inspectSchema(schema, BookController.class); + assertThatReport(report).hasUnmappedFieldCount(1).containsUnmappedFields("MyBook", "missing"); + } + @Test void reportWorksForQueryWithExtensionType() { String schema = """