PIYO - Tech & Life -

Grapeを使ったAPIで独自のエラーコードも一緒に返す工夫

API Grape Ruby Rails

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_configgem を使って、環境によらずアプリケーション全体でその設定を使いまわすような感じで作ってみている。

RailsConfigの導入は公式を見れば十分だと思う。

railsconfig/rails_config

導入できたら全体の設定ファイルである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)

定義も一覧しやすくなったし、呼び出しもすっきりした。まあまあいいところに落ち着いたと思う。

もっといい方法、一般的な方法があったら知りたいです。