diff --git a/spring-graphql/src/main/java/org/springframework/graphql/execution/DataFetcherExceptionResolverAdapter.java b/spring-graphql/src/main/java/org/springframework/graphql/execution/DataFetcherExceptionResolverAdapter.java
index 09b569e4..20110cff 100644
--- a/spring-graphql/src/main/java/org/springframework/graphql/execution/DataFetcherExceptionResolverAdapter.java
+++ b/spring-graphql/src/main/java/org/springframework/graphql/execution/DataFetcherExceptionResolverAdapter.java
@@ -17,6 +17,7 @@ package org.springframework.graphql.execution;
import java.util.Collections;
import java.util.List;
+import java.util.function.BiFunction;
import graphql.GraphQLError;
import graphql.schema.DataFetchingEnvironment;
@@ -27,23 +28,34 @@ import org.springframework.lang.Nullable;
/**
* Adapter for {@link DataFetcherExceptionResolver} that pre-implements the
- * asynchronous contract and exposes the following synchronous methods:
+ * asynchronous contract and exposes the following synchronous protected methods:
*
* - {@link #resolveToSingleError}
*
- {@link #resolveToMultipleErrors}
*
*
+ * Use {@link #from(BiFunction)} to create an instance or extend this class
+ * and override one of its resolve methods.
+ *
*
Implementations can also express interest in ThreadLocal context
* propagation, from the underlying transport thread, via
* {@link #setThreadLocalContextAware(boolean)}.
*
* @author Rossen Stoyanchev
*/
-public class DataFetcherExceptionResolverAdapter implements DataFetcherExceptionResolver {
+public abstract class DataFetcherExceptionResolverAdapter implements DataFetcherExceptionResolver {
private boolean threadLocalContextAware;
+ /**
+ * Protected constructor since this class is meant to be extended to provide
+ * the actual exception resolution logic.
+ */
+ protected DataFetcherExceptionResolverAdapter() {
+ }
+
+
/**
* Sub-classes can set this to indicate that ThreadLocal context from the
* transport handler (e.g. HTTP handler) should be restored when resolving
@@ -111,4 +123,23 @@ public class DataFetcherExceptionResolverAdapter implements DataFetcherException
return null;
}
+
+ /**
+ * Factory method to create a {@link DataFetcherExceptionResolverAdapter} that
+ * resolves exceptions with the given {@code BiFunction}.
+ * @param resolver the resolver function to use
+ * @return the created instance
+ */
+ public static DataFetcherExceptionResolverAdapter from(
+ BiFunction resolver) {
+
+ return new DataFetcherExceptionResolverAdapter() {
+
+ @Override
+ protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
+ return resolver.apply(ex, env);
+ }
+ };
+ }
+
}
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandlerTests.java
index a09b1585..3cb0efc4 100644
--- a/spring-graphql/src/test/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandlerTests.java
+++ b/spring-graphql/src/test/java/org/springframework/graphql/execution/ExceptionResolversExceptionHandlerTests.java
@@ -18,11 +18,10 @@ package org.springframework.graphql.execution;
import java.time.Duration;
import java.util.Collections;
-import java.util.function.BiFunction;
+import java.util.List;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
-import graphql.GraphQL;
import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
import graphql.schema.DataFetchingEnvironment;
@@ -43,16 +42,25 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class ExceptionResolversExceptionHandlerTests {
+ private final GraphQlSetup graphQlSetup =
+ GraphQlSetup.schemaContent("type Query { greeting: String }")
+ .queryFetcher("greeting", (env) -> {
+ throw new IllegalArgumentException("Invalid greeting");
+ });
+
+ private final ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
+
+
@Test
void resolveException() throws Exception {
- GraphQL graphQl = graphQl((ex, env) ->
- Mono.just(Collections.singletonList(
+ DataFetcherExceptionResolver resolver =
+ DataFetcherExceptionResolverAdapter.from((ex, env) ->
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage())
- .errorType(ErrorType.BAD_REQUEST).build())));
+ .errorType(ErrorType.BAD_REQUEST).build());
- ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
- ExecutionResult result = graphQl.executeAsync(input).get();
+ ExecutionResult result = this.graphQlSetup.exceptionResolver(resolver).toGraphQl()
+ .executeAsync(this.input).get();
GraphQlResponse response = GraphQlResponse.from(result);
assertThat(response.errorCount()).isEqualTo(1);
@@ -65,16 +73,16 @@ public class ExceptionResolversExceptionHandlerTests {
@Test
void resolveExceptionWithReactorContext() throws Exception {
- GraphQL graphQl = graphQl((ex, env) ->
- Mono.deferContextual((view) -> Mono.just(Collections.singletonList(
+ DataFetcherExceptionResolver resolver =
+ (ex, env) -> Mono.deferContextual((view) -> Mono.just(Collections.singletonList(
GraphqlErrorBuilder.newError(env)
.message("Resolved error: " + ex.getMessage() + ", name=" + view.get("name"))
- .errorType(ErrorType.BAD_REQUEST).build()))));
+ .errorType(ErrorType.BAD_REQUEST).build())));
- ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
ReactorContextManager.setReactorContext(Context.of("name", "007"), input);
- ExecutionResult result = graphQl.executeAsync(input).get();
+ ExecutionResult result = this.graphQlSetup.exceptionResolver(resolver).toGraphQl()
+ .executeAsync(this.input).get();
GraphQlResponse response = GraphQlResponse.from(result);
assertThat(response.errorCount()).isEqualTo(1);
@@ -87,18 +95,20 @@ public class ExceptionResolversExceptionHandlerTests {
nameThreadLocal.set("007");
TestThreadLocalAccessor accessor = new TestThreadLocalAccessor<>(nameThreadLocal);
try {
- GraphQL graphQl = graphQl(threadLocalContextAwareResolver((ex, env) ->
- GraphqlErrorBuilder.newError(env)
- .message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
- .errorType(ErrorType.BAD_REQUEST)
- .build()));
+ DataFetcherExceptionResolverAdapter resolver =
+ DataFetcherExceptionResolverAdapter.from((ex, env) ->
+ GraphqlErrorBuilder.newError(env)
+ .message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
+ .errorType(ErrorType.BAD_REQUEST)
+ .build());
+
+ resolver.setThreadLocalContextAware(true);
- ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
ContextView view = ReactorContextManager.extractThreadLocalValues(accessor, Context.empty());
ReactorContextManager.setReactorContext(view, input);
- Mono result = Mono.delay(Duration.ofMillis(10))
- .flatMap((aLong) -> Mono.fromFuture(graphQl.executeAsync(input)));
+ Mono result = Mono.delay(Duration.ofMillis(10)).flatMap((aLong) ->
+ Mono.fromFuture(this.graphQlSetup.exceptionResolver(resolver).toGraphQl().executeAsync(this.input)));
GraphQlResponse response = GraphQlResponse.from(result);
assertThat(response.errorCount()).isEqualTo(1);
@@ -111,10 +121,11 @@ public class ExceptionResolversExceptionHandlerTests {
@Test
void unresolvedException() throws Exception {
- GraphQL graphQl = graphQl((exception, environment) -> Mono.empty());
+ DataFetcherExceptionResolverAdapter resolver =
+ DataFetcherExceptionResolverAdapter.from((ex, env) -> null);
- ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
- ExecutionResult result = graphQl.executeAsync(input).get();
+ ExecutionResult result = this.graphQlSetup.exceptionResolver(resolver).toGraphQl()
+ .executeAsync(this.input).get();
GraphQlResponse response = GraphQlResponse.from(result);
assertThat(response.errorCount()).isEqualTo(1);
@@ -127,36 +138,13 @@ public class ExceptionResolversExceptionHandlerTests {
@Test
void suppressedException() throws Exception {
- GraphQL graphQl = graphQl((ex, env) -> Mono.just(Collections.emptyList()));
- ExecutionInput input = ExecutionInput.newExecutionInput().query("{ greeting }").build();
- ExecutionResult result = graphQl.executeAsync(input).get();
+ ExecutionResult result = this.graphQlSetup
+ .exceptionResolver((ex, env) -> Mono.just(Collections.emptyList())).toGraphQl()
+ .executeAsync(input).get();
String greeting = GraphQlResponse.from(result).rawValue("greeting");
assertThat(greeting).isNull();
}
- private static GraphQL graphQl(DataFetcherExceptionResolver exceptionResolver) {
- return GraphQlSetup.schemaContent("type Query { greeting: String }")
- .queryFetcher("greeting", (env) -> {
- throw new IllegalArgumentException("Invalid greeting");
- })
- .exceptionResolver(exceptionResolver)
- .toGraphQl();
- }
-
- private static DataFetcherExceptionResolver threadLocalContextAwareResolver(
- BiFunction resolver) {
-
- DataFetcherExceptionResolverAdapter adapter = new DataFetcherExceptionResolverAdapter() {
-
- @Override
- protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
- return resolver.apply(ex, env);
- }
- };
- adapter.setThreadLocalContextAware(true);
- return adapter;
- }
-
}
diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java
index cc9c848b..5d5ee9d1 100644
--- a/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java
+++ b/spring-graphql/src/test/java/org/springframework/graphql/web/WebGraphQlHandlerTests.java
@@ -19,11 +19,9 @@ package org.springframework.graphql.web;
import java.net.URI;
import java.time.Duration;
import java.util.Collections;
-import java.util.function.BiFunction;
-import graphql.GraphQLError;
import graphql.GraphqlErrorBuilder;
-import graphql.schema.DataFetchingEnvironment;
+import graphql.schema.DataFetcher;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -45,18 +43,25 @@ public class WebGraphQlHandlerTests {
private static final WebInput webInput = new WebInput(
URI.create("https://abc.org"), new HttpHeaders(), Collections.singletonMap("query", "{ greeting }"), null, "1");
+
+ private final GraphQlSetup graphQlSetup = GraphQlSetup.schemaContent("type Query { greeting: String }");
+
+ private final DataFetcher