Skip to content

Commit ba2be06

Browse files
committed
Fix NullPointerException in SchemaMapping inspector
Prior to this commit, the new nullness checks in the schema mapping inspection (see gh-1220) would fail for null field definitions. While not common in schemas, such types can be cnotributed by the Federation support for the JVM. This commit ensures that such types are considered as nullness "unspecified" and does not break the inspection process. Fixes gh-1333
1 parent 7874767 commit ba2be06

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.function.Function;
3535
import java.util.function.Predicate;
3636

37+
import graphql.language.FieldDefinition;
3738
import graphql.language.NonNullType;
3839
import graphql.language.Type;
3940
import graphql.schema.DataFetcher;
@@ -383,7 +384,11 @@ private void checkDataFetcherRegistrations() {
383384
}
384385

385386
private Nullness resolveNullness(GraphQLFieldDefinition fieldDefinition) {
386-
return resolveNullness(fieldDefinition.getDefinition().getType());
387+
FieldDefinition definition = fieldDefinition.getDefinition();
388+
if (definition != null) {
389+
return resolveNullness(definition.getType());
390+
}
391+
return Nullness.UNSPECIFIED;
387392
}
388393

389394
private Nullness resolveNullness(Type type) {

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

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818

1919
import java.util.concurrent.CompletableFuture;
2020

21+
import graphql.Scalars;
22+
import graphql.schema.GraphQLFieldDefinition;
23+
import graphql.schema.GraphQLNonNull;
24+
import graphql.schema.GraphQLObjectType;
25+
import graphql.schema.GraphQLSchema;
26+
import graphql.schema.GraphQLSchemaElement;
27+
import graphql.schema.GraphQLTypeVisitorStub;
28+
import graphql.schema.SchemaTransformer;
29+
import graphql.schema.idl.SchemaGenerator;
30+
import graphql.util.TraversalControl;
31+
import graphql.util.TraverserContext;
2132
import org.jspecify.annotations.NonNull;
2233
import org.jspecify.annotations.NullMarked;
2334
import org.jspecify.annotations.NullUnmarked;
@@ -349,6 +360,24 @@ void reportIsEmptyWhenSchemaFieldNonNullAndAsyncTypeNullable() {
349360
assertThatReport(report).isEmpty();
350361
}
351362

363+
@Test
364+
void doesNotFailWhenNullFieldDefinitionType() {
365+
String schemaContent = """
366+
type Query {
367+
bookById(id: ID): DefaultBook
368+
}
369+
type DefaultBook {
370+
id: ID
371+
title: String
372+
}
373+
""";
374+
375+
GraphQLSchema schema = SchemaGenerator.createdMockedSchema(schemaContent);
376+
schema = SchemaTransformer.transformSchema(schema, new NullFieldTypeVisitor());
377+
SchemaReport report = inspectSchema(schema, initializer -> { }, DefaultBookController.class);
378+
assertThatReport(report).containsUnmappedFields("DefaultBook", "_service");
379+
}
380+
352381
@Controller
353382
@NullUnmarked
354383
static class DefaultBookController {
@@ -382,9 +411,6 @@ static class NonNullBookController {
382411

383412
}
384413

385-
386-
387-
388414
@Controller
389415
@NullUnmarked
390416
static class NullableAsyncBookController {
@@ -400,6 +426,26 @@ record Book(String id, String title) {
400426

401427
}
402428

429+
static class NullFieldTypeVisitor extends GraphQLTypeVisitorStub {
430+
@Override
431+
public TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext<GraphQLSchemaElement> context) {
432+
if (node.getName().equals("title")) {
433+
GraphQLObjectType type = GraphQLObjectType.newObject()
434+
.name("_Service")
435+
.field(
436+
GraphQLFieldDefinition.newFieldDefinition()
437+
.name("sdl")
438+
.type(new GraphQLNonNull(Scalars.GraphQLString))
439+
.build())
440+
.build();
441+
GraphQLFieldDefinition field = GraphQLFieldDefinition.newFieldDefinition()
442+
.name("_service").type(GraphQLNonNull.nonNull(type)).build();
443+
return insertAfter(context, field);
444+
}
445+
return TraversalControl.CONTINUE;
446+
}
447+
}
448+
403449
}
404450

405451
@Nested

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,12 @@ protected SchemaReport inspectSchema(String schemaContent, Class<?>... controlle
4343

4444
protected SchemaReport inspectSchema(
4545
String schemaContent, Consumer<SchemaMappingInspector.Initializer> consumer, Class<?>... controllers) {
46-
4746
GraphQLSchema schema = SchemaGenerator.createdMockedSchema(schemaContent);
47+
return inspectSchema(schema, consumer, controllers);
48+
}
49+
50+
protected SchemaReport inspectSchema(
51+
GraphQLSchema schema, Consumer<SchemaMappingInspector.Initializer> consumer, Class<?>... controllers) {
4852
RuntimeWiring runtimeWiring = createRuntimeWiring(controllers);
4953
SchemaMappingInspector.Initializer initializer = SchemaMappingInspector.initializer();
5054
consumer.accept(initializer);

0 commit comments

Comments
 (0)