/**
* エラー一覧で保存できなかった行の下に出す文言を、失敗の種類から決める(純粋関数のみ。指示書BF §1)。
*
* ★期待値表は `lib/errors/__tests__/save-error-message.test.ts`★(指示書BF §1 の表の行と同じ並び)。
* ★エラー一覧の画面(`ErrorsScreen.tsx`)だけ★で使う。アラート・D-Sec アラートの画面は変えない。
*
* ★戻り値は辞書のキー(日本語原文)★ — 表示のときに `renderSaveErrorMessage()`(`t()` を通す)で出す。
* 保存前の検証で止めた行(その他・備考なし)は `saveErrorDraftsWithCheck()` が `NOTE_REQUIRED_ROW_MESSAGE` を入れる(今のまま)。
*
* 判定に使うもの(`ApiFetchError` の `status` / `details` / `serverMessage`。`lib/api/client.ts`):
* - 422 の区別は ★`details` のキー★で行う(`error_list_id` / `note`)。サーバーの `code` はどれも `validation_failed` で区別できない
* - ★選べない行と、選択肢に無い・無効との区別は `details.error_list_id` の中の文言★
* (「この行ではエラー内容を選べません」。`lib/errors/status-update.ts` の手順2)。それ以外の `error_list_id` は手順3(無効)とみなす
* - ★備考の長さと、その他・備考なしの区別は `details.note` の中の文言★ —
* 長さは「…文字以内で…」(`lib/prechecks/status-update.ts` の `parsePrecheckStatusUpdateBody()`)、それ以外は備考なし(手順4)
* - ★`details` は実行時に絞る★(`ApiErrorDetails` は union。文字列の配列でなければ「無い」とみなす)
*/
import { ApiFetchError } from "@/lib/api/client";
import { PRECHECK_NOTE_MAX_LENGTH } from "@/lib/config";
import type { ApiErrorDetails } from "@/lib/types";
import { NOTE_REQUIRED_ROW_MESSAGE } from "./edit-drafts";
export const SAVE_ERROR_LIST_NOT_SELECTABLE = "この行ではエラー内容を選べません";
export const SAVE_ERROR_LIST_UNAVAILABLE = "選んだエラー内容は今は使えません。選び直してください";
/** `{n}` は `PRECHECK_NOTE_MAX_LENGTH`(表示のときに `renderSaveErrorMessage()` が埋める) */
export const SAVE_ERROR_NOTE_TOO_LONG = "備考は{n}文字以内で入力してください";
export const SAVE_ERROR_VALIDATION_FALLBACK = "入力内容を確かめてください";
export const SAVE_ERROR_NOT_FOUND = "このエラーは削除されたため保存できません。画面を読み込み直してください";
export const SAVE_ERROR_UNAUTHORIZED = "ログインし直してください";
export const SAVE_ERROR_SERVER =
"サーバーで保存に失敗しました(DB への書き込みエラー)。時間をおいて保存し直し、続くときは管理者に連絡してください";
export const SAVE_ERROR_NETWORK = "通信に失敗しました。接続を確かめて保存し直してください";
export const SAVE_ERROR_DEFAULT = "保存に失敗しました";
/** サーバーが「選べない行」で返す `details.error_list_id` の文言(`lib/errors/status-update.ts` の手順2) */
const NOT_SELECTABLE_DETAIL = "この行ではエラー内容を選べません";
/** サーバーが備考の長さで返す `details.note` の文言の目印(「400文字以内で入力してください」) */
const NOTE_LENGTH_DETAIL_MARKER = "文字以内";
/** `details[key]` の文字列(1つ以上)。キーが無い・配列でない・文字列が1つも無い(数値の配列など)ときは null */
function stringDetails(details: ApiErrorDetails, key: string): string[] | null {
if (details === null || typeof details !== "object") return null;
const value = (details as Record<string, unknown>)[key];
if (!Array.isArray(value)) return null;
const strings = value.filter((v): v is string => typeof v === "string");
return strings.length > 0 ? strings : null;
}
/** 保存の失敗(`saveErrorDraftsWithCheck()` の `failed` の値)→ 行の下に出す文言(辞書のキー) */
export function saveErrorMessage(e: unknown): string {
if (!(e instanceof ApiFetchError)) return SAVE_ERROR_DEFAULT;
const { status } = e;
if (status === 422) {
const errorList = stringDetails(e.details, "error_list_id");
if (errorList !== null) {
return errorList.includes(NOT_SELECTABLE_DETAIL)
? SAVE_ERROR_LIST_NOT_SELECTABLE
: SAVE_ERROR_LIST_UNAVAILABLE;
}
const note = stringDetails(e.details, "note");
if (note !== null) {
return note.some((m) => m.includes(NOTE_LENGTH_DETAIL_MARKER))
? SAVE_ERROR_NOTE_TOO_LONG
: NOTE_REQUIRED_ROW_MESSAGE;
}
return e.serverMessage ?? SAVE_ERROR_VALIDATION_FALLBACK;
}
if (status === 404) return SAVE_ERROR_NOT_FOUND;
if (status === 401) return SAVE_ERROR_UNAUTHORIZED;
if (status >= 500) return SAVE_ERROR_SERVER;
if (status === 0) return SAVE_ERROR_NETWORK;
return SAVE_ERROR_DEFAULT;
}
/** 行の文言を表示する(`t()` を通し、備考の長さの `{n}` を埋める)。★保存前の検証の文言もこれで出す★ */
export function renderSaveErrorMessage(message: string, t: (text: string) => string): string {
const text = t(message);
return message === SAVE_ERROR_NOTE_TOO_LONG
? text.replace("{n}", String(PRECHECK_NOTE_MAX_LENGTH))
: text;
}