Skip to content

Commit

Permalink
Added e164 validation
Browse files Browse the repository at this point in the history
  • Loading branch information
maurer2 authored and colinhacks committed May 16, 2024
1 parent 9f20922 commit dc2aaee
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 0 deletions.
1 change: 1 addition & 0 deletions deno/lib/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export type StringValidation =
| "duration"
| "ip"
| "base64"
| "e164"
| { includes: string; position?: number }
| { startsWith: string }
| { endsWith: string };
Expand Down
37 changes: 37 additions & 0 deletions deno/lib/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,3 +906,40 @@ test("IP validation", () => {
invalidIPs.every((ip) => ipSchema.safeParse(ip).success === false)
).toBe(true);
});

test("E.164 validation", () => {
const e164Number = z.string().e164();
expect(e164Number.safeParse("+1555555").success).toBe(true);

const validE164Numbers = [
"+1555555", // min-length (7 + 1)
"+15555555",
"+155555555",
"+1555555555",
"+15555555555",
"+155555555555",
"+1555555555555",
"+15555555555555",
"+155555555555555",
"+105555555555555",
"+100555555555555", // max-length (15 + 1)
];

const invalidE164Numbers = [
"", // empty
"+", // only plus sign
"-", // wrong sign
"555555555", // missing plus sign
"+1 555 555 555", // space after plus sign
"+1555 555 555", // space after plus sign
"+1555 555 555", // space between numbers
"+1555+555", // multiple plus signs
"+1555555555555555", // too long
"+115abc55", // non numeric characters in number part
];

expect(validE164Numbers.every((number) => e164Number.safeParse(number).success)).toBe(true);
expect(
invalidE164Numbers.every((number) => e164Number.safeParse(number).success === false)
).toBe(true);
});
26 changes: 26 additions & 0 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,11 @@ export type ZodStringCheck =
| { kind: "duration"; message?: string }
| { kind: "ip"; version?: IpVersion; message?: string }
| { kind: "base64"; message?: string }
<<<<<<< HEAD
| { kind: "json"; message?: string };
=======
| { kind: "e164"; message?: string };
>>>>>>> 84694186 (Added e164 validation)

export interface ZodStringDef extends ZodTypeDef {
checks: ZodStringCheck[];
Expand Down Expand Up @@ -618,9 +622,14 @@ const ipv6Regex =
const base64Regex =
/^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;

<<<<<<< HEAD
// based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
const hostnameRegex =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
=======
// https://blog.stevenlevithan.com/archives/validate-phone-number#r4-3 (regex from there allows spaces)
const e164Regex = /^\+(?:[0-9]){6,14}[0-9]$/;
>>>>>>> 84694186 (Added e164 validation)

// simple
// const dateRegexSource = `\\d{4}-\\d{2}-\\d{2}`;
Expand Down Expand Up @@ -1035,6 +1044,16 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
message: check.message,
});
}
} else if (check.kind === "e164") {
if (!e164Regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "e164",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
Expand Down Expand Up @@ -1122,6 +1141,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
}

e164(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "e164", ...errorUtil.errToObj(message) });
}

datetime(
options?:
| string
Expand Down Expand Up @@ -1353,6 +1376,9 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
get isBase64() {
return !!this._def.checks.find((ch) => ch.kind === "base64");
}
get isE164() {
return !!this._def.checks.find((ch) => ch.kind === "e164");
}

get minLength() {
let min: number | null = null;
Expand Down
1 change: 1 addition & 0 deletions src/ZodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export type StringValidation =
| "duration"
| "ip"
| "base64"
| "e164"
| { includes: string; position?: number }
| { startsWith: string }
| { endsWith: string };
Expand Down
37 changes: 37 additions & 0 deletions src/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -905,3 +905,40 @@ test("IP validation", () => {
invalidIPs.every((ip) => ipSchema.safeParse(ip).success === false)
).toBe(true);
});

test("E.164 validation", () => {
const e164Number = z.string().e164();
expect(e164Number.safeParse("+1555555").success).toBe(true);

const validE164Numbers = [
"+1555555", // min-length (7 + 1)
"+15555555",
"+155555555",
"+1555555555",
"+15555555555",
"+155555555555",
"+1555555555555",
"+15555555555555",
"+155555555555555",
"+105555555555555",
"+100555555555555", // max-length (15 + 1)
];

const invalidE164Numbers = [
"", // empty
"+", // only plus sign
"-", // wrong sign
"555555555", // missing plus sign
"+1 555 555 555", // space after plus sign
"+1555 555 555", // space after plus sign
"+1555 555 555", // space between numbers
"+1555+555", // multiple plus signs
"+1555555555555555", // too long
"+115abc55", // non numeric characters in number part
];

expect(validE164Numbers.every((number) => e164Number.safeParse(number).success)).toBe(true);
expect(
invalidE164Numbers.every((number) => e164Number.safeParse(number).success === false)
).toBe(true);
});
20 changes: 20 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ export type ZodStringCheck =
| { kind: "ip"; version?: IpVersion; message?: string }
| { kind: "base64"; message?: string }
| { kind: "json"; message?: string };
| { kind: "e164"; message?: string };

export interface ZodStringDef extends ZodTypeDef {
checks: ZodStringCheck[];
Expand Down Expand Up @@ -621,6 +622,8 @@ const base64Regex =
// based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
const hostnameRegex =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)+([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
// https://blog.stevenlevithan.com/archives/validate-phone-number#r4-3 (regex from there allows spaces)
const e164Regex = /^\+(?:[0-9]){6,14}[0-9]$/;

// simple
// const dateRegexSource = `\\d{4}-\\d{2}-\\d{2}`;
Expand Down Expand Up @@ -1035,6 +1038,16 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
message: check.message,
});
}
} else if (check.kind === "e164") {
if (!e164Regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "e164",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
Expand Down Expand Up @@ -1122,6 +1135,10 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
}

e164(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "e164", ...errorUtil.errToObj(message) });
}

datetime(
options?:
| string
Expand Down Expand Up @@ -1353,6 +1370,9 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
get isBase64() {
return !!this._def.checks.find((ch) => ch.kind === "base64");
}
get isE164() {
return !!this._def.checks.find((ch) => ch.kind === "e164");
}

get minLength() {
let min: number | null = null;
Expand Down

0 comments on commit dc2aaee

Please sign in to comment.