内部向けWeb APIでエラーレスポンスどう表現するか？


![](https://qiita-image-store.s3.amazonaws.com/0/889/7ddbd45a-dfed-df01-8460-2b96a445d7be.png)

サーバサイドからフロントエンド(Vueなど)に返すレスポンスのデータモデルを考えてみた。主にPrivate API(内部向けのAPI)の用途で考えたため、不特定多数が利用するパブリックAPIには向いていない可能性がある。

## 3つのエラーデータ型

フロントエンドのユースケースに合わせて3つのデータ型を用意した。

1. `MultipleError` …… エラーメッセージを単純に表示したい画面向け
2. `FormError` …… フォームのバリデーションエラーを入力欄ごとに表示したい画面向け
3. `TableFormError` …… 表形式のフォームで、バリデーションエラーをセルごとに表示したい画面向け

エラーメッセージをひとつだけ含む`SingleError`というのも考えたが、`MultipleError`で代用できるという結論に至ったため省いた。

## エラーデータ型を定義する目的

サーバサイドのエラー生成処理は、エンドポイントごとに差異があることが分かっていた。また、フロントエンド開発者がサーバサイドがどのような形式でエラーを返すかを知るには、APIを叩いてみるか、(必ずしも可読性が高いとは言えない)サーバサイドのソースコードを読み解くといった労力を割いていた。

この問題を解決するために、エラーデータ型をモデリングし整頓されたデザインを検討した。このモデルは、サーバサイドが決められた形式でエラーを返すことを約束するので、サーバサイド開発者とフロントエンド開発者、両者の学習コストを低減し、保守性を高めることができると考える。

## TypeScriptでのデータ型

レスポンスデータモデルは、TypeScriptで次のように表現することができる:

```typescript
// レスポンス型
type Response<T, U extends Errors> = Success<T> | Failure<U>

// 成功時
type Success<T extends any> = {
    type: "success"
    data: T
}

// 失敗時
type Failure<T extends Errors> = {
    type: "failure"
    error: T
}

type Errors = MultipeMessageError | FormError | TableFormError

// エラーメッセージが複数ありうるエラー
type MultipeMessageError = {
    type: "MultipeMessageError"
    messages: string[]
}

// フォームのバリデーションエラーなど
type FormError<FieldNames extends string = string> = {
    type: "FormError"
    fields: {
        [FieldName in FieldNames]: {
            messages: string[]
        }
    }
}

// 表形式フォームのバリデーションエラー
type TableFormError<RowNames extends string = string, ColNames extends string = string> = {
    type: "TableFormError"
    cells: Array<{
        row: RowNames
        col: ColNames
        messages: string[]
    }>
}
```

`MultipeMessageError`の例:

```typescript
const r1: Response<any, MultipeMessageError> = {
    "type": "failure",
    "error": {
        "type": "MultipeMessageError",
        "messages": [
            "・・・でなければなりません",
            "・・・でなければなりません",
            "・・・でなければなりません"
        ]
    }
}
```

`FormError`の例。`FormError`のジェネリクスパラメータはオプションだが、フィールド名を指定すると、エラーオブジェクトに含まれれるフィールドを制約できる。

```typescript
const r2: Response<any, FormError<"field1" | "field2" | "field3">> = {
    "type": "failure",
    "error": {
        "type": "FormError",
        "fields": {
            "field1": {
                "messages": [
                    "・・・でなければなりません",
                    "・・・でなければなりません",
                    "・・・でなければなりません"
                ]
            },
            "field2": {
                "messages": [
                    "・・・でなければなりません",
                    "・・・でなければなりません",
                    "・・・でなければなりません"
                ]
            },
            "field3": {
                "messages": [
                    "・・・でなければなりません",
                    "・・・でなければなりません",
                    "・・・でなければなりません"
                ]
            }
        }
    }
}
```

`TableFormError`の例。`TableFormError`のジェネリクスパラメータはオプションだが、1つ目に`row`に取りうる値、2つ目に`col`に取りうる値を指定すると、それだけに制約することができる。

```typescript
const r3: Response<any, TableFormError<string, "field1" | "field2">> = {
    "type": "failure",
    "error": {
        "type": "TableFormError",
        "cells": [
            {
                "row": "record1",
                "col": "field1",
                "messages": ["...でなければなりません", "...でなければなりません", "...でなければなりません"]
            },
            {
                "row": "record1",
                "col": "field2",
                "messages": ["...でなければなりません", "...でなければなりません"]
            },
            {
                "row": "record2",
                "col": "field1",
                "messages": ["...でなければなりません", "...でなければなりません", "...でなければなりません"]
            },
            {
                "row": "record2",
                "col": "field2",
                "messages": ["...でなければなりません", "...でなければなりません"]
            }
        ]
    }
}
```

## 採用しなかったもの

Web APIの標準的なエラー表現として[RFC 7807 - Problem Details for HTTP APIs](https://tools.ietf.org/html/rfc7807)が提案されている。今回これを採用しなかった理由は、問題解決ドキュメントのURLをエラーオブジェクトに含める要件があるため。内部向けのAPIなので、ドキュメントを作成するのに十分な時間が見込めない。

## 参考文献

* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification#response_object) … Response Objectのデータ型を参考にした
* [WebAPIでエラーをどう表現すべき？15のサービスを調査してみた - Qiita](https://qiita.com/suin/items/f7ac4de914e9f3f35884)