关于 openapi-contract
用于检查前后端 API 调用是否符合 OpenAPI 合约的参考技能,帮助开发者发现端点、类型、请求和响应不一致,并介绍契约优先开发与自动化测试。
主要功能
- 01端点与方法检查
- 02请求和响应模式验证
- 03常见合约差异诊断
- 04契约优先开发指南
- 05自动化测试与 CI 示例
适用场景
- 01查找与 API 规范不符的前端路径
- 02检查请求字段和响应类型
- 03在 CI 流程中添加合约测试
MCP 相关
该技能介绍使用 api-explorer MCP 工具搜索 API 路径并获取特定端点详情。它建议进行针对性端点查询,而不是加载完整模式。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill openapi-contract安装后在 Claude Code 中输入/openapi-contract确认已加载。
详细介绍
OpenAPI 契约验证 - 快速参考
不适用此技能的情况
- GraphQL API - 使用
graphql-contract技能 - 身份验证流程 - 使用
auth-flow-validation技能 - API 版本管理策略 - 使用
api-versioning技能 - 类型生成设置 - 使用
type-generation技能
深入了解:使用
mcp__api-explorer__get_api_endpoint_details验证特定端点。
契约验证概述
┌─────────────────────────────────────────────────────────────┐
│ CONTRACT VALIDATION │
├─────────────────────────────────────────────────────────────┤
│ │
│ Frontend Code OpenAPI Spec Backend │
│ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │ fetch('/api/ │ ←──→ │ paths: │ ←──→│ @Controller │ │
│ │ users') │ │ /users: │ │ @GetMapping │ │
│ │ │ │ get: │ │ │ │
│ │ type User { │ ←──→ │ schemas: │ ←──→│ class UserDto│ │
│ │ id: number │ │ User: │ │ Long id │ │
│ │ } │ │ id:int │ │ │ │
│ └──────────────┘ └────────────┘ └──────────────┘ │
│ │
│ MUST MATCH SOURCE OF MUST MATCH │
│ TRUTH │
└─────────────────────────────────────────────────────────────┘
常见差异类型
1. 路径不匹配
# OpenAPI Spec
paths:
/users/{userId}: # Backend path
get: ...
# Frontend Code
fetch('/api/users/${id}') # Frontend uses different path!
检测:
# Find API calls in frontend
grep -r "fetch\|axios\|http\." src/
# Compare with OpenAPI paths
# Use api-explorer to list paths
修复: 更新前端以使用正确的路径,或配置基础 URL。
2. 类型不匹配
# OpenAPI Spec
components:
schemas:
User:
properties:
age:
type: string # Backend uses string
# Frontend Code
interface User {
age: number; # Frontend expects number!
}
检测:
// Frontend type
interface CreateUserDto {
age: number; // MISMATCH
}
// Should be
interface CreateUserDto {
age: string; // Match OpenAPI spec
}
3. 缺少必填字段
# OpenAPI Spec
components:
schemas:
CreateUserRequest:
required:
- email
- name
- role # Required!
properties:
email: { type: string }
name: { type: string }
role: { type: string }
# Frontend Code
const payload = {
email: user.email,
name: user.name,
// role is missing! <-- Will fail validation
};
4. 响应结构不匹配
# OpenAPI Spec - Paginated response
paths:
/users:
get:
responses:
200:
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
meta:
$ref: '#/components/schemas/PaginationMeta'
# Frontend Code - Expects array directly
const users: User[] = await response.json(); // WRONG!
// Should be
const { data, meta } = await response.json();
const users: User[] = data;
验证清单
按端点验证
| 检查项 | 前端 | 后端(OpenAPI) | 状态 |
|---|---|---|---|
| 路径 | /api/users | /users | MISMATCH |
| 方法 | POST | POST | OK |
| Content-Type | application/json | application/json | OK |
| 请求正文 | 与架构匹配 | CreateUserRequest | CHECK |
| 响应类型 | 与架构匹配 | User | CHECK |
| 状态码 | 处理 400、401 | 200、400、401、500 | PARTIAL |
请求正文验证
// OpenAPI Schema
interface CreateUserRequest {
email: string; // required
name: string; // required
age?: number; // optional
role: UserRole; // required, enum
}
// Validate frontend matches
interface FrontendPayload {
email: string; // OK - required
name: string; // OK - required
age?: number; // OK - optional
role: 'admin' | 'user'; // CHECK - enum values match?
}
响应处理验证
// OpenAPI Response
interface ApiResponse<T> {
data: T;
meta?: {
page: number;
total: number;
};
error?: {
code: string;
message: string;
};
}
// Frontend should handle all cases
async function fetchUsers(): Promise<User[]> {
const response = await fetch('/api/users');
if (!response.ok) {
const error = await response.json();
throw new ApiError(error.error.code, error.error.message);
}
const result: ApiResponse<User[]> = await response.json();
return result.data;
}
契约优先开发
1. 先定义 OpenAPI 规范
# openapi.yaml
openapi: 3.0.3
info:
title: User API
version: 1.0.0
paths:
/users:
post:
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
CreateUserRequest:
type: object
required: [email, name]
properties:
email:
type: string
format: email
name:
type: string
minLength: 2
maxLength: 100
User:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
name:
type: string
createdAt:
type: string
format: date-time
2. 为两端生成类型
# Frontend (TypeScript)
npx openapi-typescript openapi.yaml -o src/api/types.ts
# Backend (Java/Spring)
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g spring \
-o generated/
3. 根据生成的类型进行实现
// Frontend - uses generated types
import type { CreateUserRequest, User } from './api/types';
async function createUser(data: CreateUserRequest): Promise<User> {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
return response.json();
}
// Backend - uses generated DTOs
@PostMapping("/users")
public ResponseEntity<User> createUser(
@Valid @RequestBody CreateUserRequest request) {
// Implementation uses generated types
}
MCP api-explorer 用法
高效验证查询
// 1. Search for endpoint
await mcp__api_explorer__search_api({
query: "users",
searchIn: ["paths"],
limit: 10
});
// 2. Get specific endpoint details
await mcp__api_explorer__get_api_endpoint_details({
path: "/users",
method: "POST"
});
// 3. Get request/response schemas
await mcp__api_explorer__get_api_models({
model: "CreateUserRequest",
compact: true
});
避免浪费 Token
// DON'T - Loads entire spec
await mcp__api_explorer__get_api_schema({ format: "full" });
// DO - Query specific endpoints
await mcp__api_explorer__get_api_endpoint_details({
path: "/users/{id}",
method: "GET"
});
自动化契约测试
Prism(模拟服务)
# Start mock server from OpenAPI spec
npx @stoplight/prism-cli mock openapi.yaml
# Run frontend tests against mock
npm test -- --api-url=http://localhost:4010
Schemathesis(API 模糊测试)
# Test backend against OpenAPI spec
schemathesis run openapi.yaml --base-url=http://localhost:8080
# Validate all endpoints
schemathesis run openapi.yaml \
--checks all \
--validate-schema
Dredd(契约测试)
# Test implementation matches spec
dredd openapi.yaml http://localhost:8080
CI/CD 集成
GitHub Actions
name: Contract Validation
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate OpenAPI spec
run: npx @redocly/cli lint openapi.yaml
- name: Generate types
run: npx openapi-typescript openapi.yaml -o src/api/types.ts
- name: Check types unchanged
run: git diff --exit-code src/api/types.ts
- name: Run contract tests
run: npm run test:contract
反模式
| 反模式 | 不良影响 | 正确做法 |
|---|---|---|
| 手动同步类型 | 随时间产生偏差 | 从规范生成 |
| 忽略 OpenAPI 规范 | 缺少唯一事实来源 | 契约优先开发 |
| 硬编码 URL | 环境问题 | 配置基础 URL |
| CI 中没有验证 | 问题发现得太晚 | 自动化契约测试 |
| API 使用 Any 类型 | 缺少类型安全 | 生成正确的类型 |
快速故障排除
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 类型不同步 | 手动更新 | 从规范重新生成 |
| 404 错误 | 路径不匹配 | 检查 OpenAPI 路径 |
| 400 Bad Request | 缺少必填字段 | 根据架构验证 |
| 响应异常 | 响应结构已更改 | 更新前端类型 |
| CORS 错误 | 后端配置 | 检查允许的来源 |
相关技能
评论
支持 Markdown