🚨

TypeScriptで多層構造のエラーメッセージからエラーコードの型定義を生成する

に公開
const ERROR_MESSAGES = {
  admin: {
    post: {
      "not-found": "投稿が見つかりません。",
      "invalid-type": "投稿のタイプが無効です。",
    },
  },
  user: {
    post: {
      "not-found": "投稿が見つかりません。",
      "invalid-type": "投稿のタイプが無効です。",
    },
    search: {
      "invalid-query": "検索クエリが無効です。",
      "no-results": "検索結果が見つかりません。",
    },
  },
};

// エラーコード使用例
throw new ApplicationServiceError('admin/post/not-found'); // "admin/post/not-found" はエラーコード

上の例のように、多層構造のエラーメッセージを管理するとする。

ApplicationServiceErrorのインスタンス生成時にエラーコードを与えるとき、未定義のエラーコードを使っている場合はエラーになるようにしたいが、エラーコードを別途一つづつ定義するのは面倒。

かといって

type ErrorCode =
  | "admin/post/not-found"
  | "admin/post/invalid-type"
  | "user/post/not-found"
  | "user/post/invalid-type"
  | "user/search/invalid-query"
  | "user/search/no-results";

こんな型定義を別途管理する手間を省きたい。
ERROR_MESSAGESの構造はエラーコードの定義を兼ねることができそうなのでどうにかできるはず。

解決策

// ERROR_MESSAGESの構造を再帰的に表現しておく
type ErrorMessagesStruct = {
  [key: string]: string | ErrorMessagesStruct;
};

// 再帰的にキーを結合してパスを作る
type JoinKeys<T, Prefix extends string = ""> = {
  [K in keyof T]: T[K] extends ErrorMessagesStruct
    ? JoinKeys<T[K], `${Prefix}${K & string}/`>
    : `${Prefix}${K & string}`;
}[keyof T];

type ErrorCodes = JoinKeys<typeof ERROR_MESSAGES>;

export const code1: ErrorCodes = "admin/post/not-found"; // OK
export const code2: ErrorCodes = "user/search/no-results"; // OK
export const code3: ErrorCodes = "admin/post/nonexistent-error-code"; // エラーになる

これでよさそう。

コミューン株式会社

Discussion