From af74edbcc750da855f8d2a9148426f8029beedb2 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Wed, 5 Feb 2025 10:24:43 +0100 Subject: [PATCH] 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 --- .../DefaultExecutionGraphQlService.java | 11 ++++++++++- .../DefaultExecutionGraphQlServiceTests.java | 19 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultExecutionGraphQlService.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultExecutionGraphQlService.java index c444f65d..1a0e48b9 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultExecutionGraphQlService.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DefaultExecutionGraphQlService.java @@ -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)); }); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultExecutionGraphQlServiceTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultExecutionGraphQlServiceTests.java index ef9293d9..7c404682 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultExecutionGraphQlServiceTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/DefaultExecutionGraphQlServiceTests.java @@ -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); + } + }