Open18

OpenAPI 3.0以上の仕様について

shimakaze_softshimakaze_soft
# 必須
# OpenAPIのどのバージョンを利用して記述しているかを定義する。 
# バージョンはセマンティックバージョニングで定義されており、記述もセマンティックバージョニングに従って記述する。
openapi: "3.0.0"
 
# 必須
# APIのメタデータを定義する。
info:
  ...
 
# APIを提供するサーバーを配列で記述する。
servers:
  ...

# APIを整理するために利用するタグを定義する。 
tags:
  ...
 
# 必須
# APIとして利用可能なパスおよび操作を定義する
paths:
  ...

# いろいろなオブジェクトをコンポーネントとして再利用可能な形で定義する。
components:
  ...

https://future-architect.github.io/articles/20200409/

info

info自体は必須項目となる。

  • titleは必須であり、文字列型を定義。APIのタイトルを入れる。
  • descriptionは、文字列型を定義。APIに関する説明を定義する。マークダウンを使ってリッチテキスト表示もできる。
  • versionは必須であり、文字列型を定義。記述しているAPI設計書(openapi.yaml自身)のバージョン
info:
  title: "Sample API"
  description: "Sample API provides sample function."
  version: "1.0.0"

servers

https://zenn.dev/link/comments/8f32c1d06c2d84

tags

  • 必須
  • 1URIで1つのタグのみ定義する
  • リソース名を単数形で記載する
  • キャメルケース

良い例

postid: ""
tag:
  - product # GoアプリケーションのhandlerやTypeScriptのclassの単位となる

悪い例

postid: ""
tag:
  - products

postid: ""
tag:
  - user
  - product
shimakaze_softshimakaze_soft

operationId

  • 必須
  • {HTTPメソッド}{機能物理名}を記載する
  • キャメルケース
# GET /products
operationId: getProducts

# GET /products/:product_id
operationId: getProductByProductId

# POST /products
operationId: postProducts

# PUT /products/:product_id
operationId: putProduct

# DELETE /product/:product_id
operationId: deleteProduct

description

  • 必須
  • APIの機能概要を記載する。
description: IDを指定して商品情報を取得する。
shimakaze_softshimakaze_soft

summary

  • 必須
  • {機能ID} {機能論理名}で定義する
summary: XXX-0001 商品参照

security

  • 必須
  • 認証の要否で以下のように定義する

認証なし

security: []

認証あり

security:
  - isAuthorized: []

OAuth認証

securityDefinitions:
  isAuthorized:
    type: oauth2
    flow: accessCode
    authorizationUrl: 'https://example.com/authorize'
    tokenUrl: 'https://example.com/.well-known/jwks.json'

https://qiita.com/poruruba/items/a384b34408fbba3b5e47

shimakaze_softshimakaze_soft

parameters

parametersの基本的な種類

  • query
  • path
  • header
  • cookie

GET/DELETE API の場合

  • in: PATHはパラメータ
  • in: queryはクエリパラメータ
  • description: 必須
  • name: 物理名を定義する
    • 命名規約
    • スネークケース
    • 原則略語は禁止
    • type: arrayの場合、xxx_listやxxx_arrayはNGとする

良い例

- in: path
  name: product_id
  type: string
  description: プロダクトID
  required: true
- in: query
  name: product_types
  type: array
  description: プロダクト種別
- in: query
  name: is_defective
  type: boolean
  description: 不良品フラグ

悪い例

- in: path
  name: productId # キャメルケースはNG
  type: string
  description: プロダクトID
  required: false # 不要
- in: query
  name: product_type_list # xxx_listはNG
  type: array
  description: プロダクト種別
- in: query
  name: defective_flag # trueとfalseがどちらの状態を示すのか不明瞭であるため非推奨
  type: boolean
  description: 不良品フラグ
shimakaze_softshimakaze_soft

POST/PUT API の場合

  • in: リクエストボディ
    • in: bodyのみ利用可能
  • name: 全てname: bodyとする
  • required: リクエストボディが必須でない場合を除いてrequired: trueを定義する
  • schema: リクエストモデルをtype: objectで定義する

OAS3.0からリクエストボディはRequest Body ObjectとしてParameterとは別の概念になったため、in:bodyと言う指定方法は廃止される。以下にリクエストボディの方法を示している。

https://zenn.dev/link/comments/1d8555609432c0

良い例

parameters:
  - in: body
    name: body
    required: true
    schema:
      $ref: '#/definitions/postProductsRequest'

