Skip to content

Commit

Permalink
fix[#1979]: Increment Email validation (#1982)
Browse files Browse the repository at this point in the history
* Increment email validation

Supported:
   - local-part -> Dot-string / Quoted-string
   - domain -> latin letters, numbers, "-" / IPv4 address
Unsupported:
   - domain -> IPv6

* Fix import type in some test files

The error generated is:
   TS2307: Cannot find module '@jest/globals' or
	its corresponding type declarations.

* Format code with prettier config

* Add more IPv4 test

* Add Invalid IPv4 tests

* Fix tests

* Lint fix

---------

Co-authored-by: Colin McDonnell <colinmcd94@gmail.com>
Co-authored-by: Colin McDonnell <colinmcd@alum.mit.edu>
  • Loading branch information
3 people authored Feb 8, 2023
1 parent 37d83e8 commit 1e5b29e
Show file tree
Hide file tree
Showing 13 changed files with 142 additions and 36 deletions.
1 change: 1 addition & 0 deletions deno/lib/__tests__/discriminatedUnions.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
const test = Deno.test;

Expand Down
1 change: 1 addition & 0 deletions deno/lib/__tests__/firstparty.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
const test = Deno.test;

Expand Down
11 changes: 7 additions & 4 deletions deno/lib/__tests__/partials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,13 @@ test("required with mask", () => {
expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional);
});


test("required with mask containing a nonexistent key", () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string().optional(),
});
object.required({
age: true,
// @ts-expect-error should not accept unexpected keys.
Expand All @@ -201,17 +206,15 @@ test("required with mask -- ignore falsy values", () => {
field: z.string().optional().default("asdf"),
country: z.string().optional(),
});

// @ts-expect-error
const requiredObject = object.required({ age: true, country: false });
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNumber);
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault);
expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional);

});


