Swaggerファイルの分割・統合

APIがいっぱいあって、1つのSwaggerファイルに全部を書きたくないときのためのメモ。

複数ファイルへの分割

サンプルとして、以下のファイルを複数ファイルに分割する。

openapi: '3.0.0'
info:
  title: サンプルAPI
  description: API仕様サンプル用
  termsOfService: http://example.com/terms/
  contact:
    name: APIサポートセンター
    url: http://www.example.com/support
    email: support@example.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
servers:
- url: https://development.example.com
  description: 開発環境
- url: https://staging.example.com
  description: 保守環境
- url: https://{username}.example.com:{port}/v2
  description: 本番環境
  variables:
    username:
      default: taro
      description: ユーザ名
    port:
      enum:
        - '8443'
        - '443'
      default: '8443'
      description: ポート番号
paths:
  /test/customer:
    post:
      summary: お客さま情報登録
      description: お客さま情報を登録する
      tags:
        - customer
      parameters:
        - name: Content-Language
          in: header
          required: true
          schema:
            type: string
            example: ja
      requestBody:
        content:
          text/plain:
            schema:
              type: string
              example: あいうえお
          application/json:
            schema:
              $ref: '#/components/schemas/createCustomerInfo'
      responses:
        '200':
          description: 正常系
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: メッセージ
                    example: "Success"
        '400':
          description: エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    get:
      summary: お客さま情報取得
      description: お客さま情報を取得する
      tags:
        - customer
      parameters:
        - name: id
          in: query
          required: true
          schema:
            type: string
            description: 会員ID
            example: CUS0001
        - name: mail
          in: query
          required: false
          schema:
            type: string
            description: メールアドレス
            example: sample@example.com
      responses:
        '200':
          description: 正常系
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/createCustomerInfo'
  /test/{id}:
    get:
      description: 商品情報を取得する
      tags:
        - item
      security:
        - Bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            description: 商品ID
            example: AA0001
      responses:
        '200':
          description: 正常
components:
  schemas:
    createCustomerInfo:
      description: お客様情報
      type: object
      properties:
        id:
          type: string
          description: 会員ID
          example: AA001
          maxLength: 10
          format: ^[A-Za-z0-9]+$
        name:
          type: string
          description: |
            + 名前
            + aaa
          example: 鈴木太郎
        mail:
          type: string
          description: メールアドレス
          example: taro@co.jp
        age:
          type: number
          description: 年齢
          example: 30
          maximum: 130
          minimum: 20
      required:
        - id
        - name
        - mail
    Error:
      description: エラーオブジェクト
      type: object
      properties:
        code:
          type: string
          description: エラーコード
          example: E00301
        message:
          type: string
          description: エラーメッセージ
          example: IDは必須項目です
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      description: アクセストークン
tags: 
- name: customer
  description: お客さま情報操作のAPIグループ
  externalDocs: 
    description: お客さま情報操作のAPIグループに関する追加情報
    url: https://example.com
- name: item
  description: 商品情報操作のAPIグループ
externalDocs:
  description: APIに関する追加情報
  url: https://example.com

今回はAPIごとに別ファイルに分割することを目標とし、最終的なフォルダ構成は以下のものを目指す。

apiDocSample
 |
 |--- index.yml
 |
 |--- paths
       |
       |--- customer.yml
       |--- item.yml

ルートファイルの作成

ルートファイルには以下のように記載する。 pathsには各APIのURLを記述する。今回はAPIごとに別ファイル化したいので、各APIのURLの下には$refで参照先ファイルを指定する。
paths以外の項目は分割前のファイルと同じように記述する。

# ルートファイル
# pathsの参照先を指定する
openapi: '3.0.0'
info:
  title: サンプルAPI
  description: API仕様サンプル用
  termsOfService: http://example.com/terms/
  contact:
    name: APIサポートセンター
    url: http://www.example.com/support
    email: support@example.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
servers:
- url: https://development.example.com
  description: 開発環境
- url: https://staging.example.com
  description: 保守環境
- url: https://{username}.example.com:{port}/v2
  description: 本番環境
  variables:
    username:
      default: taro
      description: ユーザ名
    port:
      enum:
        - '8443'
        - '443'
      default: '8443'
      description: ポート番号
paths:
  /test/customer:
    $ref: ./paths/customer.yml
  /test/{id}:
    $ref: ./paths/item.yml
components:
  schemas:
    createCustomerInfo:
      description: お客様情報
      type: object
      properties:
        id:
          type: string
          description: 会員ID
          example: AA001
          maxLength: 10
          format: ^[A-Za-z0-9]+$
        name:
          type: string
          description: |
            + 名前
            + aaa
          example: 鈴木太郎
        mail:
          type: string
          description: メールアドレス
          example: taro@co.jp
        age:
          type: number
          description: 年齢
          example: 30
          maximum: 130
          minimum: 20
      required:
        - id
        - name
        - mail
    Error:
      description: エラーオブジェクト
      type: object
      properties:
        code:
          type: string
          description: エラーコード
          example: E00301
        message:
          type: string
          description: エラーメッセージ
          example: IDは必須項目です
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      description: アクセストークン
tags: 
- name: customer
  description: お客さま情報操作のAPIグループ
  externalDocs: 
    description: お客さま情報操作のAPIグループに関する追加情報
    url: https://example.com
- name: item
  description: 商品情報操作のAPIグループ