悪い例

parameters:
  - in: body
    name: postProductsBody
    required: false # 不要
    schema:
      type: object # TypeScriptのInterfaceの自動生成時に型が適切に定義されない
      properties:
        product_name:
          type: string

バリデーション

必須

  • required: 必須パラメータのみrequired: trueを定義する
  • default: 必須でないパラメータでもデフォルト値がある場合は定義する

  • type: 必須
    • 文字列:string
    • 数値:number
    • 整数値:integer
    • ブール値:boolean
    • 配列:array
    • オブジェクト:object
  • type: arrayの場合、配列要素itemsのtypeも必須
  • type: nullは原則として利用しない
  • 複数のタイプを定義しない

  • 文字列
    • 最大桁数:maxLength
    • 最小桁数:minLength
  • 数値または整数値
    • 最大値(境界値を含む):maximum
    • 最小値(境界値を含む):maximum
    • 境界値を含まない場合のみexclusiveMinimum: trueまたはexclusiveMaximum: trueを定義
  • 配列
    • 最大要素数:maxItems
    • 最小要素数:minItems
    • required: trueの場合は原則としてminItems: 1を定義する

区分値

  • enum 必須
  • descriptionに区分値の論理名を記載する
# ex. enum
name: gender
type: string
enum:
  - '00'
  - '01'
  - '02'
description: |
  性別
    00: 不明
    01: 男
    02: 女

日付/日時/時刻

日付

  • ISO8601拡張形式(YYYY-MM-DD)とする
  • example: 2020-01-31
  • name: 接尾辞_date
  • type: string
  • format: date
created_date:
    type: string
    example: '2020-01-31'
    format: date

日時

  • タイムゾーン指定子付きISO8601形式とする
  • 秒精度(YYYY-MM-DDThh:mm:ss+TZD)の場合
  • example: 2020-01-31T23:59:59+09:00
  • name: 接尾辞_date_time
  • type: string
  • format: date-time
created_date_time:
    type: string
    example: '2020-01-31T23:59:59+09:00'
    format: date-time
  • ミリ秒精度(YYYY-MM-DDThh:mm:ss.sss+TZD)の場合
  • 秒精度(YYYY-MM-DDThh:mm:ss+TZD)の場合
  • example: 2020-01-31T23:59:59.000+09:00
  • name: 接尾辞_date_time
  • type: string
  • pattern: 必須
