もう絶対迷わないエラーレスポンスの作り方【RFC7807】

この記事は「[ゆめみ その2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yumemi2)」の5日目の記事です。

# ある日のこと…
<img width="583" alt="スクリーンショット 2019-12-03 23.08.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391127/ba5b6418-e23c-591d-56c0-4026fee8ebcc.png">

「いきなりそんなこと言われても…」

上司「ついでにいろんなところで使いまわせるように普遍的なのにしてね」

「えぇ…」

「こういう時にエラーレスポンスが**標準化されてRFCでまとめられてたら**いいのになぁ…」


# ありました

[RFC7807 : Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807)

RFC7807では開発においてHTTP APIのために新しくエラーレスポンスを定義するのを避けるために、プログラムが読み取れるような問題詳細（Problem Details）を`application/problem+json`または`application/problem+xml`で定義しています。

## 問題詳細（Problem Details）ってなんぞや？

type, title, status, detail, instance + αからなるオブジェクトで、これを用いてどんな問題が発生しているのかを説明します。

>type (String) :エラーの詳細ドキュメントへのURL
>title (String) :人間が読むことのできる短いサマリー
>status (String) :サーバによって生成されたHTTPステータスコード
>detail (String) :人間が読むことのできる説明文
>instance (String) :問題の発生箇所の参照URI

ちなみにこれを全て使う必要はなく、状況によって使い分けたり新しく必要なメンバーを追加することでもできます。


# Example
ECサイトにおいてユーザのキャッシュ残高が足りないことを示すエラーの場合

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

{
 // エラー詳細ドキュメントへのURL
 "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.",
 // 問題発生箇所のURI
 "instance": "/account/12345/msgs/abc",
 // statuは省略

 // 口座の残高
 "balance": 30,
 // 扱うことのできる口座へのリンク
 "accounts": [
    "/account/12345",
    "/account/67890"
 ]
}
```

この例だと新たに`balance`と`accounts`を定義することで、現在の口座の残高とそのユーザが扱うことのできる口座へのリンクを追加しています。

# 注意点
問題詳細（Problem Details）を定義する上でいくつか注意しなくてはいけないことがあります。
>1.情報漏洩
>2.発生場所へのリンク
>3.statusメンバーについて

## 1.情報漏洩

親切心からあれもこれもと必要以上に情報を記載するとエラーレスポンスの情報から予期せぬシステムにアクセスされたり、最悪ユーザのプライバシーを侵害してしまう可能性があります。

上記のECサイトの例ではユーザの残高や使用できる口座のリンクを見れてしまいます。

通信が完全に暗号化されていたり安全が保証されているならいいですが、そうでない場合はできる限り避けましょう。


## 2.発生場所へのリンク
問題が発生した際にデバッグが簡単になるからとスタックダンプやエラーログをそのまま出力したり、実装の詳細を掲載することは避けましょう。

悪意あるユーザに見られた場合、サーバの実装の詳細やデータなどが公開される危険性があります。


## 3.statusメンバーについて
statusメンバーはあくまでHTTPステータスをコピーしているだけです。

一般的なソフトウェアがこのメンバーで伝達されるステータスコードを認識したり利用したりする可能性は低いので、あまり使われることはないでしょう。

ただし複数のサーバを中継したりする場合は中継したサーバでHTTPステータスが変わる場合がある（らしい）ので、そういう時に使用しましょう。


# 参考：実務で使えるようにいい感じにしてみる

ここまでを踏まえた上で実用で使えそうなエラーレスポンスを考えてみます。（あくまで個人の意見です）

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

{
 "title": "FORBIDDEN",
 "detail": "Please contact your administrator.",
 "error_code": "403-001
}
```

あとはきちんとIP制限された詳細ドキュメントが既にある or 作る予定ならば`type`を追加しても良いと思います。

…えっ、これだけかって？

だって下手に詳細を出力してインシデントの危険性を上げるよりも、エラーが出たら`error_code`だけ伝えて保守してる管理者にログ見てもらった方が早いし安全だと思うんです（個人の意見です）

それに実装する際に`error_code`で出力するエラーコードをきっちり設計して適切に使用すれば、それを見ただけでどこでどんな問題が発生しているかを判別できるのでエラーを特定するのも楽になる…んじゃないですかね（個人の意見です）

# 最後に

結局のところ、それぞれのプロダクトに合った最適なエラーレスポンスというのは要件や設計によって大幅に変わってくるので、これは参考程度に収めて頑張ってBestなエラーレスポンスを設計しましょう。

自分もまだまだ模索中なので、もっと良い方法を思いついたら是非ともコメントで教えてくださると助かります。

# 後日談

「やっとエラーレスポンスの設計終わったから、ちょっとだけ休憩時間できたな」

「今のうちに[ゆめみ その2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/yumemi2)に投稿する記事でも書くか！」

上司「次はエラーコードの設計よろしく」

「えっ」

### 参考資料
[Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807)