Skip to content

Commit 3b1c9c0

Browse files
committed
Update reference docs for annotated exception handlers
Closes gh-160
1 parent da29846 commit 3b1c9c0

File tree

3 files changed

+105
-17
lines changed

3 files changed

+105
-17
lines changed

spring-graphql-docs/src/docs/asciidoc/index.adoc

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -499,25 +499,27 @@ added by <<server.interception, WebGraphQlInterceptor>> components.
499499

500500

501501
[[execution.exceptions]]
502-
=== Exception Resolution
502+
=== Exceptions
503503

504-
A GraphQL Java application can register a `DataFetcherExceptionHandler` to decide how to
505-
represent exceptions from the data layer in the "errors" section of the GraphQL response.
504+
In GraphQL Java, `DataFetcherExceptionHandler` decides how to represent exceptions from
505+
data fetching in the "errors" section of the response. An application can register a
506+
single handler only.
506507

507-
Spring for GraphQL has a built-in `DataFetcherExceptionHandler` that is configured for use
508-
by the default <<execution.graphqlsource>> builder. It allows applications to register
509-
one or more Spring `DataFetcherExceptionResolver` components that are invoked sequentially
510-
until one resolves the `Exception` to a (possibly empty) list of `graphql.GraphQLError`
511-
objects.
508+
Spring for GraphQL registers a `DataFetcherExceptionHandler` that provides default
509+
handling and enables the `DataFetcherExceptionResolver` contract. An application can
510+
register any number of resolvers via <<execution.graphqlsource>> builder and those are in
511+
order until one them resolves the `Exception` to a `List<graphql.GraphQLError>`.
512+
The Spring Boot starter detects beans of this type.
512513

513-
`DataFetcherExceptionResolver` is an asynchronous contract. For most implementations, it
514-
would be sufficient to extend `DataFetcherExceptionResolverAdapter` and override
515-
one of its `resolveToSingleError` or `resolveToMultipleErrors` methods that
516-
resolve exceptions synchronously.
514+
`DataFetcherExceptionResolverAdapter` is a convenient base class with protected methods
515+
`resolveToSingleError` and `resolveToMultipleErrors`.
517516

518-
A `GraphQLError` can be assigned to a category via `graphql.ErrorClassification`.
519-
In Spring GraphQL, you can also assign via `ErrorType` which has the following common
520-
classifications that applications can use to categorize errors:
517+
The <<controllers>> programming model enables handling data fetching exceptions with
518+
annotated exception handler methods with a flexible method signature, see
519+
<<controllers.exception-handler>> for details.
520+
521+
A `GraphQLError` can be assigned to a category based on the GraphQL Java
522+
`graphql.ErrorClassification`, or the Spring GraphQL `ErrorType`, which defines the following:
521523

522524
- `BAD_REQUEST`
523525
- `UNAUTHORIZED`
@@ -534,6 +536,7 @@ error details.
534536
Unresolved exception are logged at ERROR level along with the `executionId` to correlate
535537
to the error sent to the client. Resolved exceptions are logged at DEBUG level.
536538

539+
537540
[[execution.exceptions.request]]
538541
==== Request Exceptions
539542

@@ -1662,6 +1665,88 @@ Batch mapping methods can return:
16621665

16631666

16641667

