GoでOpenAPIファイルからAPIコードを自動生成する方法〜oapi-codegenの使い方〜

## はじめに
Go言語でAPIを構築する際に、手動でコードを書くのは手間がかかるだけでなく、エラーが発生しやすくなります。そこで、OpenAPI仕様を活用し、自動でコードを生成する方法を解説します。本記事では、**oapi-codegen**を使用して、Goコードを自動生成する手順を丁寧に説明します。

## oapi-codegenとは？
**oapi-codegen**は、OpenAPI 3.0仕様に基づいてGoコードを自動生成するツールです。以下のような機能をサポートしています：

- APIモデルの生成
- Ginベースのサーバーコード生成
- クライアントコードの生成
- OpenAPI仕様を埋め込むコード生成

https://github.com/oapi-codegen/oapi-codegen


## 前提条件
- Goがインストールされている
- oapi-codegenのインストール済み

以下のコマンドで``oapi-codegen``をインストールできます：

```
go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
```

以下のコマンドでバージョンが出力されるか確認する
```
$ oapi-codegen -version
```
もし、
```
zsh: command not found: oapi-codegen
```
になった場合は、パスを通す。
```
go-api-newspaper % go env | grep PATH
# => GOPATH='/Users/jima/go'

go-api-newspaper %ls /Users/jima/go/bin      
# => gopls           oapi-codegen    staticcheck

go-api-newspaper % export PATH=$PATH:/Users/jima/go/bin
go-api-newspaper % echo 'export PATH=$PATH:/Users/jima/go/bin' >> ~/.zshrc
go-api-newspaper % source ~/.zshrc
go-api-newspaper % oapi-codegen -version
# => github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen
# => v2.4.1
```

## OpenAPI仕様のYAMLファイルを作成する
以下は、本記事で使用するサンプルのOpenAPI仕様です。このファイルを``openapi.yaml``として保存してください。

```yaml:openapi.yaml
openapi: 3.0.3
info:
  title: Go API Template # APIの名称。新聞管理用のテンプレートを定義。
  version: 1.0.0         # APIのバージョン。メジャー、マイナー、パッチ番号で管理。
servers:
  - url: http://0.0.0.0:8080/api/v1  # Dockerや他の環境でAPIをテストする際のベースURL。
  - url: http://localhost:8080/api/v1 # ローカルホストでの開発時に使用。
  - url: http://127.0.0.1:8080/api/v1 # ループバックアドレスを使用する場合のURL。
paths:
  /newspaper:
    post:
      summary: Create a new newspaper # 新聞記事を新規作成するエンドポイント。
      operationId: createNewspaper    # 操作を一意に識別するID。
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewspaperCreateRequest' # 新規作成に必要なデータの構造を参照。
        required: true # リクエストボディが必須であることを指定。
      responses:
        '201':
          description: Created # リソースが正常に作成された場合のレスポンス。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewspaperResponse' # 作成された新聞データのレスポンス構造。
        '400':
          description: Bad Request # 入力データが不正だった場合のレスポンス。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse' # エラー情報の構造を参照。
  /newspaper/{id}:
    get:
      summary: Find newspaper by ID # IDで新聞記事を取得するエンドポイント。
      operationId: getNewspaperById
      parameters:
        - name: id
          in: path
          required: true # パスパラメータが必須であることを指定。
          schema:
            type: integer # IDは整数型。
      responses:
        '200':
          description: OK # 正常にデータが取得された場合。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewspaperResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found # 指定されたIDの新聞記事が見つからない場合。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    patch:
      summary: Update a newspaper by ID # IDで新聞記事を更新するエンドポイント。
      operationId: updateNewspaperById
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NewspaperUpdateRequest' # 更新データの構造を参照。
        required: true
      responses:
        '200':
          description: Updated # 更新成功時のレスポンス。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewspaperResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    delete:
      summary: Delete a newspaper by ID # IDで新聞記事を削除するエンドポイント。
      operationId: deleteNewspaperById
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        '204':
          description: No Content # 成功した場合、コンテンツなしのレスポンスを返す。
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    NewspaperResponse:
      type: object
      properties:
        id:
          type: integer # 新聞記事の一意の識別子。
        title:
          type: string  # 新聞記事のタイトル。
        columnName:
          type: string  # コラム名を指定。
      required:
        - id
        - title
        - columnName
    NewspaperUpdateRequest:
      type: object
      properties:
        title:
          type: string  # 更新対象のタイトル。
        columnName:
          type: string  # 更新対象のコラム名。
    NewspaperCreateRequest:
      type: object
      properties:
        title:
          type: string  # 作成時に必要な新聞記事のタイトル。
        columnName:
          type: string  # 作成時に必要なコラム名。
      required:
        - title
        - columnName
    ErrorResponse:
      type: object
      properties:
        message:
          type: string  # エラーに関する詳細な説明を含む文字列。
      required:
        - message # エラーメッセージは必須プロパティ。
```

## oapi-codegenでコードを生成する
oapi-codegenの設定を記載した``config.yaml``を作成します：

```yaml:config.yaml
package: api           # 生成されるコードのパッケージ名
generate:
  models: true         # モデルコードの生成
  gin-server: true     # Gin用サーバーコードの生成
  client: true         # クライアントコードの生成
  embedded-spec: true  # OpenAPI仕様を埋め込む
output: api/api.gen.go # 生成されるコードの出力先
```

そして、以下のコマンドを実行します。

```
oapi-codegen --config=./config.yaml ./openapi.yaml
```
このコマンドを実行すると、``api.gen.go``というファイルが自動生成されます。

## 生成されたコードの確認
``api.gen.go``には、以下のようなコードが生成されます：
- リクエスト/レスポンス用のモデル
- Ginベースのエンドポイントハンドラ
- クライアントコード

## まとめ
本記事では、``oapi-codegen``を使用して、GoのAPIコードを効率的に生成する方法を解説しました。コード生成により、開発スピードの向上やバグの削減が期待できます。ぜひ、実際のプロジェクトで活用してみてください！