import type { ApiFailure } from "../../contracts/errors.js"; import { formatMessage, type ParameterlessMessageKey, } from "../i18n/index.js"; export type FormValues = Readonly>; export type FieldName = Extract; export type FieldErrors = Readonly< Partial, string>> >; export type FormResult = | Readonly<{ ok: true; value: Value }> | Readonly<{ ok: false; error: ApiFailure }>; export type FormResultState = | "idle" | "success" | "validation-error" | "conflict" | "unavailable"; export type MappedValidationFailure = Readonly<{ fieldErrors: FieldErrors; formErrors: readonly string[]; }>; const VALIDATION_COPY = Object.freeze({ REQUIRED: "form.validation.required", too_small: "form.validation.tooSmall", too_big: "form.validation.tooBig", invalid_type: "form.validation.invalidType", invalid_format: "form.validation.invalidFormat", invalid_value: "form.validation.invalidValue", }); type MessageResolver = (key: ParameterlessMessageKey) => string; const defaultMessage: MessageResolver = (key) => formatMessage("ko-KR", key); export function validationMessage( code: string, message: MessageResolver = defaultMessage, ): string { return message( VALIDATION_COPY[code as keyof typeof VALIDATION_COPY] ?? "form.validation.unknown", ); } export function mapValidationFailureToFields( failure: ApiFailure, allowedFields: readonly FieldName[], message: MessageResolver = defaultMessage, ): MappedValidationFailure { if (failure.kind !== "VALIDATION_REJECTED") { return Object.freeze({ fieldErrors: Object.freeze({}), formErrors: [] }); } const allowed = new Set(allowedFields); const fieldErrors: Partial, string>> = {}; const formErrors: string[] = []; const issues = failure.validationIssues ?? []; if (issues.length === 0) { formErrors.push(message("form.validation.retry")); } for (const issue of issues) { const field = issue.path.split(".").at(0) ?? ""; if (allowed.has(field)) { const name = field as FieldName; fieldErrors[name] ??= validationMessage(issue.code, message); } else { formErrors.push(message("form.validation.unknownField")); } } return Object.freeze({ fieldErrors: Object.freeze(fieldErrors), formErrors: Object.freeze([...new Set(formErrors)]), }); }