RailsでHTTP APIのエラーレスポンスを "problem detail" 形式で返却する

Web API開発において、エラーをどういう形式で返却するか、というのは設計する際の悩みポイントかもしれません。エラーレスポンスの仕様の1つとして RFC7807 Problem Details for HTTP APIs があります。

この形式をサポートしつつ Rails や Sinatra でも使える Ruby ライブラリをこの度作りましたのでそのご紹介。その名も ProblemDetails.

本記事では Rails での使い方について。

ProblemDetailsの機能概要

gem では以下の機能をサポートしています。

  • RFC7807形式を実装したオブジェクトクラス
  • Railsサポート: problem detail 形式でレスポンスする problem renderer
  • Sinatraサポート: problem render function

そもそも problem details 形式とは?

実際のレスポンスは以下のようになります。Content-Type: application/problem+json というのが特徴的。レスポンスボディ自体はJSON形式です。

HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
Content-Language: en

{
  "type": "https://example.com/probs/out-of-credit",
  "title": "You do not have enough credit.",
  "detail": "Your current balance is 30, but that costs 50.",
  "instance": "/account/12345/msgs/abc",
  "balance": 30,
  "accounts": ["/account/12345",
               "/account/67890"]
}

本記事では RFC7807 の詳細は割愛しますが、日本語の記事ですとHTTP APIの詳細なエラー情報をレスポンスに持たせるための仕様が詳しいです。

Rails で problem detail 形式のエラーを返却する

インストール

problem_details-rails gem をインストールしてください。

# Gemfile

gem 'problem_details-rails'

使い方

コントローラで render メソッドに problem: オプションを渡せるようになります。 problem: の値は Hash にする必要があります。与えられた Hash に加え、RFCで規定された予約キーが自動的にマージされた値がエラーメッセージとしてレンダリングされます。

例えば以下の例ではバリデーションに失敗すると problem detail 形式でエラーレスポンスが返却されます。

# app/controllers/api/posts_controller.rb

class Api::PostsController < ApplicationController
  def create
    @post = Post.new(params[:post])
    if @post.save
      render json: @post
    else
      render problem: { errors: @post.errors }, status: :unprocessable_entity
    end
  end
end
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json; charset=utf-8

{
  "type": "about:blank",
  "title": "Unprocessable Entity",
  "status": 422,
  "errors": {
    "body": [
      "can't be blank"
    ]
  }
}

gem を使う便利ポイントとしては、 problem detail 形式の細かいことを知らなくても problem: に任意のエラーメッセージを入れるだけで problem detail 形式が返却される点です。以下の処理が自動的に行われます。

  • Content-Typeがセットされる
  • RFCで規定された予約キー status, type, title が自動セットされる
    • HTTPのレスポンスコードと同じ値がボディにもセットされる
    • type が自動的にセットされる(RFCで規定されているデフォルト値 abount:blank が入る)
    • status コードから自動で title がセットされる
  • RFCで予約されていないキー(例: errors) はトップレベルのキーとしてマージされる

なお、type, title 等の予約キーは problem: {...} の中で明示的に指定することで上書くことは可能です。

まとめ

problem detail 形式をサポートするライブラリ ProblemDetails の Rails での使い方を紹介しました。

Happy API life!!

こう思う

problem details 形式の記事はサーバサイドのはあるけど、クライアントサイドの記事は見たことないなあ、、という感想で problem details を使ってどうクライアントサイドがレバレッジされてるか気になります。そしてその他のエラー形式を使っているなど、皆様どう運用されているのかな...? 気になることろです。