LoginSignup
9

More than 5 years have passed since last update.

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

Posted at

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

3つのエラーデータ型

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

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

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

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

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

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

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の例:

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

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

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に取りうる値を指定すると、それだけに制約することができる。

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が提案されている。今回これを採用しなかった理由は、問題解決ドキュメントのURLをエラーオブジェクトに含める要件があるため。内部向けのAPIなので、ドキュメントを作成するのに十分な時間が見込めない。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9