File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
/**
* 오류 객체 → 화면 문구.
*
* 검증기와 분리한 이유는 Angular가 `Validators`에 문구를 두지 않은 이유와 같다 — 같은 규칙도
* 자리에 따라 다른 말로 안내해야 한다("10자 이상"과 "비밀번호는 10자 이상"). 검증기는 무엇이
* 틀렸는지만 말하고, 그것을 무슨 말로 옮길지는 여기서(또는 호출부의 `overrides`로) 정한다.
*
* 한 번에 **한 문구만** 낸다 — 입력 칸 아래 헬퍼 자리가 한 줄이고, 오류를 쌓아 보여주면 어느
* 것부터 고쳐야 할지 알기 어렵다. 순서는 `MESSAGE_ORDER`가 정한다(비어 있음 → 길이 → 조합).
*/
import type { FoxValidationErrors } from './fox-validators';
/** 오류 키별 기본 문구. 값이 필요한 것은 오류 객체의 맥락을 받아 만든다. */
type MessageFactory = (detail: unknown) => string;
function detailOf<T>(detail: unknown): T {
return detail as T;
}
const DEFAULT_MESSAGES: Record<string, MessageFactory> = {
required: () => '필수 입력 항목입니다.',
minlength: (detail) =>
`${detailOf<{ requiredLength: number }>(detail).requiredLength}자 이상 입력해 주세요.`,
maxlength: (detail) =>
`${detailOf<{ requiredLength: number }>(detail).requiredLength}자 이내로 입력해 주세요.`,
pattern: () => '형식이 올바르지 않습니다.',
email: () => '이메일 형식이 올바르지 않습니다.',
characterKinds: (detail) =>
`영문, 숫자, 특수문자 중 ${
detailOf<{ requiredKinds: number }>(detail).requiredKinds
}종류 이상을 섞어 주세요.`,
repeatedCharacters: (detail) =>
`같은 문자를 ${detailOf<{ max: number }>(detail).max}번까지만 연달아 쓸 수 있습니다.`,
sequentialCharacters: (detail) =>
`연속된 문자나 숫자를 ${
detailOf<{ max: number }>(detail).max
}자를 넘겨 쓸 수 없습니다.`,
notContaining: (detail) =>
`${detailOf<{ forbidden: string }>(detail).forbidden}은(는) 포함할 수 없습니다.`,
equalTo: () => '값이 일치하지 않습니다.',
};
/** 먼저 고쳐야 하는 것부터. 여기 없는 키는 뒤에 남은 순서대로 본다. */
const MESSAGE_ORDER = [
'required',
'minlength',
'maxlength',
'pattern',
'email',
'characterKinds',
'repeatedCharacters',
'sequentialCharacters',
'notContaining',
'equalTo',
];
/** 오류 키 → 문구 덮어쓰기. 문자열이면 그대로, 함수면 맥락을 받아 만든다. */
export type FoxValidationMessages = Record<
string,
string | ((detail: unknown) => string)
>;
/**
* 오류 객체에서 보여줄 문구 하나를 고른다. 오류가 없으면 `undefined`.
* 덮어쓰기에 없는 키는 기본 문구로 떨어지고, 기본 문구에도 없으면 키를 그대로 쓰지 않고
* `undefined`를 돌려준다 — 정체불명의 영문 키를 사용자에게 보이지 않기 위해서다.
*/
export function foxValidationMessage(
errors: FoxValidationErrors | null,
overrides: FoxValidationMessages = {}
): string | undefined {
if (!errors) {
return undefined;
}
const keys = Object.keys(errors);
const ordered = [
...MESSAGE_ORDER.filter((key) => keys.includes(key)),
...keys.filter((key) => !MESSAGE_ORDER.includes(key)),
];
for (const key of ordered) {
const override = overrides[key];
if (typeof override === 'string') {
return override;
}
if (typeof override === 'function') {
return override(errors[key]);
}
const fallback = DEFAULT_MESSAGES[key];
if (fallback) {
return fallback(errors[key]);
}
}
return undefined;
}