From 4c3b953a3217cdb92561a627008f02e585af463a Mon Sep 17 00:00:00 2001 From: igalklebanov Date: Sat, 31 Dec 2022 02:55:37 +0200 Subject: [PATCH 01/40] `ZodTemplateLiteral` initial commit. --- deno/lib/README.md | 34 +++++++++---------- deno/lib/types.ts | 84 ++++++++++++++++++++++++++++++++++++++++++++-- src/types.ts | 80 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 21 deletions(-) diff --git a/deno/lib/README.md b/deno/lib/README.md index b502de431..d0a97ea10 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -47,8 +47,9 @@ - [Sponsors](#sponsors) - [Ecosystem](#ecosystem) - [Installation](#installation) - - [Node/npm](#nodenpm) - - [Deno](#deno) + - [Requirements](#requirements) + - [Node/npm](#from-npm-nodebun) + - [Deno](#from-denolandx-deno) - [Basic usage](#basic-usage) - [Primitives](#primitives) - [Literals](#literals) @@ -1294,7 +1295,7 @@ const myUnion = z.discriminatedUnion("status", [ z.object({ status: z.literal("failed"), error: z.instanceof(Error) }), ]); -myUnion.parse({ type: "success", data: "yippie ki yay" }); +myUnion.parse({ status: "success", data: "yippie ki yay" }); ``` ## Records @@ -1682,7 +1683,7 @@ Given any Zod schema, you can call its `.parse` method to check `data` is valid. const stringSchema = z.string(); stringSchema.parse("fish"); // => returns "fish" -stringSchema.parse(12); // throws Error('Non-string type: number'); +stringSchema.parse(12); // throws error ``` ### `.parseAsync` @@ -1692,11 +1693,10 @@ stringSchema.parse(12); // throws Error('Non-string type: number'); If you use asynchronous [refinements](#refine) or [transforms](#transform) (more on those later), you'll need to use `.parseAsync` ```ts -const stringSchema1 = z.string().refine(async (val) => val.length < 20); -const value1 = await stringSchema.parseAsync("hello"); // => hello +const stringSchema = z.string().refine(async (val) => val.length <= 8); -const stringSchema2 = z.string().refine(async (val) => val.length > 20); -const value2 = await stringSchema.parseAsync("hello"); // => throws +await stringSchema.parseAsync("hello"); // => returns "hello" +await stringSchema.parseAsync("hello world"); // => throws error ``` ### `.safeParse` @@ -1781,7 +1781,7 @@ type RefineParams = { }; ``` -For advanced cases, the second argument can also be a function that returns `RefineParams`/ +For advanced cases, the second argument can also be a function that returns `RefineParams`. ```ts const longString = z.string().refine( @@ -1892,7 +1892,7 @@ const Strings = z.array(z.string()).superRefine((val, ctx) => { You can add as many issues as you like. If `ctx.addIssue` is _not_ called during the execution of the function, validation passes. -Normally refinements always create issues with a `ZodIssueCode.custom` error code, but with `superRefine` you can create any issue of any code. Each issue code is described in detail in the Error Handling guide: [ERROR_HANDLING.md](ERROR_HANDLING.md). +Normally refinements always create issues with a `ZodIssueCode.custom` error code, but with `superRefine` it's possible to throw issues of any `ZodIssueCode`. Each issue code is described in detail in the Error Handling guide: [ERROR_HANDLING.md](ERROR_HANDLING.md). #### Abort early @@ -1921,7 +1921,7 @@ const schema = z.number().superRefine((val, ctx) => { #### Type refinements -If you provide a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to `.refine()` or `superRefine()`, the resulting type will be narrowed down to your predicate's type. This is useful if you are mixing multiple chained refinements and transformations: +If you provide a [type predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to `.refine()` or `.superRefine()`, the resulting type will be narrowed down to your predicate's type. This is useful if you are mixing multiple chained refinements and transformations: ```ts const schema = z @@ -1936,15 +1936,15 @@ const schema = z code: z.ZodIssueCode.custom, // customize your issue message: "object should exist", }); - return false; } - return true; + + return z.NEVER; // The return value is not used, but we need to return something to satisfy the typing }) // here, TS knows that arg is not null .refine((arg) => arg.first === "bob", "`first` is not `bob`!"); ``` -> ⚠️ You must **still** call `ctx.addIssue()` if using `superRefine()` with a type predicate function. Otherwise the refinement won't be validated. +> ⚠️ You **must** use `ctx.addIssue()` instead of returning a boolean value to indicate whether the validation passes. If `ctx.addIssue` is _not_ called during the execution of the function, validation passes. ### `.transform` @@ -1971,12 +1971,12 @@ emailToDomain.parse("colinhacks@example.com"); // => example.com #### Validating during transform -The `.transform` method can simultaneously validate and transform the value. This is often simpler and less duplicative than chaining `refine` and `validate`. +The `.transform` method can simultaneously validate and transform the value. This is often simpler and less duplicative than chaining `transform` and `refine`. As with `.superRefine`, the transform function receives a `ctx` object with a `addIssue` method that can be used to register validation issues. ```ts -const Strings = z.string().transform((val, ctx) => { +const numberInString = z.string().transform((val, ctx) => { const parsed = parseInt(val); if (isNaN(parsed)) { ctx.addIssue({ @@ -2464,7 +2464,7 @@ This more declarative API makes schema definitions vastly more concise. [https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes) -Good type inference support, but limited options for object type masking (no `.pick` , `.omit` , `.extend` , etc.). No support for `Record` s (their `Record` is equivalent to Zod's `object` ). They DO support branded and readonly types, which Zod does not. +Good type inference support, but limited options for object type masking (no `.pick` , `.omit` , `.extend` , etc.). No support for `Record` s (their `Record` is equivalent to Zod's `object` ). They DO support readonly types, which Zod does not. - Supports "pattern matching": computed properties that distribute over unions - Supports readonly types diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 895da711f..d5ece9bb4 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -1713,7 +1713,7 @@ export class ZodArray< if (ctx.common.async) { return Promise.all( - (ctx.data as any[]).map((item, i) => { + ([...ctx.data] as any[]).map((item, i) => { return def.type._parseAsync( new ParseInputLazyPath(ctx, item, ctx.path, i) ); @@ -1723,7 +1723,7 @@ export class ZodArray< }); } - const result = (ctx.data as any[]).map((item, i) => { + const result = ([...ctx.data] as any[]).map((item, i) => { return def.type._parseSync( new ParseInputLazyPath(ctx, item, ctx.path, i) ); @@ -4286,6 +4286,80 @@ export class ZodPipeline< } } +////////////////////////////////////////// +////////////////////////////////////////// +////////// ////////// +////////// ZodTemplateLiteral ////////// +////////// ////////// +////////////////////////////////////////// +////////////////////////////////////////// + +type TemplateLiteralPrimitivePart = + | string + | number + | bigint + | boolean + | null + | undefined; + +type TemplateLiteralZodTypePart = ZodType; + +type appendToTemplateLiteral< + Template extends string, + Suffix extends TemplateLiteralPrimitivePart | ZodType +> = Suffix extends TemplateLiteralPrimitivePart + ? `${Template}${Suffix}` + : Suffix extends ZodType + ? Output extends TemplateLiteralPrimitivePart + ? `${Template}${Output}` + : never + : never; + +export interface ZodTemplateLiteralDef extends ZodTypeDef { + parts: readonly (string | number | bigint | ZodType)[]; + typeName: ZodFirstPartyTypeKind.ZodTemplateLiteral; +} + +export class ZodTemplateLiteral