欢迎回来!让我们直接深入 API 错误处理的世界,了解问题详情标准。在第一部分中,我们剖析了由不一致的错误报告引发的若干挑战——随着更多 API 的生产,数字生态系统不断扩张,这些挑战也随之加剧。从提供可操作错误消息的重要性,到自定义错误方法和安全漏洞的陷阱,我们看到了 RFC 9457 的问题详情如何提供了一个强大的框架来标准化 API 错误响应。
我们强调了从 RFC 7807 到 RFC 9457 的演变,其中包括 API 错误表述的重大进展,将错误处理从开发者的噩梦转变为一个精简、信息丰富且可操作的过程。此标准不仅使开发者的错误处理更加直观,还增强了数字平台的安全性和一致性。
现在,在对标准的好处和框架有了扎实理解之后,让我们转入实际应用。本节将指导您如何在 API 中利用问题详情,通过真实的示例和资源确保您的系统以最佳方式传递“坏消息”。
问题详情入门
在团队或组织中引入任何标准时,通常最难的一步就是开始。请先熟悉 RFC 9457 [1]。
不要从零开始
为了避免常见的陷阱并加速问题详情的采用,请利用以下资源和工件,它们将帮助您快速上手,并使您的团队能够提供一致的错误处理。
注册中心
如本系列第一部分所述,新的 RFC 9457 引入了通用问题类型 URI 注册中心的概念。托管在 IANA [2] 的官方注册中心以及 SmartBear 问题注册中心 [3] 在确定哪些问题类型与您的 API 相关时,都是宝贵的资源。
- IANA 注册中心:该官方注册中心包含了标准化的、可直接使用的或可作为定义您自己的问题类型时参考的问题类型 URI。
- SmartBear 问题注册中心:该注册中心提供了 SmartBear 团队整理的、针对各种 API 场景的常见问题类型目录。将来,其中一些可能会迁移到 IANA 注册中心。
对象、模式和可扩展性
标准的好处在于,我们可以避免与错误详情的形状相关的许多“细枝末节”讨论。RFC 提供了以下非规范性的 HTTP 问题详情 JSON Schema,它保证了错误的基本形状。
在需要提供比上述 JSON Schema 所涵盖信息更多的情况下,请放心,内置的可扩展性是一种强大的机制,允许您根据团队的需求来调整标准。
可扩展性也带来了自身的挑战。因此,最佳实践是清晰地定义您的扩展点,并告知使用问题详情的客户端必须忽略它们无法识别的扩展。为了使实施者(以及使用响应的客户端)能够预测此过程,我还建议制作一个包含您扩展的 JSON Schema。
这是在 SmartBear 新 API 中用于问题详情的 JSON Schema [5],其中包含 errors
和 code
扩展
{
"$schema": "https://json-schema.fullstack.org.cn/draft/2019-09/schema",
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "A URI reference that identifies the problem type.",
"format": "uri",
"maxLength": 1024
},
"status": {
"type": "integer",
"description": "The HTTP status code generated by the origin server for this occurrence of the problem.",
"format": "int32",
"minimum": 100,
"maximum": 599
},
"title": {
"type": "string",
"description": "A short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem, except for purposes of localization.",
"maxLength": 1024
},
"detail": {
"type": "string",
"description": "A human-readable explanation specific to this occurrence of the problem.",
"maxLength": 4096
},
"instance": {
"type": "string",
"description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced.",
"maxLength": 1024
},
"code": {
"type": "string",
"description": "An API specific error code aiding the provider team understand the error based on their own potential taxonomy or registry.",
"maxLength": 50
},
"errors": {
"type": "array",
"description": "An array of error details to accompany a problem details response.",
"maxItems": 1000,
"items": {
"type": "object",
"description": "An object to provide explicit details on a problem towards an API consumer.",
"properties": {
"detail": {
"type": "string",
"description": "A granular description on the specific error related to a body property, query parameter, path parameters, and/or header.",
"maxLength": 4096
},
"pointer": {
"type": "string",
"description": "A JSON Pointer to a specific request body property that is the source of error.",
"maxLength": 1024
},
"parameter": {
"type": "string",
"description": "The name of the query or path parameter that is the source of error.",
"maxLength": 1024
},
"header": {
"type": "string",
"description": "The name of the header that is the source of error.",
"maxLength": 1024
},
"code": {
"type": "string",
"description": "A string containing additional provider specific codes to identify the error context.",
"maxLength": 50
}
},
"required": [
"detail"
]
}
}
},
"required": [
"detail"
]
}
这提供了强大而详细的能力,可以描述与参数或请求体相关的错误发生情况。
让我们根据上述 JSON Schema 来看几个例子
- 对于缺少请求参数(例如查询参数)的问题,我们可以利用
errors
扩展,通过 details
和 parameter
属性提供关于缺失参数的明确信息
{
"type": "https://problems-registry.smartbear.com/missing-request-parameter",
"status": 400,
"title": "Missing request parameter",
"detail": "The request is missing an expected query or path parameter.",
"code": "400-03",
"errors": [
{
"detail": "The query parameter {name} is required.",
"parameter": "name"
}
]
}
- 对于请求体属性格式不正确的问题,我们可以利用
errors
扩展,通过 details
和 pointer
(它指定了属性位置的 JSON 指针)属性提供关于该问题的明确信息以及属性位置
{
"type": "https://problems-registry.smartbear.com/invalid-body-property-format",
"status": 400,
"title": "Invalid Body Property Format",
"detail": "The request body contains a malformed property.",
"code": "400-04",
"errors": [
{
"detail": "Must be a positive integer",
"pointer": "/quantity"
}
]
}
- 如果我们发现多个错误,并希望将所有违规情况返回给客户端,而不是强制进行过于“冗长”的交互,我们可以利用
errors
数组扩展来包含与关联问题类型相关的所有适用错误的详细信息
{
"type": "https://problems-registry.smartbear.com/business-rule-violation",
"status": 422,
"title": "Business Rule Violation",
"detail": "The request body is invalid and not meeting business rules.",
"code": "422-01",
"errors": [
{
"detail": "Maximum quantity allowed in 999",
"pointer": "/quantity"
},
{
"detail": "We do not offer `next-day` delivery to non-EU addresses",
"pointer": "/shippingAddress/country"
},
{
"detail": "We do not offer `next-day` delivery to non-EU addresses",
"pointer": "/shippingOption"
}
]
}
利用可用于 OpenAPI 的现成域加速
为了使在您的下一个 API 项目中采用问题详情变得更加容易,我已将上述资产打包到一个现成的SwaggerHub 域 [4] 中,该域可以从您的 OpenAPI 描述的各个部分进行引用。
该域包含
- 模式: HTTP 问题详情的完整和扩展(又称“观点化”)模式