test("partial with mask", async () => {
const object = z.object({
name: z.string(),
Expand Down
64 changes: 52 additions & 12 deletions deno/lib/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,62 @@ test("email validations", () => {
expect(() => email.parse("asdf@-asdf.com")).toThrow();
expect(() => email.parse("asdf@-a(sdf.com")).toThrow();
expect(() => email.parse("asdf@-asdf.com(")).toThrow();
expect(() =>
email.parse("pawan.anand@%9y83&#$%R&#$%R&%#$R%%^^%5rw3ewe.d.d.aaaa.wef.co")
).toThrow();
});

test("more email validations", () => {
const data = [
`"josé.arrañoça"@domain.com`,
`"сайт"@domain.com`,
`"💩"@domain.com`,
`"🍺🕺🎉"@domain.com`,
`poop@💩.la`,
`"🌮"@i❤️tacos.ws`,
"sss--asd@i❤️tacos.ws",
const validEmails = [
`very.common@example.com`,
`disposable.style.email.with+symbol@example.com`,
`other.email-with-hyphen@example.com`,
`fully-qualified-domain@example.com`,
`user.name+tag+sorting@example.com`,
`x@example.com`,
`example-indeed@strange-example.com`,
`test/test@test.com`,
`example@s.example`,
`" "@example.org`,
`"john..doe"@example.org`,
`mailhost!username@example.org`,
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
`user%example.com@example.org`,
`user-@example.org`,
`postmaster@[123.123.123.123]`,
`user@my-example.com`,
`a@b.cd`,
`work+user@mail.com`,
`user@[68.185.127.196]`,
`ipv4@[85.129.96.247]`,
`valid@[79.208.229.53]`,
];
const email = z.string().email();
for (const datum of data) {
email.parse(datum);
}
const invalidEmails = [
`Abc.example.com`,
`A@b@c@example.com`,
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
`just"not"right@example.com`,
`this is"not\allowed@example.com`,
`this\ still\"not\\allowed@example.com`,
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
`QA[icon]CHOCOLATE[icon]@test.com`,
`invalid@-start.com`,
`invalid@end.com-`,
`a.b@c.d`,
`invalid@[1.1.1.-1]`,
`invalid@[68.185.127.196.55]`,
`temp@[192.168.1]`,
`temp@[9.18.122.]`,
];
const emailSchema = z.string().email();
expect(
validEmails.every((email) => emailSchema.safeParse(email).success)
).toBe(true);
expect(
invalidEmails.every(
(email) => emailSchema.safeParse(email).success === false
)
).toBe(true);
});

test("url validations", () => {
Expand Down
1 change: 1 addition & 0 deletions deno/lib/__tests__/unions.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
const test = Deno.test;

Expand Down
12 changes: 9 additions & 3 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,12 @@ const uuidRegex =
// from https://stackoverflow.com/a/46181/1550155
// old version: too slow, didn't support unicode
// const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i;
//old email regex
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
// eslint-disable-next-line
export const emailRegex =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;

const emailRegex =
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|([^-]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}))$/;

// interface IsDateStringOptions extends StringDateOptions {
/**
Expand Down Expand Up @@ -4495,7 +4498,10 @@ export const coerce = {
number: ((arg) =>
ZodNumber.create({ ...arg, coerce: true })) as typeof ZodNumber["create"],
boolean: ((arg) =>
ZodBoolean.create({ ...arg, coerce: true })) as typeof ZodBoolean["create"],
ZodBoolean.create({
...arg,
coerce: true,
})) as typeof ZodBoolean["create"],
bigint: ((arg) =>
ZodBigInt.create({ ...arg, coerce: true })) as typeof ZodBigInt["create"],
date: ((arg) =>
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/discriminatedUnions.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { expect, test } from "@jest/globals";

import * as z from "../index";
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/firstparty.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { test } from "@jest/globals";

import { util } from "../helpers/util";
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/object-in-es5-env.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { expect, test } from "@jest/globals";

import * as z from "../index";
Expand Down
8 changes: 6 additions & 2 deletions src/__tests__/partials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ test("required with mask", () => {
});

test("required with mask containing a nonexistent key", () => {
const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string().optional(),
});
object.required({
age: true,
// @ts-expect-error should not accept unexpected keys.
Expand All @@ -193,15 +199,13 @@ test("required with mask containing a nonexistent key", () => {
});

test("required with mask -- ignore falsy values", () => {

const object = z.object({
name: z.string(),
age: z.number().optional(),
field: z.string().optional().default("asdf"),
country: z.string().optional(),
});


// @ts-expect-error
const requiredObject = object.required({ age: true, country: false });
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
Expand Down
64 changes: 52 additions & 12 deletions src/__tests__/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,62 @@ test("email validations", () => {
expect(() => email.parse("asdf@-asdf.com")).toThrow();
expect(() => email.parse("asdf@-a(sdf.com")).toThrow();
expect(() => email.parse("asdf@-asdf.com(")).toThrow();
expect(() =>
email.parse("pawan.anand@%9y83&#$%R&#$%R&%#$R%%^^%5rw3ewe.d.d.aaaa.wef.co")
).toThrow();
});

test("more email validations", () => {
const data = [
`"josé.arrañoça"@domain.com`,
`"сайт"@domain.com`,
`"💩"@domain.com`,
`"🍺🕺🎉"@domain.com`,
`poop@💩.la`,
`"🌮"@i❤️tacos.ws`,
"sss--asd@i❤️tacos.ws",
const validEmails = [
`very.common@example.com`,
`disposable.style.email.with+symbol@example.com`,
`other.email-with-hyphen@example.com`,
`fully-qualified-domain@example.com`,
`user.name+tag+sorting@example.com`,
`x@example.com`,
`example-indeed@strange-example.com`,
`test/test@test.com`,
`example@s.example`,
`" "@example.org`,
`"john..doe"@example.org`,
`mailhost!username@example.org`,
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
`user%example.com@example.org`,
`user-@example.org`,
`postmaster@[123.123.123.123]`,
`user@my-example.com`,
`a@b.cd`,
`work+user@mail.com`,
`user@[68.185.127.196]`,
`ipv4@[85.129.96.247]`,
`valid@[79.208.229.53]`,
];
const email = z.string().email();
for (const datum of data) {
email.parse(datum);
}
const invalidEmails = [
`Abc.example.com`,
`A@b@c@example.com`,
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
`just"not"right@example.com`,
`this is"not\allowed@example.com`,
`this\ still\"not\\allowed@example.com`,
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
`QA[icon]CHOCOLATE[icon]@test.com`,
`invalid@-start.com`,
`invalid@end.com-`,
`a.b@c.d`,
`invalid@[1.1.1.-1]`,
`invalid@[68.185.127.196.55]`,
`temp@[192.168.1]`,
`temp@[9.18.122.]`,
];
const emailSchema = z.string().email();
expect(
validEmails.every((email) => emailSchema.safeParse(email).success)
).toBe(true);
expect(
invalidEmails.every(
(email) => emailSchema.safeParse(email).success === false
)
).toBe(true);
});

test("url validations", () => {
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/unions.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-ignore TS6133
import { expect, test } from "@jest/globals";

import * as z from "../index";
Expand Down
12 changes: 9 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,9 +519,12 @@ const uuidRegex =
// from https://stackoverflow.com/a/46181/1550155
// old version: too slow, didn't support unicode
// const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i;
//old email regex
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
// eslint-disable-next-line
export const emailRegex =
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;

const emailRegex =
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|([^-]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}))$/;

// interface IsDateStringOptions extends StringDateOptions {
/**
Expand Down Expand Up @@ -4495,7 +4498,10 @@ export const coerce = {
number: ((arg) =>
ZodNumber.create({ ...arg, coerce: true })) as typeof ZodNumber["create"],
boolean: ((arg) =>
ZodBoolean.create({ ...arg, coerce: true })) as typeof ZodBoolean["create"],
ZodBoolean.create({
...arg,
coerce: true,
})) as typeof ZodBoolean["create"],
bigint: ((arg) =>
ZodBigInt.create({ ...arg, coerce: true })) as typeof ZodBigInt["create"],
date: ((arg) =>
Expand Down

0 comments on commit 1e5b29e

Please sign in to comment.