Skip to content

Commit

Permalink
Document how to configure custom ExecutionStrategy
Browse files Browse the repository at this point in the history
Closes gh-832
  • Loading branch information
rstoyanchev committed May 18, 2024
1 parent 7dd9cf5 commit aa1ee77
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 28 deletions.
33 changes: 33 additions & 0 deletions spring-graphql-docs/modules/ROOT/pages/request-execution.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,39 @@ https://github.com/graphql-java/graphql-java-extended-validation[Extended Valida
library.


[[execution.graphqlsource.execution-strategy]]
=== `ExecutionStrategy`

An `ExecutionStrategy` in GraphQL Java drives the fetching of requested fields.
To create an `ExecutionStrategy`, you need to provide a `DataFetcherExceptionHandler`.
By default, Spring for GraphQL creates the exception handler to use as described in
xref:request-execution.adoc#execution.exceptions[Exceptions] and sets it on the
`GraphQL.Builder`. GraphQL Java then uses that to create `AsyncExecutionStrategy`
instances with the configured exception handler.

If you need to create a custom `ExecutionStrategy`, you can detect
``DataFetcherExceptionResolver``s and create an exception handler in the same way, and use
it to create the custom `ExecutionStrategy`. For example, in a Spring Boot application:

[source,java,indent=0,subs="verbatim,quotes"]
----
@Bean
GraphQlSourceBuilderCustomizer sourceBuilderCustomizer(
ObjectProvider<DataFetcherExceptionResolver> resolvers) {
DataFetcherExceptionHandler exceptionHandler =
DataFetcherExceptionResolver.createExceptionHandler(resolvers.stream().toList());
AsyncExecutionStrategy strategy = new CustomAsyncExecutionStrategy(exceptionHandler);
return sourceBuilder -> sourceBuilder.configureGraphQl(builder ->
builder.queryExecutionStrategy(strategy).mutationExecutionStrategy(strategy));
}
----




[[execution.graphqlsource.schema-transformation]]
=== Schema Transformation

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -26,10 +26,12 @@

/**
* Contract to resolve exceptions from {@link graphql.schema.DataFetcher}s.
* Implementations are typically declared as beans in Spring configuration and
* are invoked sequentially until one emits a List of {@link GraphQLError}s.
* Resolves are typically declared as Spring beans and invoked in turn until one
* resolves the exception by emitting a (possibly empty) {@code GraphQLError} list.
* Use the static factory method {@link #createExceptionHandler} to create a
* {@link DataFetcherExceptionHandler} from a list of resolvers.
*
* <p>Most resolver implementations can extend
* <p>Resolver implementations can extend
* {@link DataFetcherExceptionResolverAdapter} and override one of its
* {@link DataFetcherExceptionResolverAdapter#resolveToSingleError resolveToSingleError} or
* {@link DataFetcherExceptionResolverAdapter#resolveToMultipleErrors resolveToMultipleErrors}
Expand Down Expand Up @@ -85,13 +87,13 @@ protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironmen
}

/**
* Factory method to create a {@link DataFetcherExceptionResolver} from a
* list of resolvers. Spring for GraphQL uses this method to set
* {@link graphql.GraphQL.Builder#defaultDataFetcherExceptionHandler(DataFetcherExceptionHandler)}
* from resolvers found in Spring configuration, and that default handler
* is used in turn to create each {@code ExecutionStrategy}. Applications
* may also find this factory method useful when creating a custom
* {@code ExecutionStrategy}.
* Factory method to create a {@link DataFetcherExceptionHandler} from a
* list of {@link DataFetcherExceptionResolver}'s. This is used internally
* in {@link AbstractGraphQlSourceBuilder} to set the exception handler on
* {@link graphql.GraphQL.Builder}, which in turn is used to create
* {@link graphql.execution.ExecutionStrategy}'s. Applications may also use
* this method to create an exception handler when they to need to initialize
* a custom {@code ExecutionStrategy}.
* <p>Resolvers are invoked in turn until one resolves the exception by
* emitting a (possibly empty) {@code GraphQLError} list. If the exception
* remains unresolved, the handler creates a {@code GraphQLError} with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,19 @@
* {@link DataFetcherExceptionHandler} that invokes {@link DataFetcherExceptionResolver}'s
* in a sequence until one returns a list of {@link GraphQLError}'s.
*
* <p>Use {@link DataFetcherExceptionResolver#createExceptionHandler(List)} to
* create an instance.
*
* @author Rossen Stoyanchev
*/
class ExceptionResolversExceptionHandler implements DataFetcherExceptionHandler {

private static final Log logger = LogFactory.getLog(ExceptionResolversExceptionHandler.class);


private final List<DataFetcherExceptionResolver> resolvers;


/**
* Create an instance.
* @param resolvers the resolvers to use
Expand All @@ -60,9 +65,11 @@ class ExceptionResolversExceptionHandler implements DataFetcherExceptionHandler

@Override
@SuppressWarnings("deprecation")
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters params) {
Throwable exception = unwrapException(params);
DataFetchingEnvironment env = params.getDataFetchingEnvironment();
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(
DataFetcherExceptionHandlerParameters handlerParameters) {

Throwable exception = unwrapException(handlerParameters);
DataFetchingEnvironment env = handlerParameters.getDataFetchingEnvironment();
ContextSnapshot snapshot = ContextSnapshot.captureFrom(env.getGraphQlContext());
try {
return Flux.fromIterable(this.resolvers)
Expand All @@ -80,34 +87,34 @@ public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(Data
}
}

private DataFetcherExceptionHandlerResult handleResolverError(
Throwable resolverException, Throwable originalException, DataFetchingEnvironment environment) {

if (logger.isWarnEnabled()) {
logger.warn("Failure while resolving " + originalException.getMessage(), resolverException);
}
return createInternalError(originalException, environment);
}

private Throwable unwrapException(DataFetcherExceptionHandlerParameters params) {
Throwable ex = params.getException();
return ((ex instanceof CompletionException) ? ex.getCause() : ex);
}

private void logResolvedException(Throwable ex, DataFetcherExceptionHandlerResult result) {
if (logger.isDebugEnabled()) {
logger.debug("Resolved " + ex.getClass().getSimpleName() +
" to GraphQL error(s): " + result.getErrors(), ex);
String name = ex.getClass().getSimpleName();
logger.debug("Resolved " + name + " to GraphQL error(s): " + result.getErrors(), ex);
}
}

private DataFetcherExceptionHandlerResult handleResolverError(
Throwable resolverException, Throwable originalException, DataFetchingEnvironment env) {

if (logger.isWarnEnabled()) {
logger.warn("Failure while resolving " + originalException.getMessage(), resolverException);
}
return createInternalError(originalException, env);
}

private DataFetcherExceptionHandlerResult createInternalError(Throwable ex, DataFetchingEnvironment environment) {
ExecutionId executionId = environment.getExecutionId();
private DataFetcherExceptionHandlerResult createInternalError(Throwable ex, DataFetchingEnvironment env) {
ExecutionId executionId = env.getExecutionId();
if (logger.isErrorEnabled()) {
logger.error("Unresolved " + ex.getClass().getSimpleName() + " for executionId " + executionId, ex);
}
return DataFetcherExceptionHandlerResult
.newResult(GraphqlErrorBuilder.newError(environment)
.newResult(GraphqlErrorBuilder.newError(env)
.errorType(ErrorType.INTERNAL_ERROR)
.message(ErrorType.INTERNAL_ERROR + " for " + executionId)
.build())
Expand Down

0 comments on commit aa1ee77

Please sign in to comment.