Posted at

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


はじめに

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を別途作るとか。

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