API 契约草稿
本页提供 OpenAPI 3.0 完整路由定义示例、RFC 7807 Problem Details 错误格式、API 版本控制策略对比、破坏性变更判断标准,以及 Pact 框架契约测试代码,帮助前后端用可机器校验的契约对齐。
技能可约定:鉴权方式(Bearer、Cookie)、幂等与重试、常见 4xx/5xx 语义,以及版本前缀与弃用标注。
输出尽量保持「可机器校验」:字段类型、必填、示例与 enum 范围写清,减少联调返工。
对内对外 API 建议分层:公开接口强调 semver 与弃用周期;内部接口可收紧错误体结构,便于网关与客户端统一解析。分页、排序与过滤参数应在一处定义,避免各端自行约定。
- 错误体:统一 code / message / details(可选),并给出至少一个 4xx 与一个 5xx 示例。
- 安全:敏感字段不出现在示例中;文件上传单独说明大小与类型限制。
- 与实现同步:契约变更后提示同步集成测试与 Mock。
OpenAPI 3.0 完整路由定义
包含 requestBody、responses、security 与 $ref 复用的完整示例:
# OpenAPI 3.0 完整路由定义示例(含 requestBody/responses/security)
openapi: 3.0.3
info:
title: Items API
version: 1.0.0
servers:
- url: https://api.example.com/v1
paths:
/items:
post:
operationId: createItem
summary: 创建条目
tags: [items]
security:
- bearerAuth: [] # 必须认证
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateItemRequest'
example:
title: "示例条目"
description: "描述文本"
responses:
'201':
description: 创建成功
content:
application/json:
schema:
$ref: '#/components/schemas/Item'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
CreateItemRequest:
type: object
required: [title]
properties:
title: { type: string, minLength: 1, maxLength: 200 }
description: { type: string, nullable: true }
responses:
ValidationError:
description: 请求参数验证失败
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
RFC 7807 错误格式与安全
在 components.responses 中集中定义 Problem Details(RFC 7807);每个 path 的 responses 用 $ref 引用,避免同一形状在多处手写分叉。
// RFC 7807 Problem Details 标准错误格式
// Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/item-not-found",
"title": "Item Not Found",
"status": 404,
"detail": "The item with ID 'abc123' does not exist",
"instance": "/requests/req-xyz-789"
}
// 验证失败示例(400)— 可扩展字段
{
"type": "https://api.example.com/problems/validation-error",
"title": "Validation Error",
"status": 400,
"detail": "Request body contains invalid fields",
"errors": [
{ "field": "title", "message": "Title is required" },
{ "field": "price", "message": "Must be a positive number" }
]
}
// 服务器错误示例(500)— 不泄露内部细节
{
"type": "https://api.example.com/problems/internal-error",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred. TraceId: req-abc-123"
// ❌ 不要暴露:stack trace、SQL 语句、内部服务名
}
版本控制策略与破坏性变更
URL 前缀(如 /v1/)、Header 协商或子域名策略选一种写进 SKILL;破坏性变更需说明并存期、迁移指南与 deprecated 字段。
// API 版本控制三种策略对比
// 策略1: URL 路径版本(最直观,推荐对外 API)
GET /v1/users/123 // v1 版本
GET /v2/users/123 // v2 版本(可并存)
// 策略2: 请求头版本(URL 更简洁,但缓存复杂)
GET /users/123
Accept-Version: 2.0 // 或: API-Version: 2
// 策略3: 查询参数版本(不推荐,缓存问题更多)
GET /users/123?version=2
// 破坏性变更 vs 非破坏性变更判断标准:
// ✅ 非破坏性(向后兼容,无需版本升级):
// - 新增可选字段(旧客户端忽略)
// - 新增 API 端点
// - 放宽字段验证规则
// ❌ 破坏性变更(必须升级主版本):
// - 删除或重命名字段
// - 改变字段类型(string → number)
// - 改变 enum 值语义
// - 改变鉴权要求
// - 改变错误码/HTTP 状态码
// Sunset Header:通知客户端旧版本弃用
HTTP/1.1 200 OK
Sunset: Sat, 31 Dec 2025 23:59:59 GMT
Deprecation: true
Link: <https://api.example.com/v2/docs>; rel="successor-version"
Pact 框架契约测试(Consumer-Provider):
// Pact 契约测试:Consumer 端定义期望
import { Pact } from '@pact-foundation/pact';
const provider = new Pact({
consumer: 'frontend-app',
provider: 'items-api',
port: 1234,
});
describe('Items API Contract', () => {
it('should create item', async () => {
await provider.addInteraction({
state: 'user is authenticated',
uponReceiving: 'a request to create item',
withRequest: {
method: 'POST', path: '/v1/items',
headers: { Authorization: 'Bearer token', 'Content-Type': 'application/json' },
body: { title: 'Test Item' },
},
willRespondWith: {
status: 201,
body: { id: like('abc123'), title: 'Test Item', createdAt: like('2025-01-01') },
},
});
// Consumer 测试通过后生成 pact 文件,Provider 验证该契约
const result = await createItem({ title: 'Test Item' });
expect(result.id).toBeDefined();
});
});
- 对外:semver + changelog 锚点;对内:可仅要求路由前缀与网关 strip 规则一致。
- 弃用:响应头
Sunset/Deprecation与文档日期对齐(若采用)。
起草主流程(skill-flow-block)
从产品语言到可合并的 OpenAPI 草案,建议按下列顺序展开;Agent 可在每步产出检查清单。
[ 输入:业务名词、用例、鉴权假设 ]
|
v
[ 资源与路径:复数、嵌套深度、分页/过滤参数表 ]
|
v
[ schema:必填、可空、示例、错误 components ]
|
v
[ 版本与弃用:前缀或 Header、deprecated 与迁移说明 ]
|
v
[ 校验:spectral / CI diff,输出待修复项列表 ]
路径前缀预览
根据主版本号生成常见 URL 前缀占位(协作参考,非规范强制)。
预览
SKILL 片段
---
name: api-contract-draft
description: 从产品描述生成 OpenAPI 3.0 草案与错误格式约定
---
# 输入分析
1. 识别资源名词与动作,建立 REST 路径(复数集合名,2层嵌套为限)
2. 确认鉴权方式:Bearer JWT / API Key / OAuth2 scope
3. 确认分页策略:cursor-based(大数据集)或 offset(小列表)
# Schema 与 components
4. requestBody schema:required 字段、类型、minLength/maxLength/enum 约束
5. 成功响应 schema:必填字段、可空字段(nullable: true),附 example
6. 错误响应:components.responses 集中定义,路径用 $ref 引用
7. 错误格式:优先 RFC 7807 application/problem+json
# 版本与破坏性变更
8. 版本策略:URL 路径前缀(/v1/)或 Accept-Version 请求头,择一并文档化
9. 破坏性变更:删除字段、改类型、改 enum、改鉴权——必须 bump 主版本
10. 非破坏性:新增可选字段、新增端点——可在现有版本追加
11. 弃用期:Sunset / Deprecation 响应头 + 迁移文档链接
# 契约测试
12. Consumer 端用 Pact 定义期望,生成 pact 文件
13. Provider 端验证 pact 文件,CI 中阻断破坏性变更
14. 契约变更后同步更新集成测试与 API Mock
15. Spectral 规则集校验 OpenAPI 规范,告警逐条解决不静默忽略