-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added support for type negation with z.not() and .not() method …
…in schema validation #2862
- Loading branch information
1 parent
821d45b
commit 61dc91e
Showing
7 changed files
with
446 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,7 @@ | |
- [Dates](#dates) | ||
- [Zod enums](#zod-enums) | ||
- [Native enums](#native-enums) | ||
- [Not](#not) | ||
- [Optionals](#optionals) | ||
- [Nullables](#nullables) | ||
- [Objects](#objects) | ||
|
@@ -1133,6 +1134,27 @@ You can access the underlying object with the `.enum` property: | |
FruitEnum.enum.Apple; // "apple" | ||
``` | ||
|
||
## Not | ||
|
||
You can use `z.not()` to create a schema that rejects a specific type. This wraps the schema in a `ZodNot` instance and returns the result. | ||
|
||
```ts | ||
const schema = z.not(z.string()); | ||
|
||
schema.parse(1234); // => passes, as 1234 is not a string | ||
schema.parse("hello"); // => throws an error, as "hello" is a string | ||
``` | ||
|
||
For convenience, you can also call the `.not()` method on an existing schema. | ||
|
||
```ts | ||
const schema = z.string().email().not(); | ||
|
||
schema.parse(1234); // => passes, as 1234 is not a string or a valid email | ||
schema.parse("hello"); // => passes, as "hello" is not a valid email | ||
schema.parse("[email protected]"); // => throws an error, as "[email protected]" is a valid email | ||
``` | ||
|
||
## Optionals | ||
|
||
You can make any schema optional with `z.optional()`. This wraps the schema in a `ZodOptional` instance and returns the result. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,7 @@ | |
- [.min/.max/.length](#minmaxlength) | ||
- [Unions](#unions) | ||
- [Discriminated unions](#discriminated-unions) | ||
- [Not](#not) | ||
- [Optionals](#optionals) | ||
- [Nullables](#nullables) | ||
- [Enums](#enums) | ||
|
@@ -941,6 +942,27 @@ const B = z.discriminatedUnion("status", [ | |
const AB = z.discriminatedUnion("status", [...A.options, ...B.options]); | ||
``` | ||
|
||
## Not | ||
|
||
你可以用 `z.not()` 来创建一个拒绝特定类型的模式。这个方法会将模式包装在 `ZodNot` 实例中并返回结果。 | ||
|
||
```ts | ||
const schema = z.not(z.string()); | ||
|
||
schema.parse(1234); // => 通过,因为 1234 不是字符串 | ||
schema.parse("hello"); // => 抛出错误,因为 "hello" 是字符串 | ||
``` | ||
|
||
为了方便,你也可以在现有的模式上直接调用 `.not()` 方法。 | ||
|
||
```ts | ||
const schema = z.string().email().not(); | ||
|
||
schema.parse(1234); // => 通过,因为 1234 不是字符串或有效的邮箱地址 | ||
schema.parse("hello"); // => 通过,因为 "hello" 不是有效的邮箱地址 | ||
schema.parse("[email protected]"); // => 抛出错误,因为 "[email protected]" 是一个有效的邮箱地址 | ||
``` | ||
|
||
## Optionals | ||
|
||
你可以用`z.optional()`使任何模式成为可选: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,7 @@ | |
- [Dates](#dates) | ||
- [Zod enums](#zod-enums) | ||
- [Native enums](#native-enums) | ||
- [Not](#not) | ||
- [Optionals](#optionals) | ||
- [Nullables](#nullables) | ||
- [Objects](#objects) | ||
|
@@ -252,7 +253,7 @@ Sponsorship at any level is appreciated and encouraged. If you built a paid prod | |
<td align="center"> | ||
<p></p> | ||
<p> | ||
<a href="https://speakeasyapi.dev/?utm_source=zod+docs"> | ||
<a href="https://speakeasy.com/?utm_source=zod+docs"> | ||
<picture height="40px"> | ||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/colinhacks/zod/assets/3084745/b1d86601-c7fb-483c-9927-5dc24ce8b737"> | ||
<img alt="speakeasy" height="40px" src="https://github.com/colinhacks/zod/assets/3084745/647524a4-22bb-4199-be70-404207a5a2b5"> | ||
|
@@ -261,7 +262,7 @@ Sponsorship at any level is appreciated and encouraged. If you built a paid prod | |
<br /> | ||
SDKs & Terraform providers for your API | ||
<br/> | ||
<a href="https://speakeasyapi.dev/?utm_source=zod+docs" style="text-decoration:none;">speakeasyapi.dev</a> | ||
<a href="https://speakeasy.com/?utm_source=zod+docs" style="text-decoration:none;">speakeasy.com</a> | ||
</p> | ||
<p></p> | ||
</td> | ||
|
@@ -490,6 +491,7 @@ There are a growing number of tools that are built atop or support Zod natively! | |
- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Generate Zod schemas from your Prisma schema. | ||
- [`Supervillain`](https://github.com/Southclaws/supervillain): Generate Zod schemas from your Go structs. | ||
- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema. | ||
- [`drizzle-zod`](https://orm.drizzle.team/docs/zod): Emit Zod schemas from your Drizzle schema. | ||
- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod. | ||
- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models. | ||
- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas. | ||
|
@@ -1132,6 +1134,27 @@ You can access the underlying object with the `.enum` property: | |
FruitEnum.enum.Apple; // "apple" | ||
``` | ||
|
||
## Not | ||
|
||
You can use `z.not()` to create a schema that rejects a specific type. This wraps the schema in a `ZodNot` instance and returns the result. | ||
|
||
```ts | ||
const schema = z.not(z.string()); | ||
|
||
schema.parse(1234); // => passes, as 1234 is not a string | ||
schema.parse("hello"); // => throws an error, as "hello" is a string | ||
``` | ||
|
||
For convenience, you can also call the `.not()` method on an existing schema. | ||
|
||
```ts | ||
const schema = z.string().email().not(); | ||
|
||
schema.parse(1234); // => passes, as 1234 is not a string or a valid email | ||
schema.parse("hello"); // => passes, as "hello" is not a valid email | ||
schema.parse("[email protected]"); // => throws an error, as "[email protected]" is a valid email | ||
``` | ||
|
||
## Optionals | ||
|
||
You can make any schema optional with `z.optional()`. This wraps the schema in a `ZodOptional` instance and returns the result. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// @ts-ignore TS6133 | ||
import { expect } from "https://deno.land/x/[email protected]/mod.ts"; | ||
const test = Deno.test; | ||
|
||
import * as z from "../index.ts"; | ||
|
||
/** Note | ||
* Since z.never() allows no values, z.not(z.never()) should allow all values, including undefined. Hence, there are no failing test cases for notNever. | ||
* There are no passing validation cases for notAny, as it should reject everything. | ||
*/ | ||
|
||
// Set 1 | ||
const str = z.string().not(); | ||
const email = z.string().email().not(); | ||
const num = z.number().not(); | ||
const arr = z.array(z.string()).not(); | ||
const rec = z.record(z.string()).not(); | ||
const obj = z.object({ username: z.string() }).not(); | ||
|
||
// Set 2 | ||
const bigInt = z.bigint().not(); | ||
const bool = z.boolean().not(); | ||
const date = z.date().not(); | ||
const sym = z.symbol().not(); | ||
const undef = z.undefined().not(); | ||
const nul = z.null().not(); | ||
const voidSchema = z.void().not(); | ||
const anySchema = z.any().not(); | ||
const unknownSchema = z.unknown().not(); | ||
const neverSchema = z.never().not(); | ||
|
||
// Set 3 | ||
const notString = z.not(z.string()); | ||
const notNumber = z.not(z.number()); | ||
const notStringArray = z.not(z.array(z.string())); | ||
const notRecord = z.not(z.record(z.string())); | ||
const notObject = z.not(z.object({ username: z.string() })); | ||
|
||
// Set 4 | ||
const notBigInt = z.not(z.bigint()); | ||
const notBoolean = z.not(z.boolean()); | ||
const notDate = z.not(z.date()); | ||
const notSymbol = z.not(z.symbol()); | ||
const notUndefined = z.not(z.undefined()); | ||
const notNull = z.not(z.null()); | ||
const notVoid = z.not(z.void()); | ||
const notAny = z.not(z.any()); | ||
const notUnknown = z.not(z.unknown()); | ||
const notNever = z.not(z.never()); | ||
|
||
test("passing validations", () => { | ||
// Set 1 | ||
str.parse(1234); | ||
email.parse(1234); | ||
email.parse("abcd"); | ||
num.parse("1234"); | ||
arr.parse([1234]); | ||
rec.parse({ key: 1234 }); | ||
obj.parse({ username: 1234 }); | ||
|
||
// Set 2 | ||
bigInt.parse(10); | ||
bool.parse(1234); | ||
date.parse("not a date"); | ||
sym.parse("1234"); | ||
undef.parse(null); | ||
nul.parse(undefined); | ||
voidSchema.parse(null); | ||
neverSchema.parse(undefined); // `z.never()` allows no values, so `notNever` can accept any value. | ||
|
||
// Set 3 | ||
notString.parse(1234); | ||
notNumber.parse("1234"); | ||
notStringArray.parse([1234]); | ||
notRecord.parse({ key: 1234 }); | ||
notObject.parse({ username: 1234 }); | ||
|
||
// Set 4 | ||
notBigInt.parse(10); | ||
notBoolean.parse(1234); | ||
notDate.parse("not a date"); | ||
notSymbol.parse("1234"); | ||
notUndefined.parse(null); | ||
notNull.parse(undefined); | ||
notVoid.parse(null); | ||
notNever.parse(undefined); // `z.never()` allows no values, so `notNever` can accept any value. | ||
}); | ||
|
||
test("failing validations", () => { | ||
// Set 1 | ||
expect(() => str.parse("1234")).toThrow(); | ||
expect(() => email.parse("[email protected]")).toThrow(); | ||
expect(() => num.parse(1234)).toThrow(); | ||
expect(() => arr.parse(["1234"])).toThrow(); | ||
expect(() => rec.parse({ key: "value" })).toThrow(); | ||
expect(() => obj.parse({ username: "1234" })).toThrow(); | ||
|
||
// Set 2 | ||
expect(() => bigInt.parse(BigInt(10))).toThrow(); | ||
expect(() => bool.parse(true)).toThrow(); | ||
expect(() => date.parse(new Date())).toThrow(); | ||
expect(() => sym.parse(Symbol("symbol"))).toThrow(); | ||
expect(() => undef.parse(undefined)).toThrow(); | ||
expect(() => nul.parse(null)).toThrow(); | ||
expect(() => voidSchema.parse(undefined)).toThrow(); | ||
expect(() => anySchema.parse(undefined)).toThrow(); // `z.any()` allows any value, so `notAny` should fail for any input. | ||
expect(() => unknownSchema.parse(undefined)).toThrow(); // Same as `z.any()` | ||
|
||
// Set 3 | ||
expect(() => notString.parse("1234")).toThrow(); | ||
expect(() => notNumber.parse(1234)).toThrow(); | ||
expect(() => notStringArray.parse(["1234"])).toThrow(); | ||
expect(() => notRecord.parse({ key: "value" })).toThrow(); | ||
expect(() => notObject.parse({ username: "1234" })).toThrow(); | ||
|
||
// Set 4 | ||
expect(() => notBigInt.parse(BigInt(10))).toThrow(); | ||
expect(() => notBoolean.parse(true)).toThrow(); | ||
expect(() => notDate.parse(new Date())).toThrow(); | ||
expect(() => notSymbol.parse(Symbol("symbol"))).toThrow(); | ||
expect(() => notUndefined.parse(undefined)).toThrow(); | ||
expect(() => notNull.parse(null)).toThrow(); | ||
expect(() => notVoid.parse(undefined)).toThrow(); | ||
expect(() => notAny.parse(undefined)).toThrow(); // `z.any()` allows any value, so `notAny` should fail for any input. | ||
expect(() => notUnknown.parse(undefined)).toThrow(); // Same as `z.any()` | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.