新人エンジニアが初めてOpenAPI仕様書を書いたのでメモ（Swagger）

こんにちは。今年4月に入社した、文系出身新人エンジニアです。

今回は、社内で初めてSwaggerを使ってAPI仕様書を書いたので、備忘録を書こうと思います。

### Swaggerとは？

Swaggerとは、Open API仕様書に基づいてAPIの仕様書を簡単に書くためのフレームワークです。web上でSwaggerを書くための `Swagger Editor` 、定義したAPIを確認するための `Swagger UI` など、いろいろなツールがあるそうですが、今回はpycharmに標準搭載（？）されているSwaggerのプレビュー機能だけで簡単に書けたので、これらは使用しませんでした。VS codeの場合は 、 `swagger viewer` という拡張機能で、リアルタイムに綺麗なUIを使って仕様書を確認しながら書き進めることができます。

参考までに、上がpycharmで下がVScodeのUIです。

![スクリーンショット 2024-10-31 17.44.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3638231/fd620ba0-063d-9fb8-4b4a-82e359be5c6d.png)
![スクリーンショット 2024-10-31 17.46.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3638231/f050d3f2-afcb-ad96-f664-2d28e4993d30.png)

### 書いてみる。

今回書いてみたサンプルの全体像は以下の通りです。

```yaml
openapi: 3.0.0
info:
  title: "Get Story API"
  description: "指定された条件に基づいて物語を生成するAPIです"
  version: "1.0.0"

servers:
  - url: "https://nyanko.jp"
    description: "本番環境"

tags:
  - name: fantasy
    description: "ファンタジー小説を書きます"
  - name: mystery
    description: "ミステリー小説を書きます"
  - name: fairy tale
    description: "童話を書きます"

paths:
  /api/v1.0.0/story/fantasy:
    get:
      tags:
        - fantasy
      summary: "ファンタジー小説を書く"
      description: "このエンドポイントは、指定された条件に基づいてファンタジー小説を書きます。"
      parameters:
        - in: path
          name: Protagonist
          required: true
          schema:
            type: string
          description: "主人公を指定します。"
        - in: path
          name: Setting
          required: true
          schema:
            type: string
          description: "物語の舞台を指定します。"
      responses:
        "200":
          description: "成功した応答。指定された条件に基づいて書いた、ファンタジー小説を返します。"
          content:
            application/json:
              schema:
                type: object
                properties:
                  title:
                    type: string
                  body:
                    type: string
        "400":
          description: "無効なパラメータが提供された場合のエラー。"
        "500":
          description: "サーバー内部のエラー。"

  /api/v1.0.0/story/mystery:
    get:
      tags:
        - mystery
      summary: "ミステリー小説を書く"
      description: "このエンドポイントは、指定された条件に基づいてミステリー小説を書きます。"
      parameters:
        - in: path
          name: Incident_Type
          required: true
          schema:
            type: string
          description: "事件の種類を指定します。"
        - in: path
          name: Setting
          required: true
          schema:
            type: string
          description: "物語の舞台を指定します。"
      responses:
        "200":
          description: "成功した応答。指定された条件に基づいて書いた、ミステリー小説を返します。"
          content:
            application/json:
              schema:
                type: object
                properties:
                  title:
                    type: string
                  body:
                    type: string
        "400":
          description: "無効なパラメータが提供された場合のエラー。"
        "500":
          description: "サーバー内部のエラー。"

  /api/v1.0.0/story/fairy_tale:
    get:
      tags:
        - fairy tale
      summary: "童話を書く"
      description: "このエンドポイントは、指定された条件に基づいてファンタジー小説を書きます。"
      parameters:
        - in: path
          name: Protagonist
          required: true
          schema:
            type: string
          description: "主人公を指定します。"
        - in: path
          name: Setting
          required: true
          schema:
            type: string
          description: "物語の舞台を指定します。"
        - in: path
          name: Target
          required: true
          schema:
            type: string
          description: "ターゲットとする子供の年齢を指定します。"
      responses:
        "200":
          description: "成功した応答。指定された条件に基づいて書いた、ファンタジー小説を返します。"
          content:
            application/json:
              schema:
                type: object
                properties:
                  title:
                    type: string
                  body:
                    type: string
        "400":
          description: "無効なパラメータが提供された場合のエラー。"
        "500":
          description: "サーバー内部のエラー。"

```

はじめに、基本情報を定義します。

1. openapi：使用するOpenAPIのバージョンを記載
2. info：APIの名前、概要、バージョンなどの情報を記載
3. servers：APIが動いているサーバを指定。複数環境ある場合は、複数指定することも可能。

```yaml
openapi: 3.0.0
info:
  title: "Get Story API"
  description: "指定された条件に基づいて物語を生成するAPIです"
  version: "1.0.0"

servers:
  - url: "https://nyanko.jp"
    description: "本番環境"
```

次に、タグを定義します。１つのyamlファイルに複数のAPIを定義する場合は、タグをつけて管理すると扱いやすいです。

```yaml
tags:
  - name: fantasy
    description: "ファンタジー小説を書きます"
  - name: mystery
    description: "ミステリー小説を書きます"
  - name: fairy tale
    description: "童話を書きます"
```

ここで定義したタグは、各エンドポイントの定義部分で使うことができます。

```yaml
paths:
  /api/v1.0.0/story/fantasy:
    get:
      tags:
        - fantasy
      summary: "ファンタジー小説を書く"
      .........
```

最後に、各エンドポイントを定義していきます。

pathsの中に、エンドポイントと先出のタグ付け、概要などを定義します。パラメータがある場合は、parametersに名前と必須か否か、型と概要を記載します。

その後、レスポンスコードとレスポンスの内容を定義します。

```yaml
paths:
  /api/v1.0.0/story/fantasy:
    get:
      tags:
        - fantasy
      summary: "ファンタジー小説を書く"
      description: "このエンドポイントは、指定された条件に基づいてファンタジー小説を書きます。"
      parameters:
        - in: path
          name: Protagonist
          required: true
          schema:
            type: string
          description: "主人公を指定します。"
        - in: path
          name: Setting
          required: true
          schema:
            type: string
          description: "物語の舞台を指定します。"
      responses:
        "200":
          description: "成功した応答。指定された条件に基づいて書いた、ファンタジー小説を返します。"
          content:
            application/json:
              schema:
                type: object
                properties:
                  title:
                    type: string
                  body:
                    type: string
        "400":
          description: "無効なパラメータが提供された場合のエラー。"
        "500":
          description: "サーバー内部のエラー。"
```

また、複数のAPIで同一のレスポンス形式を共有している場合は、下記のように省略することも可能です。

```yaml
responses:
        "200":
          description: "成功した応答。指定された条件に基づいて書いた、ファンタジー小説を返します。"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FantasyStoryResponse"
        "400":
          description: "無効なパラメータが提供された場合のエラー。"
        "500":
          description: "サーバー内部のエラー。"

components:
  schemas:
    FantasyStoryResponse:
      type: object
      properties:
        title:
          type: string
        body:
          type: string
```

### まとめ
初心者でも簡単にAPI仕様書が書けて便利でした！そして、エンドポイントとパラメータを何らかのLLMに渡せば、大体仕上げてくれるのもよきポイントでした()