Web開発をする際にフロントエンドとバックエンドでやりとりするAPIのスキーマを合わせるのは大変ではありませんか?
本記事では、OpenAPI を“共通言語”として使い、仕様のズレや認識違いを最小化するために、最小構成から始めて、パラメータ、バリデーション、エラー、セキュリティ、そして WebSocket のハンドシェイクまで、現場でよく使う書き方を短い YAML サンプルを使って、 Swagger Editor に貼って挙動を確認し使い方を学んでいきましょう!
他のチートシート
git/gh コマンド(gitコマンド以外にもgitの概念も書いてあります)
lazygit
Docker コマンド(dockerコマンド以外にもdockerの概念の記事へのリンクもあります)
ステータスコード
TypeScript
Go/Gorm
testing/gomock
C#/.NET/Unity
Ruby・Ruby on Rails
SQL
NoSQL
Vim
プルリクエスト・マークダウン記法チートシート
ShellScript(UNIX/Linuxコマンド)チートシート
ファイル操作コマンドチートシート
VSCode Github Copilot拡張機能
OpenAI Assistants API
GitHub API
変数・関数(メソッド)・クラス命名規則
他のシリーズ記事
チートシート
様々な言語,フレームワーク,ライブラリなど開発技術の使用方法,基本事項,応用事例を網羅し,手引書として記載したシリーズ
git/gh,lazygit,docker,vim,typescript,プルリクエスト/マークダウン,ステータスコード,ファイル操作,OpenAI AssistantsAPI,Ruby/Ruby on Rails のチートシートがあります.以下の記事に遷移した後,各種チートシートのリンクがあります.
TypeScriptで学ぶプログラミングの世界
プログラミング言語を根本的に理解するシリーズ
情報処理技術者試験合格への道 [IP・SG・FE・AP]
情報処理技術者試験に出題されるコンピュータサイエンス用語の紹介や単語集
IAM AWS User クラウドサービスをフル活用しよう!
AWSのサービスを例にしてバックエンドとインフラ開発の手法を説明するシリーズです.
AWS UserのGCP浮気日記
GCPの様子をAWSと比較して考えてみるシリーズ
Project Gopher: Unlocking Go’s Secrets
Go言語や標準ライブラリの深掘り調査レポート
Swagger と OpenAPI とは?
- OpenAPI: HTTP API の仕様を機械可読な形で記述するためのオープン標準。YAML/JSON で、エンドポイント、パラメータ、リクエスト/レスポンス、エラー、セキュリティなどを一貫して表現できます。
- Swagger: もともとの仕様名や、その周辺ツール群(エディタ、UI、コード生成器など)のブランド名。現在の仕様は「OpenAPI」と呼ばれ、Swagger は主にツール名として使われます。
Swagger Editor(ブラウザで試す)
Swagger Editorにブラウザでアクセスして OpenAPI を編集・プレビューできる公式ツールです。ドラフトの検証にも便利です。
- 使い方:
- 画面左のエディタにOpenAPI YAMLを貼り付ける
- 右側にドキュメント(Swagger UI 風の表示)が生成される
- 保存はローカルにコピーするか、Export を使用
- 注意: Swagger Editor は設計・確認用に使いましょう。生成や実装は各プロジェクトのビルド手順に従ってください。
Swaggerに構成要素の説明
キー | 用途 | 代表的な下位要素 | メモ |
---|---|---|---|
openapi |
OpenAPI 仕様のバージョン | 例: 3.0.3 , 3.1.0
|
ドキュメント全体の規格を示す |
info |
API のメタ情報 |
title , version , description , contact
|
ドキュメントの表紙にあたる |
servers[] |
ベース URL の一覧 |
url , description , variables
|
環境ごとに複数記述可 |
paths |
エンドポイント群 | /{path} |
ここに各 API の道筋を定義 |
paths./{path} |
パスごとの定義 |
get/post/put/... , parameters
|
メソッドと共通パラメータを保持 |
paths./{path}.method |
メソッドの詳細 |
summary , description , parameters , requestBody , responses , security , tags
|
1 つの操作単位(Operation) |
parameters[] |
値の受け取り(ボディ以外) |
in , name , schema , required
|
path/query/header/cookie で分類 |
requestBody |
リクエストボディ |
content → MIME → schema , examples
|
JSON などの本文 |
responses |
応答の定義 | ステータスコード → description , content
|
200/201/400/404 など |
components |
再利用部品の集約 |
schemas , responses , parameters , securitySchemes
|
名前で参照($ref )できる |
components.schemas |
型定義(JSON Schema) |
type , properties , required , バリデーション |
入出力の形を厳密化 |
components.responses |
共通レスポンス |
description , content
|
何度も使うエラーなどを格納 |
components.parameters |
共通パラメータ | 同上 | 例: X-Request-Id
|
components.securitySchemes |
認証方式 |
http (bearer), apiKey , oauth2
|
security で適用 |
security |
ドキュメント/パス単位の認可設定 | スキーム名: [] | 空配列指定で “認証不要” を表現 |
REST サンプル:
# OpenAPI のバージョンを指定
openapi: 3.0.3
# API のメタ情報
info:
title: Sample API # タイトル
version: 0.1.0 # API 仕様のバージョン(セマンティックバージョニング推奨)
# ベース URL(相対 URL にするとリバプロ配下でも使いやすい)
servers:
- url: / # 例: 本番は https://api.example.com 等に差し替え
# エンドポイント定義
paths:
/healthz: # ヘルスチェック用のパス(GET のみ)
get:
summary: Liveness probe # 一行説明(UI に表示される)
responses:
'200': # HTTP ステータスコード(文字列で書くのが無難)
description: OK # 応答の説明
content:
application/json: # メディアタイプ(MIME)
schema:
type: object # 応答ボディの形(JSON Schema 準拠)
properties:
status: { type: string, example: ok } # 任意の例(UI に表示)
/items: # リソース作成の例(POST)
post:
summary: Create item # 操作の概要
requestBody: # リクエストボディの定義(必須/メディアタイプ/スキーマ)
required: true
content:
application/json: # JSON で送ることを示す
schema:
$ref: '#/components/schemas/CreateItemRequest' # 再利用スキーマを参照($ref)
responses:
'201': { description: Created } # 作成成功(Location 等のヘッダを追加しても良い)
'400': { $ref: '#/components/responses/BadRequest' } # バリデーションエラー等(共通応答)
# 共有部品の定義
components:
schemas:
CreateItemRequest: # 入力の型定義(クライアントが送る JSON)
type: object # オブジェクト型
required: [name] # 必須フィールド
properties:
name: { type: string, example: Lemon Soda } # アイテム名(例付き)
Error: # エラー共通フォーマット(どの API でも再利用)
type: object
required: [code, message] # 最低限必要なキー
properties:
code: { type: string, example: INVALID_INPUT } # エラー種別(機械可読)
message: { type: string, example: name is required } # 人が読む説明
responses:
BadRequest: # 共通の 400 応答(どの Operation からも参照可能)
description: Bad Request # 説明
content:
application/json: # 応答のメディアタイプ
schema: { $ref: '#/components/schemas/Error' } # エラーフォーマットを適用
SwaggerEditorで上記のswaggerを確認すると以下のようになります
WebSocket ハンドシェイク サンプル:
厳密にはGETではないのかな
# OpenAPI のバージョン
openapi: 3.0.3
# メタ情報
info:
title: Sample WS API # タイトル
version: 0.1.0 # バージョン
# ベース URL
servers:
- url: / # 相対 URL 例
paths:
/ws:
get: # WebSocket は HTTP GET のハンドシェイクで開始
summary: WebSocket handshake # 要約
description: |
接続例: ws://localhost/ws?room=ROOM123 # クライアント → サーバ 接続例
送信例: { "value": 0.7 } # クライアント送信例
受信例: { "average": 0.733, "count": 3 } # サーバ応答例
parameters:
- in: query # クエリパラメータに含める
name: room # ルーム識別子
required: true # 必須
schema: { type: string } # 文字列型
responses:
'101': { description: Switching Protocols } # プロトコル切替応答(WS 確立)
SwaggerEditorで上記のswaggerを確認すると以下のようになります
チーム開発での使い方
OpenAPI は「コミュニケーションの共通言語」です。バックエンド・フロントエンド・QA が同じ仕様を見て話せるよう、次のようなフローをおすすめします。
- 仕様ドラフト
- Issue/PR で変更点の背景と要件を共有
- Swagger Editor(
https://editor.swagger.io
)やエディタ上で YAML を編集 - エンドポイント・型・バリデーション・エラーを最小でも記述
- レビュー(Contract First)
- PR で
api/swagger/*.yml
をレビュー - 用語・命名・境界(責務)を合わせる
- サンプル例(
examples
)とエラーの網羅性を確認
- 生成と整合性チェック
- 言語や環境に合ったコード生成ツール(例: OpenAPI Generator, Swagger Codegen, oapi-codegen(Go) など)で型やクライアント/サーバの雛形を生成
- CI で仕様の静的検証(例: Spectral)やコード生成の整合性確認を実施
- 実装と E2E
- バックエンドは仕様どおり実装し、OpenAPI に追随
- フロントは生成型/ドキュメントを基に Mock/結合テストを構築
- 変更管理
- 破壊的変更はメジャーバンプ or 互換レイヤ(新フィールド追加や冪等化)
- リリースノートに API 変更を明記
Tips:
- 共通の
components
を活用し、エラー・パラメータの再利用性を高める - 分割が必要になったらファイル分割 +
openapi
のツールで結合(将来検討)
プロジェクト構成例(どこに何を書く?)
API 仕様と実装を分かりやすく保つための、一般的な配置例です。
project/
openapi/ # OpenAPI の仕様ファイル(.yml/.json)
sample-api.yml # 例: REST のエンドポイント仕様
sample-ws.yml # 例: WebSocket(HTTP ハンドシェイクを説明)
server/ # サーバ実装
client/ # クライアント実装
基本の流れ:
- 仕様を書く:
openapi/*.yml
を編集 - 自動検証: CI で静的検証やスキーマ整合性チェック
- 必要ならコード生成: 各言語のツールで型/SDK/サーバ雛形を生成
最小構成
最小限の OpenAPI ドキュメント。タイトル・バージョン・サーバ・パスを定義します。最初は空の paths
から始めて、少しずつエンドポイントを足していくと迷いません。
openapi: 3.0.3
info:
title: Example API
version: 0.1.0
servers:
- url: / # リバプロ配下などで相対にする運用に親和的
paths: {}
シンプルな GET(ヘルスチェック)
一番シンプルな GET の例。サーバが生きているかを確認する 200 OK
応答を JSON で返します。
paths:
/api/healthz:
get:
summary: Liveness probe endpoint
tags: [Health]
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
status: { type: string, example: ok }
共通で使うスキーマを定義する
API 全体で使い回すエラーフォーマットや基本スキーマを components
にまとめると、各エンドポイントがスッキリします。
components:
schemas:
User: # ユーザの基本スキーマ(以下のメソッド例で参照)
type: object
required: [id, email, name]
properties:
id: { type: string, example: usr_8m2q9 }
email: { type: string, format: email, example: momo47@example.com }
name: { type: string, example: Momo Shiratama }
createdAt: { type: string, format: date-time, example: 2025-09-15T10:23:45Z }
Error: # エラーの共通形式
type: object
required: [code, message]
properties:
code: { type: string, example: INVALID_EMAIL }
message: { type: string, example: email format is invalid }
details:
type: array
items: { type: string }
example: ["email must contain @"]
responses:
BadRequest:
description: Bad Request
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
NotFound:
description: Not Found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
パスパラメータ・クエリパラメータ・ヘッダ
URL の一部を変数にする「パスパラメータ」、?key=value
形式の「クエリパラメータ」、X-Request-Id
のような「ヘッダ」の書き方です。
# Componentsは上記で定義したものを再利用します
paths:
/api/users/{userId}:
get:
summary: Get user by ID
tags: [User]
parameters:
- in: path
name: userId
required: true
schema: { type: string }
example: usr_7Fq2Kc9P
- in: query
name: include
description: 追加情報の種別(カンマ区切り)
schema: { type: string }
example: profile,roles
- in: header
name: X-Request-Id
schema: { type: string }
example: req-92b1b8f6
responses:
'200':
description: Found
content:
application/json:
schema:
$ref: '#/components/schemas/User'
すべての HTTP メソッドの例(GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS/TRACE)
基本的な使い分けと、OpenAPI での定義例をまとめます。
GET(取得)
変更を伴わない読み取り。キャッシュ対象になることが多いです。
paths:
/users/{id}:
get:
summary: Get user by id
parameters:
- in: path
name: id
required: true
schema: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'404':
description: Not Found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
POST(作成)
サーバ側で新規リソースを作る操作。
paths:
/users:
post:
summary: Create user
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
responses:
'201':
description: Created
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'400':
description: Bad Request
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
PUT(全体置換)
リソース全体を指定状態に置き換える操作。
paths:
/users/{id}:
put:
summary: Replace user
parameters:
- in: path
name: id
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
responses:
'200':
description: Replaced
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'400':
description: Bad Request
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
PATCH(部分更新)
一部フィールドのみ更新。
paths:
/users/{id}:
patch:
summary: Update user partially
parameters:
- in: path
name: id
required: true
schema: { type: string }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name: { type: string }
email: { type: string, format: email }
responses:
'200':
description: Updated
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'400':
description: Bad Request
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
DELETE(削除)
リソースの削除。
paths:
/users/{id}:
delete:
summary: Delete user
parameters:
- in: path
name: id
required: true
schema: { type: string }
responses:
'204': { description: No Content }
'404':
description: Not Found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
HEAD(ヘッダのみ)
GET と同じだがボディを返さない。存在確認やメタ情報取得に使います。
paths:
/users/{id}:
head:
summary: Head user
parameters:
- in: path
name: id
required: true
schema: { type: string }
responses:
'200':
description: Headers only
OPTIONS(サポートメソッドの確認)
CORS やクライアントの探索用。
paths:
/users:
options:
summary: Options for users
responses:
'204':
description: No Content
headers:
Allow:
schema: { type: string }
example: "GET,POST,OPTIONS"
TRACE(ループバックテスト)
デバッグ用途。多くの API では無効化しますが、仕様としての書き方例を示します。
paths:
/debug/trace:
trace:
summary: Trace request
responses:
'200': { description: Trace OK }
バリデーションの書き方(minimum/maximum/pattern/default/nullable)
入力チェックは仕様に書けます。実装やドキュメント、フロントの型にも反映できるので積極的に定義しましょう。
components:
schemas:
SearchRequest:
type: object
properties:
q: { type: string, minLength: 1, maxLength: 100, example: query }
limit: { type: integer, minimum: 1, maximum: 100, default: 20 }
cursor: { type: string, nullable: true, example: c_5q1a }
priceJPY: { type: number, multipleOf: 10, example: 620 }
sku: { type: string, pattern: "^[A-Z]{3}-[0-9]{4}$", example: ABC-1234 }
ページネーションの例(page/perPage と cursor 併記)
ページ番号方式とカーソル方式の両対応の例です。実案件に合わせてどちらか片方でも構いません。
paths:
/api/products:
get:
summary: List products
parameters:
- in: query
name: page
schema: { type: integer, minimum: 1, default: 1 }
- in: query
name: perPage
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
- in: query
name: cursor
schema: { type: string, nullable: true }
responses:
'200':
description: OK
headers:
Link:
description: RFC5988 Link(next/prev)
schema: { type: string }
example: '<https://api.example.com/api/products?cursor=c_Next>; rel="next"'
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: object
properties:
id: { type: string, example: prd_Y1z9 }
name: { type: string, example: Uji Matcha }
nextCursor: { type: string, nullable: true, example: c_Next }
total: { type: integer, example: 128 }
multipart/form-data とファイルアップロード
画像や CSV をアップロードする典型例です。OpenAPI では type: string
に format: binary
を付けます。
paths:
/api/uploads/images:
post:
summary: Upload image
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
altText:
type: string
example: Seasonal kakigori
responses:
'201': { description: Created }
共通パラメータの定義(components/parameters)
よく使うヘッダやクエリを共通化して再利用できます。
components:
parameters:
XRequestId:
in: header
name: X-Request-Id
schema: { type: string }
required: false
example: req-7b5f4f02
paths:
/api/orders:
get:
summary: List orders
parameters:
- $ref: '#/components/parameters/XRequestId'
responses:
'200': { description: OK }
レスポンスヘッダの例(RateLimit など)
API の利用制限をクライアントへ伝えるときに便利です。
responses:
'200':
description: OK
headers:
X-RateLimit-Remaining:
schema: { type: integer }
example: 147
X-RateLimit-Reset:
schema: { type: integer, description: unix epoch seconds }
example: 1726308120
WebSocket の記述
WebSocket 自体は OpenAPI で厳密に表現しづらいため、HTTP のハンドシェイク(GET /ws
)を仕様化し、やり取りする JSON は説明文で示すのが定番です。
- OpenAPI は WebSocket をネイティブ表現できません。通常は「ハンドシェイク用の HTTP エンドポイント(例:
GET /ws
)」を API として記載し、以降のメッセージ仕様は説明文や別仕様で補足します。 - 一般的な書き方の例:
paths:
/ws:
get:
summary: WebSocket endpoint for room-based audio value aggregation
description: |
WebSocket 接続先: `ws://<host>/ws?room=<ROOM_ID>`。
送信例: { "value": 0.7 }
受信例: { "average": 0.733, "count": 3 }
parameters:
- in: query
name: room
required: true
schema: { type: string }
responses:
'101': { description: Switching Protocols }
Go での生成(gen)とチェック(check)
oapi-codegen
を用いて OpenAPI から Go の型を生成することができる。また、Makefile の swagger-check
で「生成物が最新か」を自動チェックできます(CI での差分検知にも使えます)。
コマンド一覧(抜粋)
Makefileでコマンドを定義します.プロジェクトに合わせてパスなどの変更をしてください.
services/
以下にマイクロサービスがあると仮定してマイクロサービスごとの型を定義するためにswaggerを書くとします.
swagger-gen:
@echo "Generating OpenAPI types with oapi-codegen for all api/swagger/*.yml..."
@set -e; for f in $$(ls api/swagger/*.yml 2>/dev/null || true); do \
name=$$(basename $$f .yml); \
outdir=services/$$name/internal; \
outfile=$$outdir/$$name.gen.go; \
echo " - $$f -> $$outfile"; \
mkdir -p $$outdir; \
go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.0 -generate types -package openapi -o $$outfile $$f; \
done
swagger-check:
@echo "Checking OpenAPI generation is up-to-date for api/swagger/*.yml..."
@status=0; \
for f in $$(ls api/swagger/*.yml 2>/dev/null || true); do \
name=$$(basename $$f .yml); \
outdir=services/$$name/internal; \
target=$$outdir/$$name.gen.go; \
tmpfile=$$(mktemp); \
mkdir -p $$outdir; \
go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.0 -generate types -package openapi -o $$tmpfile $$f; \
if [ ! -f $$target ] || ! diff -q $$tmpfile $$target >/dev/null; then \
echo "OpenAPI generated code is outdated for $$f -> $$target. Run 'make swagger-gen' and commit changes." >&2; \
diff -u $$target $$tmpfile || true; \
status=1; \
fi; \
rm -f $$tmpfile; \
done; \
exit $$status
以下のコマンドをMakefileがあるディレクトリで実行する
# 生成(api/swagger/<name>.yml → services/<name>/internal/<name>.gen.go)
make swagger-gen
# 整合性チェック(未生成/差分があると失敗)
make swagger-check
上記のMakefileでは以下のような仕様で動きます
Makefileで定義したswagger-gen が行うこと
- 各
api/swagger/<name>.yml
に対してservices/<name>/internal/<name>.gen.go
を生成する
Makefileで定義したswagger-check が行うこと
- 各
api/swagger/<name>.yml
に対して一時ファイルへ生成 - 既存の
services/<name>/internal/<name>.gen.go
とdiff
- 差分があれば失敗し、メッセージで「make swagger-gen を実行してコミットしてください」と案内
- 生成には
oapi-codegen@v2.5.0
を使用(-generate types -package openapi
)
生成されるもの
-
対象:
api/swagger/*.yml
(例:api/swagger/gateway.yml
) -
出力:
services/<name>/internal/<name>.gen.go
- 例:
api/swagger/gateway.yml
→services/gateway/internal/gateway.gen.go
- 例:
-
オプション:
-generate types -package openapi
- サーバ骨格までは生成せず、まずは型定義のみを共有する運用(初めて使う方向けに型定義のみ出力する)
型定義のみ出力した結果
以下のswaggerファイルにおいて型定義のみ自動生成する
openapi: 3.0.3
info:
title: Example API
version: 0.1.0
servers:
- url: /
components:
schemas:
User:
type: object
required: [id, email, name]
properties:
id: { type: string, example: usr_8m2q9 }
email: { type: string, format: email, example: momo47@example.com }
name: { type: string, example: Momo Shiratama }
createdAt: { type: string, format: date-time, example: 2025-09-15T10:23:45Z }
responses:
NotFound:
description: Not Found
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
paths:
/users/{id}:
get:
summary: Get user by id
parameters:
- in: path
name: id
required: true
schema: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'404':
description: Not Found
content:
application/json:
schema: { $ref: '#/components/responses/NotFound'}
goファイルとして型のみ生成した結果は以下のようになる
// Package openapi provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.0 DO NOT EDIT.
package openapi
import (
"time"
openapi_types "github.com/oapi-codegen/runtime/types"
)
// Error defines model for Error.
type Error struct {
Code string `json:"code"`
Details *[]string `json:"details,omitempty"`
Message string `json:"message"`
}
// User defines model for User.
type User struct {
CreatedAt *time.Time `json:"createdAt,omitempty"`
Email openapi_types.Email `json:"email"`
Id string `json:"id"`
Name string `json:"name"`
}
--generate types
オプションを外してサーバー実装まで行った場合のgoファイル生成結果
サーバー実装も自動生成した結果
// Package openapi provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.0 DO NOT EDIT.
package openapi
import (
"bytes"
"compress/gzip"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path"
"strings"
"time"
"github.com/getkin/kin-openapi/openapi3"
"github.com/labstack/echo/v4"
"github.com/oapi-codegen/runtime"
openapi_types "github.com/oapi-codegen/runtime/types"
)
// Error defines model for Error.
type Error struct {
Code string `json:"code"`
Details *[]string `json:"details,omitempty"`
Message string `json:"message"`
}
// User defines model for User.
type User struct {
CreatedAt *time.Time `json:"createdAt,omitempty"`
Email openapi_types.Email `json:"email"`
Id string `json:"id"`
Name string `json:"name"`
}
// RequestEditorFn is the function signature for the RequestEditor callback function
type RequestEditorFn func(ctx context.Context, req *http.Request) error
// Doer performs HTTP requests.
//
// The standard http.Client implements this interface.
type HttpRequestDoer interface {
Do(req *http.Request) (*http.Response, error)
}
// Client which conforms to the OpenAPI3 specification for this service.
type Client struct {
// The endpoint of the server conforming to this interface, with scheme,
// https://api.deepmap.com for example. This can contain a path relative
// to the server, such as https://api.deepmap.com/dev-test, and all the
// paths in the swagger spec will be appended to the server.
Server string
// Doer for performing requests, typically a *http.Client with any
// customized settings, such as certificate chains.
Client HttpRequestDoer
// A list of callbacks for modifying requests which are generated before sending over
// the network.
RequestEditors []RequestEditorFn
}
// ClientOption allows setting custom parameters during construction
type ClientOption func(*Client) error
// Creates a new Client, with reasonable defaults
func NewClient(server string, opts ...ClientOption) (*Client, error) {
// create a client with sane default values
client := Client{
Server: server,
}
// mutate client and add all optional params
for _, o := range opts {
if err := o(&client); err != nil {
return nil, err
}
}
// ensure the server URL always has a trailing slash
if !strings.HasSuffix(client.Server, "/") {
client.Server += "/"
}
// create httpClient, if not already present
if client.Client == nil {
client.Client = &http.Client{}
}
return &client, nil
}
// WithHTTPClient allows overriding the default Doer, which is
// automatically created using http.Client. This is useful for tests.
func WithHTTPClient(doer HttpRequestDoer) ClientOption {
return func(c *Client) error {
c.Client = doer
return nil
}
}
// WithRequestEditorFn allows setting up a callback function, which will be
// called right before sending the request. This can be used to mutate the request.
func WithRequestEditorFn(fn RequestEditorFn) ClientOption {
return func(c *Client) error {
c.RequestEditors = append(c.RequestEditors, fn)
return nil
}
}
// The interface specification for the client above.
type ClientInterface interface {
// GetUsersId request
GetUsersId(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error)
}
func (c *Client) GetUsersId(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetUsersIdRequest(c.Server, id)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
if err := c.applyEditors(ctx, req, reqEditors); err != nil {
return nil, err
}
return c.Client.Do(req)
}
// NewGetUsersIdRequest generates requests for GetUsersId
func NewGetUsersIdRequest(server string, id string) (*http.Request, error) {
var err error
var pathParam0 string
pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id)
if err != nil {
return nil, err
}
serverURL, err := url.Parse(server)
if err != nil {
return nil, err
}
operationPath := fmt.Sprintf("/users/%s", pathParam0)
if operationPath[0] == '/' {
operationPath = "." + operationPath
}
queryURL, err := serverURL.Parse(operationPath)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", queryURL.String(), nil)
if err != nil {
return nil, err
}
return req, nil
}
func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error {
for _, r := range c.RequestEditors {
if err := r(ctx, req); err != nil {
return err
}
}
for _, r := range additionalEditors {
if err := r(ctx, req); err != nil {
return err
}
}
return nil
}
// ClientWithResponses builds on ClientInterface to offer response payloads
type ClientWithResponses struct {
ClientInterface
}
// NewClientWithResponses creates a new ClientWithResponses, which wraps
// Client with return type handling
func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) {
client, err := NewClient(server, opts...)
if err != nil {
return nil, err
}
return &ClientWithResponses{client}, nil
}
// WithBaseURL overrides the baseURL.
func WithBaseURL(baseURL string) ClientOption {
return func(c *Client) error {
newBaseURL, err := url.Parse(baseURL)
if err != nil {
return err
}
c.Server = newBaseURL.String()
return nil
}
}
// ClientWithResponsesInterface is the interface specification for the client with responses above.
type ClientWithResponsesInterface interface {
// GetUsersIdWithResponse request
GetUsersIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetUsersIdResponse, error)
}
type GetUsersIdResponse struct {
Body []byte
HTTPResponse *http.Response
JSON200 *User
JSON404 *Error
}
// Status returns HTTPResponse.Status
func (r GetUsersIdResponse) Status() string {
if r.HTTPResponse != nil {
return r.HTTPResponse.Status
}
return http.StatusText(0)
}
// StatusCode returns HTTPResponse.StatusCode
func (r GetUsersIdResponse) StatusCode() int {
if r.HTTPResponse != nil {
return r.HTTPResponse.StatusCode
}
return 0
}
// GetUsersIdWithResponse request returning *GetUsersIdResponse
func (c *ClientWithResponses) GetUsersIdWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetUsersIdResponse, error) {
rsp, err := c.GetUsersId(ctx, id, reqEditors...)
if err != nil {
return nil, err
}
return ParseGetUsersIdResponse(rsp)
}
// ParseGetUsersIdResponse parses an HTTP response from a GetUsersIdWithResponse call
func ParseGetUsersIdResponse(rsp *http.Response) (*GetUsersIdResponse, error) {
bodyBytes, err := io.ReadAll(rsp.Body)
defer func() { _ = rsp.Body.Close() }()
if err != nil {
return nil, err
}
response := &GetUsersIdResponse{
Body: bodyBytes,
HTTPResponse: rsp,
}
switch {
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
var dest User
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON200 = &dest
case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404:
var dest Error
if err := json.Unmarshal(bodyBytes, &dest); err != nil {
return nil, err
}
response.JSON404 = &dest
}
return response, nil
}
// ServerInterface represents all server handlers.
type ServerInterface interface {
// Get user by id
// (GET /users/{id})
GetUsersId(ctx echo.Context, id string) error
}
// ServerInterfaceWrapper converts echo contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
}
// GetUsersId converts echo context to params.
func (w *ServerInterfaceWrapper) GetUsersId(ctx echo.Context) error {
var err error
// ------------- Path parameter "id" -------------
var id string
err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true})
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err))
}
// Invoke the callback with all the unmarshaled arguments
err = w.Handler.GetUsersId(ctx, id)
return err
}
// This is a simple interface which specifies echo.Route addition functions which
// are present on both echo.Echo and echo.Group, since we want to allow using
// either of them for path registration
type EchoRouter interface {
CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route
}
// RegisterHandlers adds each server route to the EchoRouter.
func RegisterHandlers(router EchoRouter, si ServerInterface) {
RegisterHandlersWithBaseURL(router, si, "")
}
// Registers handlers, and prepends BaseURL to the paths, so that the paths
// can be served under a prefix.
func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) {
wrapper := ServerInterfaceWrapper{
Handler: si,
}
router.GET(baseURL+"/users/:id", wrapper.GetUsersId)
}
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
"H4sIAAAAAAAC/6yU30/bQAzH/5XI22NoQinayBOdBlM3YJNgmzRUoSNnqFHvBz4Hraryv093164UKu1l",
"T8259tdff+LLElpnvLNoJUCzhNDO0Kj0eMLsOD54dh5ZCFO4dRrjL/5Wxs8RGphc/BifTT7enJyPJ2dQ",
"gix8DAdhsvfQl6BRFM3DVtU1oFE0L0wXpGidFUW2OIZpCSRoUu4rnVVAMatFPBsMQd2/cJNl7xwbJQWF",
"guyTmpN+7asvgfGxI0Yd7aS5NprTv/nu9gFbif2+B9wFhFEJ6rFs+xjWw8O9+mhv//Bqv26GB83o8BeU",
"kI1BA1oJ7gkZ3EUsDbGtZ5xxo3fHq8Cgdea5Wi7YoUR6W6YLfPPeDB+PdiVbZV7QPHfGFZczYiXKqH9C",
"TJzXXpLaa459CQHbjkkWl3HbMsUPqBh53Mksnm7T6XQ93eefV1Dm3YxK+d+NmZmIhz4Kk71zaXVIkv2T",
"PEgx/jaBEp6QAzkLDdSD/UEdJ3YerfIEDRwM6sEBlOCVzJKjqgvIoVqS7uPxHtMLjm9eCTk70dDAJ5S4",
"FGGiUyUrg4IcoLleAsVGUW2Nosl4NryEO1yNpXYsfD+NycE7GzKjYV3nG2gFbXKjvJ9Tm/xUDyGOtnym",
"95bxDhp4U23ueLW64FXa5cRMY2iZvGQyX79ELKN69N865e/IjlYXTopT11mdd6IzRvEiQy0i++J2UZDO",
"lQH5aQ224zk0UEE/7f8EAAD//6nfeJy/BAAA",
}
// GetSwagger returns the content of the embedded swagger specification file
// or error if failed to decode
func decodeSpec() ([]byte, error) {
zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
if err != nil {
return nil, fmt.Errorf("error base64 decoding spec: %w", err)
}
zr, err := gzip.NewReader(bytes.NewReader(zipped))
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %w", err)
}
var buf bytes.Buffer
_, err = buf.ReadFrom(zr)
if err != nil {
return nil, fmt.Errorf("error decompressing spec: %w", err)
}
return buf.Bytes(), nil
}
var rawSpec = decodeSpecCached()
// a naive cached of a decoded swagger spec
func decodeSpecCached() func() ([]byte, error) {
data, err := decodeSpec()
return func() ([]byte, error) {
return data, err
}
}
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
res := make(map[string]func() ([]byte, error))
if len(pathToFile) > 0 {
res[pathToFile] = rawSpec
}
return res
}
// GetSwagger returns the Swagger specification corresponding to the generated code
// in this file. The external references of Swagger specification are resolved.
// The logic of resolving external references is tightly connected to "import-mapping" feature.
// Externally referenced files must be embedded in the corresponding golang packages.
// Urls can be supported but this task was out of the scope.
func GetSwagger() (swagger *openapi3.T, err error) {
resolvePath := PathToRawSpec("")
loader := openapi3.NewLoader()
loader.IsExternalRefsAllowed = true
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
pathToFile := url.String()
pathToFile = path.Clean(pathToFile)
getSpec, ok := resolvePath[pathToFile]
if !ok {
err1 := fmt.Errorf("path not found: %s", pathToFile)
return nil, err1
}
return getSpec()
}
var specData []byte
specData, err = rawSpec()
if err != nil {
return
}
swagger, err = loader.LoadFromData(specData)
if err != nil {
return
}
return
}
chi サーバ生成をしたい場合の参考
型だけでなくハンドラの雛形が必要になったら、以下のように -generate chi-server
を追加できます
go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@v2.5.0 \
-generate types,chi-server -package openapi \
-o services/gateway-api/internal/gateway-api.gen.go api/swagger/gateway-api.yml
(参考)Go 以外での生成方法(OpenAPI Generator 例)
言語別の SDK やサーバスタブを自動生成できます。openapi-generator-cli
のインストールは公式ドキュメントを参照してください。
- TypeScript(fetch クライアント)
openapi-generator-cli generate \
-i openapi/sample-api.yml \
-g typescript-fetch \
-o client/ts
- Java(Spring サーバスタブ)
openapi-generator-cli generate \
-i openapi/sample-api.yml \
-g spring \
-o server/java-spring
- Python(クライアント)
openapi-generator-cli generate \
-i openapi/sample-api.yml \
-g python \
-o client/python
- C#(ASP.NET Core サーバスタブ)
openapi-generator-cli generate \
-i openapi/sample-api.yml \
-g aspnetcore \
-o server/dotnet
- Swift(iOS クライアント)
openapi-generator-cli generate \
-i openapi/sample-api.yml \
-g swift5 \
-o client/ios
- Rust(クライアント)
openapi-generator-cli generate \
-i openapi/sample-api.yml \
-g rust \
-o client/rust
ヒント:
-
-i
に OpenAPI の入力ファイル、-g
にジェネレーター(ターゲット)、-o
に出力先を指定 -
-c config.json
で各言語特有のオプションを細かく設定可能 - CI では生成物の整合性チェック(差分検出)や Lint(Spectral 等)を組み合わせると安心です