Closed
Description
Describe the bug
When parsing an invalid GraphQL schema, it is possible that the SchemaProblem
exception thrown by SchemaGenerator
will contain errors that have null source locations. i.e. GraphQLError.getLocations()
can return a list containing nulls. When these GraphQLErrors are serialised via GraphQLError.toSpecification()
, an NPE is thrown due to GraphQLErrorHelper.location()
expecting a non-null SourceLocation
argument.
To Reproduce
import graphql.GraphQLError;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.errors.SchemaProblem;
String sdl = """
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])
type Query {
user: User
}
type User @key(fields: "id") {
id: ID!
}
""";
try {
SchemaGenerator.createdMockedSchema(sdl);
} catch (SchemaProblem e) {
e.getErrors().stream().map(GraphQLError::toSpecification).forEach(System.out::println);
}
The code inside the catch block will throw an NPE.
A test for the bug:
def "toSpecification filters out null error locations"() {
given:
def error = ValidationError.newValidationError()
.validationErrorType(ValidationErrorType.UnknownType)
.sourceLocations([null, mkLocation(333, 1)])
.description("Test ValidationError")
.build()
def expectedMap = [
locations: [
[line: 333, column: 1]
],
message: "Test ValidationError",
extensions: [classification:"ValidationError"]
]
expect:
error.toSpecification() == expectedMap
}
Potential solutions
A simple fix would be for BaseError.getLocations()
to add an additional null check to node.getSourceLocation()
:
@Override
public List<SourceLocation> getLocations() {
return node == null || node.getSourceLocation() == null ? Collections.singletonList(NO_WHERE) : Collections.singletonList(node.getSourceLocation());
}
Or the potential of a nullable SourceLocation could be pushed further down into GraphQLErrorHelper
:
public static Object location(SourceLocation location) {
if (location == null) {
return null;
}
int line = location.getLine();
int column = location.getColumn();
if (line < 1 || column < 1) {
return null;
}
return Map.of("line", line, "column", column);
}
Metadata
Metadata
Assignees
Labels
No labels