openapi-contract について
フロントエンドとバックエンドの API 利用が OpenAPI 契約に準拠しているか確認するリファレンススキルです。エンドポイント、型、リクエスト、レスポンスの不一致を見つけ、契約優先の開発と自動テストを案内します。
主な機能
- 01エンドポイントとメソッドの確認
- 02リクエストとレスポンスのスキーマ検証
- 03一般的な契約不一致の診断
- 04契約優先開発のガイド
- 05自動テストと CI の例
活用シーン
- 01API 仕様と異なるフロントエンドのパスの発見
- 02リクエストフィールドとレスポンス型の確認
- 03CI パイプラインへの契約テストの追加
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 | 不一致 |
| メソッド | POST | POST | OK |
| Content-Type | application/json | application/json | OK |
| リクエストボディ | スキーマに一致 | CreateUserRequest | 確認 |
| レスポンス型 | スキーマに一致 | User | 確認 |
| ステータスコード | 400、401 を処理 | 200、400、401、500 | 一部対応 |
リクエストボディの検証
// 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
});
トークンの無駄を避ける
// 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 対応