chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import type { Result } from "../../application/result.ts";
|
||||
import type { AppFailure } from "../../contracts/errors.ts";
|
||||
import {
|
||||
formatMessage,
|
||||
type ParameterlessMessageKey,
|
||||
} from "../i18n/index.ts";
|
||||
|
||||
export type FormValues = Readonly<Record<string, unknown>>;
|
||||
export type FieldName<Values extends FormValues> = Extract<keyof Values, string>;
|
||||
export type FieldErrors<Values extends FormValues> = Readonly<
|
||||
Partial<Record<FieldName<Values>, string>>
|
||||
>;
|
||||
|
||||
export type FormResult<Value> = Result<Value>;
|
||||
|
||||
export type FormResultState =
|
||||
| "idle"
|
||||
| "success"
|
||||
| "validation-error"
|
||||
| "conflict"
|
||||
| "effect-unknown"
|
||||
| "unavailable";
|
||||
|
||||
export type MappedValidationFailure<Values extends FormValues> = Readonly<{
|
||||
fieldErrors: FieldErrors<Values>;
|
||||
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<Values extends FormValues>(
|
||||
failure: AppFailure,
|
||||
allowedFields: readonly FieldName<Values>[],
|
||||
message: MessageResolver = defaultMessage,
|
||||
): MappedValidationFailure<Values> {
|
||||
if (failure.kind !== "VALIDATION_REJECTED") {
|
||||
return Object.freeze({ fieldErrors: Object.freeze({}), formErrors: [] });
|
||||
}
|
||||
const allowed = new Set<string>(allowedFields);
|
||||
const fieldErrors: Partial<Record<FieldName<Values>, 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<Values>;
|
||||
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)]),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user