Handle early GraphQLExceptions in ExecutionGraphQlService

Prior to this commit, the `ExecutionGraphQlService` would throw
`GrahphQLException` instances if errors happen early in the process, for
example in some cases of document parsing/validation.

Such exception would be thrown directly and not reflected in the GraphQL
response if those are `GraphQLError`.

This commit ensures that a fallback GraphQL response is created in this
case and that it contains the relevant `GraphQLError` information.

Fixes gh-1118
This commit is contained in:
Brian Clozel
2025-02-05 10:24:43 +01:00
parent 474fbcafa1
commit af74edbcc7
2 changed files with 27 additions and 3 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,8 +22,11 @@ import java.util.List;
import java.util.function.BiFunction;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.GraphQLContext;
import graphql.GraphQLError;
import graphql.GraphQLException;
import graphql.execution.ExecutionIdProvider;
import graphql.execution.instrumentation.dataloader.EmptyDataLoaderRegistryInstance;
import io.micrometer.context.ContextSnapshotFactory;
@@ -102,6 +105,12 @@ public class DefaultExecutionGraphQlService implements ExecutionGraphQlService {
ExecutionInput executionInputToUse = registerDataLoaders(executionInput);
return Mono.fromFuture(this.graphQlSource.graphQl().executeAsync(executionInputToUse))
.onErrorResume(GraphQLException.class, (exception) -> {
if (exception instanceof GraphQLError graphQLError) {
return Mono.just(ExecutionResult.newExecutionResult().addError(graphQLError).build());
}
return Mono.error(exception);
})
.map((result) -> new DefaultExecutionGraphQlResponse(executionInputToUse, result));
});
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.graphql.execution;
import java.util.Map;
import graphql.ErrorType;
import org.dataloader.DataLoaderRegistry;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
@@ -28,6 +29,7 @@ import org.springframework.graphql.ExecutionGraphQlRequest;
import org.springframework.graphql.ExecutionGraphQlResponse;
import org.springframework.graphql.GraphQlSetup;
import org.springframework.graphql.TestExecutionRequest;
import org.springframework.graphql.support.DefaultExecutionGraphQlRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,7 +37,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* Unit tests for {@link DefaultExecutionGraphQlService}.
*
* @author Rossen Stoyanchev
* @since 1.2.4
*/
public class DefaultExecutionGraphQlServiceTests {
@@ -64,4 +65,18 @@ public class DefaultExecutionGraphQlServiceTests {
assertThat(dataLoaderRegistry.getDataLoaders()).hasSize(1);
}
@Test
void shouldHandleGraphQlErrors() {
GraphQlSource graphQlSource = GraphQlSetup.schemaContent("type Query { greeting: String }")
.queryFetcher("greeting", (env) -> "hi")
.toGraphQlSource();
DefaultExecutionGraphQlService graphQlService = new DefaultExecutionGraphQlService(graphQlSource);
ExecutionGraphQlRequest request = new DefaultExecutionGraphQlRequest("{ greeting }", "unknown",
null, null, "uniqueId", null);
ExecutionGraphQlResponse response = graphQlService.execute(request).block();
assertThat(response.getExecutionResult().getErrors()).singleElement()
.hasFieldOrPropertyWithValue("errorType", ErrorType.ValidationError);
}
}