- 示例: 针对受支持问题类型的大量代表性响应示例列表

- 响应: 一个随时可引用的、针对受支持问题类型的 OpenAPI 兼容响应列表

在下面的部分中,我将详细介绍如何在 OpenAPI 描述中利用免费公共的域。
在 OpenAPI 中使用问题详情
在 OpenAPI 描述中利用问题详情比您想象的要容易,并且通过利用上述一些工件,它变得更加简单。让我们为简单的图书商店 API 创建一个 OpenAPI 描述,以展示如何帮助改进 API 设计中的错误响应。
在首次编写 OpenAPI 描述时,我将设置基本对象(信息、标签、服务器)、用于检索书籍和下订单的两个资源,以及书籍和订单资源的相关模式。
openapi: 3.0.3
info:
title: Bookstore API
version: 0.0.1
description: |
The **Books API** - allows searching of books from the book catalog as well as retrieving the specific details on a selected book. Once you find the book you are looking for, you can make an order.
termsOfService: https://swagger.org.cn/terms/
contact:
name: DevRel at SmartBear
email: [email protected]
license:
name: Apache 2.0
url: https://apache.ac.cn/licenses/LICENSE-2.0.html
tags:
- name: Bookstore
description: APIs for our fictional bookstore
servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/frank-kilcommins/Bookstore-API/1.0.0
paths:
/books:
get:
summary: Get a list of books based on the provided criteria
description: |
This API method supports searching the book catalog based on book title or author name
operationId: getBooks
tags:
- Bookstore
parameters:
- name: title
description: The title (or partial title) of a book
in: query
required: false
schema:
type: string
maxLength: 200
format: string
- name: author
description: The author’s name (or partial author name)
in: query
required: false
schema:
type: string
maxLength: 150
format: string
- name: limit
description: The maximum number of books to return
in: query
required: false
schema:
type: integer
format: int64
minimum: 1
maximum: 1000
default: 10
responses:
'200':
$ref: '#/components/responses/books'
'400':
description: 400 Bad Request
'401':
description: 401 Unauthorized
'500':
description: 500 Internal Server Error
content:
application/json:
schema:
type: object
properties:
code:
type: integer
format: int32
example: 500
message:
type: string
example: "Internal Server Error"
details:
type: string
example: "An unexpected error occurred"
/orders:
post:
summary: Place book order
description: |
This API method allows placing an order for one or more books
operationId: createOrder
tags:
- Bookstore
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/OrderDetails'
'401':
description: 401 Unauthorized
'422':
description: Validation Error
'500':
description: Internal Server Error
components:
schemas:
BookOrder:
type: object
properties:
bookId:
type: string
description: The book identifier
format: uuid
maxLength: 36
example: 87da4501-4b52-4ea2-a2be-7dda8650f7eb
quantity:
type: integer
format: int64
minimum: 1
maximum: 10000
Order:
properties:
books:
type: array
maxItems: 100
items:
$ref: '#/components/schemas/BookOrder'
deliveryAddress:
type: string
minLength: 10
maxLength: 500
type: object
required:
- books
- deliveryAddress
additionalProperties: false
OrderDetails:
properties:
books:
type: array
description: The books that are part of the order
maxItems: 1000
items:
$ref: '#/components/schemas/BookOrder'
deliveryAddress:
type: string
description: The address to deliver the order to
maxLength: 1000
id:
type: string
description: The order identifier
format: uuid
maxLength: 36
example: 87da4501-4b52-4ea2-a2be-7dda8650f7eb
createdAt:
type: string
description: When the order was created
format: date-time
pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[012][0-9]:[0-5][0-9]:[0-5][0-9]Z$'
maxLength: 250
updatedAt:
type: string
description: When the order was updated
format: date-time
pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[012][0-9]:[0-5][0-9]:[0-5][0-9]Z$'
maxLength: 250
status:
$ref: '#/components/schemas/OrderStatusEnum'
type: object
required:
- books
- deliveryAddress
- id
- createdAt
- updatedAt
OrderStatusEnum:
type: string
enum:
- placed
- paid
- delivered
Book:
description: The schema object for a Book
type: object
additionalProperties: false
properties:
id:
description: the identifier for a book
type: string
format: uuid
maxLength: 36
example: 87da4501-4b52-4ea2-a2be-7dda8650f7eb
title:
type: string
description: The book title
maxLength: 1000
example: "Designing APIs with Swagger and OpenAPI"
authors:
type: array
description: A list of book authors
maxItems: 1000
items:
type: string
description: A string containing an author's name
maxLength: 250
minItems: 1
maxItems: 1000
example: "[Joshua S. Ponelat, Lukas L. Rosenstock]"
published:
type: string
format: date
pattern: "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
maxLength: 250
example: "2022-05-01"
responses:
books:
description: List of books
content:
application/json:
schema:
type: array
maxItems: 1000
items:
$ref: '#/components/schemas/Book'
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: api_key
security:
- ApiKeyAuth: []
这个简单的 OpenAPI 描述为我提供了以下交互式 API 文档,这可能被认为是健壮的,因为它涵盖了多种错误响应。然而,它缺少关键元素来帮助揭示 API 可能发生的潜在错误。

