Strawberry GraphQL 错误处理完全指南
GraphQL 作为一种强类型查询语言,其错误处理机制与传统 REST API 有着显著不同。本文将深入探讨如何在 Strawberry GraphQL 框架中处理各种类型的错误,帮助开发者构建更健壮的 GraphQL 服务。
一、GraphQL 错误处理概述
在 GraphQL 中,错误主要分为三类:
- 查询验证错误(Validation Errors)
- 类型系统错误(Type Errors)
- 执行时错误(Execution Errors)
与传统 HTTP 错误码不同,GraphQL 总是返回 200 OK 状态码,即使发生错误,也会将错误信息包含在响应体中。这种设计使得客户端能够更灵活地处理部分成功和部分失败的情况。
二、查询验证错误处理
当客户端发送的查询不符合 GraphQL 规范或 Schema 定义时,Strawberry 会在执行前进行验证并返回验证错误。
示例场景:
{
nonExistentField
}
错误响应:
{
"errors": [
{
"message": "Cannot query field 'nonExistentField' on type 'Query'.",
"locations": [{"line": 2, "column": 3}]
}
]
}
特点:
- 验证错误会阻止查询执行
- 错误信息包含具体位置(行号、列号)
- 遵循 GraphQL 规范,不可自定义验证规则
三、类型系统错误处理
当解析器返回值与定义的类型不匹配时,Strawberry 会抛出类型错误。
常见情况:
- 非空字段返回了 None
- 返回类型与定义不符
示例代码:
@strawberry.type
class Query:
@strawberry.field
def required_field() -> str: # 非空字符串
return None # 错误!
错误响应:
{
"errors": [
{
"message": "Cannot return null for non-nullable field Query.required_field",
"path": ["required_field"]
}
]
}
最佳实践:
- 对于可能为空的字段,使用 Optional 类型注解
- 在解析器内部做好空值检查
四、执行时错误处理
当解析器执行过程中抛出未捕获的异常时,Strawberry 会自动捕获并将其转换为错误响应。
示例代码:
@strawberry.type
class Query:
@strawberry.field
def user() -> User:
raise DatabaseError("Connection failed")
响应特点:
{
"data": null,
"errors": [
{
"message": "Connection failed",
"path": ["user"]
}
]
}
日志记录: Strawberry 默认会将所有执行错误记录到 strawberry.execution
日志器中,便于服务器端排查问题。
五、部分响应与错误混合处理
GraphQL 的一个重要特性是支持部分响应,即当某些字段解析失败时,其他成功解析的字段仍会返回。
示例场景:
@strawberry.type
class Query:
@strawberry.field
def valid_field() -> str:
return "Success"
@strawberry.field
def failing_field() -> str:
raise Exception("Failed")
响应示例:
{
"data": {
"valid_field": "Success",
"failing_field": null
},
"errors": [
{
"message": "Failed",
"path": ["failing_field"]
}
]
}
注意事项:
- 只有可选字段(Nullable)才能返回 null
- 非空字段出错会导致整个父字段变为 null
六、业务错误的最佳实践
对于预期的业务错误,推荐将其建模为 Schema 的一部分,而不是抛出异常。
1. 简单场景:使用 Optional 类型
@strawberry.type
class Query:
@strawberry.field
def find_user(id: ID) -> Optional[User]:
if not user_exists(id):
return None
return User(id=id, ...)
2. 复杂场景:使用 Union 类型
对于需要返回详细错误信息的场景,可以使用联合类型:
@strawberry.type
class LoginSuccess:
user: User
token: str
@strawberry.type
class InvalidCredentialsError:
message: str
remaining_attempts: int
LoginResult = strawberry.union("LoginResult", (LoginSuccess, InvalidCredentialsError))
@strawberry.mutation
def login(username: str, password: str) -> LoginResult:
if not validate_credentials(username, password):
return InvalidCredentialsError(
message="Invalid credentials",
remaining_attempts=get_remaining_attempts(username)
)
return LoginSuccess(user=get_user(username), token=generate_token())
客户端查询示例:
mutation Login($username: String!, $password: String!) {
login(username: $username, password: $password) {
__typename
... on LoginSuccess {
user { id name }
token
}
... on InvalidCredentialsError {
message
remainingAttempts
}
}
}
七、错误处理策略总结
| 错误类型 | 处理方式 | 适用场景 | |---------|---------|---------| | 验证错误 | 自动处理 | 查询语法错误 | | 类型错误 | 类型系统保障 | 返回值类型不匹配 | | 意外错误 | 异常捕获 | 服务器内部错误 | | 业务错误 | Schema 建模 | 预期内的业务规则违反 |
八、高级技巧
-
自定义错误格式化:可以继承
Schema
类并重写process_errors
方法来自定义错误格式 -
错误扩展:通过扩展(Extensions)添加额外的错误元数据
-
全局错误处理:使用中间件实现统一的错误处理逻辑
class ErrorHandlingMiddleware:
async def resolve(self, next_, root, info, **args):
try:
return await next_(root, info, **args)
except Exception as e:
logger.error(f"Error during {info.parent_type.name}.{info.field_name}: {e}")
raise
schema = strawberry.Schema(..., extensions=[ErrorHandlingMiddleware])
通过合理运用这些错误处理技术,开发者可以构建出既健壮又易于调试的 GraphQL API。记住,良好的错误处理不仅是技术实现,更是 API 设计的重要组成部分。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考