API 错误分类:从清理需求到契约规格

这个案例展示如何把“优化 API 错误”这种模糊需求,改写成 SDK、AI 生成客户端和评审者都能依赖的稳定契约。

风险客户端兼容与重试
边界只改 envelope,不改 endpoint 行为
证据Fixtures、SDK 示例、OpenAPI examples

写 spec 之前的需求

弱工单

优化 API 错误。
让客户端更容易处理。

Spec-First 改写

Feature: Stable API error taxonomy
Owner: API Platform
Status: Draft for review

Goal:
- 统一 error code、category、message、trace_id 和 retryable。
- 保持现有 HTTP status 行为。
- 让生成客户端不用解析字符串就能分支处理。

Non-goals:
- 不改变 endpoint 业务逻辑。
- 第一版不移除 legacy 字段。
- 不新增认证策略。

阻止隐性破坏的契约

error-envelope.md

{
  "error": {
    "code": "ORDER_ALREADY_EXISTS",
    "category": "conflict",
    "message": "An order already exists for this idempotency key.",
    "trace_id": "req_01H...",
    "retryable": false,
    "details": {}
  }
}

acceptance-criteria.md

- Given 参数校验失败
  When API 返回 422
  Then error.category 是 validation,且 details.field_errors 存在。

- Given 重复 idempotency key
  When API 返回 409
  Then error.retryable 是 false,code 保持稳定。

compatibility.md

- 保留 legacy top-level message 一个版本。
- 新 envelope 字段只做 additive response change。
- SDK fixture 发布后再更新文档示例。
- 迁移期间记录 unknown category 日志。

test-evidence.md

自动化:
- 400/401/409/422 契约 fixtures
- SDK parser 兼容测试
- OpenAPI example snapshot

手工:
- client support 已评审发布说明
- 日志包含 category 和 trace_id

为什么它应该进入规格包

区分形状和行为

spec 把变更限制在 error envelope,避免实现漂移到 endpoint 语义。

保护生成客户端

稳定 category 和 retryable 字段给 AI 生成 SDK 一个机器可读的分支点。

留下迁移证据

fixtures、示例和日志让兼容性在发布前可见,而不是等工单来了再确认。

改 API 响应前,先套用这个模式

先生成规格包,再配合 API 契约检查清单,确保每个响应示例都有证据。

编辑说明

这个案例聚焦 API 契约安全:错误格式被视为公开行为,而不是内部清理。