Update DataFetcherExceptionResolverAdapter
Make it protected since it needs to be extended and add factory method with a BiFunction as an alternative.
This commit is contained in:
@@ -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:
|
||||
* <ul>
|
||||
* <li>{@link #resolveToSingleError}
|
||||
* <li>{@link #resolveToMultipleErrors}
|
||||
* </ul>
|
||||
*
|
||||
* <p>Use {@link #from(BiFunction)} to create an instance or extend this class
|
||||
* and override one of its resolve methods.
|
||||
*
|
||||
* <p>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<Throwable, DataFetchingEnvironment, GraphQLError> resolver) {
|
||||
|
||||
return new DataFetcherExceptionResolverAdapter() {
|
||||
|
||||
@Override
|
||||
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
|
||||
return resolver.apply(ex, env);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> 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<ExecutionResult> result = Mono.delay(Duration.ofMillis(10))
|
||||
.flatMap((aLong) -> Mono.fromFuture(graphQl.executeAsync(input)));
|
||||
Mono<ExecutionResult> 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<Throwable, DataFetchingEnvironment, GraphQLError> resolver) {
|
||||
|
||||
DataFetcherExceptionResolverAdapter adapter = new DataFetcherExceptionResolverAdapter() {
|
||||
|
||||
@Override
|
||||
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
|
||||
return resolver.apply(ex, env);
|
||||
}
|
||||
};
|
||||
adapter.setThreadLocalContextAware(true);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Object> errorDataFetcher = env -> {
|
||||
throw new IllegalArgumentException("Invalid greeting");
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
void reactorContextPropagation() {
|
||||
WebGraphQlHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }")
|
||||
.queryFetcher("greeting", (env) ->
|
||||
Mono.deferContextual((context) -> {
|
||||
Object name = context.get("name");
|
||||
return Mono.delay(Duration.ofMillis(50)).map((aLong) -> "Hello " + name);
|
||||
}))
|
||||
.toWebGraphQlHandler();
|
||||
DataFetcher<Object> dataFetcher = (env) -> Mono.deferContextual((context) -> {
|
||||
Object name = context.get("name");
|
||||
return Mono.delay(Duration.ofMillis(50)).map((aLong) -> "Hello " + name);
|
||||
});
|
||||
|
||||
Mono<WebOutput> outputMono = handler.handleRequest(webInput)
|
||||
.contextWrite((context) -> context.put("name", "007"));
|
||||
Mono<WebOutput> outputMono =
|
||||
this.graphQlSetup.queryFetcher("greeting", dataFetcher).toWebGraphQlHandler()
|
||||
.handleRequest(webInput)
|
||||
.contextWrite((context) -> context.put("name", "007"));
|
||||
|
||||
String greeting = GraphQlResponse.from(outputMono).toEntity("greeting", String.class);
|
||||
assertThat(greeting).isEqualTo("Hello 007");
|
||||
@@ -64,19 +69,18 @@ public class WebGraphQlHandlerTests {
|
||||
|
||||
@Test
|
||||
void reactorContextPropagationToExceptionResolver() {
|
||||
WebGraphQlHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }")
|
||||
.queryFetcher("greeting", (env) -> {
|
||||
throw new IllegalArgumentException("Invalid greeting");
|
||||
})
|
||||
.exceptionResolver((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()))))
|
||||
.toWebGraphQlHandler();
|
||||
DataFetcherExceptionResolver exceptionResolver =
|
||||
(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())));
|
||||
|
||||
Mono<WebOutput> outputMono = handler.handleRequest(webInput).contextWrite((cxt) -> cxt.put("name", "007"));
|
||||
Mono<WebOutput> outputMono = this.graphQlSetup.queryFetcher("greeting", this.errorDataFetcher)
|
||||
.exceptionResolver(exceptionResolver)
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(webInput)
|
||||
.contextWrite((cxt) -> cxt.put("name", "007"));
|
||||
|
||||
GraphQlResponse response = GraphQlResponse.from(outputMono);
|
||||
assertThat(response.errorCount()).isEqualTo(1);
|
||||
@@ -92,13 +96,13 @@ public class WebGraphQlHandlerTests {
|
||||
nameThreadLocal.set("007");
|
||||
TestThreadLocalAccessor<String> threadLocalAccessor = new TestThreadLocalAccessor<>(nameThreadLocal);
|
||||
try {
|
||||
WebGraphQlHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }")
|
||||
|
||||
Mono<WebOutput> outputMono = this.graphQlSetup
|
||||
.queryFetcher("greeting", env -> "Hello " + nameThreadLocal.get())
|
||||
.webInterceptor((input, next) -> Mono.delay(Duration.ofMillis(10)).flatMap((aLong) -> next.next(input)))
|
||||
.threadLocalAccessor(threadLocalAccessor)
|
||||
.toWebGraphQlHandler();
|
||||
|
||||
Mono<WebOutput> outputMono = handler.handleRequest(webInput);
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(webInput);
|
||||
|
||||
String greeting = GraphQlResponse.from(outputMono).toEntity("greeting", String.class);
|
||||
assertThat(greeting).isEqualTo("Hello 007");
|
||||
@@ -114,20 +118,18 @@ public class WebGraphQlHandlerTests {
|
||||
nameThreadLocal.set("007");
|
||||
TestThreadLocalAccessor<String> threadLocalAccessor = new TestThreadLocalAccessor<>(nameThreadLocal);
|
||||
try {
|
||||
WebGraphQlHandler handler = GraphQlSetup.schemaContent("type Query { greeting: String }")
|
||||
.queryFetcher("greeting", env -> {
|
||||
throw new IllegalArgumentException("Invalid greeting");
|
||||
})
|
||||
.exceptionResolver(threadLocalContextAwareExceptionResolver((ex, env) ->
|
||||
GraphqlErrorBuilder.newError(env)
|
||||
.message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
|
||||
.errorType(ErrorType.BAD_REQUEST).build())
|
||||
)
|
||||
DataFetcherExceptionResolverAdapter exceptionResolver = DataFetcherExceptionResolverAdapter.from((ex, env) ->
|
||||
GraphqlErrorBuilder.newError(env)
|
||||
.message("Resolved error: " + ex.getMessage() + ", name=" + nameThreadLocal.get())
|
||||
.errorType(ErrorType.BAD_REQUEST).build());
|
||||
exceptionResolver.setThreadLocalContextAware(true);
|
||||
|
||||
Mono<WebOutput> outputMono = this.graphQlSetup.queryFetcher("greeting", this.errorDataFetcher)
|
||||
.exceptionResolver(exceptionResolver)
|
||||
.webInterceptor((input, next) -> Mono.delay(Duration.ofMillis(10)).flatMap((aLong) -> next.next(input)))
|
||||
.threadLocalAccessor(threadLocalAccessor)
|
||||
.toWebGraphQlHandler();
|
||||
|
||||
Mono<WebOutput> outputMono = handler.handleRequest(webInput);
|
||||
.toWebGraphQlHandler()
|
||||
.handleRequest(webInput);
|
||||
|
||||
GraphQlResponse response = GraphQlResponse.from(outputMono);
|
||||
assertThat(response.errorCount()).isEqualTo(1);
|
||||
@@ -138,18 +140,4 @@ public class WebGraphQlHandlerTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static DataFetcherExceptionResolver threadLocalContextAwareExceptionResolver(
|
||||
BiFunction<Throwable, DataFetchingEnvironment, GraphQLError> resolver) {
|
||||
|
||||
DataFetcherExceptionResolverAdapter adapter = new DataFetcherExceptionResolverAdapter() {
|
||||
|
||||
@Override
|
||||
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
|
||||
return resolver.apply(ex, env);
|
||||
}
|
||||
};
|
||||
adapter.setThreadLocalContextAware(true);
|
||||
return adapter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user