created_date_time:
    type: string
    example: '2020-01-31T23:59:59.000+09:00'
    pattern: '^((?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9][0-9][0-9])[+|-]([0-9][0-9]:[0-9][0-9])$'`

時刻

  • ISO8601形式(hh:mm)とする
  • example: 23:59
  • name: 接尾辞_time
  • type: string
  • pattern: 必須
created_time:
    type: string
    example: 23:59
    pattern: '^(2[0-3]|[01][0-9]):([0-5][0-9])$'

その他

正規表現で表現できる文字列はpatternを利用して定義すること

shimakaze_softshimakaze_soft

responses

GET APIの場合

description

  • 必須
  • HTTPステータスコードのメッセージを記載すること

schema

  • HTTPステータス:200の場合
    • type: objectでレスポンスモデルを定義する
    • required: 必須で返る項目を定義する
    • 再利用可能なモデルをdefinitions配下に定義する
      • 複合的なモデルを定義する場合はallOfを利用する

良い例

getProductsResponse:
  allOf:
    - type: object
      properties:
        products:
          type: array
          items:
            $ref: "#/definitions/product"
        required:
          - products
    - $ref: "#/definitions/pagination"

悪い例

getProductsResponse:
  type: array # TypeScriptのInterfaceが適切に定義されません
  items:
    product:
      type: object
        properties:
          product_id:
            type: string
            # required: true を定義しないとundefined許容の変数となり不要なType Guardが必要になる
          product_name:
            type: string
  • HTTPステータスコード:400系または500系の場合
    • 共通で定義されたレスポンスモデルを利用すること

examples

  • ステータスコード:200の場合のみapplication/jsonという命名で必須
  • 必須項目は必ず値を記載すること
200:
  description: OK
  schema:
    $ref: '#/definitions/getProductsResponse'
  examples:
    application/json: # Mockサーバのレスポンスになるためフロントエンド開発者も編集する
      products:
        - product_name: Example Product
          create_date: '2020-01-01'
400:
  description: Bad Request
  schema:
    $ref: '#/definitions/ErrorResponse'

500:
  description: Internal Server Error
  schema:
    $ref: '#/definitions/ErrorResponse'
shimakaze_softshimakaze_soft

POST/PUT/DELETE APIの場合

description

  • 必須
  • HTTPステータスコードのメッセージを記載すること

schema

  • 原則不要
  • 必要な場合はtype: objectでレスポンスモデルを定義する

examples

  • schemaを定義した場合のみ記載する
  • ステータスコード:200の場合のみapplication/jsonという命名で必須
  • 必須項目は必ず値を記載すること
shimakaze_softshimakaze_soft

models

リクエストモデル

  • URI単位で1モデルを定義する
  • 命名規約
    • キャメルケース
    • postXxxxRequestまたはputXxxxRequest
# POST /products
postProductRequest:
  type: object
  properties:
    proeuct_name:
      type: string
  required:
    - product_name

# PUT /products/:product_id
putProductRequest:
  type: object
  properties:
    proeuct_id:
      type: string
    proeuct_name:
      type: string
  required:
    - product_id

レスポンスモデル

  • URI単位で1モデルを定義する
  • リソースモデルをそのまま利用できる場合は不要
  • 命名規約
    • キャメルケース
    • getXxxxResponse
# GET /products
getProductResponse:
  type: object
  properties:
    proeucts:
      type: array
      items:
        $refs: "#/definitions/product"

# GET /products/:product_id
responses:
  200:
    description: OK
      schema:
        $ref: "#/definitions/product" # リソースモデルをそのまま利用する場合は不要

リソースモデル

  • リソースや共通で利用するエンティティの単位で単数形で定義する
  • 命名規約
    • キャメルケース
pagination:
  type: object
  properties:
    total_counts:
      type: integer
    offset:
      type: integer
    limit:
      type: integer
  required:
    - total_counts
    - offset
    - limit
shimakaze_softshimakaze_soft

HTTPステータス

  • 原則としてRFC 7231で定義されているレスポンスステータスコードを利用します
  • 以下、設計者が特に意識すべきものを抜粋して記載します。

共通

  • バリデーションエラー:400 Bad Request
  • 業務エラー:400 Bad Request
  • 認証エラー:401 Unauthorized
  • 認可エラー:403 Forbidden
  • システムエラー:500 Internal Server Error

GET

  • 正常系:200 OK
  • 検索系APIで結果0件:200 OK
  • キー検索系APIで対象リソースが存在しないエラー:404 Not Found

POST

  • 正常系(同期):201 Created
  • 正常系(非同期):202 Accepted
  • 一意制約違反エラー:409 Conflict
  • 親リソースが存在しないエラー:404 Not Found

PUT

  • 正常系(同期):200 OK
  • 正常系(非同期):202 Accepted
  • 対象リソースが存在しないエラー:404 Not Found

DELETE

  • 正常系:204 No Content
  • 対象リソースが存在しないエラー:404 Not Found

HTTPステータスコードの一覧と詳細は以下に詳しく載っている

https://developer.mozilla.org/ja/docs/Web/HTTP/Status/200

shimakaze_softshimakaze_soft

リクエストボディ

下記以外の方法でリクエストボディを送ることができる。こちらの方法が一般的になる。

https://zenn.dev/link/comments/0a22cc4bbc12c9

/hogeというpathに対してエンドポイントを作成する例

multipart/form-data

paths:
  /hoge:
    # post/put/delete
    post:
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                foo:
                  type: string
                bar:
                  type: integer
                baz: # ファイル
                  type: string
                  format: binary

JSON リクエスト

paths:
  /hoge:
    # post/put/delete
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                foo:
                  type: string
                bar:
                  type: integer

XML リクエスト

ここでは、後述の コンポーネントを利用して書いている。

paths:
  /hoge:
    post:
      requestBody:
        content:
          application/xml:
            schema:
              $ref: "#/components/schemas/piyo" 

components:
  schemas:
    piyo:
      type: object
      properties:
        foo:
          type: string
        bar:
          type: integer

ファイルアップロード

jpeg ファイルアップロード

paths:
  /hoge:
    post:
      requestBody:
        content:
          image/jpeg:
            schema:
              type: string
              format: binary

画像 ファイルアップロード

サブタイプにアスタリスクも指定できる。

paths:
  /hoge:
    post:
      requestBody:
        content:
          image/*:
            schema:
              type: string
              format: binary
shimakaze_softshimakaze_soft

サーバー (servers)

APIが動いているサーバーの情報を記述できるほか、Swagger UI から API を試してみるときに使われる。

servers:
  - url: https://example.com/api/v1

配列なので複数書くことができます。

servers:
  - url: http://example.com/api/v1
  - url: https://example.com/api/v1

複数プロトコルが存在する場合などに、variables を使って一つにまとめることができる。

servers:
  - url: "{protocol}://test{number}.example.com:{port}/api/v1" 
    variables:
      protocol:
        default: https
        enum:
          - http
          - https
      number:
        default: "1" 
      port:
        default: "443" 
        enum:
          - "443" 
          - "8080" 
shimakaze_softshimakaze_soft

コンポーネント (components)

コンポーネントは、OpenAPIyaml 内で同じ構造が何度も出てくる場合に参照して使いまわせる仕組み。

複数のBodyの形式をサポート

  • JSON
  • XML
paths:
  /hoge:
    post:
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/piyo" 
          application/xml:
            schema:
              $ref: "#/components/schemas/piyo" 
      responses:
        "200":
          description: ok

components:
  schemas:
    piyo:
      type: object
      properties:
        foo:
          type: object
          properties:
            bar:
              type: string

コンポーネントの実装方法のサンプルは以下が参考になる

https://www.blog.danishi.net/2022/01/22/post-5758

components:
  schemas:
    # 共通モデル

  parameters:
    # GET などで取得範囲を変更するときなどに利用

  requestBodies:
    # 共通で使う POST データ

  responses:
    # 共通のレスポンス定義、 paths の中で利用
shimakaze_softshimakaze_soft

securitySchemes - 認証

認証情報は、components の中の securitySchemes の中に記述する。
それを、認証が必要な要素の security キーから参照する。

クッキーを使った認証も書くことができますが、クッキーとして飛ばすキーと値は固定のものしか記述できず、セッション認証のようなことはできない。

この辺の設定は複雑なので、こちらに詳しく書かれている。

https://garafu.blogspot.com/2020/05/how-to-use-oas.html#security-schemes

基本認証 (Basic認証)

paths:
  /hoge:
    get:
      security:
        - BasicAuth: [] # 配列内は権限のスコープ。OAuth と OpenIDConnect 以外は空配列
      responses:
        "200":
          description: ok
components:
  securitySchemes:
    BasicAuth: # 任意の識別子
      type: http
      scheme: basic

OAuth 認証

paths:
  /hoge:
    get:
      security: 
        - OAuth2:
          - read
      responses:
        "200":
          description: ok
    post:
      security: 
        - OAuth2:
          - write
      responses:
        "200":
          description: ok

components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://example.com/oauth/authorize
          tokenUrl: https://example.com/oauth/token
          scopes:
            read: "読み取り権限"
            write: "書き込み権限"
            create_review: "Post new review."

クッキー (Cookie)

APIのテストをするならサーバ側で、テスト用の固定のセッションIDを用意したりする必要がある。

paths:
  /hoge:
    get:
      security: 
        - CookieAuth: []
      responses:
        "200":
          description: ok

components:
  securitySchemes:
    CookieAuth:
      type: apiKey
      in: cookie
      name: SSID

JWT

paths:
  /hoge:
    get:
      security: 
        - bearer: []
      responses:
        "200":
          description: ok

components:
  securitySchemes:
    bearer:
      type: http
      description: JWT Token Authenticatio
      scheme: bearer
      bearerFormat: JWT

Header - ApiKey

paths:
  /hoge:
    get:
      security: 
        - apikey: []
      responses:
        "200":
          description: ok

  #-------------------------------
  # Reusable security
  #-------------------------------
  securitySchemes:
    apikey:
      type: apiKey
      name: x-api-key
      in: header
      description: API Key Authentication

OpenIDConnect

paths:
  /hoge:
    get:
      security: 
        - sample_oidc_auth: []
      responses:
        "200":
          description: ok

components:
  securitySchemes:
    sample_oidc_auth:
      type: openIdConnect
      openIdConnectUrl: "https://oidc.sample.com/signin"
shimakaze_softshimakaze_soft

データ型

type format description
integer int32 符号付き32ビット整数
int64 符号付き64ビット整数。いわゆる long 型。APIのレスポンスではうまく処理できないケースがあるので文字列にすることも考える。
number float 浮動小数
double 倍精度浮動小数。
string - 文字列
byte Base64エンコードされた文字列
binary バイナリ文字列
date RFC3339のfull-date形式文字列(=W3C-DTFのComplete Date)。YYYY-MM-DD(例: 1997-07-16)。
date-time RFC3339のdate-time形式文字列(=W3C-DTFのComplete date plus hours, minutes and seconds)。YYYY-MM-DDThh:mm:ssTZD(例: 1997-07-16T19:20:30+01:00)。
email メールアドレス
password パスアード
uuid UUID
boolean - 真偽
shimakaze_softshimakaze_soft

pathsの詳細

paths:
  "/shops/{shopId}/reviews":
    post:
      summary: "Get specified shop's reviews."
      description: "Get specified shop's reviews."
      tags:
        - shops
      deprecated: false
      parameters:
        ...
      requestBody:
        ...
      responses:
        ...
      security:
        ...

pathのmethodの種類

  • get
  • put
  • post
  • delete
  • options
  • head
  • patch
  • trace

deprecated

廃止になった操作かどうかを定義する。
デフォルト false

shimakaze_softshimakaze_soft

allOfの使い方

components:
  schemas:
    Order:
      description: '注文'
      type: object
      required:
        - order_id
        - description
      properties:
        order_id:
          title: "注文 ID"
          type: string
          example: "a1b2c3"
        description:
          title: "注文に関するあれこれ"
          type: string
          example: "24 -TWENTY FOUR- 10周年記念コンプリートDVD-BOX"

上記のようなレスポンスがあるします。
新規追加とかでリクエストパラメータとして指定する際に、 必要な項目は以下の二つのどちらかになります。

  • ユーザーの入力されたものを使う
  • 自動的に決まってくる項目になる

前者はレスポンスパラメータとも共通で貼りますが、 後者の項目(id とか作成日時とか)はまだ存在していません。

ここで例えば、リクエストボディ用のOrderレスポンス用のOrderに分ける、みたいな解決方法を取ってしまうと、 段々と schema がコピペだらけになってきてしまい、行数がかなり増えて管理が大変になってきます。

ここでallOfを使う。

components:
  schemas:
    OrderId:
      description: '注文 ID'
      type: object
      required:
        - order_id
      properties:
        order_id:
          title: '注文 ID'
          type: string
          example: 'a1b2c3'

    OrderModel:
      description: '注文モデル'
      type: object
      required:
        - description
      properties:
        description:
          title: '注文に関するあれこれ'
          type: string
          example: '24 -TWENTY FOUR- 10周年記念コンプリートDVD-BOX'

    OrderReponse:
      description: '注文'
      allOf:
        - $ref: '#/components/schemas/OrderId'
        - $ref: '#/components/schemas/OrderModel'

    OrderRequest:
      description: '注文'
      allOf:
        - $ref: '#/components/schemas/OrderModel'

共通部分を、OrderModelとしてくくり出してしまい、 レスポンスとして返したいものは OrderReponse という名前にしておきます。
ここで allOf を指定して、リスト形式で中で使われるものを全部列挙する。

こうすることで、適切にマージされた schema を利用することができるようになります。

allOf, anyOf, oneOfの違い

  • allOf
  • anyOf
  • oneOf

allOf

array の中身すべての schema に合致することが条件。

anyOf

array のうち、ひとつ以上の schema に合致することが条件。

oneOf

array のうち、必ずひとつの schema に合致することが条件。
anyOf と違い、array 中のふたつ以上の schema に合致してしまうとfailする。

参考記事

https://girigiribauer.com/tech/20190318

https://tamamemo.hatenablog.com/entry/2015/07/09/224854

shimakaze_softshimakaze_soft

exampleの使い方

paths:
  /hello:
    get:
      tags:
        - hellos
      responses:
        '200':
          description: 登録されている支払い方法が返される。
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/responses/HelloResponse1"
                  - $ref: "#/components/responses/HelloResponse2"

              examples:
                response1:
                  summary: HelloResponse1の通常レスポンスです
                  value:
                    success1:
                      message_title: Response1
                      message: ハロー1のレスポンスのexampleです

                response2:
                  summary: HelloResponseの特別なレスポンスです
                  value:
                    success1:
                      message_title: Response2
                      message: ハロー2のレスポンスのexampleです

components:
  responses:
    HelloResponse1:
      type: object
      properties:
        success1:
          type: object
          properties:
            msg_title:
              type: string
              example: HelloResponse1
            message:
              type: string
              example: ハロー1のレスポンスです。

    HelloResponse2:
      type: object
      properties:
        success1:
          type: object
          properties:
            msg_title:
              type: string
              example: HelloResponse2
            message:
              type: string
              example: ハロー2のレスポンスです。