4. OpenAPI 中定义错误响应:在 paths 中定义 4xx/5xx 响应,使用 $ref 引用 components/schemas

在 OpenAPI 规范中,仅定义成功的 2xx 响应是不够的。一个健壮的 API 必须明确声明所有可能的错误场景,包括客户端错误(4xx)和服务器错误(5xx)。本章将讲解如何在 paths 中为每个操作定义标准化的错误响应,并通过 $ref 引用 components/schemas 中的复用模型,实现错误码的断言与通信机制验证。

4.1 错误响应的标准结构

每个错误响应在 OpenAPI 中都是一个完整的 response 对象,包含 description 和 content。为了确保所有错误响应格式统一,我们将其定义在 components/responses 中,然后在 paths 中通过 $ref 引用。

响应码 语义 典型场景
400 Bad Request 请求参数校验失败 缺少必填字段、字段类型错误、格式不合法
401 Unauthorized 身份认证失败 Token 过期、无效凭证、未携带认证头
403 Forbidden 权限不足 用户无操作权限、资源访问受限
404 Not Found 资源不存在 请求路径错误、资源 ID 无效
409 Conflict 资源冲突 唯一键重复、版本冲突、状态不允许操作
422 Unprocessable Entity 业务逻辑校验失败 业务规则违反、依赖条件不满足
429 Too Many Requests 请求频率超限 API 限流触发
500 Internal Server Error 服务器内部错误 未捕获异常、数据库连接失败
502 Bad Gateway 上游服务不可用 网关代理超时、后端服务宕机
503 Service Unavailable 服务暂时不可用 系统维护、过载保护

4.2 定义可复用的错误响应 Schema

首先在 components/schemas 中定义统一的错误响应体结构。所有错误响应共享相同的 JSON 格式,包含 code、message、request_id 和可选的 details 字段。

components:
  schemas:
    ApiError:
      type: object
      required:
        - code
        - message
        - request_id
      properties:
        code:
          type: string
          description: "业务错误码,如 'USER_NOT_FOUND'"
          example: "INVALID_PARAMETER"
        message:
          type: string
          description: "人类可读的错误描述"
          example: "请求参数 'email' 格式不正确"
        request_id:
          type: string
          format: uuid
          description: "请求唯一标识,用于日志追踪"
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        details:
          type: array
          description: "字段级错误详情(可选)"
          items:
            type: object
            properties:
              field:
                type: string
                example: "email"
              issue:
                type: string
                example: "must be a valid email address"
    ValidationError:
      allOf:
        - $ref: '#/components/schemas/ApiError'
        - type: object
          required:
            - details
          properties:
            code:
              example: "VALIDATION_FAILED"
            message:
              example: "请求参数校验失败,请检查输入"

4.3 在 components/responses 中定义命名响应

将常用的错误响应封装为命名响应,方便在多个路径中复用。每个响应引用上面定义的 Schema,并设置对应的 description。

components:
  responses:
    BadRequest:
      description: "请求参数错误"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
    Unauthorized:
      description: "身份认证失败"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    NotFound:
      description: "请求的资源不存在"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    Conflict:
      description: "资源冲突"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    InternalServerError:
      description: "服务器内部错误"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'

4.4 在 paths 中引用错误响应

在具体的 API 路径操作中,通过 $ref 引用上述命名响应。每个操作除了定义成功的 2xx 响应外,必须列出所有可能返回的错误状态码及其对应的响应引用。

paths:
  /users/{userId}:
    get:
      summary: "获取用户信息"
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: "成功返回用户信息"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'
    put:
      summary: "更新用户信息"
      responses:
        '200':
          description: "更新成功"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '500':
          $ref: '#/components/responses/InternalServerError'
关键实践: 每个操作必须显式声明所有可能返回的 4xx/5xx 状态码。遗漏错误响应定义会导致生成的客户端 SDK 无法正确处理异常情况,破坏通信机制的健壮性。

4.5 错误响应引用关系图

下图展示了从 paths 到 components/responses 再到 components/schemas 的完整引用链路,以及错误响应在 API 通信中的位置。

错误响应 $ref 引用链路 paths 层 /users/{userId} get: responses: '200': ... '400': $ref → '404': $ref → $ref components/responses BadRequest: description: "请求参数错误" content: schema: $ref → NotFound: schema: $ref → InternalServerError: schema: $ref → $ref components/schemas ApiError - code: string - message: string - request_id: uuid - details: array ValidationError (extends ApiError) 错误响应通信机制验证 客户端根据 OpenAPI 文档断言错误码 → 服务端返回标准化错误体 → 测试框架自动校验 code/message/request_id 错误码断言测试流程 ① 发送请求 ② 接收响应 ③ 提取 code/message ④ 断言匹配文档 ⑤ 通过/失败

4.6 错误响应定义的最佳实践

  • 统一错误体结构:所有错误响应使用相同的 JSON 结构,包含 code、message、request_id,便于客户端统一解析。
  • 业务错误码 vs HTTP 状态码:HTTP 状态码表示大类错误(4xx/5xx),业务错误码 code 字段提供更细粒度的错误标识,如 USER_NOT_FOUND、INSUFFICIENT_BALANCE。
  • 必选字段声明:在 Schema 中使用 required 强制要求 code、message、request_id 必须返回,确保断言可依赖。
  • 避免重复定义:所有错误响应在 components/responses 中定义一次,多个路径通过 $ref 引用,减少维护成本。
  • 覆盖所有错误路径:每个操作必须声明所有可能返回的 4xx/5xx 状态码,遗漏定义会导致文档不完整,测试无法覆盖异常场景。
提示: 在测试框架中,可以编写一个通用的错误响应断言函数,接收 code 和 message 预期值,自动校验响应状态码、JSON 结构和字段内容。结合 OpenAPI 文档,实现自动化错误码断言通信机制验证。

4.7 错误响应定义示例:完整路径

以下是一个完整的路径定义示例,展示了如何为 POST /orders 操作声明所有可能的错误响应。

paths:
  /orders:
    post:
      summary: "创建订单"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateOrderRequest'
      responses:
        '201':
          description: "订单创建成功"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Order'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          description: "业务校验失败"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '429':
          description: "请求频率超限"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '500':
          $ref: '#/components/responses/InternalServerError'
        '503':
          description: "服务暂时不可用"
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
注意: 不要将错误响应定义在 paths 内部重复编写。始终使用 $ref 引用 components/responses 中的命名响应。这不仅是规范要求,更是保证文档一致性和可维护性的关键。