0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Swagger(OpenAPI) チートシート

Last updated at Posted at 2025-09-15

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 を編集・プレビューできる公式ツールです。ドラフトの検証にも便利です。

  • 使い方:
    1. 画面左のエディタにOpenAPI YAMLを貼り付ける
    2. 右側にドキュメント(Swagger UI 風の表示)が生成される
    3. 保存はローカルにコピーするか、Export を使用
  • 注意: Swagger Editor は設計・確認用に使いましょう。生成や実装は各プロジェクトのビルド手順に従ってください。

スクリーンショット 2025-09-15 17.16.55.png

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を確認すると以下のようになります
スクリーンショット 2025-09-15 17.19.26.png

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を確認すると以下のようになります
スクリーンショット 2025-09-15 17.20.15.png

チーム開発での使い方

OpenAPI は「コミュニケーションの共通言語」です。バックエンド・フロントエンド・QA が同じ仕様を見て話せるよう、次のようなフローをおすすめします。

  1. 仕様ドラフト
  • Issue/PR で変更点の背景と要件を共有
  • Swagger Editor(https://editor.swagger.io)やエディタ上で YAML を編集
  • エンドポイント・型・バリデーション・エラーを最小でも記述
  1. レビュー(Contract First)
  • PR で api/swagger/*.yml をレビュー
  • 用語・命名・境界(責務)を合わせる
  • サンプル例(examples)とエラーの網羅性を確認
  1. 生成と整合性チェック
  • 言語や環境に合ったコード生成ツール(例: OpenAPI Generator, Swagger Codegen, oapi-codegen(Go) など)で型やクライアント/サーバの雛形を生成
  • CI で仕様の静的検証(例: Spectral)やコード生成の整合性確認を実施
  1. 実装と E2E
  • バックエンドは仕様どおり実装し、OpenAPI に追随
  • フロントは生成型/ドキュメントを基に Mock/結合テストを構築
  1. 変更管理
  • 破壊的変更はメジャーバンプ 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 }

スクリーンショット 2025-09-15 17.28.45.png

共通で使うスキーマを定義する

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'

スクリーンショット 2025-09-15 17.55.20.png

すべての 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' }

スクリーンショット 2025-09-15 17.57.36.png

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' }

スクリーンショット 2025-09-15 17.58.10.png

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' }

スクリーンショット 2025-09-15 17.58.47.png

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' }

スクリーンショット 2025-09-15 17.59.07.png

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' }

スクリーンショット 2025-09-15 17.59.42.png

HEAD(ヘッダのみ)

GET と同じだがボディを返さない。存在確認やメタ情報取得に使います。

paths:
  /users/{id}:
    head:
      summary: Head user
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Headers only

スクリーンショット 2025-09-15 18.00.01.png

OPTIONS(サポートメソッドの確認)

CORS やクライアントの探索用。

paths:
  /users:
    options:
      summary: Options for users
      responses:
        '204':
          description: No Content
          headers:
            Allow:
              schema: { type: string }
              example: "GET,POST,OPTIONS"

スクリーンショット 2025-09-15 18.00.26.png

TRACE(ループバックテスト)

デバッグ用途。多くの API では無効化しますが、仕様としての書き方例を示します。

paths:
  /debug/trace:
    trace:
      summary: Trace request
      responses:
        '200': { description: Trace OK }

スクリーンショット 2025-09-15 18.01.24.png

バリデーションの書き方(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 }

スクリーンショット 2025-09-15 18.03.05.png

ページネーションの例(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 }

スクリーンショット 2025-09-15 18.04.10.png

multipart/form-data とファイルアップロード

画像や CSV をアップロードする典型例です。OpenAPI では type: stringformat: 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 }

スクリーンショット 2025-09-15 18.04.41.png

共通パラメータの定義(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 }

スクリーンショット 2025-09-15 18.06.13.png

レスポンスヘッダの例(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

スクリーンショット 2025-09-15 18.06.40.png

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 }

スクリーンショット 2025-09-15 18.07.45.png

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.godiff
  • 差分があれば失敗し、メッセージで「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.ymlservices/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 等)を組み合わせると安心です
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?