RSwagを使ったAPI仕様書の自動作成

## 前置き

公式Docにも掲題手順はありますが、備忘録として自分の言葉でも残して置こうと思い書きました。

### 公式Doc

https://github.com/rswag/rswag?tab=readme-ov-file#enable-swagger-endpoints-for-swagger-ui

### この記事で説明すること
- RSwagを使ったAPIDoc自動生成する環境構築

### この記事で対応しないこと
- レスポンスのプロパティ情報の生成
- デプロイツールを使った場合のDoc自動作成

## 手順

### RSwagインストール

gemファイルに次の行を追加
`gem 'rswag'`

app環境のCLIで

```shell
bundle install
```

インストールジェネレータを実行

```shell
rails g rswag:install
```

このコマンドでswaggerのAPI仕様書用のルーティングが自動作成されるので、アクセスできるか確かめてみる。
`/api-docs` にアクセス（例：http://127.0.0.1:3000/api-docs）

### APIテストの自動作成

下記コマンドでテストファイルを自動作成してくれる
（名前空間を使用していて、`api/v1/users`というパスであった場合）

```
rails generate rspec:swagger API::V1::UsersController
```

```ruby:users_spec.rb
require 'swagger_helper'

RSpec.describe 'api/v1/users', type: :request do

  path '/api/v1/users' do

    get('list users') do
      response(200, 'successful') do

        after do |example|
          example.metadata[:response][:content] = {
            'application/json' => {
              example: JSON.parse(response.body, symbolize_names: true)
            }
          }
        end
        run_test!
      end
    end

    post('create user') do
      response(200, 'successful') do

        after do |example|
          example.metadata[:response][:content] = {
            'application/json' => {
              example: JSON.parse(response.body, symbolize_names: true)
            }
          }
        end
        run_test!
      end
    end
  end

  path '/api/v1/users/{id}' do
    # You'll want to customize the parameter types...
    parameter name: 'id', in: :path, type: :string, description: 'id'

    get('show user') do
      response(200, 'successful') do
        let(:id) { '123' }

        after do |example|
          example.metadata[:response][:content] = {
            'application/json' => {
              example: JSON.parse(response.body, symbolize_names: true)
            }
          }
        end
        run_test!
      end
    end

    delete('delete user') do
      response(200, 'successful') do
        let(:id) { '123' }

        after do |example|
          example.metadata[:response][:content] = {
            'application/json' => {
              example: JSON.parse(response.body, symbolize_names: true)
            }
          }
        end
        run_test!
      end
    end
  end
end

```

### rspecのテストファイルを使ってAPI仕様書の自動生成

Swagger yamlファイルを生成する

```
rake rswag:specs:swaggerize
```

ここでテストツールであるrspecをインストールできておらずエラー

```
Rswag::Specs::SwaggerFormatter --dry-run --order defined No examples found. 
/usr/src/app/spec/swagger_helper.rb:3:in `require': 
        cannot load such file -- rails_helper (LoadError)
```

gemfileに下記追記しインストールします。
`gem 'rspec-rails'`
```
bundle install
```

```
rails generate rspec:install
```

無事に自動生成完了

```yaml:swagger/v1/swagger.yaml
---
openapi: 3.0.1
info:
  title: API V1
  version: v1
paths:
  "/api/v1/users":
    get:
      summary: list users
      responses:
        '200':
          description: successful
    post:
      summary: create user
      responses:
        '200':
          description: successful
  "/api/v1/users/{id}":
    parameters:
    - name: id
      in: path
      description: id
      required: true
      schema:
        type: string
    get:
      summary: show user
      responses:
        '200':
          description: successful
    delete:
      summary: delete user
      responses:
        '200':
          description: successful
servers:
- url: https://{defaultHost}
  variables:
    defaultHost:
      default: localhost:3000
```

## 実際にできたAPIDoc

`http://localhost:3000/api-docs/index.html`にアクセスでき、下記API仕様書も確認できたが、肝心のレスポンス内容が記述されていない。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2629116/af0b2ef3-814d-7916-3e84-f97b7bbbf704.png)

Swagger yamlファイルを生成する`rake rswag:specs:swaggerize`コマンドはテストファイル(**_spec.rb)を元に生成するっぽいので、テストファイルに`properties`要素を書けばレスポンス内容が書かれた仕様書は自動作成されそうです。
また別の機会に試したいと思います。

```ruby:**_spec.rb
      response '200', 'blog found' do
        schema type: :object,

        # この部分
          properties: {
            id: { type: :integer },
            title: { type: :string },
            content: { type: :string }
          },
          
          required: [ 'id', 'title', 'content' ]

        let(:id) { Blog.create(title: 'foo', content: 'bar').id }
        run_test!
      end
```