externalDocs:
  description: APIに関する追加情報
  url: https://example.com

vscode拡張機能「OpenAPI (Swagger) Editor」を入れていると、以下の点で便利だったのでメモ
$refで参照しているファイルにも「右クリック」→「定義へ移動」で移動することができる
・ルートのindex.ymlで、参照しているymlファイルの内容も含めたプレビューを表示することができる

APIごとのファイル作成

お客さま情報操作(customer.yml)

post:
  summary: お客さま情報登録
  description: お客さま情報を登録する
  tags:
    - customer
  parameters:
    - name: Content-Language
      in: header
      required: true
      schema:
        type: string
        example: ja
  requestBody:
    content:
      text/plain:
        schema:
          type: string
          example: あいうえお
      application/json:
        schema:
          $ref: '../index.yml#/components/schemas/createCustomerInfo'
  responses:
    '200':
      description: 正常系
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                description: メッセージ
                example: "Success"
    '400':
      description: エラー
      content:
        application/json:
          schema:
            $ref: '../index.yml#/components/schemas/Error'
get:
  summary: お客さま情報取得
  description: お客さま情報を取得する
  tags:
    - customer
  parameters:
    - name: id
      in: query
      required: true
      schema:
        type: string
        description: 会員ID
        example: CUS0001
    - name: mail
      in: query
      required: false
      schema:
        type: string
        description: メールアドレス
        example: sample@example.com
  responses:
    '200':
      description: 正常系
      content:
        application/json:
          schema:
            type: array
            items:
              $ref: '../index.yml#/components/schemas/createCustomerInfo'

商品情報API(item.yml)

get:
  description: 商品情報を取得する
  tags:
    - item
  security:
    - Bearer: []
  parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        description: 商品ID
        example: AA0001
  responses:
    '200':
      description: 正常

複数ファイルの統合

複数ファイルの統合のツールとして swagger-cli を使用する。
swagger-cli

◇ツールのインストール

npm install @apidevtools/swagger-cli

◇ツールの実行 package.jsonのscriptsに以下の記述を追加する。
-tオプション:出力形式の選択で、「json」と「yaml」から選択
-oオプション:出力先のパスを設定

"swaggerIntegration": "swagger-cli bundle -o result.yml -t yaml index.yml"

実行コマンド

npm run swaggerIntegration

出力されるymlファイルは以下の通りで、分割前のymlファイルと同一となっている

openapi: 3.0.0
info:
  title: サンプルAPI
  description: API仕様サンプル用
  termsOfService: 'http://example.com/terms/'
  contact:
    name: APIサポートセンター
    url: 'http://www.example.com/support'
    email: support@example.com
  license:
    name: Apache 2.0
    url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
  version: 1.0.0
servers:
  - url: 'https://development.example.com'
    description: 開発環境
  - url: 'https://staging.example.com'
    description: 保守環境
  - url: 'https://{username}.example.com:{port}/v2'
    description: 本番環境
    variables:
      username:
        default: taro
        description: ユーザ名
      port:
        enum:
          - '8443'
          - '443'
        default: '8443'
        description: ポート番号
paths:
  /test/customer:
    post:
      summary: お客さま情報登録
      description: お客さま情報を登録する
      tags:
        - customer
      parameters:
        - name: Content-Language
          in: header
          required: true
          schema:
            type: string
            example: ja
      requestBody:
        content:
          text/plain:
            schema:
              type: string
              example: あいうえお
          application/json:
            schema:
              $ref: '#/components/schemas/createCustomerInfo'
      responses:
        '200':
          description: 正常系
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    description: メッセージ
                    example: Success
        '400':
          description: エラー
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    get:
      summary: お客さま情報取得
      description: お客さま情報を取得する
      tags:
        - customer
      parameters:
        - name: id
          in: query
          required: true
          schema:
            type: string
            description: 会員ID
            example: CUS0001
        - name: mail
          in: query
          required: false
          schema:
            type: string
            description: メールアドレス
            example: sample@example.com
      responses:
        '200':
          description: 正常系
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/createCustomerInfo'
  '/test/{id}':
    get:
      description: 商品情報を取得する
      tags:
        - item
      security:
        - Bearer: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            description: 商品ID
            example: AA0001
      responses:
        '200':
          description: 正常
components:
  schemas:
    createCustomerInfo:
      description: お客様情報
      type: object
      properties:
        id:
          type: string
          description: 会員ID
          example: AA001
          maxLength: 10
          format: '^[A-Za-z0-9]+$'
        name:
          type: string
          description: |
            + 名前
            + aaa
          example: 鈴木太郎
        mail:
          type: string
          description: メールアドレス
          example: taro@co.jp
        age:
          type: number
          description: 年齢
          example: 30
          maximum: 130
          minimum: 20
      required:
        - id
        - name
        - mail
    Error:
      description: エラーオブジェクト
      type: object
      properties:
        code:
          type: string
          description: エラーコード
          example: E00301
        message:
          type: string
          description: エラーメッセージ
          example: IDは必須項目です
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      description: アクセストークン
tags:
  - name: customer
    description: お客さま情報操作のAPIグループ
    externalDocs:
      description: お客さま情報操作のAPIグループに関する追加情報
      url: 'https://example.com'
  - name: item
    description: 商品情報操作のAPIグループ
externalDocs:
  description: APIに関する追加情報
  url: 'https://example.com'