1668+
[[controllers.exception-handler]]
1669+
=== `@GraphQlExceptionHandler`
1670+
1671+
Use `@GraphQlExceptionHandler` methods to handle exceptions from data fetching with a
1672+
flexible <<controllers.exception-handler.signature,method signature>>. When declared in a
1673+
controller, exception handler methods apply to exceptions from the same controller:
1674+
1675+
[source,java,indent=0,subs="verbatim,quotes"]
1676+
----
1677+
@Controller
1678+
public class BookController {
1679+
1680+
@QueryMapping
1681+
public Book bookById(@Argument Long id) {
1682+
// ...
1683+
}
1684+
1685+
@GraphQlExceptionHandler
1686+
public GraphQLError handle(BindException ex) {
1687+
return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
1688+
}
1689+
1690+
}
1691+
----
1692+
1693+
When declared in an `@ControllerAdvice`, exception handler methods apply across controllers:
1694+
1695+
[source,java,indent=0,subs="verbatim,quotes"]
1696+
----
1697+
@ControllerAdvice
1698+
public class GlobalExceptionHandler {
1699+
1700+
@GraphQlExceptionHandler
1701+
public GraphQLError handle(BindException ex) {
1702+
return GraphQLError.newError().errorType(ErrorType.BAD_REQUEST).message("...").build();
1703+
}
1704+
1705+
}
1706+
----
1707+
1708+
Exception handling via `@GraphQlExceptionHandler` methods is applied automatically to
1709+
controller invocations. To handle exceptions from other `graphql.schema.DataFetcher`
1710+
implementations, not based on controller methods, obtain a
1711+
`DataFetcherExceptionResolver` from `AnnotatedControllerConfigurer`, and register it in
1712+
`GraphQlSource.Builder` as a <<execution.exceptions,DataFetcherExceptionResolver>>.
1713+
1714+
1715+
1716+
1717+
[[controllers.exception-handler.signature]]
1718+
==== Method Signature
1719+
1720+
Exception handler methods support a flexible method signature with method arguments
1721+
resolved from a `DataFetchingEnvironment,` and matching to those of
1722+
<<controllers.schema-mapping.arguments,@SchemaMapping methods>>.
1723+
1724+
Supported return types are listed below:
1725+
1726+
[cols="1,2"]
1727+
|===
1728+
| Return Type | Description
1729+
1730+
| `graphql.GraphQLError`
1731+
| Resolve the exception to a single field error.
1732+
1733+
| `Collection<GraphQLError>`
1734+
| Resolve the exception to multiple field errors.
1735+
1736+
| `void`
1737+
| Resolve the exception without response errors.
1738+
1739+
| `Object`
1740+
| Resolve the exception to a single error, to multiple errors, or none.
1741+
The return value must be `GraphQLError`, `Collection<GraphQLError>`, or `null`.
1742+
1743+
| `Mono<T>`
1744+
| For asynchronous resolution where `<T>` is one of the supported, synchronous, return types.
1745+
1746+
|===
1747+
1748+
1749+
16651750

16661751
[[security]]
16671752
== Security

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/GraphQlExceptionHandler.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
* {@link org.springframework.graphql.execution.GraphQlSource.Builder#exceptionResolvers(List)
3939
* GraphQlSource.Builder}.
4040
*
41+
* <p>Supported return types are listed in the Spring for GraphQL reference documentation
42+
* in the section {@literal "Annotated Controllers"}.
43+
*
4144
* @author Rossen Stoyanchev
4245
* @since 1.2
4346
*/

spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerExceptionResolver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ private interface ReturnValueAdapter {
367367
*/
368368
static ReturnValueAdapter createFor(MethodParameter returnType) {
369369
Class<?> parameterType = returnType.getParameterType();
370-
if (parameterType == void.class || parameterType == Void.class) {
370+
if (parameterType == void.class) {
371371
return forVoid;
372372
}
373373
else if (parameterType.equals(GraphQLError.class)) {
@@ -381,7 +381,7 @@ else if (Collection.class.isAssignableFrom(parameterType)) {
381381
else if (Mono.class.isAssignableFrom(parameterType)) {
382382
returnType = returnType.nested();
383383
Class<?> nestedType = returnType.getNestedParameterType();
384-
if (nestedType == void.class || nestedType == Void.class) {
384+
if (nestedType == Void.class) {
385385
return forMonoVoid;
386386
}
387387
if (Collection.class.isAssignableFrom(nestedType)) {

0 commit comments

Comments
 (0)