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)
定義も一覧しやすくなったし、呼び出しもすっきりした。まあまあいいところに落ち着いたと思う。
もっといい方法、一般的な方法があったら知りたいです。