RailsでGrapeをっていうgemを使うとちょちょいっとAPIが作れるという話は以前にも書いた。
このときはお試しだったんだけど、今回ちょっと本格的に書くかもという感じになってちゃんとエラー処理とかもしないといけなくなった。それにあたり試行錯誤したことをちょっとメモっておこうと思う。
普通にエラーを返す
以下フォーマットはJSONだとする。
エラーはerror!メソッドによって発生させることができる。error!は例外を使っているようなので呼んだ時点で処理が中断するため、エラー発生後の処理のことは考えなくていい。
1class API < Grape::API
2 resource :users do
3 get '/' do
4 error!("Unauthorized! Invalid token.", 401)
5 end
6 end
7end
このAPIからはステータスコード401で次のようなJSONが戻ってくる。
1{
2 "error" : "Unauthorized! Invalid token."
3}
独自のエラーコードも戻したくなる
具体的にどのようなエラーが起こったか、という詳細はエラーメッセージやHTTPのステータスコードだけで判別するのは厳しい。アプリケーション独自のエラーコードを使ってもっと詳細な情報まで返してあげたほうが呼び出し元のクライアントためになると思う。
そのためにGrapeのErrorFormatterというのを使える。文字通りエラー出力のフォーマットをカスタマイズするための物だと思う。
こんなFormatterを定義して、error_formatterに指定してみた。
1module ErrorFormatter
2 def self.call message, backtrace, options, env
3 if message.is_a?(Hash)
4 { error: message[:message], code: message[:code] }.to_json
5 else
6 { error: message }.to_json
7 end
8 end
9end
10
11class API < Grape::API
12 error_formatter :json, ErrorFormatter
13 resource :users do
14 ...
15 end
16end
1つめの引数messageにはerror!メソッドの第1引数がそのまま渡るので、ここにHashを渡してしまおうというわけ。
使い方はこうなる。
1error!({message: "Unauthorized!", code: 123}, 401)
レスポンスはこんな感じになる。
1{
2 "error" : "Unauthorized!",
3 "code" : 123
4}
あとはドキュメントとかを整備しておけば123番のエラーはこれだ!みたいなのがより詳細に判別できるようになると思う。
ヘルパーにする
毎度Hashを組み立てるのが面倒だからGrapeのヘルパー機構を使ってもう少し楽に呼び出せる方が良さそう。
1helpers do
2 def my_error!(message, error_code, status)
3 error!({message: message, code: error_code}, status)
4 end
5end
6
7my_error!("Unauthorized!", 123, 401) # こう呼べる
エラー定義をまとめる
APIのいろいろな箇所でmy_error!(うんぬん)があるのは生じい鬱陶しいんじゃないかと思う。あそこのエラーメッセージちょっと変えたいなと思ってもいろいろなところから探さないといけないし。
ということで、エラー定義を一箇所にまとめる方法を模索している。今のところrails_configgem を使って、環境によらずアプリケーション全体でその設定を使いまわすような感じで作ってみている。
RailsConfigの導入は公式を見れば十分だと思う。
導入できたら全体の設定ファイルであるconfig/settings.ymlにいろいろ書いていく。
1errors:
2 unauthorized_token:
3 message: Unauthorized. Invalid token.
4 code: 124
5 status: 401
6 unauthorized_user:
7 message: Unauthorized. Invalid user.
8 code: 125
9 status: 401
RailsConfigにより、上のように書いた内容はアプリケーション内でSettings.errors.unauthorized_token.messageなどとして呼び出せるため、エラー箇所がすっきりするんじゃないかと思う。
最終的にはmy_error!を書き換えていい感じにしてみた。
1def my_error!(error)
2 error!({message: error.message, code: error.code}, error.status)
3end
4
5# 呼び出し
6my_error!(Settings.errors.unauthorized_token)
定義も一覧しやすくなったし、呼び出しもすっきりした。まあまあいいところに落ち着いたと思う。
もっといい方法、一般的な方法があったら知りたいです。