Help us understand the problem. What is going on with this article?

grape-swagger + grape-swagger-entityでRails + GrapeによるWeb APIのドキュメントを自動生成する

More than 1 year has passed since last update.

はじめに

Rails 5 APIモード + Grapeで実装された既存のWeb APIに、ドキュメント自動生成の機構を追加するサンプル。

今回は、実装からAPIドキュメントを生成するという前提で記事を書いていく。実装からAPIドキュメントを生成することで、別々に管理される実装とドキュメントが乖離していってしまうのを防ぐことが出来る。特に、個人開発や少人数チームにおいてフロント・バックエンド共に同じ人が書くと言った場合に有効だろう。

今回の記事でベースとする既存のWeb APIは、Rails 5 APIモード + Grape + Grape::Entityで作るWeb API - Qiitaで作成したものとする。

前提条件

検証環境は以下の通り。

ProductName:    Mac OS X
ProductVersion: 10.14.5
BuildVersion:   18F132
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]
Rails 5.2.1.1

RubyやRailsのバージョンが若干古いのは、ベースとなるプロジェクト( Rails 5 APIモード + Grape + Grape::Entityで作るWeb API - Qiita )をそのまま使っているためだ。

方針

  • 実装からAPIドキュメントを生成する。
    • /v1/swagger_docs でswagger定義ファイルを配信する。
    • swagger定義ファイルの生成には grape-swaggergrape-swagger-entity gemを利用する。
  • /v1/docs でSwagger UIを利用したAPIドキュメントの配信を行う。
    • swagger_ui_engine gemを利用する。

下準備

Gemのインストール

まずは必要なGemをインストールする。今回利用するのは以下の3つ。

Gemfile に追記しインストール。

gem 'grape-swagger', '~> 0.33.0'
gem 'grape-swagger-entity', '~> 0.3.3'
gem 'swagger_ui_engine', '~> 1.1', '>= 1.1.3'
$ bundle install

swagger定義ファイルを配信する

まずは、swagger定義ファイルを自動生成し配信するよう設定する。

今回生成したいのはAPI V1のドキュメントなので、V1のRootに追記。
app/api 以下のディレクトリ構成は こちら を参照。)

app/api/v1/root.rb
module V1
  class Root < Grape::API
    # 省略

    mount V1::Authors
    mount V1::Books

    # V1::Rootの末尾に追記
    add_swagger_documentation(
      doc_version: '1.0.0',
      info: {
        title: 'grape-on-rails-api',
        description: 'grape-on-rails-apiのAPIドキュメント'
      }
    )
  end
end

その後、 /v1/swagger_doc にアクセスすると、Swaggerの定義ファイルが配信されているのを確認することが出来る。
末尾に .json をつけるとJSON形式でも取得することが出来る。

image.png

SwaggerUIで閲覧できるようにする

YAMLやJSON形式のファイルを人間が直接読むのはつらいので、HTMLの文書で配信する。

まずは config/initializers 以下に設定ファイルを作成。swaggerドキュメントのpathを教えてやる。

config/initializers/swagger_ui_engine.rb
SwaggerUiEngine.configure do |config|
  config.swagger_url = {
    v1: '/v1/swagger_doc'
  }
end

その後、 config/routes.rb でルーティングの記述をする。

config/routes.rb
Rails.application.routes.draw do
  # 省略
  mount SwaggerUiEngine::Engine, at: '/v1/docs'
end

こんな感じでAPIドキュメントを閲覧できる。

image.png

image.png

使い方

ここまでやった手順で生成されるAPIドキュメントだけでも一見便利だけど、どんなレスポンスが返ってくるのかは実際にAPIを叩いてみるまでわからない、というのではAPIドキュメントの意味が無い。

Entityにドキュメンテーションを追加する

まずは、Entityファイルにドキュメンテーションを追加する。

かんたんな例として V1::Entities::AuthorEntity から。

app/api/v1/entities/author_entity.rb
module V1
  module Entities
    class AuthorEntity < Grape::Entity
      expose :id, documentation: { type: 'integer', required: true }
      expose :name, documentation: { type: 'string', required: true }
    end
  end
end