我如何捕获不一致的错误?
为了确保我们不会忘记在 API 中应用 HTTP 问题详情标准,我建议将以下 Spectral 规则添加到您的治理风格指南中。如果您的错误定义不包含任何内容,或者使用意料之外的格式来提供错误详情,这两条规则将提供反馈。
# Author: Frank Kilcommins (https://github.com/frankkilcommins)
no-errors-without-content:
message: Error responses MUST describe the error
description: Error responses should describe the error that occurred. This is useful for the API consumer to understand what went wrong and how to fix it. Please provide a description of the error in the response.
given: $.paths[*]..responses[?(@property.match(/^(4|5)/))]
then:
field: content
function: truthy
formats: [oas3]
severity: warn
# Author: Phil Sturgeon (https://github.com/philsturgeon)
no-unknown-error-format:
message: Error response should use a standard error format.
description: Error responses can be unique snowflakes, different to every API, but standards exist to make them consistent, which reduces surprises and increase interoperability. Please use either RFC 7807 (https://tools.ietf.org/html/rfc7807) or the JSON:API Error format (https://jsonapi.fullstack.org.cn/format/#error-objects).
given: $.paths[*]..responses[?(@property.match(/^(4|5)/))].content.*~
then:
function: enumeration
functionOptions:
values:
- application/vnd.api+json
- application/problem+json
- application/problem+xml
formats: [oas3]
severity: warn
有了这些规则,我得到了关于我的图书商店 API 0.0.1
版本的以下“观点化”反馈
63:15 warning no-errors-without-content Error responses MUST describe the error paths./books.get.responses[400]
65:15 warning no-errors-without-content Error responses MUST describe the error paths./books.get.responses[401]
70:30 warning no-unknown-error-format Error response should use a standard error format. paths./books.get.responses[500].content.application/json
105:15 warning no-errors-without-content Error responses MUST describe the error paths./orders.post.responses[401]
107:15 warning no-errors-without-content Error responses MUST describe the error paths./orders.post.responses[422]
109:15 warning no-errors-without-content Error responses MUST describe the error paths./orders.post.responses[500]
我如何改进错误响应?
既然我已经掌握了初始设计中的不足之处,我如何改进错误响应呢?由于 SwaggerHub 问题详情域已公开,我可以利用它轻松改进图书商店 API 中的错误响应。
在许多场景中,可以直接利用直接响应和示例,因为它们通用地表示了结构。这就是我对 POST /orders
资源所做的事情
/orders:
post:
summary: Place book order
description: |
This API method allows placing an order for one or more books
operationId: createOrder
tags:
- Bookstore
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/OrderDetails'
'400':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/BadRequest'
'401':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized'
'422':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ValidationError'
'500':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError'
'503':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable'
这使得错误表示更加丰富和明确

在其他情况下,您可能希望定制错误响应,因为只有某些示例可能适用(或者您可能想创建自己的示例)。在利用暴露的模式的同时,这仍然是可行的,这也是我针对 GET /books
路径所需要的,因为与请求体相关的示例不适用。下面,我设置了响应的内容和编码,同时仍然利用了可重用域暴露的模式。我还明确引用了适用于该路径的示例。
responses:
'200':
$ref: '#/components/responses/books'
'400':
description: |
The request was malformed or could not be processed.
Examples of `Bad Request` problem detail responses:
- [Missing request parameter](https://problems-registry.smartbear.com/missing-request-parameter/)
- [Invalid request parameter format](https://problems-registry.smartbear.com/invalid-request-parameter-format/)
- [Invalid request parameter value](https://problems-registry.smartbear.com/invalid-request-parameter-value/)
content:
application/problem+json:
schema:
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/schemas/ProblemDetails'
examples:
missingRequestParameterWithErrors:
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/examples/missing-request-parameter-with-errors'
invalidRequestParameterFormatWithErrors:
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/examples/invalid-request-parameter-format-with-errors'
invalidRequestParameterValueWithErrors:
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/examples/invalid-request-parameter-value-with-errors'
'401':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/Unauthorized'
'500':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServerError'
'503':
$ref: 'https://api.swaggerhub.com/domains/smartbear-public/ProblemDetails/1.0.0#/components/responses/ServiceUnavailable'
改进后的图书商店 API 可在 SwaggerHub [7] 中直接查看。
实际应用示例
令人鼓舞的是,许多 API 提供商、工具供应商和编程框架都在采用该标准。
以下是一些已采用该标准的 SmartBear API
结论
有了所提供的资源和示例,您已做好充分准备,可以在您的 API 中开始实施问题详情。请毫不犹豫地利用公共域和注册中心来加速您的进程,减少初始开销,并确保您的团队不必重复造轮子。更重要的是,这些工具有助于保持整个 API 生态系统的一致性,从而改善最终用户和开发者的错误处理体验。