OpenAPI & Swagger まとめ

## 内容

OpenAPIとSwaggerについての備忘録 兼 チートシート。

## 用語
OpenAPI, OpenAPI Specification, Swaggerについて。

|OpenAPI  |フォーマットのこと。<br>このフォーマットを使うと、機械可読可能なREST API設計が作成できる。  |
|---|---|
|OpenAPI Specification  |ルールのこと。<br>OpenAPIを記述するための書式ルール。|
|Swagger  |OpenAPIでAPI設計を作成する際に使うツール。|

Swaggerは以下３種類が含まれている。

|Swagger Editor|OpenAPIを記述するエディター。|
|---|---|
|Swagger UI|Swagger Editorで作成したOpenAPIをドキュメントとして表示できる。|
|Swagger Codegen|Swagger Editorで作成したOpenAPIをコードとして出力できる。|


### 備考

GithubでOpenAPIのマニュアルが確認できる。
https://github.com/OAI/OpenAPI-Specification/tree/master/versions
OpenAPI-Specification/versions/確認したいVer.

## Swagger Editorの使用方法
主に以下４通りから。
- Dockerイメージ
- Github
- Web
- IDEのプラグイン

## 記述フォーマット
JSONかYAMLだが、YAMLが一般的。

## データ型
以下が主なデータ型。
これらは、Schemaオブジェクトに定義する。
integer, number, stringに関しては、詳細なformatを指定できる。

- integer: 整数
  - int32
  - int64
- number: 浮動小数
  - float
  - double
- string: 文字列
  - byte
  - binary
  - date
  - date-time
  - email
  - hostname
  - uri
- boolean: 真偽
- array: 配列
- object: オブジェクト

### 記述例

```Yaml
openapi: "3.0.3"

info:
  title: "Sample"
  version: "1.0"

paths: {}

components:
  schemas:
    Int:
      type: integer
      format: int64
    Date:
      type: string
      format: date
```

## ルートオブジェクト

主なルートオブジェクトは、以下７種類。

```Yaml
openapi: 使用するopenapiのバージョン。　※必須項目
info: APIのメタデータ（タイトルとか仕様書のバージョン）。 ※必須項目
servers: APIが動くサーバー情報。配列で定義。
tags: API分類用のタグ。配列で定義。
paths: APIのパスや操作 ※必須項目。オブジェクトで定義。
security: APIのセキュリティ要件について。配列で定義。
components: データ型の例のように、OpenAPIの中で何度も使うオブジェクトを定義する。オブジェクトで定義。
```

## info

APIのメタデータを定義するオブジェクト。

```Yaml
openapi: "3.0.3"

info:
  # APIのタイトル
  title: "Sample API"
  # 仕様書のバージョン
  version: "1.0"
  # APIの詳細説明。mark downが使える。
  description: |
    # 概要
    - xxxからのリクエストをoooでレスポンスする。

paths: {}
```

## servers

接続先のURLや環境情報を定義するオブジェクト。

```Yaml
openapi: "3.0.3"

info:
  title: "Sample API"
  version: "1.0"

servers:
  # 接続先URL
  - url: "http://localhost:{port}"
    # 接続先URLについての説明
    description: "Local"
    # 接続先URLで指定しているテンプレート変数の内容についての定義
    variables:
      # 変数名
      port:
        # 値
        enum: ["3000", "8080"]
        # default値は必須
        default: "8080"
  - url: "https://dev.sample.com"
    description: "Dev"
  - url: "https://stg.sample.com"
    description: "Staging"
  - url: "https://sample.com"
    description: "Production"

paths: {}
```
![スクリーンショット_2021-02-20_20_06_04（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/917499/3348b020-6a3e-ab9a-2085-4e5d1c2611d4.png)

## paths

APIの具体的なエンドポイントや操作内容を定義するオブジェクト。
以下のパートに分かれて定義する。

```Yaml
paths:
    # メソッド
    get:
      # メタデータ
      summary:
      # リクエストパラメータ
      parameters:
      # リクエストボディ
      requestBody:
      # レスポンス
      responses:
      # セキュリティ
      security:
```

### summary

```Yaml
paths:
  # パス
  "/sample/versions/{version}/sample_api":
    get:
      # 操作の概要説明
      summary: "get sample parameters"
      # 操作の詳細説明
      description: "description: get sample parameters"
      # タグ
      tags: ["Sample"]
      # APIがactiveかどうか
      deprecated: false
```
deprecated: true にした場合は、swagger UIの方はグレーアウトされる。
![スクリーンショット_2021-02-20_20_24_58（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/917499/6391e4e8-0883-4cd8-5a6e-367c0402f395.png)

