Review a shared input validation refactor

src/account-validation.tsTypeScript
@@ -0,0 +1,53 @@
1+export type AccountField = "email" | "displayName";
2+
3+export class ValidationError extends Error {
4+ constructor(
5+ public readonly code: "INVALID_INPUT",
6+ public readonly field: AccountField | "input",
7+ ) {
8+ super(code);
9+ }
10+}
11+
12+export interface AccountValidationInput {
13+ email?: string | null;
14+ displayName?: string | null;
15+}
16+
17+export interface NormalizedAccountInput {
18+ email?: string;
19+ displayName?: string;
20+}
21+
22+function normalize(
23+ value: string | null | undefined,
24+ maximumLength: number,
25+ field: AccountField,
26+): string | undefined {
27+ if (!value) {
28+ return undefined;
29+ }
30+
31+ if (value.length > maximumLength) {
32+ throw new ValidationError("INVALID_INPUT", field);
33+ }
34+
35+ return value.trim();
36+}
37+
38+export function validateAccountInput(
39+ input: AccountValidationInput,
40+): NormalizedAccountInput {
41+ const email = normalize(input.email, 254, "email");
42+ const displayName = normalize(input.displayName, 40, "displayName");
43+
44+ if (email !== undefined && !/^[^@\s]+@[^@\s]+$/.test(email)) {
45+ throw new ValidationError("INVALID_INPUT", "email");
46+ }
47+
48+ if (displayName !== undefined && displayName.length === 0) {
49+ throw new ValidationError("INVALID_INPUT", "displayName");
50+ }
51+
52+ return { email, displayName };
53+}