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