Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge add zodtemplateliteral igalklebanov #1

Merged
merged 51 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4c3b953
`ZodTemplateLiteral` initial commit.
igalklebanov Dec 31, 2022
6aeb3e4
`ZodTemplateLiteral` regex building & parsing.
igalklebanov Dec 31, 2022
d87d819
add `ZodTemplateLiteral` @ firstparty.test.
igalklebanov Dec 31, 2022
a04487d
some inference test cases for `ZodTemplateLiteral`.
igalklebanov Jan 1, 2023
2dee64e
Merge branch 'master' into template-literal
igalklebanov Jan 1, 2023
8f4dfd6
Merge branch 'master' into template-literal
igalklebanov Jan 3, 2023
9c0d1e8
append to regexString instead of rebuild @ `ZodTemplateLiteral.addPart`.
igalklebanov Jan 4, 2023
40be49a
initial parse tests for `ZodTemplateLiteral`.
igalklebanov Jan 4, 2023
6a0248c
"internalize" `addPart`, add `addLiteral` & `addInterpolatedPosition`.
igalklebanov Jan 6, 2023
d125352
move `ZodTemplateLiteral` unit test cases and minor fixes.
igalklebanov Jan 7, 2023
ded7abc
minor fixes, more tests.
igalklebanov Jan 8, 2023
a733f54
minor fixes, more tests pt. 2.
igalklebanov Jan 8, 2023
3e41e01
more tests pt. 3.
igalklebanov Jan 12, 2023
f1c9f01
more tests pt. 4.
igalklebanov Jan 13, 2023
3e95eee
add `ZodTemplateLiteral` README.md section.
igalklebanov Jan 13, 2023
b601918
add a simpler example @ ZodTemplateLiteral docs.
igalklebanov Jan 13, 2023
48242c0
add regex limitations remark.
igalklebanov Jan 13, 2023
6c5e1b0
allow coercion to ZodTemplateLiteral.
igalklebanov Jan 14, 2023
5889229
minor readability pass on number stuff @ ZodTemplateLiteral.
igalklebanov Jan 14, 2023
5b3b825
add ZodBranded support @ ZodTemplateLiteral.
igalklebanov Jan 14, 2023
c5ff237
support ZodAny @ ZodTemplateLiteral.
igalklebanov Jan 14, 2023
cc0b366
support ZodAny @ ZodTemplateLiteral.
igalklebanov Jan 14, 2023
762d5b6
minor README.md changes.
igalklebanov Jan 14, 2023
7b83117
add ZodTemplateLiteral coerce test.
igalklebanov Jan 14, 2023
153157a
add ZodTemplateLiteral custom errors for unsupported stuff.
igalklebanov Jan 19, 2023
e869080
add ZodTemplateLiteral custom errors for unsupported stuff. pt. 2.
igalklebanov Jan 19, 2023
859b623
add ZodTemplateLiteral custom errors for unsupported stuff pt. 3.
igalklebanov Jan 19, 2023
a58e4ac
use official MDN way of escaping for regex.
igalklebanov Jan 19, 2023
703b8ac
explicitly state exponent notation is not supported @ ZodTemplateLiteral
igalklebanov Jan 19, 2023
95d25d9
add missing `.toThrow()` @ ZodTemplateLiteral tests.
igalklebanov Jan 19, 2023
ed18f17
explicitly state `.trim()` is not supported @ ZodTemplateLiteral.
igalklebanov Jan 19, 2023
5fad401
fix mongodb connection string example and tests.
igalklebanov Jan 20, 2023
70913d3
add measurment example from README to ZodTemplateLiteral tests.
igalklebanov Jan 20, 2023
ff298a1
extract ZodTemplateLiteral errors to ZodError.ts
igalklebanov Jan 20, 2023
99458bc
Merge branch 'master' into template-literal
igalklebanov Feb 7, 2023
094dccb
add `z.string().cuid2()` support.
igalklebanov Feb 7, 2023
72c112b
fix prettier error @ benchmarks primitives.
igalklebanov Feb 7, 2023
97c0539
Merge branch 'master' into template-literal
igalklebanov Feb 11, 2023
86e2963
Merge branch 'master' into template-literal
igalklebanov Mar 2, 2023
2920ce1
Merge branch 'master' into template-literal
igalklebanov Mar 22, 2023
588f7dd
add support for new regex based string checks.
igalklebanov Mar 22, 2023
b959cf7
rename to `.interpolated` & `.literal`.
igalklebanov Mar 22, 2023
30d6ca7
rename to `.interpolated` & `.literal` pt. 2.
igalklebanov Mar 22, 2023
8c3c9cd
Merge branch 'colinhacks:master' into template-literal
igalklebanov May 22, 2023
b949f2a
handle case insensitive regexes.
igalklebanov May 22, 2023
e2ba25f
Merge branch 'colinhacks:master' into template-literal
igalklebanov Aug 7, 2023
a8e9afd
Merge branch 'master' into template-literal
igalklebanov Oct 27, 2023
0253718
prettier the readme.
igalklebanov Nov 30, 2023
a54a094
Merge remote-tracking branch 'zod/master' into template-literal
igalklebanov Nov 30, 2023
04ab765
Merge remote-tracking branch 'igalklebanov/template-literal' into mer…
charlescatta Apr 3, 2024
1ce249c
fix dulicate case in merge
charlescatta Apr 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
- [Promises](#promises)
- [Instanceof](#instanceof)
- [Functions](#functions)
- [Template Literals](#template-literals)
- [Preprocess](#preprocess)
- [Custom schemas](#custom-schemas)
- [Schema methods](#schema-methods)
Expand Down Expand Up @@ -2020,6 +2021,124 @@ myFunction.returnType();
* `args: ZodTuple` The first argument is a tuple (created with `z.tuple([...])` and defines the schema of the arguments to your function. If the function doesn't accept arguments, you can pass an empty tuple (`z.tuple([])`).
* `returnType: any Zod schema` The second argument is the function's return type. This can be any Zod schema. -->

## Template Literals

Building on the knowledge above, Zod supports creating typescript [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) with runtime validation. These types allow for stricter type checking of string inputs, as an alternative to `z.string()` which infers to a string.

A template literal type consists of [string literal types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) and interpolated positions (typescript types inside `${}` slots, e.g. `${number}`).

To create a template literal builder:

```ts
const templateLiteral = z.templateLiteral(); // infers to ``.
```

- To add string literal types to an existing template literal:

```ts
templateLiteral.literal("Hello"); // infers to `Hello`.
templateLiteral.literal(3.14); // infers to `3.14`.
```

This method accepts strings, numbers, booleans, nulls and undefined.

- To add interpolated positions to an existing template literal:

```ts
templateLiteral.interpolated(z.string()); // infers to `${string}`.
templateLiteral.interpolated(z.number()); // infers to `${number}`.
templateLiteral.interpolated(z.boolean()); // infers to `true` | `false`.
templateLiteral.interpolated(z.literal("foo")); // infers to `foo`.
templateLiteral.interpolated(z.null()); // infers to `null`.
templateLiteral.interpolated(z.undefined()); // infers to `undefined`.
templateLiteral.interpolated(z.bigint()); // infers to `${bigint}`.
templateLiteral.interpolated(z.any()); // infers to `${any}`.
```

Any Zod type (or union) with an underlying type of string, number, boolean, null,
undefined or bigint can be used as an interpolated position (template literals
included!). You can use additional built-in runtime validations (refinements
excluded) in each of these types and the template literal builder will do its
best (within the limitations of regular expressions) to support them when parsing.

### Examples

URL:

```ts
const url = z
.templateLiteral()
.literal("https://")
.interpolated(z.string().min(1))
.literal(".")
.interpolated(z.enum(["com", "net"]));
// infers to `https://${string}.com` | `https://${string}.net`.

url.parse("https://google.com"); // passes
url.parse("https://google.net"); // passes
url.parse("http://google.com"); // throws
url.parse("https://.com"); // throws
url.parse("https://google"); // throws
url.parse("https://google."); // throws
url.parse("https://google.gov"); // throws
```

Measurement:

```ts
const measurement = z.coerce
.templateLiteral()
.interpolated(z.number().finite())
.interpolated(
z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional()
);
// infers to `${number}` | `${number}px` | `${number}em` | `${number}rem` | `${number}vh` | `${number}vw` | `${number}vmin` | `${number}vmax
```

MongoDB connection string:

```ts
const connectionString = z
.templateLiteral()
.literal("mongodb://")
.interpolated(
z
.templateLiteral()
.interpolated(z.string().regex(/\w+/).describe("username"))
.literal(":")
.interpolated(z.string().regex(/\w+/).describe("password"))
.literal("@")
.optional()
)
.interpolated(z.string().regex(/\w+/).describe("host"))
.literal(":")
.interpolated(z.number().finite().int().positive().describe("port"))
.interpolated(
z
.templateLiteral()
.literal("/")
.interpolated(
z.string().regex(/\w+/).optional().describe("defaultauthdb")
)
.interpolated(
z
.templateLiteral()
.literal("?")
.interpolated(z.string().regex(/^\w+=\w+(&\w+=\w+)*$/))
.optional()
.describe("options")
)
.optional()
);
// infers to:
// | `mongodb://${string}:${number}`
// | `mongodb://${string}:${number}/${string}`
// | `mongodb://${string}:${number}/${string}?${string}`
// | `mongodb://${string}:${string}@${string}:${number}`
// | `mongodb://${string}:${string}@${string}:${number}/${string}`
// | `mongodb://${string}:${string}@${string}:${number}/${string}?${string}`
```

## Preprocess

> Zod now supports primitive coercion without the need for `.preprocess()`. See the [coercion docs](#coercion-for-primitives) for more information.
Expand Down
119 changes: 119 additions & 0 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
- [Promises](#promises)
- [Instanceof](#instanceof)
- [Functions](#functions)
- [Template Literals](#template-literals)
- [Preprocess](#preprocess)
- [Custom schemas](#custom-schemas)
- [Schema methods](#schema-methods)
Expand Down Expand Up @@ -2020,6 +2021,124 @@ myFunction.returnType();
* `args: ZodTuple` The first argument is a tuple (created with `z.tuple([...])` and defines the schema of the arguments to your function. If the function doesn't accept arguments, you can pass an empty tuple (`z.tuple([])`).
* `returnType: any Zod schema` The second argument is the function's return type. This can be any Zod schema. -->

## Template Literals

Building on the knowledge above, Zod supports creating typescript [template literal types](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html) with runtime validation. These types allow for stricter type checking of string inputs, as an alternative to `z.string()` which infers to a string.

A template literal type consists of [string literal types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types) and interpolated positions (typescript types inside `${}` slots, e.g. `${number}`).

To create a template literal builder:

```ts
const templateLiteral = z.templateLiteral(); // infers to ``.
```

- To add string literal types to an existing template literal:

```ts
templateLiteral.literal("Hello"); // infers to `Hello`.
templateLiteral.literal(3.14); // infers to `3.14`.
```

This method accepts strings, numbers, booleans, nulls and undefined.

- To add interpolated positions to an existing template literal:

```ts
templateLiteral.interpolated(z.string()); // infers to `${string}`.
templateLiteral.interpolated(z.number()); // infers to `${number}`.
templateLiteral.interpolated(z.boolean()); // infers to `true` | `false`.
templateLiteral.interpolated(z.literal("foo")); // infers to `foo`.
templateLiteral.interpolated(z.null()); // infers to `null`.
templateLiteral.interpolated(z.undefined()); // infers to `undefined`.
templateLiteral.interpolated(z.bigint()); // infers to `${bigint}`.
templateLiteral.interpolated(z.any()); // infers to `${any}`.
```

Any Zod type (or union) with an underlying type of string, number, boolean, null,
undefined or bigint can be used as an interpolated position (template literals
included!). You can use additional built-in runtime validations (refinements
excluded) in each of these types and the template literal builder will do its
best (within the limitations of regular expressions) to support them when parsing.

### Examples

URL:

```ts
const url = z
.templateLiteral()
.literal("https://")
.interpolated(z.string().min(1))
.literal(".")
.interpolated(z.enum(["com", "net"]));
// infers to `https://${string}.com` | `https://${string}.net`.

url.parse("https://google.com"); // passes
url.parse("https://google.net"); // passes
url.parse("http://google.com"); // throws
url.parse("https://.com"); // throws
url.parse("https://google"); // throws
url.parse("https://google."); // throws
url.parse("https://google.gov"); // throws
```

Measurement:

```ts
const measurement = z.coerce
.templateLiteral()
.interpolated(z.number().finite())
.interpolated(
z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional()
);
// infers to `${number}` | `${number}px` | `${number}em` | `${number}rem` | `${number}vh` | `${number}vw` | `${number}vmin` | `${number}vmax
```

MongoDB connection string:

```ts
const connectionString = z
.templateLiteral()
.literal("mongodb://")
.interpolated(
z
.templateLiteral()
.interpolated(z.string().regex(/\w+/).describe("username"))
.literal(":")
.interpolated(z.string().regex(/\w+/).describe("password"))
.literal("@")
.optional()
)
.interpolated(z.string().regex(/\w+/).describe("host"))
.literal(":")
.interpolated(z.number().finite().int().positive().describe("port"))
.interpolated(
z
.templateLiteral()
.literal("/")
.interpolated(
z.string().regex(/\w+/).optional().describe("defaultauthdb")
)
.interpolated(
z
.templateLiteral()
.literal("?")
.interpolated(z.string().regex(/^\w+=\w+(&\w+=\w+)*$/))
.optional()
.describe("options")
)
.optional()
);
// infers to:
// | `mongodb://${string}:${number}`
// | `mongodb://${string}:${number}/${string}`
// | `mongodb://${string}:${number}/${string}?${string}`
// | `mongodb://${string}:${string}@${string}:${number}`
// | `mongodb://${string}:${string}@${string}:${number}/${string}`
// | `mongodb://${string}:${string}@${string}:${number}/${string}?${string}`
```

## Preprocess

> Zod now supports primitive coercion without the need for `.preprocess()`. See the [coercion docs](#coercion-for-primitives) for more information.
Expand Down
34 changes: 33 additions & 1 deletion deno/lib/ZodError.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { TypeOf, ZodType } from "./index.ts";
import type { TypeOf, ZodFirstPartyTypeKind, ZodType } from "./index.ts";
import { Primitive } from "./helpers/typeAliases.ts";
import { util, ZodParsedType } from "./helpers/util.ts";

Expand Down Expand Up @@ -332,3 +332,35 @@ export type ZodErrorMap = (
issue: ZodIssueOptionalMessage,
_ctx: ErrorMapCtx
) => { message: string };

export class ZodTemplateLiteralUnsupportedTypeError extends Error {
constructor() {
super("Unsupported zod type!");

const actualProto = new.target.prototype;
if (Object.setPrototypeOf) {
// eslint-disable-next-line ban/ban
Object.setPrototypeOf(this, actualProto);
} else {
(this as any).__proto__ = actualProto;
}
this.name = "ZodTemplateLiteralUnsupportedTypeError";
}
}

export class ZodTemplateLiteralUnsupportedCheckError extends Error {
constructor(typeKind: ZodFirstPartyTypeKind, check: string) {
super(
`${typeKind}'s "${check}" check is not supported in template literals!`
);

const actualProto = new.target.prototype;
if (Object.setPrototypeOf) {
// eslint-disable-next-line ban/ban
Object.setPrototypeOf(this, actualProto);
} else {
(this as any).__proto__ = actualProto;
}
this.name = "ZodTemplateLiteralUnsupportedCheckError";
}
}
20 changes: 20 additions & 0 deletions deno/lib/__tests__/coerce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,23 @@ test("date coercion", () => {
expect(() => schema.parse([])).toThrow(); // z.ZodError
expect(schema.parse(new Date())).toBeInstanceOf(Date);
});

test("template literal coercion", () => {
const schema = z.coerce
.templateLiteral()
.interpolated(z.number().finite())
.interpolated(
z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional()
);
expect(schema.parse(300)).toEqual("300");
expect(schema.parse(BigInt(300))).toEqual("300");
expect(schema.parse("300")).toEqual("300");
expect(schema.parse("300px")).toEqual("300px");
expect(schema.parse("300em")).toEqual("300em");
expect(schema.parse("300rem")).toEqual("300rem");
expect(schema.parse("300vh")).toEqual("300vh");
expect(schema.parse("300vw")).toEqual("300vw");
expect(schema.parse("300vmin")).toEqual("300vmin");
expect(schema.parse("300vmax")).toEqual("300vmax");
expect(schema.parse(["300px"])).toEqual("300px");
});
2 changes: 2 additions & 0 deletions deno/lib/__tests__/firstparty.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ test("first party switch", () => {
break;
case z.ZodFirstPartyTypeKind.ZodReadonly:
break;
case z.ZodFirstPartyTypeKind.ZodTemplateLiteral:
break;
default:
util.assertNever(def);
}
Expand Down
Loading
Loading