### parameters

```Yaml
paths:
  "/sample/{id}/samples":
    post:
      summary: "sample"
      description: "description: sample"
      tags: ["Sample"]
      deprecated: false
      # リクエストパラメータ
      parameters:
        # パラメータ名
        - name: id
          # パラメータの定義場所（query, header, path, cookie）
          in: path
          # パラメータの説明
          description: "id in path"
          # パラメータが必須か（デフォルトはfalse）
          required: true
          # パラメータの型
          schema: { type: string }
          # サンプルパラメータ
          example: "example"
        - name: token
          in: cookie
          description: "one time token"
          schema: { type: string }
          example: "token"
      responses:
        "201":
          description: "Success"
```

### 【備考】HTTPメソッドとステータスコードについて

- GET

|ステータスコード|成功 / 失敗|概要|内容|
|---|---|---|---|
|200|成功|OK|正常|
|304|成功|Not Modified|キャッシュ利用時|
|400|失敗|Bad Request|クライアント側のリクエスト内容の不備|
|401|失敗|Unauthorized|認証エラー：リクエストしてきたユーザーが未認証|
|403|失敗|Forbidden|認可エラー：認証済みだが、アクセス権限がない|
|404|失敗|Not Found|リクエストしたリソースが存在しない|
|429|失敗|Too Many Requests|レートリミットの制限を超えた|
|500|失敗|Internal Server Error|サーバー側で障害が発生した場合|
|503|失敗|Service Unavailable|高負荷の状態で応答ができない|

- POST

|ステータスコード|成功 / 失敗|概要|内容|
|---|---|---|---|
|200|成功|OK|正常|
|201|成功|Created|レスポンスボディが空で、Locationレスポンスヘッダーにリダイレクト先が含まれる場合|
|202|成功|Accepted|非同期処理の受付完了|
|400|失敗|Bad Request|クライアント側のリクエスト内容の不備|
|401|失敗|Unauthorized|認証エラー：リクエストしてきたユーザーが未認証|
|403|失敗|Forbidden|認可エラー：認証済みだが、アクセス権限がない|
|409|失敗|Conflict|複数のアクセスポイントから同じデータを登録しようとして衝突が起きる|
|429|失敗|Too Many Requests|レートリミットの制限を超えた|
|500|失敗|Internal Server Error|サーバー側で障害が発生した場合|
|503|失敗|Service Unavailable|高負荷の状態で応答ができない|

### requestBody

```Yaml
paths:
  "/sample/{id}/samples":
    post:
      summary: "sample"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        # リクエストボディの説明
        description: "body sample"
        # リクエストボディが必須か
        required: true
        # リクエストボディの内容
        content:
          # メディアタイプ
          application/json:
            # 中身のデータ型
            schema:
              type: object
              properties:
                num: { type: integer, example: 10 }
                word: { type: string, example: "test" }
      responses:
        "201":
          description: "success"
```
schemaの定義部分は表示切り替えが可能

![スクリーンショット 2021-02-20 22.12.29（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/917499/01961e84-4794-b0ad-d7db-32b12f8ee4b7.png)

![スクリーンショット 2021-02-20 22.12.53（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/917499/65153a61-6144-db40-7fb4-51e0c3c7e0f0.png)

### responses

```Yaml
paths:
  "/sample/{id}/samples":
    post:
      summary: "sample"
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        description: "sample"
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                num: { type: integer }
                word: { type: string }
      responses:
        # ステータスコードごとに定義する
        # yamlとjsonとの互換性のため、””で囲う
        # 4XXや5XXでまとめることも可能
        "201":
          # レスポンスの説明
          description: "success"
          # レスポンスヘッダー
          headers:
            location:
              description: "location url"
              schema: { type: string, format: url }
        "400":
          description: "client error"
          # レスポンスボディ
          content:
            application/json:
              schema:
                type: object
                properties:
                  code: { type: string }
                  type: { type: string }
                  message: { type: string }
                  errors:
                    type: array
                    items:
                      type: object
                      properties:
                        field: { type: string }
                        code: { type: string }
```
swagger UI はこんな感じ。
![スクリーンショット 2021-02-20 22.34.43（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/917499/14a31d0a-288d-3862-cac3-2e7746c2b3ad.png)

