1. Overview
1.概述
In this tutorial, we’ll learn about the error handling options in GraphQL. We’ll look at what the GraphQL spec says about the error responses. Consequently, we’ll develop an example of GraphQL error handling using Spring Boot.
在本教程中,我们将学习GraphQL中的错误处理选项。我们将研究GraphQL规范中关于错误响应的内容。因此,我们将使用Spring Boot开发一个GraphQL错误处理的例子。
2. Response per GraphQL Specification
2.根据GraphQL规范的响应
As per the GraphQL specification, every request received must return a well-formed response. This well-formed response consists of the map of data or errors from the respective successful or unsuccessful requested operation. Additionally, a response may contain partial successful result data and field errors.
根据GraphQL规范,每个收到的请求必须返回一个格式良好的响应。这个格式良好的响应包括来自各自成功或不成功的请求操作的数据或错误地图。此外,一个响应可能包含部分成功的结果数据和字段错误。
The key components of the response map are errors, data, and extensions.
响应地图的关键组成部分是错误、数据和扩展。
The errors section in the response describes any failure during the requested operation. If no error occurs, the errors component must not be present in the response. In the next section, we’ll look into the different kinds of errors described in the specification.
响应中的errors部分描述了在请求的操作期间的任何故障。如果没有发生错误,那么errors部分必须不出现在响应中。在下一节,我们将研究规范中描述的不同种类的错误。
The data section describes the result of the successful execution of the requested operation. If the operation is a query, this component is an object of query root operation type. On the other hand, if the operation is a mutation, this component is an object of the mutation root operation type.
data 部分描述了成功执行请求的操作的结果。如果操作是一个查询,这个组件是一个查询根操作类型的对象。另一方面,如果操作是一个突变,这个组件是一个突变根操作类型的对象。
If the requested operation fails even before the execution due to missing information, validation errors, or syntax errors, then the data component must not be present in the response. And if the operation fails during the execution of the operation with an unsuccessful result, then the data component must be null.
如果由于信息缺失、验证错误或语法错误,请求的操作甚至在执行前就已经失败了,那么data组件必须不出现在响应中。而如果在操作的执行过程中出现了失败的结果,那么data组件必须是null。
The response map may contain an additional component called extensions, which is a map object. The component facilitates the implementors to provide other custom contents in the response as they see fit. Hence, there are no additional restrictions on its content format.
响应地图可能包含一个叫做extensions的额外组件,它是一个地图对象。该组件便于实现者在响应中提供他们认为合适的其他自定义内容。因此,对其内容格式没有额外的限制。
If the data component isn’t present in the response, then the errors component must be present and must contain at least one error. Further, it should indicate the reasons for the failures.
如果响应中没有data组件,那么errors组件必须存在,并且必须包含至少一个错误。此外,它应该指出失败的原因。
Here’s an example of a GraphQL error:
下面是一个GraphQL错误的例子。
mutation {
addVehicle(vin: "NDXT155NDFTV59834", year: 2021, make: "Toyota", model: "Camry", trim: "XLE",
location: {zipcode: "75024", city: "Dallas", state: "TX"}) {
vin
year
make
model
trim
}
}
The error response when a unique constraint is violated will look like:
违反唯一约束时的错误响应将看起来像。
{
"data": null,
"errors": [
{
"errorType": "DataFetchingException",
"locations": [
{
"line": 2,
"column": 5,
"sourceName": null
}
],
"message": "Failed to add vehicle. Vehicle with vin NDXT155NDFTV59834 already present.",
"path": [
"addVehicle"
],
"extensions": {
"vin": "NDXT155NDFTV59834"
}
}
]
}
3. Errors Response Component per GraphQL Specification
3.根据GraphQL规范的错误响应组件
The errors section in the response is a non-empty list of errors, each of which is a map.
响应中的errors部分是一个非空的错误列表,每个错误都是一个地图。
3.1. Request Errors
3.1 请求错误
As the name suggests, request errors may occur before the operation execution if there is any issue with the request itself. It may be due to request data parsing failure, request document validation, an unsupported operation, or invalid request values.
顾名思义,如果请求本身有任何问题,请求错误可能会在操作执行前发生。这可能是由于请求数据解析失败、请求文档验证、不支持的操作或无效的请求值。
When a request error occurs, this indicates that execution has not begun, which means the data section in the response must not be present in the response. In other words, the response contains only the errors section.
当请求错误发生时,这表明执行还没有开始,这意味着响应中的data部分一定不会出现在响应中。换句话说,响应中只包含errors部分。
Let’s see an example demonstrating the case of invalid input syntax:
让我们看一个演示无效输入语法情况的例子。
query {
searchByVin(vin: "error) {
vin
year
make
model
trim
}
}
Here’s the request error response for a syntax error, which in this case was a missing quote mark:
下面是语法错误的请求错误响应,在本例中是缺少一个引号。
{
"data": null,
"errors": [
{
"message": "Invalid Syntax",
"locations": [
{
"line": 5,
"column": 8,
"sourceName": null
}
],
"errorType": "InvalidSyntax",
"path": null,
"extensions": null
}
]
}
3.2. Field Errors
3.2.现场错误
Field errors, as the name suggests, may occur due to either failure to coerce the value into the expected type or an internal error during the value resolution of a particular field. It means that field errors occur during the execution of the requested operation.
字段错误,顾名思义,可能是由于未能将值胁迫成预期的类型,或者在特定字段的值解析过程中出现内部错误。这意味着字段错误是在执行请求的操作过程中发生的。
In case of field errors, the execution of the requested operation continues and returns a partial result, meaning the data section of the response must be present along with all the field errors in the errors section.
如果出现字段错误,继续执行请求的操作并返回部分结果,这意味着响应的data 部分必须与errors 部分中的所有字段错误一起出现。
Let’s look at another example:
让我们看看另一个例子。
query {
searchAll {
vin
year
make
model
trim
}
}
This time, we’ve included the vehicle trim field, which is supposed to be non-nullable according to our GraphQL schema.
这一次,我们包含了车辆trim字段,根据我们的GraphQL模式,该字段应该是不可为空的。
However, one of the vehicles’ information has a null trim value, so we’re getting back only partial data – the vehicles whose trim value is not null – along with the error:
然而,其中一个车辆的信息有一个空的trim值,所以我们只得到部分数据–trim值不是空的车辆–以及错误。
{
"data": {
"searchAll": [
null,
{
"vin": "JTKKU4B41C1023346",
"year": 2012,
"make": "Toyota",
"model": "Scion",
"trim": "Xd"
},
{
"vin": "1G1JC1444PZ215071",
"year": 2000,
"make": "Chevrolet",
"model": "CAVALIER VL",
"trim": "RS"
}
]
},
"errors": [
{
"message": "Cannot return null for non-nullable type: 'String' within parent 'Vehicle' (/searchAll[0]/trim)",
"path": [
"searchAll",
0,
"trim"
],
"errorType": "DataFetchingException",
"locations": null,
"extensions": null
}
]
}
3.3. Error Response Format
3.3.错误响应格式
As we saw earlier, errors in the response are a collection of one or more errors. And, every error must contain a message key that describes the failure reasons so the client developer can make necessary corrections to avoid the error.
正如我们前面所看到的,响应中的errors是一个或多个错误的集合。而且,每个错误都必须包含一个描述失败原因的message键,以便客户端开发人员可以进行必要的修正以避免错误。
Each error may also contain a key called locations, which is a list of locations pointing to a line in the requested GraphQL document associated with an error. Each location is a map with keys: line and column, respectively, providing the line number and beginning column number of the associated element.
每个错误还可能包含一个名为locations的键,这是一个位置列表,指向请求的GraphQL文档中与错误有关的一行。每个位置都是一个带有键的地图:行和列,分别提供相关元素的行号和起始列号。
The other key that may be part of an error is called path. It provides the list of values from the root element traced to the particular element of the response that has the error. A path value can be a string representing the field name or index of the error element if the field value is a list. If the error is related to a field with an alias name, then the value in the path should be the alias name.
另一个可能是错误的一部分的键被称为path.它提供了从根元素追踪到响应中出现错误的特定元素的值的列表。path值可以是一个字符串,代表字段名,如果字段值是一个列表,则是错误元素的索引。如果错误与一个有别名的字段有关,那么path中的值应该是别名。
3.4. Handling Field Errors
3.4 处理字段错误
Whether a field error is raised on a nullable or non-nullable field, we should handle it as if the field returned null and the error must be added to the errors list.
无论在可归零或不可归零的字段上发生字段错误,我们都应该像字段返回null那样处理它,并且必须将该错误添加到errors列表。
In the case of a nullable field, the field’s value in the response will be null but errors must contain this field error describing the failure reasons and other information, as seen in the earlier section.
如果是空字段,该字段在响应中的值将是null,但errors必须包含描述失败原因和其他信息的这个字段错误,正如在前面的章节中看到的。
On the other hand, the parent field handles the non-nullable field error. If the parent field is non-nullable, then the error handling is propagated until we reach a nullable parent field or the root element.
另一方面,父字段处理非空字段的错误。如果父字段是不可归零的,那么错误处理会被传播,直到我们到达一个可归零的父字段或根元素。
Similarly, if a list field contains a non-nullable type and one or more list elements return null, the whole list resolves to null. Additionally, if the parent field containing the list field is non-nullable, then the error handling is propagated until we reach a nullable parent or the root element.
同样地,如果一个列表字段包含一个不可置空的类型,并且一个或多个列表元素返回null,整个列表解析为null。此外,如果包含列表字段的父字段是非空的,那么错误处理将被传播,直到我们到达一个可空的父元素或根元素。
For any reason, if multiple errors are raised for the same field during resolution, then for that field, we must add only one field error into errors.
出于任何原因,如果在解析过程中对同一字段提出了多个错误,那么对于该字段,我们必须只在errors中添加一个字段错误。
4. Spring Boot GraphQL Libraries
4.Spring Boot GraphQL库
Our Spring Boot application example uses the spring-boot-starter-graphql module, which brings in the required GraphQL dependencies.
我们的Spring Boot应用程序示例使用了spring-boot-starter-graphql模块,该模块带来了所需的GraphQL依赖项。
We’re also using the spring-graphql-test module for related testing:
我们也在使用spring-graphql-test模块进行相关测试。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.graphql</groupId>
<artifactId>spring-graphql-test</artifactId>
<scope>test</scope>
</dependency>
5. Spring Boot GraphQL Error Handling
5 Spring Boot GraphQL错误处理
In this section, we’ll mainly cover GraphQL error handling in the Spring Boot application itself. We won’t cover the GraphQL Java and GraphQL Spring Boot application development.
在本节中,我们将主要介绍Spring Boot应用程序本身的GraphQL错误处理。我们不会涉及GraphQL Java和GraphQL Spring Boot应用开发。
In our Spring Boot application example, we’ll mutate or query for vehicles based on either location or VIN (Vehicle Identification Number). We’ll see different ways to implement error handling using this example.
在我们的Spring Boot应用实例中,我们将根据位置或VIN(车辆识别码)对车辆进行突变或查询。我们将看到使用这个例子实现错误处理的不同方法。
In the following subsections, we’ll see how the Spring Boot module handles exceptions or errors.
在以下各小节中,我们将看到Spring Boot模块如何处理异常或错误。
5.1. GraphQL Response With Standard Exception
5.1.带有标准异常的GraphQL响应
Generally, in a REST application, we create a custom runtime exception class by extending RuntimeException or Throwable:
一般来说,在REST应用程序中,我们通过扩展RuntimeException或Throwable创建一个自定义的运行时异常类。
public class InvalidInputException extends RuntimeException {
public InvalidInputException(String message) {
super(message);
}
}
With this approach, we can see the GraphQL engine returns the following response:
通过这种方法,我们可以看到GraphQL引擎返回以下响应。
{
"errors": [
{
"message": "INTERNAL_ERROR for 2c69042a-e7e6-c0c7-03cf-6026b1bbe559",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"searchByLocation"
],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": null
}
In the above error response, we can see that it doesn’t contain any details of the error.
在上面的错误响应中,我们可以看到,它没有包含任何错误的细节。
By default, any exception during request processing is handled by the ExceptionResolversExceptionHandler class that implements the DataFetcherExceptionHandler interface from the GraphQL API. It allows the application to register one or more DataFetcherExceptionResolver components.
默认情况下,请求处理期间的任何异常都由ExceptionResolversExceptionHandler类处理,该类实现了GraphQL API的DataFetcherExceptionHandler接口。它允许应用程序注册一个或多个DataFetcherExceptionResolver组件。
These resolvers are sequentially invoked until one of them is able to handle the exception and resolve it to a GraphQLError. If no resolvers are able to handle the exception then the exception is categorized as an INTERNAL_ERROR. It also contains the execution id and generic error message, as shown above.
这些解析器被依次调用,直到其中一个能够处理该异常并将其解析为GraphQLError。如果没有解析器能够处理该异常,那么该异常将被归类为INTERNAL_ERROR。它还包含执行ID和通用错误信息,如上所示。
5.2. GraphQL Response With Handled Exception
5.2.带有处理过的异常的GraphQL响应
Now let’s see what the response will look like if we implement our custom exception handling.
现在让我们看看,如果我们实现了我们的自定义异常处理,响应会是什么样子。
First, we have another custom exception:
首先,我们有另一个自定义异常。
public class VehicleNotFoundException extends RuntimeException {
public VehicleNotFoundException(String message) {
super(message);
}
}
DataFetcherExceptionResolver provides an async contract. However, in most cases, it is sufficient to extend DataFetcherExceptionResolverAdapter and override one of its resolveToSingleError or resolveToMultipleErrors methods that resolve exceptions synchronously.
DataFetcherExceptionResolver提供了一个异步合约。然而,在大多数情况下,扩展DataFetcherExceptionResolverAdapter并重载其resolveToSingleError或resolveToMultipleErrors方法就足够了,这些方法可以同步地解决异常。
Now, let’s implement this component and we can return a NOT_FOUND classification along with the exception message instead of the generic error:
现在,让我们来实现这个组件,我们可以返回一个NOT_FOUND分类和异常信息,而不是一般的错误。
@Component
public class CustomExceptionResolver extends DataFetcherExceptionResolverAdapter {
@Override
protected GraphQLError resolveToSingleError(Throwable ex, DataFetchingEnvironment env) {
if (ex instanceof VehicleNotFoundException) {
return GraphqlErrorBuilder.newError()
.errorType(ErrorType.NOT_FOUND)
.message(ex.getMessage())
.path(env.getExecutionStepInfo().getPath())
.location(env.getField().getSourceLocation())
.build();
} else {
return null;
}
}
}
Here, we’ve created a GraphQLError with the appropriate classification and other error details to create a more useful response in the errors section of the JSON response:
在这里,我们创建了一个GraphQLError,带有适当的分类和其他错误细节,以便在JSON响应的errors部分创建一个更有用的响应。
{
"errors": [
{
"message": "Vehicle with vin: 123 not found.",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"searchByVin"
],
"extensions": {
"classification": "NOT_FOUND"
}
}
],
"data": {
"searchByVin": null
}
}
An important detail of this error handling mechanism is that unresolved exceptions are logged at the ERROR level along with the executionId that correlates with the error sent to the client. Any resolved exceptions, as shown above, are logged at DEBUG level in the logs.
这个错误处理机制的一个重要细节是,未解决的异常会被记录在ERROR级别,同时记录与发送到客户端的错误相关的executionId。任何已解决的异常,如上所示,在日志中被记录在DEBUG级别。
6. Conclusion
6.结语
In this tutorial, we learned different types of GraphQL errors. We also looked at how to format the GraphQL errors per the specification. Later we implemented error handling in a Spring Boot application.
在本教程中,我们学习了不同类型的GraphQL错误。我们还研究了如何按照规范格式化GraphQL错误。后来我们在Spring Boot应用程序中实现了错误处理。
As always, the complete source code is available over on GitHub.
一如既往,完整的源代码可在GitHub上获得,。