exposeの引数に、 documentation というキーで型や必須かどうかを与えてやるだけだ。そのプロパティが必須でない場合は、 required を外せば良い。

V1::Entities::BookEntity はこうなる。

app/api/v1/entities/book_entity.rb
module V1
  module Entities
    class BookEntity < Grape::Entity
      expose :id, documentation: { type: 'integer', required: true }
      expose :title, documentation: { type: 'string', required: true }
      expose :price, documentation: { type: 'integer', required: true }
      expose :tax_included_price, documentation: { type: 'integer', required: true } do |instance, options|
        instance.price * 1.08
      end
      expose :author, using: V1::Entities::AuthorEntity, documentation: { required: true }
    end
  end
end

using で別のエンティティを指定している場合は、typeはいらないので必須かどうかだけを指定している。

APIの実装にドキュメンテーションを追加する

次はAPIの実装にドキュメンテーションを追加していく。

コードが長いが、大事なのは desc に渡しているハッシュの部分だ。

app/api/v1/books.rb
module V1
  class Books < Grape::API
    resources :books do
      desc 'returns all books', {
        is_array: true, # 複数レコードを返すエンドポイントに必要
        success: V1::Entities::BookEntity # 成功時に返すレスポンスのEntity
      }
      get '/' do
        @books = Book.all
        present @books, with: V1::Entities::BookEntity
      end

      desc 'returns a book', {
        success: V1::Entities::BookEntity
      }
      params do
        requires :id, type: Integer
      end
      get '/:id' do
        @book = Book.find(params[:id])
        present @book, with: V1::Entities::BookEntity
      end

      desc 'Create a book', {
        success: V1::Entities::BookEntity
      }
      params do
        requires :title, type: String
        requires :price, type: Integer
        requires :author_id, type: Integer
      end
      post '/' do
        @book = Book.new(
          title: params[:title],
          price: params[:price],
          author_id: params[:author_id]
        )

        if @book.save
          status 201
          present @book, with: V1::Entities::BookEntity
        else
          status 400
          present @book.errors
        end
      end
    end
  end
end

descのオプションにハッシュで is_arraysuccess を与えてやると良い。

同じように V1::Authors も実装する。

app/api/v1/authors.rb
module V1
  class Authors < Grape::API
    resources :authors do
      desc 'returns all authors', {
        is_array: true,
        success: V1::Entities::AuthorEntity
      }
      get '/' do
        @authors = Author.all
        present @authors, with: V1::Entities::AuthorEntity
      end

      desc 'returns an author', {
        success: V1::Entities::AuthorEntity
      }
      params do
        requires :id, type: Integer, desc: 'Author ID'
      end
      get '/:id' do
        @author = Author.find(params[:id])
        present @author, with: V1::Entities::AuthorEntity
      end

      desc 'Create an author', {
        success: V1::Entities::AuthorEntity
      }
      params do
        requires :name, type: String, desc: 'Author name'
      end
      post '/' do
        authenticate!

        @author = Author.new(name: params[:name])

        if @author.save
          status 201
          present @author, with: V1::Entities::AuthorEntity
        else
          status 400
          present @author.errors
        end
      end

      desc 'Delete an author'
      params do
        requires :id, type: Integer, desc: "Author ID"
      end
      delete '/:id' do
        @author = Author.find(params[:id])

        if @author.destroy
          status 204
          present nil
        else
          status 400
          present @author.errors
        end
      end
    end
  end
end

結果

こんな感じで、どんなレスポンスが返ってくるかや、どんなリクエストを投げれば良いかが提示される。

スクリーンショット 2019-07-29 13.38.53.png

スクリーンショット 2019-07-29 13.39.03.png

おわりに

以上で、Rails + GrapeによるWeb APIのドキュメントを実装から自動生成することが出来た。

今回はわりと雑に作ったけど、例えばOpenAPI Generatorを使ってSwagger定義からAPIクライアントライブラリを生成したいとかなると、もっと細かく指定しなきゃいけないと思う。エラー用のEntityを作って failure に渡してやるとか、複数レコード返す用のEntityを別途作るとか。

詳しくは公式ドキュメントをどうぞ。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away