From 60a630c07f3a222ef263c69ee3292e30c68228a5 Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Sun, 15 Jan 2023 00:18:02 +0200 Subject: [PATCH 1/4] add `.includes(...)` @ `ZodString`. --- deno/lib/README.md | 138 ++++++++++++++++++------------ deno/lib/ZodError.ts | 1 + deno/lib/__tests__/string.test.ts | 8 +- deno/lib/locales/en.ts | 8 +- deno/lib/types.ts | 20 +++++ src/ZodError.ts | 1 + src/__tests__/string.test.ts | 8 +- src/locales/en.ts | 8 +- src/types.ts | 20 +++++ 9 files changed, 154 insertions(+), 58 deletions(-) diff --git a/deno/lib/README.md b/deno/lib/README.md index 49f747e9c..46eff83b8 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -52,6 +52,7 @@ - [Deno](#from-denolandx-deno) - [Basic usage](#basic-usage) - [Primitives](#primitives) +- [Coercion for primitives](#coercion-for-primitives) - [Literals](#literals) - [Strings](#strings) - [Numbers](#numbers) @@ -361,6 +362,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`zod-formik-adapter`](https://github.com/robertLichtnow/zod-formik-adapter): A community-maintained Formik adapter for Zod. - [`react-zorm`](https://github.com/esamattis/react-zorm): Standalone `
` generation and validation for React using Zod. - [`zodix`](https://github.com/rileytomasek/zodix): Zod utilities for FormData and URLSearchParams in Remix loaders and actions. +- [`remix-params-helper`](https://github.com/kiliman/remix-params-helper): Simplify integration of Zod with standard URLSearchParams and FormData for Remix apps. - [`formik-validator-zod`](https://github.com/glazy/formik-validator-zod): Formik-compliant validator library that simplifies using Zod with Formik. - [`zod-i18n-map`](https://github.com/aiji42/zod-i18n): Useful for translating Zod error messages. - [`@modular-forms/solid`](https://github.com/fabian-hiller/modular-forms): Modular form library for SolidJS that supports Zod for validation. @@ -507,8 +509,53 @@ z.unknown(); z.never(); ``` +## Coercion for primitives + +Zod now provides a more convenient way to coerce primitive values. + +```ts +const schema = z.coerce.string(); +schema.parse("tuna"); // => "tuna" +schema.parse(12); // => "12" +schema.parse(true); // => "true" +``` + +During the parsing step, the input is passed through the `String()` function, which is a JavaScript built-in for coercing data into strings. Note that the returned schema is a `ZodString` instance so you can use all string methods. + +```ts +z.coerce.string().email().min(5); +``` + +All primitive types support coercion. + +```ts +z.coerce.string(); // String(input) +z.coerce.number(); // Number(input) +z.coerce.boolean(); // Boolean(input) +z.coerce.bigint(); // BigInt(input) +z.coerce.date(); // new Date(input) +``` + +**Boolean coercion** + +Zod's boolean coercion is very simple! It passes the value into the `Boolean(value)` function, that's it. Any truthy value will resolve to `true`, any falsy value will resolve to `false`. + +```ts +z.coerce.boolean().parse("tuna"); // => true +z.coerce.boolean().parse("true"); // => true +z.coerce.boolean().parse("false"); // => true +z.coerce.boolean().parse(1); // => true +z.coerce.boolean().parse([]); // => true + +z.coerce.boolean().parse(0); // => false +z.coerce.boolean().parse(undefined); // => false +z.coerce.boolean().parse(null); // => false +``` + ## Literals +Literals are zod's equivilant to [TypeScript's Literal Types](https://www.typescriptlang.org/docs/handbook/literal-types.html) which alow only the exact given type and value. + ```ts const tuna = z.literal("tuna"); const twelve = z.literal(12); @@ -568,49 +615,6 @@ z.string().endsWith(".com", { message: "Only .com domains allowed" }); z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); ``` -## Coercion for primitives - -Zod now provides a more convenient way to coerce primitive values. - -```ts -const schema = z.coerce.string(); -schema.parse("tuna"); // => "tuna" -schema.parse(12); // => "12" -schema.parse(true); // => "true" -``` - -During the parsing step, the input is passed through the `String()` function, which is a JavaScript built-in for coercing data into strings. Note that the returned schema is a `ZodString` instance so you can use all string methods. - -```ts -z.coerce.string().email().min(5); -``` - -All primitive types support coercion. - -```ts -z.coerce.string(); // String(input) -z.coerce.number(); // Number(input) -z.coerce.boolean(); // Boolean(input) -z.coerce.bigint(); // BigInt(input) -z.coerce.date(); // new Date(input) -``` - -**Boolean coercion** - -Zod's boolean coercion is very simple! It passes the value into the `Boolean(value)` function, that's it. Any truthy value will resolve to `true`, any falsy value will resolve to `false`. - -```ts -z.coerce.boolean().parse("tuna"); // => true -z.coerce.boolean().parse("true"); // => true -z.coerce.boolean().parse("false"); // => true -z.coerce.boolean().parse(1); // => true -z.coerce.boolean().parse([]); // => true - -z.coerce.boolean().parse(0); // => false -z.coerce.boolean().parse(undefined); // => false -z.coerce.boolean().parse(null); // => false -``` - ### Datetime validation The `z.string().datetime()` method defaults to UTC validation: no timezone offsets with arbitrary sub-second decimal precision. @@ -729,21 +733,28 @@ z.date().min(new Date("1900-01-01"), { message: "Too old" }); z.date().max(new Date(), { message: "Too young!" }); ``` -**Supporting date strings** +**Coercion to Date** -To write a schema that accepts either a `Date` or a date string, use [`z.preprocess`](#preprocess). +Since [zod 3.20](https://github.com/colinhacks/zod/releases/tag/v3.20), use [`z.coerce.date()`](#coercion-for-primitives) to pass the input through `new Date(input)`. ```ts -const dateSchema = z.preprocess((arg) => { - if (typeof arg == "string" || arg instanceof Date) return new Date(arg); -}, z.date()); -type DateSchema = z.infer; +const dateSchema = z.coerce.date() +type DateSchema = z.infer // type DateSchema = Date -dateSchema.safeParse(new Date("1/12/22")); // success: true -dateSchema.safeParse("2022-01-12T00:00:00.000Z"); // success: true +/* valid dates */ +console.log( dateSchema.safeParse( '2023-01-10T00:00:00.000Z' ).success ) // true +console.log( dateSchema.safeParse( '2023-01-10' ).success ) // true +console.log( dateSchema.safeParse( '1/10/23' ).success ) // true +console.log( dateSchema.safeParse( new Date( '1/10/23' ) ).success ) // true + +/* invalid dates */ +console.log( dateSchema.safeParse( '2023-13-10' ).success ) // false +console.log( dateSchema.safeParse( '0000-00-00' ).success ) // false ``` +For older zod versions, use [`z.preprocess`](#preprocess) like [described in this thread](https://github.com/colinhacks/zod/discussions/879#discussioncomment-2036276). + ## Zod enums ```ts @@ -786,7 +797,7 @@ FishEnum.enum; You can also retrieve the list of options as a tuple with the `.options` property: ```ts -FishEnum.options; // ["Salmon", "Tuna", "Trout"]); +FishEnum.options; // ["Salmon", "Tuna", "Trout"]; ``` ## Native enums @@ -1270,12 +1281,31 @@ stringOrNumber.parse(14); // passes Zod will test the input against each of the "options" in order and return the first value that validates successfully. -For convenience, you can also use the `.or` method: +For convenience, you can also use the [`.or` method](#or): ```ts const stringOrNumber = z.string().or(z.number()); ``` +**Optional string validation:** + +To validate an optional form input, you can union the desired string validation with an empty string [literal](#literals). + +This example validates an input that is optional but needs to contain a [valid URL](#strings): + +```ts +const optionalUrl = z.union( [ + z.string().url().nullish(), + z.literal( '' ), +] ) + +console.log( optionalUrl.safeParse( undefined ).success ) // true +console.log( optionalUrl.safeParse( null ).success ) // true +console.log( optionalUrl.safeParse( '' ).success ) // true +console.log( optionalUrl.safeParse( 'https://zod.dev' ).success ) // true +console.log( optionalUrl.safeParse( 'not a valid url' ).success ) // false +``` + ## Discriminated unions A discriminated union is a union of object schemas that all share a particular key. @@ -2142,7 +2172,7 @@ z.promise(z.string()); ### `.or` -A convenience method for union types. +A convenience method for [union types](#unions). ```ts const stringOrNumber = z.string().or(z.number()); // string | number diff --git a/deno/lib/ZodError.ts b/deno/lib/ZodError.ts index 936fc7573..82a7de667 100644 --- a/deno/lib/ZodError.ts +++ b/deno/lib/ZodError.ts @@ -95,6 +95,7 @@ export type StringValidation = | "regex" | "cuid" | "datetime" + | { includes: string; position?: number } | { startsWith: string } | { endsWith: string }; diff --git a/deno/lib/__tests__/string.test.ts b/deno/lib/__tests__/string.test.ts index da4085a2d..999b388d0 100644 --- a/deno/lib/__tests__/string.test.ts +++ b/deno/lib/__tests__/string.test.ts @@ -7,7 +7,9 @@ import * as z from "../index.ts"; const minFive = z.string().min(5, "min5"); const maxFive = z.string().max(5, "max5"); const justFive = z.string().length(5); -const nonempty = z.string().nonempty("nonempty"); +const nonempty = z.string().min(1, "nonempty"); +const includes = z.string().includes("includes"); +const includesFromIndex2 = z.string().includes("includes", { position: 2 }); const startsWith = z.string().startsWith("startsWith"); const endsWith = z.string().endsWith("endsWith"); @@ -18,6 +20,8 @@ test("passing validations", () => { maxFive.parse("1234"); nonempty.parse("1"); justFive.parse("12345"); + includes.parse("XincludesXX"); + includesFromIndex2.parse("XXXincludesXX"); startsWith.parse("startsWithX"); endsWith.parse("XendsWith"); }); @@ -28,6 +32,8 @@ test("failing validations", () => { expect(() => nonempty.parse("")).toThrow(); expect(() => justFive.parse("1234")).toThrow(); expect(() => justFive.parse("123456")).toThrow(); + expect(() => includes.parse("XincludeXX")).toThrow(); + expect(() => includesFromIndex2.parse("XincludesXX")).toThrow(); expect(() => startsWith.parse("x")).toThrow(); expect(() => endsWith.parse("x")).toThrow(); }); diff --git a/deno/lib/locales/en.ts b/deno/lib/locales/en.ts index 348a84eeb..556a9dbc1 100644 --- a/deno/lib/locales/en.ts +++ b/deno/lib/locales/en.ts @@ -47,7 +47,13 @@ const errorMap: ZodErrorMap = (issue, _ctx) => { break; case ZodIssueCode.invalid_string: if (typeof issue.validation === "object") { - if ("startsWith" in issue.validation) { + if ("includes" in issue.validation) { + message = `Invalid input: must include "${issue.validation.includes}"`; + + if (typeof issue.validation.position === "number") { + message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`; + } + } else if ("startsWith" in issue.validation) { message = `Invalid input: must start with "${issue.validation.startsWith}"`; } else if ("endsWith" in issue.validation) { message = `Invalid input: must end with "${issue.validation.endsWith}"`; diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 62818249d..074ffc1e4 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -494,6 +494,7 @@ export type ZodStringCheck = | { kind: "url"; message?: string } | { kind: "uuid"; message?: string } | { kind: "cuid"; message?: string } + | { kind: "includes"; value: string; position?: number; message?: string } | { kind: "startsWith"; value: string; message?: string } | { kind: "endsWith"; value: string; message?: string } | { kind: "regex"; regex: RegExp; message?: string } @@ -694,6 +695,16 @@ export class ZodString extends ZodType { } } else if (check.kind === "trim") { input.data = input.data.trim(); + } else if (check.kind === "includes") { + if (!(input.data as string).includes(check.value, check.position)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { includes: check.value, position: check.position }, + message: check.message, + }); + status.dirty(); + } } else if (check.kind === "startsWith") { if (!(input.data as string).startsWith(check.value)) { ctx = this._getOrReturnCtx(input, ctx); @@ -798,6 +809,15 @@ export class ZodString extends ZodType { }); } + includes(value: string, options?: { message?: string; position?: number }) { + return this._addCheck({ + kind: "includes", + value: value, + position: options?.position, + ...errorUtil.errToObj(options?.message), + }); + } + startsWith(value: string, message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "startsWith", diff --git a/src/ZodError.ts b/src/ZodError.ts index 0886951cc..1871764cb 100644 --- a/src/ZodError.ts +++ b/src/ZodError.ts @@ -95,6 +95,7 @@ export type StringValidation = | "regex" | "cuid" | "datetime" + | { includes: string; position?: number } | { startsWith: string } | { endsWith: string }; diff --git a/src/__tests__/string.test.ts b/src/__tests__/string.test.ts index b20be70a6..a787d8c87 100644 --- a/src/__tests__/string.test.ts +++ b/src/__tests__/string.test.ts @@ -6,7 +6,9 @@ import * as z from "../index"; const minFive = z.string().min(5, "min5"); const maxFive = z.string().max(5, "max5"); const justFive = z.string().length(5); -const nonempty = z.string().nonempty("nonempty"); +const nonempty = z.string().min(1, "nonempty"); +const includes = z.string().includes("includes"); +const includesFromIndex2 = z.string().includes("includes", { position: 2 }); const startsWith = z.string().startsWith("startsWith"); const endsWith = z.string().endsWith("endsWith"); @@ -17,6 +19,8 @@ test("passing validations", () => { maxFive.parse("1234"); nonempty.parse("1"); justFive.parse("12345"); + includes.parse("XincludesXX"); + includesFromIndex2.parse("XXXincludesXX"); startsWith.parse("startsWithX"); endsWith.parse("XendsWith"); }); @@ -27,6 +31,8 @@ test("failing validations", () => { expect(() => nonempty.parse("")).toThrow(); expect(() => justFive.parse("1234")).toThrow(); expect(() => justFive.parse("123456")).toThrow(); + expect(() => includes.parse("XincludeXX")).toThrow(); + expect(() => includesFromIndex2.parse("XincludesXX")).toThrow(); expect(() => startsWith.parse("x")).toThrow(); expect(() => endsWith.parse("x")).toThrow(); }); diff --git a/src/locales/en.ts b/src/locales/en.ts index 6515f9849..3e8563ae2 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -47,7 +47,13 @@ const errorMap: ZodErrorMap = (issue, _ctx) => { break; case ZodIssueCode.invalid_string: if (typeof issue.validation === "object") { - if ("startsWith" in issue.validation) { + if ("includes" in issue.validation) { + message = `Invalid input: must include "${issue.validation.includes}"`; + + if (typeof issue.validation.position === "number") { + message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`; + } + } else if ("startsWith" in issue.validation) { message = `Invalid input: must start with "${issue.validation.startsWith}"`; } else if ("endsWith" in issue.validation) { message = `Invalid input: must end with "${issue.validation.endsWith}"`; diff --git a/src/types.ts b/src/types.ts index ae8eb3bc3..a163dbdad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -494,6 +494,7 @@ export type ZodStringCheck = | { kind: "url"; message?: string } | { kind: "uuid"; message?: string } | { kind: "cuid"; message?: string } + | { kind: "includes"; value: string; position?: number; message?: string } | { kind: "startsWith"; value: string; message?: string } | { kind: "endsWith"; value: string; message?: string } | { kind: "regex"; regex: RegExp; message?: string } @@ -694,6 +695,16 @@ export class ZodString extends ZodType { } } else if (check.kind === "trim") { input.data = input.data.trim(); + } else if (check.kind === "includes") { + if (!(input.data as string).includes(check.value, check.position)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { includes: check.value, position: check.position }, + message: check.message, + }); + status.dirty(); + } } else if (check.kind === "startsWith") { if (!(input.data as string).startsWith(check.value)) { ctx = this._getOrReturnCtx(input, ctx); @@ -798,6 +809,15 @@ export class ZodString extends ZodType { }); } + includes(value: string, options?: { message?: string; position?: number }) { + return this._addCheck({ + kind: "includes", + value: value, + position: options?.position, + ...errorUtil.errToObj(options?.message), + }); + } + startsWith(value: string, message?: errorUtil.ErrMessage) { return this._addCheck({ kind: "startsWith", From ecf833a9a0f8a12888b976ea9d6d1ed601575691 Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Sun, 15 Jan 2023 00:30:12 +0200 Subject: [PATCH 2/4] update README.md. --- README.md | 1 + deno/lib/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 46eff83b8..89d63e20f 100644 --- a/README.md +++ b/README.md @@ -584,6 +584,7 @@ z.string().url(); z.string().uuid(); z.string().cuid(); z.string().regex(regex); +z.string().includes(string); z.string().startsWith(string); z.string().endsWith(string); z.string().trim(); // trim whitespace diff --git a/deno/lib/README.md b/deno/lib/README.md index 46eff83b8..89d63e20f 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -584,6 +584,7 @@ z.string().url(); z.string().uuid(); z.string().cuid(); z.string().regex(regex); +z.string().includes(string); z.string().startsWith(string); z.string().endsWith(string); z.string().trim(); // trim whitespace From 3415bd855851e17384481dcd52f10b762702188f Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Sun, 15 Jan 2023 00:35:38 +0200 Subject: [PATCH 3/4] update README.md. --- README.md | 1 + deno/lib/README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 89d63e20f..6ab4f323d 100644 --- a/README.md +++ b/README.md @@ -611,6 +611,7 @@ z.string().length(5, { message: "Must be exactly 5 characters long" }); z.string().email({ message: "Invalid email address" }); z.string().url({ message: "Invalid url" }); z.string().uuid({ message: "Invalid UUID" }); +z.string().includes("tuna", { message: "Must include tuna" }); z.string().startsWith("https://", { message: "Must provide secure URL" }); z.string().endsWith(".com", { message: "Only .com domains allowed" }); z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); diff --git a/deno/lib/README.md b/deno/lib/README.md index 89d63e20f..6ab4f323d 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -611,6 +611,7 @@ z.string().length(5, { message: "Must be exactly 5 characters long" }); z.string().email({ message: "Invalid email address" }); z.string().url({ message: "Invalid url" }); z.string().uuid({ message: "Invalid UUID" }); +z.string().includes("tuna", { message: "Must include tuna" }); z.string().startsWith("https://", { message: "Must provide secure URL" }); z.string().endsWith(".com", { message: "Only .com domains allowed" }); z.string().datetime({ message: "Invalid datetime string! Must be UTC." }); From 8b4c2da1e9465226df1787a2270adf957b49d0af Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Wed, 8 Feb 2023 01:10:18 +0200 Subject: [PATCH 4/4] fix prettier error @ benchmarks primitives. --- deno/lib/benchmarks/primitives.ts | 16 +++++++++++++--- deno/lib/types.ts | 2 +- src/benchmarks/primitives.ts | 16 +++++++++++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/deno/lib/benchmarks/primitives.ts b/deno/lib/benchmarks/primitives.ts index 734dfe2bb..c91b50114 100644 --- a/deno/lib/benchmarks/primitives.ts +++ b/deno/lib/benchmarks/primitives.ts @@ -79,7 +79,10 @@ numberSuite const dateSuite = new Benchmark.Suite("z.date"); const plainDate = z.date(); -const minMaxDate = z.date().min(new Date("2021-01-01")).max(new Date("2030-01-01")); +const minMaxDate = z + .date() + .min(new Date("2021-01-01")) + .max(new Date("2030-01-01")); dateSuite .add("valid", () => { @@ -112,7 +115,7 @@ const symbolSchema = z.symbol(); symbolSuite .add("valid", () => { - symbolSchema.parse(val.symbol) + symbolSchema.parse(val.symbol); }) .add("invalid", () => { try { @@ -124,5 +127,12 @@ symbolSuite }); export default { - suites: [enumSuite, undefinedSuite, literalSuite, numberSuite, dateSuite, symbolSuite], + suites: [ + enumSuite, + undefinedSuite, + literalSuite, + numberSuite, + dateSuite, + symbolSuite, + ], }; diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 46b4c9efd..26c7f2a2e 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -521,7 +521,7 @@ const uuidRegex = // 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; // eslint-disable-next-line -const emailRegex = +export const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i; // interface IsDateStringOptions extends StringDateOptions { diff --git a/src/benchmarks/primitives.ts b/src/benchmarks/primitives.ts index 62809e275..5b0034f1d 100644 --- a/src/benchmarks/primitives.ts +++ b/src/benchmarks/primitives.ts @@ -79,7 +79,10 @@ numberSuite const dateSuite = new Benchmark.Suite("z.date"); const plainDate = z.date(); -const minMaxDate = z.date().min(new Date("2021-01-01")).max(new Date("2030-01-01")); +const minMaxDate = z + .date() + .min(new Date("2021-01-01")) + .max(new Date("2030-01-01")); dateSuite .add("valid", () => { @@ -112,7 +115,7 @@ const symbolSchema = z.symbol(); symbolSuite .add("valid", () => { - symbolSchema.parse(val.symbol) + symbolSchema.parse(val.symbol); }) .add("invalid", () => { try { @@ -124,5 +127,12 @@ symbolSuite }); export default { - suites: [enumSuite, undefinedSuite, literalSuite, numberSuite, dateSuite, symbolSuite], + suites: [ + enumSuite, + undefinedSuite, + literalSuite, + numberSuite, + dateSuite, + symbolSuite, + ], };