## tags

paths配下に設定できるtagsを定義することで、タグ付けしてまとめることができる。

```Yaml
# tagsでタグの定義をして、それをpaths配下で利用する
tags:
  - name: sampleTag
    description: "sample tag"

paths:
  "/sample/{id}/samples":
    get:
      summary: "get sample"
      # タグは複数付与可能
      tags: ["sampleTag"]
      parameters:
        - name: id
          in: path
          required: true
          schema: {type: string}
      responses:
        "200":
          description: "success"
    post:
      summary: "post sample"
      tags: ["sampleTag"]
      parameters:
        - name: id
          in: path
          required: true
          schema: {type: string}
      responses:
        "201":
          description: "success"
```

sampleTagで、2つのメソッドをまとめられた。
![スクリーンショット 2021-02-20 23.14.40（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/917499/40015e32-2418-e0c0-a2d8-d0a49fb723e7.png)


### 【備考】共通化

同じpaths内で、同じparametersを2つのメソッド内で使っているので、

```Yaml
paths:
  "/sample/{id}/samples":
    get:
      summary: "get sample"
      tags: ["sampleTag"]
      # postのparametersと同じ内容
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: "success"
    post:
      summary: "post sample"
      tags: ["sampleTag"]
      # getのparametersと同じ内容
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "201":
          description: "success"
```

記述をひとまとめにできる。

```Yaml
paths:
  "/sample/{id}/samples":
    # メソッドと同じ階層にもってこれる
    parameters:
      - name: id
        in: path
        required: true
        schema: { type: string }
    get:
      summary: "get sample"
      tags: ["sampleTag"]
      responses:
        "200":
          description: "success"
    post:
      summary: "post sample"
      tags: ["sampleTag"]
      responses:
        "201":
          description: "success"
```

## security

OpenAPIで定義可能な認証・認可のタイプを以下にまとめる。

|type|scheme|説明|
|---|---|---|
|http|basic<br>bearer|basic認証<br>JWTを使用した認可方式|
|apiKey|header<br>cookie|APIキーを使用した認可方式<br>ログインセッション|
|OAuth|-|本人認証|
|OpenID Connect|-|本人認証 + アクセス制御|

定義方法は、以下２通り。

- pathsに個別に定義

```Yaml
paths:
  "/sample/{id}/samples":
    get:
      summary: "get sample"
      tags: ["sampleTag"]
      parameters:
        - name: id
          in: path
          required: true
          schema: {type: string}
      responses:
        "200":
          description: "success"
      # components配下で定義したsecuritySchemesを指定
      security:
      # scopeがないもの（basic認証やJWTなど）は空の配列を定義
      # scopeがあるもの(OAuthやOpenID Connect)はスコープ名を定義
      - sample_auth: []
components:
  securitySchemes:
    sample_auth:
      type: http
      description: "JWTを使用した認可方式"
      scheme: bearer
      bearerFormat: JWT
```

- ルートオブジェクトとして定義し全体に適用させ、paths内で個別に解除

```Yaml
paths:
  "/sample/{id}/samples":
    get:
      summary: "get sample"
      tags: ["sampleTag"]
      parameters:
        - name: id
          in: path
          required: true
          schema: {type: string}
      responses:
        "200":
          description: "success"
      # 解除したいAPIに対して空配列を定義する
      security: []
# ルートオブジェクトとして定義
security:
  - sample_auth: []
components:
  securitySchemes:
    sample_auth:
      type: http
      description: "JWTを使用した認可方式"
      scheme: bearer
      bearerFormat: JWT
```

## components

複数箇所で使用する記述をcomponents配下にまとめて、使用箇所で参照することができる。

componentsとして使用できる要素は以下６種類。

- paths
  - schemas
  - parameters
  - requestBodies
  - responses
  - headers 
- components
  - securitySchemes

### 記述例

```Yaml
paths:
  "/sample/{id}/samples":
    get:
      summary: "get sample"
      tags: ["sampleTag"]
      parameters:
        - name: id
          in: path
          required: true
          schema: {type: string}
      responses:
        "200":
          description: "success"
          content: 
            application/json:
              schema:
                # $refで参照できる
                $ref: "#/components/schemas/sample"
components:
  securitySchemes:
    sample:
      type: object
      properties:
        id: {type: integer}
        description: {type: string}
```