├── .eslintignore ├── .nojekyll ├── .husky ├── .gitignore ├── pre-push └── pre-commit ├── FUNDING.yml ├── robots.txt ├── playground.ts ├── index.d.ts ├── _redirects ├── static ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-70x70.png ├── mstile-144x144.png ├── mstile-150x150.png ├── mstile-310x150.png ├── mstile-310x310.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── moon.svg ├── browserconfig.xml ├── site.webmanifest └── sun.svg ├── deno └── lib │ ├── mod.ts │ ├── index.ts │ ├── helpers │ ├── typeAliases.ts │ ├── errorUtil.ts │ ├── enumUtil.ts │ └── partialUtil.ts │ ├── external.ts │ ├── errors.ts │ ├── __tests__ │ ├── void.test.ts │ ├── mocker.test.ts │ ├── custom.test.ts │ ├── nan.test.ts │ ├── complex.test.ts │ ├── object-augmentation.test.ts │ ├── safeparse.test.ts │ ├── masking.test.ts │ ├── anyunknown.test.ts │ ├── base.test.ts │ ├── firstpartyschematypes.test.ts │ ├── pipeline.test.ts │ ├── parseUtil.test.ts │ ├── date.test.ts │ ├── literal.test.ts │ ├── instanceof.test.ts │ ├── nullable.test.ts │ ├── Mocker.ts │ ├── parser.test.ts │ ├── optional.test.ts │ ├── description.test.ts │ ├── crazySchema.ts │ ├── generics.test.ts │ ├── async-refinements.test.ts │ ├── unions.test.ts │ ├── branded.test.ts │ ├── bigint.test.ts │ ├── array.test.ts │ ├── nativeEnum.test.ts │ ├── firstparty.test.ts │ ├── standard-schema.test.ts │ ├── promise.test.ts │ ├── enum.test.ts │ ├── tuple.test.ts │ ├── intersection.test.ts │ ├── map.test.ts │ ├── default.test.ts │ └── pickomit.test.ts │ ├── benchmarks │ ├── realworld.ts │ ├── string.ts │ ├── index.ts │ ├── union.ts │ ├── object.ts │ ├── discriminatedUnion.ts │ ├── ipv4.ts │ └── datetime.ts │ └── standard-schema.ts ├── src ├── index.ts ├── helpers │ ├── typeAliases.ts │ ├── errorUtil.ts │ ├── enumUtil.ts │ └── partialUtil.ts ├── external.ts ├── errors.ts ├── __tests__ │ ├── void.test.ts │ ├── mocker.test.ts │ ├── custom.test.ts │ ├── nan.test.ts │ ├── complex.test.ts │ ├── object-augmentation.test.ts │ ├── safeparse.test.ts │ ├── masking.test.ts │ ├── anyunknown.test.ts │ ├── base.test.ts │ ├── object-in-es5-env.test.ts │ ├── firstpartyschematypes.test.ts │ ├── pipeline.test.ts │ ├── parseUtil.test.ts │ ├── date.test.ts │ ├── literal.test.ts │ ├── instanceof.test.ts │ ├── nullable.test.ts │ ├── parser.test.ts │ ├── optional.test.ts │ ├── Mocker.ts │ ├── description.test.ts │ ├── crazySchema.ts │ ├── language-server.source.ts │ ├── generics.test.ts │ ├── async-refinements.test.ts │ ├── unions.test.ts │ ├── branded.test.ts │ ├── bigint.test.ts │ ├── array.test.ts │ ├── nativeEnum.test.ts │ ├── firstparty.test.ts │ ├── standard-schema.test.ts │ ├── promise.test.ts │ ├── enum.test.ts │ ├── tuple.test.ts │ ├── intersection.test.ts │ ├── map.test.ts │ ├── default.test.ts │ └── pickomit.test.ts ├── benchmarks │ ├── realworld.ts │ ├── string.ts │ ├── index.ts │ ├── union.ts │ ├── object.ts │ ├── discriminatedUnion.ts │ ├── ipv4.ts │ └── datetime.ts └── standard-schema.ts ├── tsconfig.json ├── FUNDING.json ├── .prettierrc.yaml ├── tea.yaml ├── configs ├── babel.config.js ├── vitest.config.ts ├── tsconfig.types.json ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.test.json ├── jest.config.json ├── ts-jest.config.json ├── swc-jest.config.json ├── babel-jest.config.json ├── tsconfig.base.json └── rollup.config.js ├── .gitignore ├── .editorconfig ├── jest.config.json ├── .vscode ├── launch.json └── settings.json ├── .devcontainer └── devcontainer.json ├── LICENSE ├── .github └── workflows │ ├── release-canary.yml │ └── test.yml ├── .eslintrc.js ├── deno-build.mjs └── CONTRIBUTING.md /.eslintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: colinhacks 2 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /playground.ts: -------------------------------------------------------------------------------- 1 | import { z } from "./src"; 2 | 3 | z; 4 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./lib"; 2 | export as namespace Zod; 3 | -------------------------------------------------------------------------------- /_redirects: -------------------------------------------------------------------------------- 1 | /blog/* /blog/index.html 200 2 | /* / 200 3 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn test 5 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/mstile-70x70.png -------------------------------------------------------------------------------- /deno/lib/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./index.ts"; 2 | export { default as default } from "./index.ts"; 3 | -------------------------------------------------------------------------------- /static/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/mstile-144x144.png -------------------------------------------------------------------------------- /static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/mstile-150x150.png -------------------------------------------------------------------------------- /static/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/mstile-310x150.png -------------------------------------------------------------------------------- /static/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/mstile-310x310.png -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iKowalchuk/zod/HEAD/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as z from "./external"; 2 | export * from "./external"; 3 | export { z }; 4 | export default z; 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | yarn build:deno 6 | git add deno 7 | -------------------------------------------------------------------------------- /deno/lib/index.ts: -------------------------------------------------------------------------------- 1 | import * as z from "./external.ts"; 2 | export * from "./external.ts"; 3 | export { z }; 4 | export default z; 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./configs/tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../lib" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /FUNDING.json: -------------------------------------------------------------------------------- 1 | { 2 | "drips": { 3 | "ethereum": { 4 | "ownedBy": "0xAe9ae688557471b0317734a54bE095b3C675DA2f" 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | # .prettierrc or .prettierrc.yaml 2 | trailingComma: "es5" 3 | tabWidth: 2 4 | semi: true 5 | singleQuote: false 6 | printWidth: 80 7 | -------------------------------------------------------------------------------- /tea.yaml: -------------------------------------------------------------------------------- 1 | # https://tea.xyz/what-is-this-file 2 | --- 3 | version: 1.0.0 4 | codeOwners: 5 | - '0xF233A42130Bcdd8b22FFB5D9593199f31C3Eeb87' 6 | quorum: 1 7 | -------------------------------------------------------------------------------- /configs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /src/helpers/typeAliases.ts: -------------------------------------------------------------------------------- 1 | export type Primitive = 2 | | string 3 | | number 4 | | symbol 5 | | bigint 6 | | boolean 7 | | null 8 | | undefined; 9 | export type Scalars = Primitive | Primitive[]; 10 | -------------------------------------------------------------------------------- /deno/lib/helpers/typeAliases.ts: -------------------------------------------------------------------------------- 1 | export type Primitive = 2 | | string 3 | | number 4 | | symbol 5 | | bigint 6 | | boolean 7 | | null 8 | | undefined; 9 | export type Scalars = Primitive | Primitive[]; 10 | -------------------------------------------------------------------------------- /src/external.ts: -------------------------------------------------------------------------------- 1 | export * from "./errors"; 2 | export * from "./helpers/parseUtil"; 3 | export * from "./helpers/typeAliases"; 4 | export * from "./helpers/util"; 5 | export * from "./types"; 6 | export * from "./ZodError"; 7 | -------------------------------------------------------------------------------- /deno/lib/external.ts: -------------------------------------------------------------------------------- 1 | export * from "./errors.ts"; 2 | export * from "./helpers/parseUtil.ts"; 3 | export * from "./helpers/typeAliases.ts"; 4 | export * from "./helpers/util.ts"; 5 | export * from "./types.ts"; 6 | export * from "./ZodError.ts"; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.DS_Store 2 | node_modules 3 | /lib 4 | /__buildtest__ 5 | coverage 6 | .idea 7 | *.log 8 | playground.ts 9 | playground.mts 10 | deno/lib/playground.ts 11 | .eslintcache 12 | workspace.code-workspace 13 | .netlify 14 | bun.lockb 15 | -------------------------------------------------------------------------------- /static/moon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /configs/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | alias: { 6 | "@jest/globals": "vitest", 7 | }, 8 | include: ["src/**/*.test.ts"], 9 | isolate: false, 10 | watch: false, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /configs/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../lib/types", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "emitDeclarationOnly": true 8 | }, 9 | "exclude": ["../src/**/__tests__", "../src/playground.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | charset = utf-8 13 | -------------------------------------------------------------------------------- /configs/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../lib", 6 | "declaration": true, 7 | "declarationMap": false, 8 | "sourceMap": false 9 | }, 10 | "exclude": ["../src/**/__tests__", "../playground.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /configs/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "es2015", 5 | // "outDir": "./lib/esm", 6 | "declaration": false, 7 | "declarationMap": false, 8 | "sourceMap": false 9 | }, 10 | "exclude": ["../src/**/__tests__", "../src/playground.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /configs/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "__buildtest__", 6 | "target": "es5", 7 | "declaration": true, 8 | "declarationMap": false, 9 | "sourceMap": false, 10 | "noEmit": true 11 | }, 12 | "exclude": [] 13 | } 14 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": ".", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | "testRegex": "src/.*\\.test\\.ts$", 7 | "modulePathIgnorePatterns": ["language-server", "__vitest__"], 8 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 9 | "coverageReporters": ["json-summary", "text", "lcov"] 10 | } 11 | -------------------------------------------------------------------------------- /configs/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "..", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | "testRegex": "src/.*\\.test\\.ts$", 7 | "modulePathIgnorePatterns": ["language-server", "__vitest__"], 8 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 9 | "coverageReporters": ["json-summary", "text", "lcov"] 10 | } 11 | -------------------------------------------------------------------------------- /src/errors.ts: -------------------------------------------------------------------------------- 1 | import defaultErrorMap from "./locales/en"; 2 | import type { ZodErrorMap } from "./ZodError"; 3 | 4 | let overrideErrorMap = defaultErrorMap; 5 | export { defaultErrorMap }; 6 | 7 | export function setErrorMap(map: ZodErrorMap) { 8 | overrideErrorMap = map; 9 | } 10 | 11 | export function getErrorMap() { 12 | return overrideErrorMap; 13 | } 14 | -------------------------------------------------------------------------------- /configs/ts-jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "..", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest" 5 | }, 6 | "testRegex": "src/.*\\.test\\.ts$", 7 | "modulePathIgnorePatterns": ["language-server", "__vitest__"], 8 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 9 | "coverageReporters": ["json-summary", "text", "lcov"] 10 | } 11 | -------------------------------------------------------------------------------- /configs/swc-jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "..", 3 | "transform": { 4 | "^.+\\.(t|j)sx?$": "@swc/jest" 5 | }, 6 | "testRegex": "src/.*\\.test\\.ts$", 7 | "modulePathIgnorePatterns": ["language-server", "__vitest__"], 8 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 9 | "coverageReporters": ["json-summary", "text", "lcov"] 10 | } 11 | -------------------------------------------------------------------------------- /deno/lib/errors.ts: -------------------------------------------------------------------------------- 1 | import defaultErrorMap from "./locales/en.ts"; 2 | import type { ZodErrorMap } from "./ZodError.ts"; 3 | 4 | let overrideErrorMap = defaultErrorMap; 5 | export { defaultErrorMap }; 6 | 7 | export function setErrorMap(map: ZodErrorMap) { 8 | overrideErrorMap = map; 9 | } 10 | 11 | export function getErrorMap() { 12 | return overrideErrorMap; 13 | } 14 | -------------------------------------------------------------------------------- /src/helpers/errorUtil.ts: -------------------------------------------------------------------------------- 1 | export namespace errorUtil { 2 | export type ErrMessage = string | { message?: string }; 3 | export const errToObj = (message?: ErrMessage) => 4 | typeof message === "string" ? { message } : message || {}; 5 | export const toString = (message?: ErrMessage): string | undefined => 6 | typeof message === "string" ? message : message?.message; 7 | } 8 | -------------------------------------------------------------------------------- /deno/lib/helpers/errorUtil.ts: -------------------------------------------------------------------------------- 1 | export namespace errorUtil { 2 | export type ErrMessage = string | { message?: string }; 3 | export const errToObj = (message?: ErrMessage) => 4 | typeof message === "string" ? { message } : message || {}; 5 | export const toString = (message?: ErrMessage): string | undefined => 6 | typeof message === "string" ? message : message?.message; 7 | } 8 | -------------------------------------------------------------------------------- /configs/babel-jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "rootDir": "..", 3 | "testRegex": "src/.*\\.test\\.ts$", 4 | "transform": { 5 | "^.+\\.tsx?$": ["babel-jest", { "configFile": "./configs/babel.config.js" }] 6 | }, 7 | "modulePathIgnorePatterns": ["language-server", "__vitest__"], 8 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 9 | "coverageReporters": ["json-summary", "text", "lcov"] 10 | } 11 | -------------------------------------------------------------------------------- /src/__tests__/void.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | test("void", () => { 7 | const v = z.void(); 8 | v.parse(undefined); 9 | 10 | expect(() => v.parse(null)).toThrow(); 11 | expect(() => v.parse("")).toThrow(); 12 | 13 | type v = z.infer; 14 | util.assertEqual(true); 15 | }); 16 | -------------------------------------------------------------------------------- /deno/lib/__tests__/void.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | test("void", () => { 8 | const v = z.void(); 9 | v.parse(undefined); 10 | 11 | expect(() => v.parse(null)).toThrow(); 12 | expect(() => v.parse("")).toThrow(); 13 | 14 | type v = z.infer; 15 | util.assertEqual(true); 16 | }); 17 | -------------------------------------------------------------------------------- /src/__tests__/mocker.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { Mocker } from "./Mocker"; 5 | 6 | test("mocker", () => { 7 | const mocker = new Mocker(); 8 | mocker.string; 9 | mocker.number; 10 | mocker.boolean; 11 | mocker.null; 12 | mocker.undefined; 13 | mocker.stringOptional; 14 | mocker.stringNullable; 15 | mocker.numberOptional; 16 | mocker.numberNullable; 17 | mocker.booleanOptional; 18 | mocker.booleanNullable; 19 | }); 20 | -------------------------------------------------------------------------------- /static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zod", 3 | "short_name": "Zod", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "any maskable" 10 | }, 11 | { 12 | "src": "/android-chrome-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "any maskable" 16 | } 17 | ], 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } 22 | -------------------------------------------------------------------------------- /deno/lib/__tests__/mocker.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { Mocker } from "./Mocker.ts"; 6 | 7 | test("mocker", () => { 8 | const mocker = new Mocker(); 9 | mocker.string; 10 | mocker.number; 11 | mocker.boolean; 12 | mocker.null; 13 | mocker.undefined; 14 | mocker.stringOptional; 15 | mocker.stringNullable; 16 | mocker.numberOptional; 17 | mocker.numberNullable; 18 | mocker.booleanOptional; 19 | mocker.booleanNullable; 20 | }); 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": ["/**"], 12 | "preLaunchTask": "tsc: build - tsconfig.json", 13 | "program": "${workspaceFolder}/src/playground.ts", 14 | "outFiles": ["${workspaceFolder}/**/*.js"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/__tests__/custom.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("passing validations", () => { 7 | const example1 = z.custom((x) => typeof x === "number"); 8 | example1.parse(1234); 9 | expect(() => example1.parse({})).toThrow(); 10 | }); 11 | 12 | test("string params", () => { 13 | const example1 = z.custom((x) => typeof x !== "number", "customerr"); 14 | const result = example1.safeParse(1234); 15 | expect(result.success).toEqual(false); 16 | // @ts-ignore 17 | expect(JSON.stringify(result.error).includes("customerr")).toEqual(true); 18 | }); 19 | -------------------------------------------------------------------------------- /configs/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es5", "es6", "es7", "esnext", "dom"], 4 | "target": "es2018", 5 | "removeComments": false, 6 | "esModuleInterop": true, 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | "strictPropertyInitialization": false, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "downlevelIteration": true, 17 | "isolatedModules": true 18 | }, 19 | "include": ["../src/**/*", "../playground.ts", "../.eslintrc.js"] 20 | } 21 | -------------------------------------------------------------------------------- /configs/rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import typescript from "@rollup/plugin-typescript"; 3 | 4 | export default [ 5 | { 6 | input: "src/index.ts", 7 | output: [ 8 | { 9 | file: "lib/index.mjs", 10 | format: "es", 11 | sourcemap: false, 12 | exports: "named", 13 | }, 14 | { 15 | file: "lib/index.umd.js", 16 | name: "Zod", 17 | format: "umd", 18 | sourcemap: false, 19 | exports: "named", 20 | }, 21 | ], 22 | plugins: [ 23 | typescript({ 24 | tsconfig: "./configs/tsconfig.esm.json", 25 | sourceMap: false, 26 | }), 27 | ], 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /src/__tests__/nan.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | const schema = z.nan(); 7 | 8 | test("passing validations", () => { 9 | schema.parse(NaN); 10 | schema.parse(Number("Not a number")); 11 | }); 12 | 13 | test("failing validations", () => { 14 | expect(() => schema.parse(5)).toThrow(); 15 | expect(() => schema.parse("John")).toThrow(); 16 | expect(() => schema.parse(true)).toThrow(); 17 | expect(() => schema.parse(null)).toThrow(); 18 | expect(() => schema.parse(undefined)).toThrow(); 19 | expect(() => schema.parse({})).toThrow(); 20 | expect(() => schema.parse([])).toThrow(); 21 | }); 22 | -------------------------------------------------------------------------------- /static/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "/Users/colinmcd94/Documents/Projects/zod/node_modules/typescript/lib", 3 | "files.exclude": { 4 | "**/deno": true 5 | }, 6 | "files.watcherExclude": { 7 | "**/deno": true 8 | }, 9 | "search.exclude": { 10 | "**/deno": true 11 | }, 12 | "editor.defaultFormatter": "esbenp.prettier-vscode", 13 | "editor.formatOnSave": true, 14 | "[typescript]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "[javascript]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode" 19 | }, 20 | "typescript.validate.enable": true, 21 | "typescript.tsserver.experimental.enableProjectDiagnostics": true 22 | } 23 | -------------------------------------------------------------------------------- /src/__tests__/complex.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { crazySchema } from "./crazySchema"; 5 | // import * as z from "../index"; 6 | 7 | test("parse", () => { 8 | crazySchema.parse({ 9 | tuple: ["asdf", 1234, true, null, undefined, "1234"], 10 | merged: { k1: "asdf", k2: 12 }, 11 | union: ["asdf", 12, "asdf", 12, "asdf", 12], 12 | array: [12, 15, 16], 13 | // sumTransformer: [12, 15, 16], 14 | sumMinLength: [12, 15, 16, 98, 24, 63], 15 | intersection: {}, 16 | enum: "one", 17 | nonstrict: { points: 1234 }, 18 | numProm: Promise.resolve(12), 19 | lenfun: (x: string) => x.length, 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/helpers/enumUtil.ts: -------------------------------------------------------------------------------- 1 | export namespace enumUtil { 2 | type UnionToIntersectionFn = ( 3 | T extends unknown ? (k: () => T) => void : never 4 | ) extends (k: infer Intersection) => void 5 | ? Intersection 6 | : never; 7 | 8 | type GetUnionLast = UnionToIntersectionFn extends () => infer Last 9 | ? Last 10 | : never; 11 | 12 | type UnionToTuple = [T] extends [never] 13 | ? Tuple 14 | : UnionToTuple>, [GetUnionLast, ...Tuple]>; 15 | 16 | type CastToStringTuple = T extends [string, ...string[]] ? T : never; 17 | 18 | export type UnionToTupleString = CastToStringTuple>; 19 | } 20 | -------------------------------------------------------------------------------- /deno/lib/helpers/enumUtil.ts: -------------------------------------------------------------------------------- 1 | export namespace enumUtil { 2 | type UnionToIntersectionFn = ( 3 | T extends unknown ? (k: () => T) => void : never 4 | ) extends (k: infer Intersection) => void 5 | ? Intersection 6 | : never; 7 | 8 | type GetUnionLast = UnionToIntersectionFn extends () => infer Last 9 | ? Last 10 | : never; 11 | 12 | type UnionToTuple = [T] extends [never] 13 | ? Tuple 14 | : UnionToTuple>, [GetUnionLast, ...Tuple]>; 15 | 16 | type CastToStringTuple = T extends [string, ...string[]] ? T : never; 17 | 18 | export type UnionToTupleString = CastToStringTuple>; 19 | } 20 | -------------------------------------------------------------------------------- /deno/lib/__tests__/custom.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("passing validations", () => { 8 | const example1 = z.custom((x) => typeof x === "number"); 9 | example1.parse(1234); 10 | expect(() => example1.parse({})).toThrow(); 11 | }); 12 | 13 | test("string params", () => { 14 | const example1 = z.custom((x) => typeof x !== "number", "customerr"); 15 | const result = example1.safeParse(1234); 16 | expect(result.success).toEqual(false); 17 | // @ts-ignore 18 | expect(JSON.stringify(result.error).includes("customerr")).toEqual(true); 19 | }); 20 | -------------------------------------------------------------------------------- /src/__tests__/object-augmentation.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("object augmentation", () => { 7 | const Animal = z 8 | .object({ 9 | species: z.string(), 10 | }) 11 | .augment({ 12 | population: z.number(), 13 | }); 14 | // overwrites `species` 15 | const ModifiedAnimal = Animal.augment({ 16 | species: z.array(z.string()), 17 | }); 18 | ModifiedAnimal.parse({ 19 | species: ["asd"], 20 | population: 1324, 21 | }); 22 | 23 | const bad = () => 24 | ModifiedAnimal.parse({ 25 | species: "asdf", 26 | population: 1324, 27 | } as any); 28 | expect(bad).toThrow(); 29 | }); 30 | -------------------------------------------------------------------------------- /deno/lib/__tests__/nan.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | const schema = z.nan(); 8 | 9 | test("passing validations", () => { 10 | schema.parse(NaN); 11 | schema.parse(Number("Not a number")); 12 | }); 13 | 14 | test("failing validations", () => { 15 | expect(() => schema.parse(5)).toThrow(); 16 | expect(() => schema.parse("John")).toThrow(); 17 | expect(() => schema.parse(true)).toThrow(); 18 | expect(() => schema.parse(null)).toThrow(); 19 | expect(() => schema.parse(undefined)).toThrow(); 20 | expect(() => schema.parse({})).toThrow(); 21 | expect(() => schema.parse([])).toThrow(); 22 | }); 23 | -------------------------------------------------------------------------------- /deno/lib/__tests__/complex.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { crazySchema } from "./crazySchema.ts"; 6 | // import * as z from "../index"; 7 | 8 | test("parse", () => { 9 | crazySchema.parse({ 10 | tuple: ["asdf", 1234, true, null, undefined, "1234"], 11 | merged: { k1: "asdf", k2: 12 }, 12 | union: ["asdf", 12, "asdf", 12, "asdf", 12], 13 | array: [12, 15, 16], 14 | // sumTransformer: [12, 15, 16], 15 | sumMinLength: [12, 15, 16, 98, 24, 63], 16 | intersection: {}, 17 | enum: "one", 18 | nonstrict: { points: 1234 }, 19 | numProm: Promise.resolve(12), 20 | lenfun: (x: string) => x.length, 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/__tests__/safeparse.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | const stringSchema = z.string(); 6 | 7 | test("safeparse fail", () => { 8 | const safe = stringSchema.safeParse(12); 9 | expect(safe.success).toEqual(false); 10 | expect(safe.error).toBeInstanceOf(z.ZodError); 11 | }); 12 | 13 | test("safeparse pass", () => { 14 | const safe = stringSchema.safeParse("12"); 15 | expect(safe.success).toEqual(true); 16 | expect(safe.data).toEqual("12"); 17 | }); 18 | 19 | test("safeparse unexpected error", () => { 20 | expect(() => 21 | stringSchema 22 | .refine((data) => { 23 | throw new Error(data); 24 | }) 25 | .safeParse("12") 26 | ).toThrow(); 27 | }); 28 | -------------------------------------------------------------------------------- /deno/lib/__tests__/object-augmentation.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("object augmentation", () => { 8 | const Animal = z 9 | .object({ 10 | species: z.string(), 11 | }) 12 | .augment({ 13 | population: z.number(), 14 | }); 15 | // overwrites `species` 16 | const ModifiedAnimal = Animal.augment({ 17 | species: z.array(z.string()), 18 | }); 19 | ModifiedAnimal.parse({ 20 | species: ["asd"], 21 | population: 1324, 22 | }); 23 | 24 | const bad = () => 25 | ModifiedAnimal.parse({ 26 | species: "asdf", 27 | population: 1324, 28 | } as any); 29 | expect(bad).toThrow(); 30 | }); 31 | -------------------------------------------------------------------------------- /src/__tests__/masking.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("masking test", () => {}); 7 | 8 | test("require", () => { 9 | const baseSchema = z.object({ 10 | firstName: z.string(), 11 | middleName: z.string().optional(), 12 | lastName: z.union([z.undefined(), z.string()]), 13 | otherName: z.union([z.string(), z.undefined(), z.string()]), 14 | }); 15 | baseSchema; 16 | // const reqBase = baseSchema.require(); 17 | // const ewr = reqBase.shape; 18 | // expect(ewr.firstName).toBeInstanceOf(z.ZodString); 19 | // expect(ewr.middleName).toBeInstanceOf(z.ZodString); 20 | // expect(ewr.lastName).toBeInstanceOf(z.ZodString); 21 | // expect(ewr.otherName).toBeInstanceOf(z.ZodUnion); 22 | }); 23 | -------------------------------------------------------------------------------- /src/__tests__/anyunknown.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("check any inference", () => { 8 | const t1 = z.any(); 9 | t1.optional(); 10 | t1.nullable(); 11 | type t1 = z.infer; 12 | util.assertEqual(true); 13 | }); 14 | 15 | test("check unknown inference", () => { 16 | const t1 = z.unknown(); 17 | t1.optional(); 18 | t1.nullable(); 19 | type t1 = z.infer; 20 | util.assertEqual(true); 21 | }); 22 | 23 | test("check never inference", () => { 24 | const t1 = z.never(); 25 | expect(() => t1.parse(undefined)).toThrow(); 26 | expect(() => t1.parse("asdf")).toThrow(); 27 | expect(() => t1.parse(null)).toThrow(); 28 | }); 29 | -------------------------------------------------------------------------------- /deno/lib/__tests__/safeparse.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | const stringSchema = z.string(); 7 | 8 | test("safeparse fail", () => { 9 | const safe = stringSchema.safeParse(12); 10 | expect(safe.success).toEqual(false); 11 | expect(safe.error).toBeInstanceOf(z.ZodError); 12 | }); 13 | 14 | test("safeparse pass", () => { 15 | const safe = stringSchema.safeParse("12"); 16 | expect(safe.success).toEqual(true); 17 | expect(safe.data).toEqual("12"); 18 | }); 19 | 20 | test("safeparse unexpected error", () => { 21 | expect(() => 22 | stringSchema 23 | .refine((data) => { 24 | throw new Error(data); 25 | }) 26 | .safeParse("12") 27 | ).toThrow(); 28 | }); 29 | -------------------------------------------------------------------------------- /deno/lib/__tests__/masking.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("masking test", () => {}); 8 | 9 | test("require", () => { 10 | const baseSchema = z.object({ 11 | firstName: z.string(), 12 | middleName: z.string().optional(), 13 | lastName: z.union([z.undefined(), z.string()]), 14 | otherName: z.union([z.string(), z.undefined(), z.string()]), 15 | }); 16 | baseSchema; 17 | // const reqBase = baseSchema.require(); 18 | // const ewr = reqBase.shape; 19 | // expect(ewr.firstName).toBeInstanceOf(z.ZodString); 20 | // expect(ewr.middleName).toBeInstanceOf(z.ZodString); 21 | // expect(ewr.lastName).toBeInstanceOf(z.ZodString); 22 | // expect(ewr.otherName).toBeInstanceOf(z.ZodUnion); 23 | }); 24 | -------------------------------------------------------------------------------- /deno/lib/__tests__/anyunknown.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("check any inference", () => { 9 | const t1 = z.any(); 10 | t1.optional(); 11 | t1.nullable(); 12 | type t1 = z.infer; 13 | util.assertEqual(true); 14 | }); 15 | 16 | test("check unknown inference", () => { 17 | const t1 = z.unknown(); 18 | t1.optional(); 19 | t1.nullable(); 20 | type t1 = z.infer; 21 | util.assertEqual(true); 22 | }); 23 | 24 | test("check never inference", () => { 25 | const t1 = z.never(); 26 | expect(() => t1.parse(undefined)).toThrow(); 27 | expect(() => t1.parse("asdf")).toThrow(); 28 | expect(() => t1.parse(null)).toThrow(); 29 | }); 30 | -------------------------------------------------------------------------------- /src/__tests__/base.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("type guard", () => { 8 | const stringToNumber = z.string().transform((arg) => arg.length); 9 | 10 | const s1 = z.object({ 11 | stringToNumber, 12 | }); 13 | type t1 = z.input; 14 | 15 | const data = { stringToNumber: "asdf" }; 16 | const parsed = s1.safeParse(data); 17 | if (parsed.success) { 18 | util.assertEqual(true); 19 | } 20 | }); 21 | 22 | test("test this binding", () => { 23 | const callback = (predicate: (val: string) => boolean) => { 24 | return predicate("hello"); 25 | }; 26 | 27 | expect(callback((value) => z.string().safeParse(value).success)).toBe(true); // true 28 | expect(callback((value) => z.string().safeParse(value).success)).toBe(true); // true 29 | }); 30 | -------------------------------------------------------------------------------- /src/__tests__/object-in-es5-env.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | const RealSet = Set; 7 | const RealMap = Map; 8 | const RealDate = Date; 9 | 10 | test("doesn’t throw when Date is undefined", () => { 11 | delete (globalThis as any).Date; 12 | const result = z.object({}).safeParse({}); 13 | expect(result.success).toEqual(true); 14 | globalThis.Date = RealDate; 15 | }); 16 | 17 | test("doesn’t throw when Set is undefined", () => { 18 | delete (globalThis as any).Set; 19 | const result = z.object({}).safeParse({}); 20 | expect(result.success).toEqual(true); 21 | globalThis.Set = RealSet; 22 | }); 23 | 24 | test("doesn’t throw when Map is undefined", () => { 25 | delete (globalThis as any).Map; 26 | const result = z.object({}).safeParse({}); 27 | expect(result.success).toEqual(true); 28 | globalThis.Map = RealMap; 29 | }); 30 | -------------------------------------------------------------------------------- /src/__tests__/firstpartyschematypes.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { test } from "@jest/globals"; 3 | 4 | import { ZodFirstPartySchemaTypes, ZodFirstPartyTypeKind } from ".."; 5 | import { util } from "../helpers/util"; 6 | 7 | test("Identify missing [ZodFirstPartySchemaTypes]", () => { 8 | type ZodFirstPartySchemaForType = 9 | ZodFirstPartySchemaTypes extends infer Schema 10 | ? Schema extends { _def: { typeName: T } } 11 | ? Schema 12 | : never 13 | : never; 14 | type ZodMappedTypes = { 15 | [key in ZodFirstPartyTypeKind]: ZodFirstPartySchemaForType; 16 | }; 17 | type ZodFirstPartySchemaTypesMissingFromUnion = keyof { 18 | [key in keyof ZodMappedTypes as ZodMappedTypes[key] extends { _def: never } 19 | ? key 20 | : never]: unknown; 21 | }; 22 | 23 | util.assertEqual(true); 24 | }); 25 | -------------------------------------------------------------------------------- /src/__tests__/pipeline.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("string to number pipeline", () => { 7 | const schema = z.string().transform(Number).pipe(z.number()); 8 | expect(schema.parse("1234")).toEqual(1234); 9 | }); 10 | 11 | test("string to number pipeline async", async () => { 12 | const schema = z 13 | .string() 14 | .transform(async (val) => Number(val)) 15 | .pipe(z.number()); 16 | expect(await schema.parseAsync("1234")).toEqual(1234); 17 | }); 18 | 19 | test("break if dirty", () => { 20 | const schema = z 21 | .string() 22 | .refine((c) => c === "1234") 23 | .transform(async (val) => Number(val)) 24 | .pipe(z.number().refine((v) => v < 100)); 25 | const r1: any = schema.safeParse("12345"); 26 | expect(r1.error.issues.length).toBe(1); 27 | const r2: any = schema.safeParse("3"); 28 | expect(r2.error.issues.length).toBe(1); 29 | }); 30 | -------------------------------------------------------------------------------- /deno/lib/__tests__/base.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("type guard", () => { 9 | const stringToNumber = z.string().transform((arg) => arg.length); 10 | 11 | const s1 = z.object({ 12 | stringToNumber, 13 | }); 14 | type t1 = z.input; 15 | 16 | const data = { stringToNumber: "asdf" }; 17 | const parsed = s1.safeParse(data); 18 | if (parsed.success) { 19 | util.assertEqual(true); 20 | } 21 | }); 22 | 23 | test("test this binding", () => { 24 | const callback = (predicate: (val: string) => boolean) => { 25 | return predicate("hello"); 26 | }; 27 | 28 | expect(callback((value) => z.string().safeParse(value).success)).toBe(true); // true 29 | expect(callback((value) => z.string().safeParse(value).success)).toBe(true); // true 30 | }); 31 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | "postCreateCommand": "yarn install" 16 | 17 | // Configure tool-specific properties. 18 | // "customizations": {}, 19 | 20 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 21 | // "remoteUser": "root" 22 | } 23 | -------------------------------------------------------------------------------- /deno/lib/__tests__/firstpartyschematypes.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { ZodFirstPartySchemaTypes, ZodFirstPartyTypeKind } from "../index.ts"; 6 | import { util } from "../helpers/util.ts"; 7 | 8 | test("Identify missing [ZodFirstPartySchemaTypes]", () => { 9 | type ZodFirstPartySchemaForType = 10 | ZodFirstPartySchemaTypes extends infer Schema 11 | ? Schema extends { _def: { typeName: T } } 12 | ? Schema 13 | : never 14 | : never; 15 | type ZodMappedTypes = { 16 | [key in ZodFirstPartyTypeKind]: ZodFirstPartySchemaForType; 17 | }; 18 | type ZodFirstPartySchemaTypesMissingFromUnion = keyof { 19 | [key in keyof ZodMappedTypes as ZodMappedTypes[key] extends { _def: never } 20 | ? key 21 | : never]: unknown; 22 | }; 23 | 24 | util.assertEqual(true); 25 | }); 26 | -------------------------------------------------------------------------------- /src/__tests__/parseUtil.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { 5 | isAborted, 6 | isDirty, 7 | isValid, 8 | SyncParseReturnType, 9 | } from "../helpers/parseUtil"; 10 | 11 | test("parseUtil isInvalid should use structural typing", () => { 12 | // Test for issue #556: https://github.com/colinhacks/zod/issues/556 13 | const aborted: SyncParseReturnType = { status: "aborted" }; 14 | const dirty: SyncParseReturnType = { status: "dirty", value: "whatever" }; 15 | const valid: SyncParseReturnType = { status: "valid", value: "whatever" }; 16 | 17 | expect(isAborted(aborted)).toBe(true); 18 | expect(isAborted(dirty)).toBe(false); 19 | expect(isAborted(valid)).toBe(false); 20 | 21 | expect(isDirty(aborted)).toBe(false); 22 | expect(isDirty(dirty)).toBe(true); 23 | expect(isDirty(valid)).toBe(false); 24 | 25 | expect(isValid(aborted)).toBe(false); 26 | expect(isValid(dirty)).toBe(false); 27 | expect(isValid(valid)).toBe(true); 28 | }); 29 | -------------------------------------------------------------------------------- /deno/lib/__tests__/pipeline.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("string to number pipeline", () => { 8 | const schema = z.string().transform(Number).pipe(z.number()); 9 | expect(schema.parse("1234")).toEqual(1234); 10 | }); 11 | 12 | test("string to number pipeline async", async () => { 13 | const schema = z 14 | .string() 15 | .transform(async (val) => Number(val)) 16 | .pipe(z.number()); 17 | expect(await schema.parseAsync("1234")).toEqual(1234); 18 | }); 19 | 20 | test("break if dirty", () => { 21 | const schema = z 22 | .string() 23 | .refine((c) => c === "1234") 24 | .transform(async (val) => Number(val)) 25 | .pipe(z.number().refine((v) => v < 100)); 26 | const r1: any = schema.safeParse("12345"); 27 | expect(r1.error.issues.length).toBe(1); 28 | const r2: any = schema.safeParse("3"); 29 | expect(r2.error.issues.length).toBe(1); 30 | }); 31 | -------------------------------------------------------------------------------- /deno/lib/__tests__/parseUtil.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { 6 | isAborted, 7 | isDirty, 8 | isValid, 9 | SyncParseReturnType, 10 | } from "../helpers/parseUtil.ts"; 11 | 12 | test("parseUtil isInvalid should use structural typing", () => { 13 | // Test for issue #556: https://github.com/colinhacks/zod/issues/556 14 | const aborted: SyncParseReturnType = { status: "aborted" }; 15 | const dirty: SyncParseReturnType = { status: "dirty", value: "whatever" }; 16 | const valid: SyncParseReturnType = { status: "valid", value: "whatever" }; 17 | 18 | expect(isAborted(aborted)).toBe(true); 19 | expect(isAborted(dirty)).toBe(false); 20 | expect(isAborted(valid)).toBe(false); 21 | 22 | expect(isDirty(aborted)).toBe(false); 23 | expect(isDirty(dirty)).toBe(true); 24 | expect(isDirty(valid)).toBe(false); 25 | 26 | expect(isValid(aborted)).toBe(false); 27 | expect(isValid(dirty)).toBe(false); 28 | expect(isValid(valid)).toBe(true); 29 | }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Colin McDonnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/__tests__/date.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | const beforeBenchmarkDate = new Date(2022, 10, 4); 7 | const benchmarkDate = new Date(2022, 10, 5); 8 | const afterBenchmarkDate = new Date(2022, 10, 6); 9 | 10 | const minCheck = z.date().min(benchmarkDate); 11 | const maxCheck = z.date().max(benchmarkDate); 12 | 13 | test("passing validations", () => { 14 | minCheck.parse(benchmarkDate); 15 | minCheck.parse(afterBenchmarkDate); 16 | 17 | maxCheck.parse(benchmarkDate); 18 | maxCheck.parse(beforeBenchmarkDate); 19 | }); 20 | 21 | test("failing validations", () => { 22 | expect(() => minCheck.parse(beforeBenchmarkDate)).toThrow(); 23 | expect(() => maxCheck.parse(afterBenchmarkDate)).toThrow(); 24 | }); 25 | 26 | test("min max getters", () => { 27 | expect(minCheck.minDate).toEqual(benchmarkDate); 28 | expect(minCheck.min(afterBenchmarkDate).minDate).toEqual(afterBenchmarkDate); 29 | 30 | expect(maxCheck.maxDate).toEqual(benchmarkDate); 31 | expect(maxCheck.max(beforeBenchmarkDate).maxDate).toEqual( 32 | beforeBenchmarkDate 33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /deno/lib/__tests__/date.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | const beforeBenchmarkDate = new Date(2022, 10, 4); 8 | const benchmarkDate = new Date(2022, 10, 5); 9 | const afterBenchmarkDate = new Date(2022, 10, 6); 10 | 11 | const minCheck = z.date().min(benchmarkDate); 12 | const maxCheck = z.date().max(benchmarkDate); 13 | 14 | test("passing validations", () => { 15 | minCheck.parse(benchmarkDate); 16 | minCheck.parse(afterBenchmarkDate); 17 | 18 | maxCheck.parse(benchmarkDate); 19 | maxCheck.parse(beforeBenchmarkDate); 20 | }); 21 | 22 | test("failing validations", () => { 23 | expect(() => minCheck.parse(beforeBenchmarkDate)).toThrow(); 24 | expect(() => maxCheck.parse(afterBenchmarkDate)).toThrow(); 25 | }); 26 | 27 | test("min max getters", () => { 28 | expect(minCheck.minDate).toEqual(benchmarkDate); 29 | expect(minCheck.min(afterBenchmarkDate).minDate).toEqual(afterBenchmarkDate); 30 | 31 | expect(maxCheck.maxDate).toEqual(benchmarkDate); 32 | expect(maxCheck.max(beforeBenchmarkDate).maxDate).toEqual( 33 | beforeBenchmarkDate 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /src/__tests__/literal.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | const literalTuna = z.literal("tuna"); 7 | const literalFortyTwo = z.literal(42); 8 | const literalTrue = z.literal(true); 9 | 10 | const terrificSymbol = Symbol("terrific"); 11 | const literalTerrificSymbol = z.literal(terrificSymbol); 12 | 13 | test("passing validations", () => { 14 | literalTuna.parse("tuna"); 15 | literalFortyTwo.parse(42); 16 | literalTrue.parse(true); 17 | literalTerrificSymbol.parse(terrificSymbol); 18 | }); 19 | 20 | test("failing validations", () => { 21 | expect(() => literalTuna.parse("shark")).toThrow(); 22 | expect(() => literalFortyTwo.parse(43)).toThrow(); 23 | expect(() => literalTrue.parse(false)).toThrow(); 24 | expect(() => literalTerrificSymbol.parse(Symbol("terrific"))).toThrow(); 25 | }); 26 | 27 | test("invalid_literal should have `received` field with data", () => { 28 | const data = "shark"; 29 | const result = literalTuna.safeParse(data); 30 | if (!result.success) { 31 | const issue = result.error.issues[0]; 32 | if (issue.code === "invalid_literal") { 33 | expect(issue.received).toBe(data); 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /deno/lib/__tests__/literal.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | const literalTuna = z.literal("tuna"); 8 | const literalFortyTwo = z.literal(42); 9 | const literalTrue = z.literal(true); 10 | 11 | const terrificSymbol = Symbol("terrific"); 12 | const literalTerrificSymbol = z.literal(terrificSymbol); 13 | 14 | test("passing validations", () => { 15 | literalTuna.parse("tuna"); 16 | literalFortyTwo.parse(42); 17 | literalTrue.parse(true); 18 | literalTerrificSymbol.parse(terrificSymbol); 19 | }); 20 | 21 | test("failing validations", () => { 22 | expect(() => literalTuna.parse("shark")).toThrow(); 23 | expect(() => literalFortyTwo.parse(43)).toThrow(); 24 | expect(() => literalTrue.parse(false)).toThrow(); 25 | expect(() => literalTerrificSymbol.parse(Symbol("terrific"))).toThrow(); 26 | }); 27 | 28 | test("invalid_literal should have `received` field with data", () => { 29 | const data = "shark"; 30 | const result = literalTuna.safeParse(data); 31 | if (!result.success) { 32 | const issue = result.error.issues[0]; 33 | if (issue.code === "invalid_literal") { 34 | expect(issue.received).toBe(data); 35 | } 36 | } 37 | }); 38 | -------------------------------------------------------------------------------- /.github/workflows/release-canary.yml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | name: Release on npm (canary) 4 | on: 5 | push: 6 | branches: 7 | - "main" 8 | paths: 9 | - "src/**" 10 | - ".github/workflows/release-canary.yml" 11 | jobs: 12 | build_and_publish: 13 | runs-on: ubuntu-latest 14 | permissions: 15 | contents: write 16 | pull-requests: read 17 | id-token: write 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Set up Node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 20 27 | 28 | - name: Install dependencies 29 | run: | 30 | yarn install 31 | 32 | - name: Build 33 | run: | 34 | yarn clean 35 | yarn build 36 | 37 | - name: Set version 38 | run: | 39 | npm --no-git-tag-version version minor 40 | npm --no-git-tag-version version $(npm pkg get version | sed 's/"//g')-canary.$(date +'%Y%m%dT%H%M%S') 41 | 42 | - id: publish 43 | name: Publish to NPM 44 | uses: JS-DevTools/npm-publish@v3 45 | with: 46 | token: ${{ secrets.NPM_TOKEN }} 47 | dry-run: false 48 | tag: canary 49 | provenance: true 50 | -------------------------------------------------------------------------------- /src/__tests__/instanceof.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("instanceof", async () => { 8 | class Test {} 9 | class Subtest extends Test {} 10 | abstract class AbstractBar { 11 | constructor(public val: string) {} 12 | } 13 | class Bar extends AbstractBar {} 14 | 15 | const TestSchema = z.instanceof(Test); 16 | const SubtestSchema = z.instanceof(Subtest); 17 | const AbstractSchema = z.instanceof(AbstractBar); 18 | const BarSchema = z.instanceof(Bar); 19 | 20 | TestSchema.parse(new Test()); 21 | TestSchema.parse(new Subtest()); 22 | SubtestSchema.parse(new Subtest()); 23 | AbstractSchema.parse(new Bar("asdf")); 24 | const bar = BarSchema.parse(new Bar("asdf")); 25 | expect(bar.val).toEqual("asdf"); 26 | 27 | await expect(() => SubtestSchema.parse(new Test())).toThrow( 28 | /Input not instance of Subtest/ 29 | ); 30 | await expect(() => TestSchema.parse(12)).toThrow( 31 | /Input not instance of Test/ 32 | ); 33 | 34 | util.assertEqual>(true); 35 | }); 36 | 37 | test("instanceof fatal", () => { 38 | const schema = z.instanceof(Date).refine((d) => d.toString()); 39 | const res = schema.safeParse(null); 40 | expect(res.success).toBe(false); 41 | }); 42 | -------------------------------------------------------------------------------- /src/__tests__/nullable.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | function checkErrors(a: z.ZodTypeAny, bad: any) { 7 | let expected; 8 | try { 9 | a.parse(bad); 10 | } catch (error) { 11 | expected = (error as z.ZodError).formErrors; 12 | } 13 | try { 14 | a.nullable().parse(bad); 15 | } catch (error) { 16 | expect((error as z.ZodError).formErrors).toEqual(expected); 17 | } 18 | } 19 | 20 | test("Should have error messages appropriate for the underlying type", () => { 21 | checkErrors(z.string().min(2), 1); 22 | z.string().min(2).nullable().parse(null); 23 | checkErrors(z.number().gte(2), 1); 24 | z.number().gte(2).nullable().parse(null); 25 | checkErrors(z.boolean(), ""); 26 | z.boolean().nullable().parse(null); 27 | checkErrors(z.null(), null); 28 | z.null().nullable().parse(null); 29 | checkErrors(z.null(), {}); 30 | z.null().nullable().parse(null); 31 | checkErrors(z.object({}), 1); 32 | z.object({}).nullable().parse(null); 33 | checkErrors(z.tuple([]), 1); 34 | z.tuple([]).nullable().parse(null); 35 | checkErrors(z.unknown(), 1); 36 | z.unknown().nullable().parse(null); 37 | }); 38 | 39 | test("unwrap", () => { 40 | const unwrapped = z.string().nullable().unwrap(); 41 | expect(unwrapped).toBeInstanceOf(z.ZodString); 42 | }); 43 | -------------------------------------------------------------------------------- /src/__tests__/parser.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("parse strict object with unknown keys", () => { 7 | expect(() => 8 | z 9 | .object({ name: z.string() }) 10 | .strict() 11 | .parse({ name: "bill", unknownKey: 12 } as any) 12 | ).toThrow(); 13 | }); 14 | 15 | test("parse nonstrict object with unknown keys", () => { 16 | z.object({ name: z.string() }) 17 | .nonstrict() 18 | .parse({ name: "bill", unknownKey: 12 }); 19 | }); 20 | 21 | test("invalid left side of intersection", () => { 22 | expect(() => 23 | z.intersection(z.string(), z.number()).parse(12 as any) 24 | ).toThrow(); 25 | }); 26 | 27 | test("invalid right side of intersection", () => { 28 | expect(() => 29 | z.intersection(z.string(), z.number()).parse("12" as any) 30 | ).toThrow(); 31 | }); 32 | 33 | test("parsing non-array in tuple schema", () => { 34 | expect(() => z.tuple([]).parse("12" as any)).toThrow(); 35 | }); 36 | 37 | test("incorrect num elements in tuple", () => { 38 | expect(() => z.tuple([]).parse(["asdf"] as any)).toThrow(); 39 | }); 40 | 41 | test("invalid enum value", () => { 42 | expect(() => z.enum(["Blue"]).parse("Red" as any)).toThrow(); 43 | }); 44 | 45 | test("parsing unknown", () => { 46 | z.string().parse("Red" as unknown); 47 | }); 48 | -------------------------------------------------------------------------------- /src/__tests__/optional.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | function checkErrors(a: z.ZodTypeAny, bad: any) { 7 | let expected; 8 | try { 9 | a.parse(bad); 10 | } catch (error) { 11 | expected = (error as z.ZodError).formErrors; 12 | } 13 | try { 14 | a.optional().parse(bad); 15 | } catch (error) { 16 | expect((error as z.ZodError).formErrors).toEqual(expected); 17 | } 18 | } 19 | 20 | test("Should have error messages appropriate for the underlying type", () => { 21 | checkErrors(z.string().min(2), 1); 22 | z.string().min(2).optional().parse(undefined); 23 | checkErrors(z.number().gte(2), 1); 24 | z.number().gte(2).optional().parse(undefined); 25 | checkErrors(z.boolean(), ""); 26 | z.boolean().optional().parse(undefined); 27 | checkErrors(z.undefined(), null); 28 | z.undefined().optional().parse(undefined); 29 | checkErrors(z.null(), {}); 30 | z.null().optional().parse(undefined); 31 | checkErrors(z.object({}), 1); 32 | z.object({}).optional().parse(undefined); 33 | checkErrors(z.tuple([]), 1); 34 | z.tuple([]).optional().parse(undefined); 35 | checkErrors(z.unknown(), 1); 36 | z.unknown().optional().parse(undefined); 37 | }); 38 | 39 | test("unwrap", () => { 40 | const unwrapped = z.string().optional().unwrap(); 41 | expect(unwrapped).toBeInstanceOf(z.ZodString); 42 | }); 43 | -------------------------------------------------------------------------------- /deno/lib/__tests__/instanceof.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("instanceof", async () => { 9 | class Test {} 10 | class Subtest extends Test {} 11 | abstract class AbstractBar { 12 | constructor(public val: string) {} 13 | } 14 | class Bar extends AbstractBar {} 15 | 16 | const TestSchema = z.instanceof(Test); 17 | const SubtestSchema = z.instanceof(Subtest); 18 | const AbstractSchema = z.instanceof(AbstractBar); 19 | const BarSchema = z.instanceof(Bar); 20 | 21 | TestSchema.parse(new Test()); 22 | TestSchema.parse(new Subtest()); 23 | SubtestSchema.parse(new Subtest()); 24 | AbstractSchema.parse(new Bar("asdf")); 25 | const bar = BarSchema.parse(new Bar("asdf")); 26 | expect(bar.val).toEqual("asdf"); 27 | 28 | await expect(() => SubtestSchema.parse(new Test())).toThrow( 29 | /Input not instance of Subtest/ 30 | ); 31 | await expect(() => TestSchema.parse(12)).toThrow( 32 | /Input not instance of Test/ 33 | ); 34 | 35 | util.assertEqual>(true); 36 | }); 37 | 38 | test("instanceof fatal", () => { 39 | const schema = z.instanceof(Date).refine((d) => d.toString()); 40 | const res = schema.safeParse(null); 41 | expect(res.success).toBe(false); 42 | }); 43 | -------------------------------------------------------------------------------- /deno/lib/__tests__/nullable.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | function checkErrors(a: z.ZodTypeAny, bad: any) { 8 | let expected; 9 | try { 10 | a.parse(bad); 11 | } catch (error) { 12 | expected = (error as z.ZodError).formErrors; 13 | } 14 | try { 15 | a.nullable().parse(bad); 16 | } catch (error) { 17 | expect((error as z.ZodError).formErrors).toEqual(expected); 18 | } 19 | } 20 | 21 | test("Should have error messages appropriate for the underlying type", () => { 22 | checkErrors(z.string().min(2), 1); 23 | z.string().min(2).nullable().parse(null); 24 | checkErrors(z.number().gte(2), 1); 25 | z.number().gte(2).nullable().parse(null); 26 | checkErrors(z.boolean(), ""); 27 | z.boolean().nullable().parse(null); 28 | checkErrors(z.null(), null); 29 | z.null().nullable().parse(null); 30 | checkErrors(z.null(), {}); 31 | z.null().nullable().parse(null); 32 | checkErrors(z.object({}), 1); 33 | z.object({}).nullable().parse(null); 34 | checkErrors(z.tuple([]), 1); 35 | z.tuple([]).nullable().parse(null); 36 | checkErrors(z.unknown(), 1); 37 | z.unknown().nullable().parse(null); 38 | }); 39 | 40 | test("unwrap", () => { 41 | const unwrapped = z.string().nullable().unwrap(); 42 | expect(unwrapped).toBeInstanceOf(z.ZodString); 43 | }); 44 | -------------------------------------------------------------------------------- /src/__tests__/Mocker.ts: -------------------------------------------------------------------------------- 1 | function getRandomInt(max: number) { 2 | return Math.floor(Math.random() * Math.floor(max)); 3 | } 4 | 5 | const testSymbol = Symbol("test"); 6 | 7 | export class Mocker { 8 | pick = (...args: any[]) => { 9 | return args[getRandomInt(args.length)]; 10 | }; 11 | 12 | get string() { 13 | return Math.random().toString(36).substring(7); 14 | } 15 | get number() { 16 | return Math.random() * 100; 17 | } 18 | get bigint() { 19 | return BigInt(Math.floor(Math.random() * 10000)); 20 | } 21 | get boolean() { 22 | return Math.random() < 0.5; 23 | } 24 | get date() { 25 | return new Date(Math.floor(Date.now() * Math.random())); 26 | } 27 | get symbol() { 28 | return testSymbol; 29 | } 30 | get null(): null { 31 | return null; 32 | } 33 | get undefined(): undefined { 34 | return undefined; 35 | } 36 | get stringOptional() { 37 | return this.pick(this.string, this.undefined); 38 | } 39 | get stringNullable() { 40 | return this.pick(this.string, this.null); 41 | } 42 | get numberOptional() { 43 | return this.pick(this.number, this.undefined); 44 | } 45 | get numberNullable() { 46 | return this.pick(this.number, this.null); 47 | } 48 | get booleanOptional() { 49 | return this.pick(this.boolean, this.undefined); 50 | } 51 | get booleanNullable() { 52 | return this.pick(this.boolean, this.null); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /deno/lib/__tests__/Mocker.ts: -------------------------------------------------------------------------------- 1 | function getRandomInt(max: number) { 2 | return Math.floor(Math.random() * Math.floor(max)); 3 | } 4 | 5 | const testSymbol = Symbol("test"); 6 | 7 | export class Mocker { 8 | pick = (...args: any[]) => { 9 | return args[getRandomInt(args.length)]; 10 | }; 11 | 12 | get string() { 13 | return Math.random().toString(36).substring(7); 14 | } 15 | get number() { 16 | return Math.random() * 100; 17 | } 18 | get bigint() { 19 | return BigInt(Math.floor(Math.random() * 10000)); 20 | } 21 | get boolean() { 22 | return Math.random() < 0.5; 23 | } 24 | get date() { 25 | return new Date(Math.floor(Date.now() * Math.random())); 26 | } 27 | get symbol() { 28 | return testSymbol; 29 | } 30 | get null(): null { 31 | return null; 32 | } 33 | get undefined(): undefined { 34 | return undefined; 35 | } 36 | get stringOptional() { 37 | return this.pick(this.string, this.undefined); 38 | } 39 | get stringNullable() { 40 | return this.pick(this.string, this.null); 41 | } 42 | get numberOptional() { 43 | return this.pick(this.number, this.undefined); 44 | } 45 | get numberNullable() { 46 | return this.pick(this.number, this.null); 47 | } 48 | get booleanOptional() { 49 | return this.pick(this.boolean, this.undefined); 50 | } 51 | get booleanNullable() { 52 | return this.pick(this.boolean, this.null); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /deno/lib/__tests__/parser.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("parse strict object with unknown keys", () => { 8 | expect(() => 9 | z 10 | .object({ name: z.string() }) 11 | .strict() 12 | .parse({ name: "bill", unknownKey: 12 } as any) 13 | ).toThrow(); 14 | }); 15 | 16 | test("parse nonstrict object with unknown keys", () => { 17 | z.object({ name: z.string() }) 18 | .nonstrict() 19 | .parse({ name: "bill", unknownKey: 12 }); 20 | }); 21 | 22 | test("invalid left side of intersection", () => { 23 | expect(() => 24 | z.intersection(z.string(), z.number()).parse(12 as any) 25 | ).toThrow(); 26 | }); 27 | 28 | test("invalid right side of intersection", () => { 29 | expect(() => 30 | z.intersection(z.string(), z.number()).parse("12" as any) 31 | ).toThrow(); 32 | }); 33 | 34 | test("parsing non-array in tuple schema", () => { 35 | expect(() => z.tuple([]).parse("12" as any)).toThrow(); 36 | }); 37 | 38 | test("incorrect num elements in tuple", () => { 39 | expect(() => z.tuple([]).parse(["asdf"] as any)).toThrow(); 40 | }); 41 | 42 | test("invalid enum value", () => { 43 | expect(() => z.enum(["Blue"]).parse("Red" as any)).toThrow(); 44 | }); 45 | 46 | test("parsing unknown", () => { 47 | z.string().parse("Red" as unknown); 48 | }); 49 | -------------------------------------------------------------------------------- /src/benchmarks/realworld.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index"; 4 | 5 | const shortSuite = new Benchmark.Suite("realworld"); 6 | 7 | const People = z.array( 8 | z.object({ 9 | type: z.literal("person"), 10 | hair: z.enum(["blue", "brown"]), 11 | active: z.boolean(), 12 | name: z.string(), 13 | age: z.number().int(), 14 | hobbies: z.array(z.string()), 15 | address: z.object({ 16 | street: z.string(), 17 | zip: z.string(), 18 | country: z.string(), 19 | }), 20 | }) 21 | ); 22 | 23 | let i = 0; 24 | 25 | function num() { 26 | return ++i; 27 | } 28 | 29 | function str() { 30 | return (++i % 100).toString(16); 31 | } 32 | 33 | function array(fn: () => T): T[] { 34 | return Array.from({ length: ++i % 10 }, () => fn()); 35 | } 36 | 37 | const people = Array.from({ length: 100 }, () => { 38 | return { 39 | type: "person", 40 | hair: i % 2 ? "blue" : "brown", 41 | active: !!(i % 2), 42 | name: str(), 43 | age: num(), 44 | hobbies: array(str), 45 | address: { 46 | street: str(), 47 | zip: str(), 48 | country: str(), 49 | }, 50 | }; 51 | }); 52 | 53 | shortSuite 54 | .add("valid", () => { 55 | People.parse(people); 56 | }) 57 | .on("cycle", (e: Benchmark.Event) => { 58 | console.log(`${(shortSuite as any).name}: ${e.target}`); 59 | }); 60 | 61 | export default { 62 | suites: [shortSuite], 63 | }; 64 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/realworld.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index.ts"; 4 | 5 | const shortSuite = new Benchmark.Suite("realworld"); 6 | 7 | const People = z.array( 8 | z.object({ 9 | type: z.literal("person"), 10 | hair: z.enum(["blue", "brown"]), 11 | active: z.boolean(), 12 | name: z.string(), 13 | age: z.number().int(), 14 | hobbies: z.array(z.string()), 15 | address: z.object({ 16 | street: z.string(), 17 | zip: z.string(), 18 | country: z.string(), 19 | }), 20 | }) 21 | ); 22 | 23 | let i = 0; 24 | 25 | function num() { 26 | return ++i; 27 | } 28 | 29 | function str() { 30 | return (++i % 100).toString(16); 31 | } 32 | 33 | function array(fn: () => T): T[] { 34 | return Array.from({ length: ++i % 10 }, () => fn()); 35 | } 36 | 37 | const people = Array.from({ length: 100 }, () => { 38 | return { 39 | type: "person", 40 | hair: i % 2 ? "blue" : "brown", 41 | active: !!(i % 2), 42 | name: str(), 43 | age: num(), 44 | hobbies: array(str), 45 | address: { 46 | street: str(), 47 | zip: str(), 48 | country: str(), 49 | }, 50 | }; 51 | }); 52 | 53 | shortSuite 54 | .add("valid", () => { 55 | People.parse(people); 56 | }) 57 | .on("cycle", (e: Benchmark.Event) => { 58 | console.log(`${(shortSuite as any).name}: ${e.target}`); 59 | }); 60 | 61 | export default { 62 | suites: [shortSuite], 63 | }; 64 | -------------------------------------------------------------------------------- /deno/lib/__tests__/optional.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | function checkErrors(a: z.ZodTypeAny, bad: any) { 8 | let expected; 9 | try { 10 | a.parse(bad); 11 | } catch (error) { 12 | expected = (error as z.ZodError).formErrors; 13 | } 14 | try { 15 | a.optional().parse(bad); 16 | } catch (error) { 17 | expect((error as z.ZodError).formErrors).toEqual(expected); 18 | } 19 | } 20 | 21 | test("Should have error messages appropriate for the underlying type", () => { 22 | checkErrors(z.string().min(2), 1); 23 | z.string().min(2).optional().parse(undefined); 24 | checkErrors(z.number().gte(2), 1); 25 | z.number().gte(2).optional().parse(undefined); 26 | checkErrors(z.boolean(), ""); 27 | z.boolean().optional().parse(undefined); 28 | checkErrors(z.undefined(), null); 29 | z.undefined().optional().parse(undefined); 30 | checkErrors(z.null(), {}); 31 | z.null().optional().parse(undefined); 32 | checkErrors(z.object({}), 1); 33 | z.object({}).optional().parse(undefined); 34 | checkErrors(z.tuple([]), 1); 35 | z.tuple([]).optional().parse(undefined); 36 | checkErrors(z.unknown(), 1); 37 | z.unknown().optional().parse(undefined); 38 | }); 39 | 40 | test("unwrap", () => { 41 | const unwrapped = z.string().optional().unwrap(); 42 | expect(unwrapped).toBeInstanceOf(z.ZodString); 43 | }); 44 | -------------------------------------------------------------------------------- /src/__tests__/description.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | const description = "a description"; 7 | 8 | test("passing `description` to schema should add a description", () => { 9 | expect(z.string({ description }).description).toEqual(description); 10 | expect(z.number({ description }).description).toEqual(description); 11 | expect(z.boolean({ description }).description).toEqual(description); 12 | }); 13 | 14 | test("`.describe` should add a description", () => { 15 | expect(z.string().describe(description).description).toEqual(description); 16 | expect(z.number().describe(description).description).toEqual(description); 17 | expect(z.boolean().describe(description).description).toEqual(description); 18 | }); 19 | 20 | test("description should carry over to chained schemas", () => { 21 | const schema = z.string({ description }); 22 | expect(schema.description).toEqual(description); 23 | expect(schema.optional().description).toEqual(description); 24 | expect(schema.optional().nullable().default("default").description).toEqual( 25 | description 26 | ); 27 | }); 28 | 29 | test("description should not carry over to chained array schema", () => { 30 | const schema = z.string().describe(description); 31 | 32 | expect(schema.description).toEqual(description); 33 | expect(schema.array().description).toEqual(undefined); 34 | expect(z.array(schema).description).toEqual(undefined); 35 | }); 36 | -------------------------------------------------------------------------------- /src/benchmarks/string.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index"; 4 | 5 | const SUITE_NAME = "z.string"; 6 | const suite = new Benchmark.Suite(SUITE_NAME); 7 | 8 | const empty = ""; 9 | const short = "short"; 10 | const long = "long".repeat(256); 11 | const manual = (str: unknown) => { 12 | if (typeof str !== "string") { 13 | throw new Error("Not a string"); 14 | } 15 | 16 | return str; 17 | }; 18 | const stringSchema = z.string(); 19 | const optionalStringSchema = z.string().optional(); 20 | const optionalNullableStringSchema = z.string().optional().nullable(); 21 | 22 | suite 23 | .add("empty string", () => { 24 | stringSchema.parse(empty); 25 | }) 26 | .add("short string", () => { 27 | stringSchema.parse(short); 28 | }) 29 | .add("long string", () => { 30 | stringSchema.parse(long); 31 | }) 32 | .add("optional string", () => { 33 | optionalStringSchema.parse(long); 34 | }) 35 | .add("nullable string", () => { 36 | optionalNullableStringSchema.parse(long); 37 | }) 38 | .add("nullable (null) string", () => { 39 | optionalNullableStringSchema.parse(null); 40 | }) 41 | .add("invalid: null", () => { 42 | try { 43 | stringSchema.parse(null); 44 | } catch (err) {} 45 | }) 46 | .add("manual parser: long", () => { 47 | manual(long); 48 | }) 49 | .on("cycle", (e: Benchmark.Event) => { 50 | console.log(`${SUITE_NAME}: ${e.target}`); 51 | }); 52 | 53 | export default { 54 | suites: [suite], 55 | }; 56 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/string.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index.ts"; 4 | 5 | const SUITE_NAME = "z.string"; 6 | const suite = new Benchmark.Suite(SUITE_NAME); 7 | 8 | const empty = ""; 9 | const short = "short"; 10 | const long = "long".repeat(256); 11 | const manual = (str: unknown) => { 12 | if (typeof str !== "string") { 13 | throw new Error("Not a string"); 14 | } 15 | 16 | return str; 17 | }; 18 | const stringSchema = z.string(); 19 | const optionalStringSchema = z.string().optional(); 20 | const optionalNullableStringSchema = z.string().optional().nullable(); 21 | 22 | suite 23 | .add("empty string", () => { 24 | stringSchema.parse(empty); 25 | }) 26 | .add("short string", () => { 27 | stringSchema.parse(short); 28 | }) 29 | .add("long string", () => { 30 | stringSchema.parse(long); 31 | }) 32 | .add("optional string", () => { 33 | optionalStringSchema.parse(long); 34 | }) 35 | .add("nullable string", () => { 36 | optionalNullableStringSchema.parse(long); 37 | }) 38 | .add("nullable (null) string", () => { 39 | optionalNullableStringSchema.parse(null); 40 | }) 41 | .add("invalid: null", () => { 42 | try { 43 | stringSchema.parse(null); 44 | } catch (err) {} 45 | }) 46 | .add("manual parser: long", () => { 47 | manual(long); 48 | }) 49 | .on("cycle", (e: Benchmark.Event) => { 50 | console.log(`${SUITE_NAME}: ${e.target}`); 51 | }); 52 | 53 | export default { 54 | suites: [suite], 55 | }; 56 | -------------------------------------------------------------------------------- /deno/lib/__tests__/description.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | const description = "a description"; 8 | 9 | test("passing `description` to schema should add a description", () => { 10 | expect(z.string({ description }).description).toEqual(description); 11 | expect(z.number({ description }).description).toEqual(description); 12 | expect(z.boolean({ description }).description).toEqual(description); 13 | }); 14 | 15 | test("`.describe` should add a description", () => { 16 | expect(z.string().describe(description).description).toEqual(description); 17 | expect(z.number().describe(description).description).toEqual(description); 18 | expect(z.boolean().describe(description).description).toEqual(description); 19 | }); 20 | 21 | test("description should carry over to chained schemas", () => { 22 | const schema = z.string({ description }); 23 | expect(schema.description).toEqual(description); 24 | expect(schema.optional().description).toEqual(description); 25 | expect(schema.optional().nullable().default("default").description).toEqual( 26 | description 27 | ); 28 | }); 29 | 30 | test("description should not carry over to chained array schema", () => { 31 | const schema = z.string().describe(description); 32 | 33 | expect(schema.description).toEqual(description); 34 | expect(schema.array().description).toEqual(undefined); 35 | expect(z.array(schema).description).toEqual(undefined); 36 | }); 37 | -------------------------------------------------------------------------------- /src/__tests__/crazySchema.ts: -------------------------------------------------------------------------------- 1 | import * as z from "../index"; 2 | 3 | export const crazySchema = z.object({ 4 | tuple: z.tuple([ 5 | z.string().nullable().optional(), 6 | z.number().nullable().optional(), 7 | z.boolean().nullable().optional(), 8 | z.null().nullable().optional(), 9 | z.undefined().nullable().optional(), 10 | z.literal("1234").nullable().optional(), 11 | ]), 12 | merged: z 13 | .object({ 14 | k1: z.string().optional(), 15 | }) 16 | .merge(z.object({ k1: z.string().nullable(), k2: z.number() })), 17 | union: z.array(z.union([z.literal("asdf"), z.literal(12)])).nonempty(), 18 | array: z.array(z.number()), 19 | // sumTransformer: z.transformer(z.array(z.number()), z.number(), (arg) => { 20 | // return arg.reduce((a, b) => a + b, 0); 21 | // }), 22 | sumMinLength: z.array(z.number()).refine((arg) => arg.length > 5), 23 | intersection: z.intersection( 24 | z.object({ p1: z.string().optional() }), 25 | z.object({ p1: z.number().optional() }) 26 | ), 27 | enum: z.intersection(z.enum(["zero", "one"]), z.enum(["one", "two"])), 28 | nonstrict: z.object({ points: z.number() }).nonstrict(), 29 | numProm: z.promise(z.number()), 30 | lenfun: z.function(z.tuple([z.string()]), z.boolean()), 31 | }); 32 | 33 | export const asyncCrazySchema = crazySchema.extend({ 34 | // async_transform: z.transformer( 35 | // z.array(z.number()), 36 | // z.number(), 37 | // async (arg) => { 38 | // return arg.reduce((a, b) => a + b, 0); 39 | // } 40 | // ), 41 | async_refine: z.array(z.number()).refine(async (arg) => arg.length > 5), 42 | }); 43 | -------------------------------------------------------------------------------- /deno/lib/__tests__/crazySchema.ts: -------------------------------------------------------------------------------- 1 | import * as z from "../index.ts"; 2 | 3 | export const crazySchema = z.object({ 4 | tuple: z.tuple([ 5 | z.string().nullable().optional(), 6 | z.number().nullable().optional(), 7 | z.boolean().nullable().optional(), 8 | z.null().nullable().optional(), 9 | z.undefined().nullable().optional(), 10 | z.literal("1234").nullable().optional(), 11 | ]), 12 | merged: z 13 | .object({ 14 | k1: z.string().optional(), 15 | }) 16 | .merge(z.object({ k1: z.string().nullable(), k2: z.number() })), 17 | union: z.array(z.union([z.literal("asdf"), z.literal(12)])).nonempty(), 18 | array: z.array(z.number()), 19 | // sumTransformer: z.transformer(z.array(z.number()), z.number(), (arg) => { 20 | // return arg.reduce((a, b) => a + b, 0); 21 | // }), 22 | sumMinLength: z.array(z.number()).refine((arg) => arg.length > 5), 23 | intersection: z.intersection( 24 | z.object({ p1: z.string().optional() }), 25 | z.object({ p1: z.number().optional() }) 26 | ), 27 | enum: z.intersection(z.enum(["zero", "one"]), z.enum(["one", "two"])), 28 | nonstrict: z.object({ points: z.number() }).nonstrict(), 29 | numProm: z.promise(z.number()), 30 | lenfun: z.function(z.tuple([z.string()]), z.boolean()), 31 | }); 32 | 33 | export const asyncCrazySchema = crazySchema.extend({ 34 | // async_transform: z.transformer( 35 | // z.array(z.number()), 36 | // z.number(), 37 | // async (arg) => { 38 | // return arg.reduce((a, b) => a + b, 0); 39 | // } 40 | // ), 41 | async_refine: z.array(z.number()).refine(async (arg) => arg.length > 5), 42 | }); 43 | -------------------------------------------------------------------------------- /src/__tests__/language-server.source.ts: -------------------------------------------------------------------------------- 1 | import * as z from "../index"; 2 | 3 | export const filePath = __filename; 4 | 5 | // z.object() 6 | 7 | export const Test = z.object({ 8 | f1: z.number(), 9 | }); 10 | 11 | export type Test = z.infer; 12 | 13 | export const instanceOfTest: Test = { 14 | f1: 1, 15 | }; 16 | 17 | // z.object().merge() 18 | 19 | export const TestMerge = z 20 | .object({ 21 | f2: z.string().optional(), 22 | }) 23 | .merge(Test); 24 | 25 | export type TestMerge = z.infer; 26 | 27 | export const instanceOfTestMerge: TestMerge = { 28 | f1: 1, 29 | f2: "string", 30 | }; 31 | 32 | // z.union() 33 | 34 | export const TestUnion = z.union([ 35 | z.object({ 36 | f2: z.string().optional(), 37 | }), 38 | Test, 39 | ]); 40 | 41 | export type TestUnion = z.infer; 42 | 43 | export const instanceOfTestUnion: TestUnion = { 44 | f1: 1, 45 | f2: "string", 46 | }; 47 | 48 | // z.object().partial() 49 | 50 | export const TestPartial = Test.partial(); 51 | 52 | export type TestPartial = z.infer; 53 | 54 | export const instanceOfTestPartial: TestPartial = { 55 | f1: 1, 56 | }; 57 | 58 | // z.object().pick() 59 | 60 | export const TestPick = TestMerge.pick({ f1: true }); 61 | 62 | export type TestPick = z.infer; 63 | 64 | export const instanceOfTestPick: TestPick = { 65 | f1: 1, 66 | }; 67 | 68 | // z.object().omit() 69 | 70 | export const TestOmit = TestMerge.omit({ f2: true }); 71 | 72 | export type TestOmit = z.infer; 73 | 74 | export const instanceOfTestOmit: TestOmit = { 75 | f1: 1, 76 | }; 77 | -------------------------------------------------------------------------------- /src/__tests__/generics.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("generics", () => { 8 | async function stripOuter( 9 | schema: TData, 10 | data: unknown 11 | ) { 12 | return z 13 | .object({ 14 | nested: schema, // as z.ZodTypeAny, 15 | }) 16 | .transform((data) => { 17 | return data.nested!; 18 | }) 19 | .parse({ nested: data }); 20 | } 21 | 22 | const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" }); 23 | util.assertEqual>(true); 24 | }); 25 | 26 | // test("assignability", () => { 27 | // const createSchemaAndParse = ( 28 | // key: K, 29 | // valueSchema: VS, 30 | // data: unknown 31 | // ) => { 32 | // const schema = z.object({ 33 | // [key]: valueSchema, 34 | // } as { [k in K]: VS }); 35 | // return { [key]: valueSchema }; 36 | // const parsed = schema.parse(data); 37 | // return parsed; 38 | // // const inferred: z.infer> = parsed; 39 | // // return inferred; 40 | // }; 41 | // const parsed = createSchemaAndParse("foo", z.string(), { foo: "" }); 42 | // util.assertEqual(true); 43 | // }); 44 | 45 | test("nested no undefined", () => { 46 | const inner = z.string().or(z.array(z.string())); 47 | const outer = z.object({ inner }); 48 | type outerSchema = z.infer; 49 | z.util.assertEqual(true); 50 | expect(outer.safeParse({ inner: undefined }).success).toEqual(false); 51 | }); 52 | -------------------------------------------------------------------------------- /src/benchmarks/index.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import datetimeBenchmarks from "./datetime"; 4 | import discriminatedUnionBenchmarks from "./discriminatedUnion"; 5 | import ipv4Benchmarks from "./ipv4"; 6 | import objectBenchmarks from "./object"; 7 | import primitiveBenchmarks from "./primitives"; 8 | import realworld from "./realworld"; 9 | import stringBenchmarks from "./string"; 10 | import unionBenchmarks from "./union"; 11 | 12 | const argv = process.argv.slice(2); 13 | let suites: Benchmark.Suite[] = []; 14 | 15 | if (!argv.length) { 16 | suites = [ 17 | ...realworld.suites, 18 | ...primitiveBenchmarks.suites, 19 | ...stringBenchmarks.suites, 20 | ...objectBenchmarks.suites, 21 | ...unionBenchmarks.suites, 22 | ...discriminatedUnionBenchmarks.suites, 23 | ]; 24 | } else { 25 | if (argv.includes("--realworld")) { 26 | suites.push(...realworld.suites); 27 | } 28 | if (argv.includes("--primitives")) { 29 | suites.push(...primitiveBenchmarks.suites); 30 | } 31 | if (argv.includes("--string")) { 32 | suites.push(...stringBenchmarks.suites); 33 | } 34 | if (argv.includes("--object")) { 35 | suites.push(...objectBenchmarks.suites); 36 | } 37 | if (argv.includes("--union")) { 38 | suites.push(...unionBenchmarks.suites); 39 | } 40 | if (argv.includes("--discriminatedUnion")) { 41 | suites.push(...datetimeBenchmarks.suites); 42 | } 43 | if (argv.includes("--datetime")) { 44 | suites.push(...datetimeBenchmarks.suites); 45 | } 46 | if (argv.includes("--ipv4")) { 47 | suites.push(...ipv4Benchmarks.suites); 48 | } 49 | } 50 | 51 | for (const suite of suites) { 52 | suite.run({}); 53 | } 54 | 55 | // exit on Ctrl-C 56 | process.on("SIGINT", function () { 57 | console.log("Exiting..."); 58 | process.exit(); 59 | }); 60 | -------------------------------------------------------------------------------- /src/__tests__/async-refinements.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("parse async test", async () => { 7 | const schema1 = z.string().refine(async (_val) => false); 8 | expect(() => schema1.parse("asdf")).toThrow(); 9 | 10 | const schema2 = z.string().refine((_val) => Promise.resolve(true)); 11 | return await expect(() => schema2.parse("asdf")).toThrow(); 12 | }); 13 | 14 | test("parseAsync async test", async () => { 15 | const schema1 = z.string().refine(async (_val) => true); 16 | await schema1.parseAsync("asdf"); 17 | 18 | const schema2 = z.string().refine(async (_val) => false); 19 | return await expect(schema2.parseAsync("asdf")).rejects.toBeDefined(); 20 | // expect(async () => await schema2.parseAsync('asdf')).toThrow(); 21 | }); 22 | 23 | test("parseAsync async test", async () => { 24 | // expect.assertions(2); 25 | 26 | const schema1 = z.string().refine((_val) => Promise.resolve(true)); 27 | const v1 = await schema1.parseAsync("asdf"); 28 | expect(v1).toEqual("asdf"); 29 | 30 | const schema2 = z.string().refine((_val) => Promise.resolve(false)); 31 | await expect(schema2.parseAsync("asdf")).rejects.toBeDefined(); 32 | 33 | const schema3 = z.string().refine((_val) => Promise.resolve(true)); 34 | await expect(schema3.parseAsync("asdf")).resolves.toEqual("asdf"); 35 | return await expect(schema3.parseAsync("qwer")).resolves.toEqual("qwer"); 36 | }); 37 | 38 | test("parseAsync async with value", async () => { 39 | const schema1 = z.string().refine(async (val) => { 40 | return val.length > 5; 41 | }); 42 | await expect(schema1.parseAsync("asdf")).rejects.toBeDefined(); 43 | 44 | const v = await schema1.parseAsync("asdf123"); 45 | return await expect(v).toEqual("asdf123"); 46 | }); 47 | -------------------------------------------------------------------------------- /deno/lib/__tests__/generics.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("generics", () => { 9 | async function stripOuter( 10 | schema: TData, 11 | data: unknown 12 | ) { 13 | return z 14 | .object({ 15 | nested: schema, // as z.ZodTypeAny, 16 | }) 17 | .transform((data) => { 18 | return data.nested!; 19 | }) 20 | .parse({ nested: data }); 21 | } 22 | 23 | const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" }); 24 | util.assertEqual>(true); 25 | }); 26 | 27 | // test("assignability", () => { 28 | // const createSchemaAndParse = ( 29 | // key: K, 30 | // valueSchema: VS, 31 | // data: unknown 32 | // ) => { 33 | // const schema = z.object({ 34 | // [key]: valueSchema, 35 | // } as { [k in K]: VS }); 36 | // return { [key]: valueSchema }; 37 | // const parsed = schema.parse(data); 38 | // return parsed; 39 | // // const inferred: z.infer> = parsed; 40 | // // return inferred; 41 | // }; 42 | // const parsed = createSchemaAndParse("foo", z.string(), { foo: "" }); 43 | // util.assertEqual(true); 44 | // }); 45 | 46 | test("nested no undefined", () => { 47 | const inner = z.string().or(z.array(z.string())); 48 | const outer = z.object({ inner }); 49 | type outerSchema = z.infer; 50 | z.util.assertEqual(true); 51 | expect(outer.safeParse({ inner: undefined }).success).toEqual(false); 52 | }); 53 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/index.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import datetimeBenchmarks from "./datetime.ts"; 4 | import discriminatedUnionBenchmarks from "./discriminatedUnion.ts"; 5 | import ipv4Benchmarks from "./ipv4.ts"; 6 | import objectBenchmarks from "./object.ts"; 7 | import primitiveBenchmarks from "./primitives.ts"; 8 | import realworld from "./realworld.ts"; 9 | import stringBenchmarks from "./string.ts"; 10 | import unionBenchmarks from "./union.ts"; 11 | 12 | const argv = process.argv.slice(2); 13 | let suites: Benchmark.Suite[] = []; 14 | 15 | if (!argv.length) { 16 | suites = [ 17 | ...realworld.suites, 18 | ...primitiveBenchmarks.suites, 19 | ...stringBenchmarks.suites, 20 | ...objectBenchmarks.suites, 21 | ...unionBenchmarks.suites, 22 | ...discriminatedUnionBenchmarks.suites, 23 | ]; 24 | } else { 25 | if (argv.includes("--realworld")) { 26 | suites.push(...realworld.suites); 27 | } 28 | if (argv.includes("--primitives")) { 29 | suites.push(...primitiveBenchmarks.suites); 30 | } 31 | if (argv.includes("--string")) { 32 | suites.push(...stringBenchmarks.suites); 33 | } 34 | if (argv.includes("--object")) { 35 | suites.push(...objectBenchmarks.suites); 36 | } 37 | if (argv.includes("--union")) { 38 | suites.push(...unionBenchmarks.suites); 39 | } 40 | if (argv.includes("--discriminatedUnion")) { 41 | suites.push(...datetimeBenchmarks.suites); 42 | } 43 | if (argv.includes("--datetime")) { 44 | suites.push(...datetimeBenchmarks.suites); 45 | } 46 | if (argv.includes("--ipv4")) { 47 | suites.push(...ipv4Benchmarks.suites); 48 | } 49 | } 50 | 51 | for (const suite of suites) { 52 | suite.run({}); 53 | } 54 | 55 | // exit on Ctrl-C 56 | process.on("SIGINT", function () { 57 | console.log("Exiting..."); 58 | process.exit(); 59 | }); 60 | -------------------------------------------------------------------------------- /src/__tests__/unions.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("function parsing", () => { 7 | const schema = z.union([ 8 | z.string().refine(() => false), 9 | z.number().refine(() => false), 10 | ]); 11 | const result = schema.safeParse("asdf"); 12 | expect(result.success).toEqual(false); 13 | }); 14 | 15 | test("union 2", () => { 16 | const result = z 17 | .union([z.number(), z.string().refine(() => false)]) 18 | .safeParse("a"); 19 | expect(result.success).toEqual(false); 20 | }); 21 | 22 | test("return valid over invalid", () => { 23 | const schema = z.union([ 24 | z.object({ 25 | email: z.string().email(), 26 | }), 27 | z.string(), 28 | ]); 29 | expect(schema.parse("asdf")).toEqual("asdf"); 30 | expect(schema.parse({ email: "asdlkjf@lkajsdf.com" })).toEqual({ 31 | email: "asdlkjf@lkajsdf.com", 32 | }); 33 | }); 34 | 35 | test("return dirty result over aborted", () => { 36 | const result = z 37 | .union([z.number(), z.string().refine(() => false)]) 38 | .safeParse("a"); 39 | expect(result.success).toEqual(false); 40 | if (!result.success) { 41 | expect(result.error.issues).toEqual([ 42 | { 43 | code: "custom", 44 | message: "Invalid input", 45 | path: [], 46 | }, 47 | ]); 48 | } 49 | }); 50 | 51 | test("options getter", async () => { 52 | const union = z.union([z.string(), z.number()]); 53 | union.options[0].parse("asdf"); 54 | union.options[1].parse(1234); 55 | await union.options[0].parseAsync("asdf"); 56 | await union.options[1].parseAsync(1234); 57 | }); 58 | 59 | test("readonly union", async () => { 60 | const options = [z.string(), z.number()] as const; 61 | const union = z.union(options); 62 | union.parse("asdf"); 63 | union.parse(12); 64 | }); 65 | -------------------------------------------------------------------------------- /deno/lib/__tests__/async-refinements.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("parse async test", async () => { 8 | const schema1 = z.string().refine(async (_val) => false); 9 | expect(() => schema1.parse("asdf")).toThrow(); 10 | 11 | const schema2 = z.string().refine((_val) => Promise.resolve(true)); 12 | return await expect(() => schema2.parse("asdf")).toThrow(); 13 | }); 14 | 15 | test("parseAsync async test", async () => { 16 | const schema1 = z.string().refine(async (_val) => true); 17 | await schema1.parseAsync("asdf"); 18 | 19 | const schema2 = z.string().refine(async (_val) => false); 20 | return await expect(schema2.parseAsync("asdf")).rejects.toBeDefined(); 21 | // expect(async () => await schema2.parseAsync('asdf')).toThrow(); 22 | }); 23 | 24 | test("parseAsync async test", async () => { 25 | // expect.assertions(2); 26 | 27 | const schema1 = z.string().refine((_val) => Promise.resolve(true)); 28 | const v1 = await schema1.parseAsync("asdf"); 29 | expect(v1).toEqual("asdf"); 30 | 31 | const schema2 = z.string().refine((_val) => Promise.resolve(false)); 32 | await expect(schema2.parseAsync("asdf")).rejects.toBeDefined(); 33 | 34 | const schema3 = z.string().refine((_val) => Promise.resolve(true)); 35 | await expect(schema3.parseAsync("asdf")).resolves.toEqual("asdf"); 36 | return await expect(schema3.parseAsync("qwer")).resolves.toEqual("qwer"); 37 | }); 38 | 39 | test("parseAsync async with value", async () => { 40 | const schema1 = z.string().refine(async (val) => { 41 | return val.length > 5; 42 | }); 43 | await expect(schema1.parseAsync("asdf")).rejects.toBeDefined(); 44 | 45 | const v = await schema1.parseAsync("asdf123"); 46 | return await expect(v).toEqual("asdf123"); 47 | }); 48 | -------------------------------------------------------------------------------- /src/benchmarks/union.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index"; 4 | 5 | const doubleSuite = new Benchmark.Suite("z.union: double"); 6 | const manySuite = new Benchmark.Suite("z.union: many"); 7 | 8 | const aSchema = z.object({ 9 | type: z.literal("a"), 10 | }); 11 | const objA = { 12 | type: "a", 13 | }; 14 | 15 | const bSchema = z.object({ 16 | type: z.literal("b"), 17 | }); 18 | const objB = { 19 | type: "b", 20 | }; 21 | 22 | const cSchema = z.object({ 23 | type: z.literal("c"), 24 | }); 25 | const objC = { 26 | type: "c", 27 | }; 28 | 29 | const dSchema = z.object({ 30 | type: z.literal("d"), 31 | }); 32 | 33 | const double = z.union([aSchema, bSchema]); 34 | const many = z.union([aSchema, bSchema, cSchema, dSchema]); 35 | 36 | doubleSuite 37 | .add("valid: a", () => { 38 | double.parse(objA); 39 | }) 40 | .add("valid: b", () => { 41 | double.parse(objB); 42 | }) 43 | .add("invalid: null", () => { 44 | try { 45 | double.parse(null); 46 | } catch (err) {} 47 | }) 48 | .add("invalid: wrong shape", () => { 49 | try { 50 | double.parse(objC); 51 | } catch (err) {} 52 | }) 53 | .on("cycle", (e: Benchmark.Event) => { 54 | console.log(`${(doubleSuite as any).name}: ${e.target}`); 55 | }); 56 | 57 | manySuite 58 | .add("valid: a", () => { 59 | many.parse(objA); 60 | }) 61 | .add("valid: c", () => { 62 | many.parse(objC); 63 | }) 64 | .add("invalid: null", () => { 65 | try { 66 | many.parse(null); 67 | } catch (err) {} 68 | }) 69 | .add("invalid: wrong shape", () => { 70 | try { 71 | many.parse({ type: "unknown" }); 72 | } catch (err) {} 73 | }) 74 | .on("cycle", (e: Benchmark.Event) => { 75 | console.log(`${(manySuite as any).name}: ${e.target}`); 76 | }); 77 | 78 | export default { 79 | suites: [doubleSuite, manySuite], 80 | }; 81 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/union.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index.ts"; 4 | 5 | const doubleSuite = new Benchmark.Suite("z.union: double"); 6 | const manySuite = new Benchmark.Suite("z.union: many"); 7 | 8 | const aSchema = z.object({ 9 | type: z.literal("a"), 10 | }); 11 | const objA = { 12 | type: "a", 13 | }; 14 | 15 | const bSchema = z.object({ 16 | type: z.literal("b"), 17 | }); 18 | const objB = { 19 | type: "b", 20 | }; 21 | 22 | const cSchema = z.object({ 23 | type: z.literal("c"), 24 | }); 25 | const objC = { 26 | type: "c", 27 | }; 28 | 29 | const dSchema = z.object({ 30 | type: z.literal("d"), 31 | }); 32 | 33 | const double = z.union([aSchema, bSchema]); 34 | const many = z.union([aSchema, bSchema, cSchema, dSchema]); 35 | 36 | doubleSuite 37 | .add("valid: a", () => { 38 | double.parse(objA); 39 | }) 40 | .add("valid: b", () => { 41 | double.parse(objB); 42 | }) 43 | .add("invalid: null", () => { 44 | try { 45 | double.parse(null); 46 | } catch (err) {} 47 | }) 48 | .add("invalid: wrong shape", () => { 49 | try { 50 | double.parse(objC); 51 | } catch (err) {} 52 | }) 53 | .on("cycle", (e: Benchmark.Event) => { 54 | console.log(`${(doubleSuite as any).name}: ${e.target}`); 55 | }); 56 | 57 | manySuite 58 | .add("valid: a", () => { 59 | many.parse(objA); 60 | }) 61 | .add("valid: c", () => { 62 | many.parse(objC); 63 | }) 64 | .add("invalid: null", () => { 65 | try { 66 | many.parse(null); 67 | } catch (err) {} 68 | }) 69 | .add("invalid: wrong shape", () => { 70 | try { 71 | many.parse({ type: "unknown" }); 72 | } catch (err) {} 73 | }) 74 | .on("cycle", (e: Benchmark.Event) => { 75 | console.log(`${(manySuite as any).name}: ${e.target}`); 76 | }); 77 | 78 | export default { 79 | suites: [doubleSuite, manySuite], 80 | }; 81 | -------------------------------------------------------------------------------- /deno/lib/__tests__/unions.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("function parsing", () => { 8 | const schema = z.union([ 9 | z.string().refine(() => false), 10 | z.number().refine(() => false), 11 | ]); 12 | const result = schema.safeParse("asdf"); 13 | expect(result.success).toEqual(false); 14 | }); 15 | 16 | test("union 2", () => { 17 | const result = z 18 | .union([z.number(), z.string().refine(() => false)]) 19 | .safeParse("a"); 20 | expect(result.success).toEqual(false); 21 | }); 22 | 23 | test("return valid over invalid", () => { 24 | const schema = z.union([ 25 | z.object({ 26 | email: z.string().email(), 27 | }), 28 | z.string(), 29 | ]); 30 | expect(schema.parse("asdf")).toEqual("asdf"); 31 | expect(schema.parse({ email: "asdlkjf@lkajsdf.com" })).toEqual({ 32 | email: "asdlkjf@lkajsdf.com", 33 | }); 34 | }); 35 | 36 | test("return dirty result over aborted", () => { 37 | const result = z 38 | .union([z.number(), z.string().refine(() => false)]) 39 | .safeParse("a"); 40 | expect(result.success).toEqual(false); 41 | if (!result.success) { 42 | expect(result.error.issues).toEqual([ 43 | { 44 | code: "custom", 45 | message: "Invalid input", 46 | path: [], 47 | }, 48 | ]); 49 | } 50 | }); 51 | 52 | test("options getter", async () => { 53 | const union = z.union([z.string(), z.number()]); 54 | union.options[0].parse("asdf"); 55 | union.options[1].parse(1234); 56 | await union.options[0].parseAsync("asdf"); 57 | await union.options[1].parseAsync(1234); 58 | }); 59 | 60 | test("readonly union", async () => { 61 | const options = [z.string(), z.number()] as const; 62 | const union = z.union(options); 63 | union.parse("asdf"); 64 | union.parse(12); 65 | }); 66 | -------------------------------------------------------------------------------- /src/benchmarks/object.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index"; 4 | 5 | const emptySuite = new Benchmark.Suite("z.object: empty"); 6 | const shortSuite = new Benchmark.Suite("z.object: short"); 7 | const longSuite = new Benchmark.Suite("z.object: long"); 8 | 9 | const empty = z.object({}); 10 | const short = z.object({ 11 | string: z.string(), 12 | }); 13 | const long = z.object({ 14 | string: z.string(), 15 | number: z.number(), 16 | boolean: z.boolean(), 17 | }); 18 | 19 | emptySuite 20 | .add("valid", () => { 21 | empty.parse({}); 22 | }) 23 | .add("valid: extra keys", () => { 24 | empty.parse({ string: "string" }); 25 | }) 26 | .add("invalid: null", () => { 27 | try { 28 | empty.parse(null); 29 | } catch (err) {} 30 | }) 31 | .on("cycle", (e: Benchmark.Event) => { 32 | console.log(`${(emptySuite as any).name}: ${e.target}`); 33 | }); 34 | 35 | shortSuite 36 | .add("valid", () => { 37 | short.parse({ string: "string" }); 38 | }) 39 | .add("valid: extra keys", () => { 40 | short.parse({ string: "string", number: 42 }); 41 | }) 42 | .add("invalid: null", () => { 43 | try { 44 | short.parse(null); 45 | } catch (err) {} 46 | }) 47 | .on("cycle", (e: Benchmark.Event) => { 48 | console.log(`${(shortSuite as any).name}: ${e.target}`); 49 | }); 50 | 51 | longSuite 52 | .add("valid", () => { 53 | long.parse({ string: "string", number: 42, boolean: true }); 54 | }) 55 | .add("valid: extra keys", () => { 56 | long.parse({ string: "string", number: 42, boolean: true, list: [] }); 57 | }) 58 | .add("invalid: null", () => { 59 | try { 60 | long.parse(null); 61 | } catch (err) {} 62 | }) 63 | .on("cycle", (e: Benchmark.Event) => { 64 | console.log(`${(longSuite as any).name}: ${e.target}`); 65 | }); 66 | 67 | export default { 68 | suites: [emptySuite, shortSuite, longSuite], 69 | }; 70 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/object.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index.ts"; 4 | 5 | const emptySuite = new Benchmark.Suite("z.object: empty"); 6 | const shortSuite = new Benchmark.Suite("z.object: short"); 7 | const longSuite = new Benchmark.Suite("z.object: long"); 8 | 9 | const empty = z.object({}); 10 | const short = z.object({ 11 | string: z.string(), 12 | }); 13 | const long = z.object({ 14 | string: z.string(), 15 | number: z.number(), 16 | boolean: z.boolean(), 17 | }); 18 | 19 | emptySuite 20 | .add("valid", () => { 21 | empty.parse({}); 22 | }) 23 | .add("valid: extra keys", () => { 24 | empty.parse({ string: "string" }); 25 | }) 26 | .add("invalid: null", () => { 27 | try { 28 | empty.parse(null); 29 | } catch (err) {} 30 | }) 31 | .on("cycle", (e: Benchmark.Event) => { 32 | console.log(`${(emptySuite as any).name}: ${e.target}`); 33 | }); 34 | 35 | shortSuite 36 | .add("valid", () => { 37 | short.parse({ string: "string" }); 38 | }) 39 | .add("valid: extra keys", () => { 40 | short.parse({ string: "string", number: 42 }); 41 | }) 42 | .add("invalid: null", () => { 43 | try { 44 | short.parse(null); 45 | } catch (err) {} 46 | }) 47 | .on("cycle", (e: Benchmark.Event) => { 48 | console.log(`${(shortSuite as any).name}: ${e.target}`); 49 | }); 50 | 51 | longSuite 52 | .add("valid", () => { 53 | long.parse({ string: "string", number: 42, boolean: true }); 54 | }) 55 | .add("valid: extra keys", () => { 56 | long.parse({ string: "string", number: 42, boolean: true, list: [] }); 57 | }) 58 | .add("invalid: null", () => { 59 | try { 60 | long.parse(null); 61 | } catch (err) {} 62 | }) 63 | .on("cycle", (e: Benchmark.Event) => { 64 | console.log(`${(longSuite as any).name}: ${e.target}`); 65 | }); 66 | 67 | export default { 68 | suites: [emptySuite, shortSuite, longSuite], 69 | }; 70 | -------------------------------------------------------------------------------- /src/benchmarks/discriminatedUnion.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index"; 4 | 5 | const doubleSuite = new Benchmark.Suite("z.discriminatedUnion: double"); 6 | const manySuite = new Benchmark.Suite("z.discriminatedUnion: many"); 7 | 8 | const aSchema = z.object({ 9 | type: z.literal("a"), 10 | }); 11 | const objA = { 12 | type: "a", 13 | }; 14 | 15 | const bSchema = z.object({ 16 | type: z.literal("b"), 17 | }); 18 | const objB = { 19 | type: "b", 20 | }; 21 | 22 | const cSchema = z.object({ 23 | type: z.literal("c"), 24 | }); 25 | const objC = { 26 | type: "c", 27 | }; 28 | 29 | const dSchema = z.object({ 30 | type: z.literal("d"), 31 | }); 32 | 33 | const double = z.discriminatedUnion("type", [aSchema, bSchema]); 34 | const many = z.discriminatedUnion("type", [aSchema, bSchema, cSchema, dSchema]); 35 | 36 | doubleSuite 37 | .add("valid: a", () => { 38 | double.parse(objA); 39 | }) 40 | .add("valid: b", () => { 41 | double.parse(objB); 42 | }) 43 | .add("invalid: null", () => { 44 | try { 45 | double.parse(null); 46 | } catch (err) {} 47 | }) 48 | .add("invalid: wrong shape", () => { 49 | try { 50 | double.parse(objC); 51 | } catch (err) {} 52 | }) 53 | .on("cycle", (e: Benchmark.Event) => { 54 | console.log(`${(doubleSuite as any).name}: ${e.target}`); 55 | }); 56 | 57 | manySuite 58 | .add("valid: a", () => { 59 | many.parse(objA); 60 | }) 61 | .add("valid: c", () => { 62 | many.parse(objC); 63 | }) 64 | .add("invalid: null", () => { 65 | try { 66 | many.parse(null); 67 | } catch (err) {} 68 | }) 69 | .add("invalid: wrong shape", () => { 70 | try { 71 | many.parse({ type: "unknown" }); 72 | } catch (err) {} 73 | }) 74 | .on("cycle", (e: Benchmark.Event) => { 75 | console.log(`${(manySuite as any).name}: ${e.target}`); 76 | }); 77 | 78 | export default { 79 | suites: [doubleSuite, manySuite], 80 | }; 81 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/discriminatedUnion.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | import { z } from "../index.ts"; 4 | 5 | const doubleSuite = new Benchmark.Suite("z.discriminatedUnion: double"); 6 | const manySuite = new Benchmark.Suite("z.discriminatedUnion: many"); 7 | 8 | const aSchema = z.object({ 9 | type: z.literal("a"), 10 | }); 11 | const objA = { 12 | type: "a", 13 | }; 14 | 15 | const bSchema = z.object({ 16 | type: z.literal("b"), 17 | }); 18 | const objB = { 19 | type: "b", 20 | }; 21 | 22 | const cSchema = z.object({ 23 | type: z.literal("c"), 24 | }); 25 | const objC = { 26 | type: "c", 27 | }; 28 | 29 | const dSchema = z.object({ 30 | type: z.literal("d"), 31 | }); 32 | 33 | const double = z.discriminatedUnion("type", [aSchema, bSchema]); 34 | const many = z.discriminatedUnion("type", [aSchema, bSchema, cSchema, dSchema]); 35 | 36 | doubleSuite 37 | .add("valid: a", () => { 38 | double.parse(objA); 39 | }) 40 | .add("valid: b", () => { 41 | double.parse(objB); 42 | }) 43 | .add("invalid: null", () => { 44 | try { 45 | double.parse(null); 46 | } catch (err) {} 47 | }) 48 | .add("invalid: wrong shape", () => { 49 | try { 50 | double.parse(objC); 51 | } catch (err) {} 52 | }) 53 | .on("cycle", (e: Benchmark.Event) => { 54 | console.log(`${(doubleSuite as any).name}: ${e.target}`); 55 | }); 56 | 57 | manySuite 58 | .add("valid: a", () => { 59 | many.parse(objA); 60 | }) 61 | .add("valid: c", () => { 62 | many.parse(objC); 63 | }) 64 | .add("invalid: null", () => { 65 | try { 66 | many.parse(null); 67 | } catch (err) {} 68 | }) 69 | .add("invalid: wrong shape", () => { 70 | try { 71 | many.parse({ type: "unknown" }); 72 | } catch (err) {} 73 | }) 74 | .on("cycle", (e: Benchmark.Event) => { 75 | console.log(`${(manySuite as any).name}: ${e.target}`); 76 | }); 77 | 78 | export default { 79 | suites: [doubleSuite, manySuite], 80 | }; 81 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test-node: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node: ["latest"] 17 | typescript: ["5.0", "latest"] 18 | name: Test with TypeScript ${{ matrix.typescript }} on Node ${{ matrix.node }} 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: actions/setup-node@v4 22 | with: 23 | node-version: ${{ matrix.node }} 24 | - run: yarn install 25 | - run: yarn add typescript@${{ matrix.typescript }} 26 | - run: yarn build 27 | - run: yarn test 28 | 29 | test-deno: 30 | runs-on: ubuntu-latest 31 | strategy: 32 | matrix: 33 | deno: ["v1.x"] 34 | name: Test with Deno ${{ matrix.deno }} 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: actions/setup-node@v4 38 | with: 39 | node-version: latest 40 | - uses: denoland/setup-deno@v1 41 | with: 42 | deno-version: ${{ matrix.deno }} 43 | - run: yarn install 44 | - run: yarn build:deno 45 | - run: deno --version 46 | - run: deno test 47 | working-directory: ./deno/lib 48 | - run: deno run ./index.ts 49 | working-directory: ./deno/lib 50 | - run: deno run ./mod.ts 51 | working-directory: ./deno/lib 52 | - run: | 53 | deno bundle ./mod.ts ./bundle.js 54 | deno run ./bundle.js 55 | working-directory: ./deno/lib 56 | 57 | lint: 58 | runs-on: ubuntu-latest 59 | strategy: 60 | matrix: 61 | node: ["latest"] 62 | name: Lint on Node ${{ matrix.node }} 63 | steps: 64 | - uses: actions/checkout@v4 65 | - uses: actions/setup-node@v4 66 | with: 67 | node-version: ${{ matrix.node }} 68 | - run: yarn install 69 | - run: yarn prettier:check 70 | - run: yarn lint:check 71 | -------------------------------------------------------------------------------- /src/__tests__/branded.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("branded types", () => { 8 | const mySchema = z 9 | .object({ 10 | name: z.string(), 11 | }) 12 | .brand<"superschema">(); 13 | 14 | // simple branding 15 | type MySchema = z.infer; 16 | util.assertEqual< 17 | MySchema, 18 | { name: string } & { [z.BRAND]: { superschema: true } } 19 | >(true); 20 | 21 | const doStuff = (arg: MySchema) => arg; 22 | doStuff(mySchema.parse({ name: "hello there" })); 23 | 24 | // inheritance 25 | const extendedSchema = mySchema.brand<"subschema">(); 26 | type ExtendedSchema = z.infer; 27 | util.assertEqual< 28 | ExtendedSchema, 29 | { name: string } & z.BRAND<"superschema"> & z.BRAND<"subschema"> 30 | >(true); 31 | 32 | doStuff(extendedSchema.parse({ name: "hello again" })); 33 | 34 | // number branding 35 | const numberSchema = z.number().brand<42>(); 36 | type NumberSchema = z.infer; 37 | util.assertEqual(true); 38 | 39 | // symbol branding 40 | const MyBrand: unique symbol = Symbol("hello"); 41 | type MyBrand = typeof MyBrand; 42 | const symbolBrand = z.number().brand<"sup">().brand(); 43 | type SymbolBrand = z.infer; 44 | // number & { [z.BRAND]: { sup: true, [MyBrand]: true } } 45 | util.assertEqual & z.BRAND>( 46 | true 47 | ); 48 | 49 | // keeping brands out of input types 50 | const age = z.number().brand<"age">(); 51 | 52 | type Age = z.infer; 53 | type AgeInput = z.input; 54 | 55 | util.assertEqual(false); 56 | util.assertEqual(true); 57 | util.assertEqual, Age>(true); 58 | 59 | // @ts-expect-error 60 | doStuff({ name: "hello there!" }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/benchmarks/ipv4.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | const suite = new Benchmark.Suite("ipv4"); 4 | 5 | const DATA = "127.0.0.1"; 6 | const ipv4RegexA = 7 | /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/; 8 | const ipv4RegexB = 9 | /^(?:(?:(?=(25[0-5]))\1|(?=(2[0-4][0-9]))\2|(?=(1[0-9]{2}))\3|(?=([0-9]{1,2}))\4)\.){3}(?:(?=(25[0-5]))\5|(?=(2[0-4][0-9]))\6|(?=(1[0-9]{2}))\7|(?=([0-9]{1,2}))\8)$/; 10 | const ipv4RegexC = 11 | /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/; 12 | const ipv4RegexD = 13 | /^(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/; 14 | const ipv4RegexE = 15 | /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)$/; 16 | const ipv4RegexF = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/; 17 | const ipv4RegexG = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/; 18 | const ipv4RegexH = /^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\.(?!$)|$)){4}$/; 19 | const ipv4RegexI = 20 | /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/; 21 | 22 | suite 23 | .add("A", () => { 24 | return ipv4RegexA.test(DATA); 25 | }) 26 | .add("B", () => { 27 | return ipv4RegexB.test(DATA); 28 | }) 29 | .add("C", () => { 30 | return ipv4RegexC.test(DATA); 31 | }) 32 | .add("D", () => { 33 | return ipv4RegexD.test(DATA); 34 | }) 35 | .add("E", () => { 36 | return ipv4RegexE.test(DATA); 37 | }) 38 | .add("F", () => { 39 | return ipv4RegexF.test(DATA); 40 | }) 41 | .add("G", () => { 42 | return ipv4RegexG.test(DATA); 43 | }) 44 | .add("H", () => { 45 | return ipv4RegexH.test(DATA); 46 | }) 47 | .add("I", () => { 48 | return ipv4RegexI.test(DATA); 49 | }) 50 | .on("cycle", (e: Benchmark.Event) => { 51 | console.log(`${suite.name!}: ${e.target}`); 52 | }); 53 | 54 | export default { 55 | suites: [suite], 56 | }; 57 | 58 | if (require.main === module) { 59 | suite.run(); 60 | } 61 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/ipv4.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | const suite = new Benchmark.Suite("ipv4"); 4 | 5 | const DATA = "127.0.0.1"; 6 | const ipv4RegexA = 7 | /^(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))$/; 8 | const ipv4RegexB = 9 | /^(?:(?:(?=(25[0-5]))\1|(?=(2[0-4][0-9]))\2|(?=(1[0-9]{2}))\3|(?=([0-9]{1,2}))\4)\.){3}(?:(?=(25[0-5]))\5|(?=(2[0-4][0-9]))\6|(?=(1[0-9]{2}))\7|(?=([0-9]{1,2}))\8)$/; 10 | const ipv4RegexC = 11 | /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/; 12 | const ipv4RegexD = 13 | /^(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/; 14 | const ipv4RegexE = 15 | /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)$/; 16 | const ipv4RegexF = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/; 17 | const ipv4RegexG = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/; 18 | const ipv4RegexH = /^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\.(?!$)|$)){4}$/; 19 | const ipv4RegexI = 20 | /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/; 21 | 22 | suite 23 | .add("A", () => { 24 | return ipv4RegexA.test(DATA); 25 | }) 26 | .add("B", () => { 27 | return ipv4RegexB.test(DATA); 28 | }) 29 | .add("C", () => { 30 | return ipv4RegexC.test(DATA); 31 | }) 32 | .add("D", () => { 33 | return ipv4RegexD.test(DATA); 34 | }) 35 | .add("E", () => { 36 | return ipv4RegexE.test(DATA); 37 | }) 38 | .add("F", () => { 39 | return ipv4RegexF.test(DATA); 40 | }) 41 | .add("G", () => { 42 | return ipv4RegexG.test(DATA); 43 | }) 44 | .add("H", () => { 45 | return ipv4RegexH.test(DATA); 46 | }) 47 | .add("I", () => { 48 | return ipv4RegexI.test(DATA); 49 | }) 50 | .on("cycle", (e: Benchmark.Event) => { 51 | console.log(`${suite.name!}: ${e.target}`); 52 | }); 53 | 54 | export default { 55 | suites: [suite], 56 | }; 57 | 58 | if (require.main === module) { 59 | suite.run(); 60 | } 61 | -------------------------------------------------------------------------------- /deno/lib/__tests__/branded.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("branded types", () => { 9 | const mySchema = z 10 | .object({ 11 | name: z.string(), 12 | }) 13 | .brand<"superschema">(); 14 | 15 | // simple branding 16 | type MySchema = z.infer; 17 | util.assertEqual< 18 | MySchema, 19 | { name: string } & { [z.BRAND]: { superschema: true } } 20 | >(true); 21 | 22 | const doStuff = (arg: MySchema) => arg; 23 | doStuff(mySchema.parse({ name: "hello there" })); 24 | 25 | // inheritance 26 | const extendedSchema = mySchema.brand<"subschema">(); 27 | type ExtendedSchema = z.infer; 28 | util.assertEqual< 29 | ExtendedSchema, 30 | { name: string } & z.BRAND<"superschema"> & z.BRAND<"subschema"> 31 | >(true); 32 | 33 | doStuff(extendedSchema.parse({ name: "hello again" })); 34 | 35 | // number branding 36 | const numberSchema = z.number().brand<42>(); 37 | type NumberSchema = z.infer; 38 | util.assertEqual(true); 39 | 40 | // symbol branding 41 | const MyBrand: unique symbol = Symbol("hello"); 42 | type MyBrand = typeof MyBrand; 43 | const symbolBrand = z.number().brand<"sup">().brand(); 44 | type SymbolBrand = z.infer; 45 | // number & { [z.BRAND]: { sup: true, [MyBrand]: true } } 46 | util.assertEqual & z.BRAND>( 47 | true 48 | ); 49 | 50 | // keeping brands out of input types 51 | const age = z.number().brand<"age">(); 52 | 53 | type Age = z.infer; 54 | type AgeInput = z.input; 55 | 56 | util.assertEqual(false); 57 | util.assertEqual(true); 58 | util.assertEqual, Age>(true); 59 | 60 | // @ts-expect-error 61 | doStuff({ name: "hello there!" }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/benchmarks/datetime.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | const datetimeValidationSuite = new Benchmark.Suite("datetime"); 4 | 5 | const DATA = "2021-01-01"; 6 | const MONTHS_31 = new Set([1, 3, 5, 7, 8, 10, 12]); 7 | const MONTHS_30 = new Set([4, 6, 9, 11]); 8 | 9 | const simpleDatetimeRegex = /^(\d{4})-(\d{2})-(\d{2})$/; 10 | const datetimeRegexNoLeapYearValidation = 11 | /^\d{4}-((0[13578]|10|12)-31|(0[13-9]|1[0-2])-30|(0[1-9]|1[0-2])-(0[1-9]|1\d|2\d))$/; 12 | const datetimeRegexWithLeapYearValidation = 13 | /^((\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\d|3[01])|(0[469]|11)-(0[1-9]|[12]\d|30)|(02)-(0[1-9]|1\d|2[0-8])))$/; 14 | 15 | datetimeValidationSuite 16 | .add("new Date()", () => { 17 | return !isNaN(new Date(DATA).getTime()); 18 | }) 19 | .add("regex (no validation)", () => { 20 | return simpleDatetimeRegex.test(DATA); 21 | }) 22 | .add("regex (no leap year)", () => { 23 | return datetimeRegexNoLeapYearValidation.test(DATA); 24 | }) 25 | .add("regex (w/ leap year)", () => { 26 | return datetimeRegexWithLeapYearValidation.test(DATA); 27 | }) 28 | .add("capture groups + code", () => { 29 | const match = DATA.match(simpleDatetimeRegex); 30 | if (!match) return false; 31 | 32 | // Extract year, month, and day from the capture groups 33 | const year = Number.parseInt(match[1], 10); 34 | const month = Number.parseInt(match[2], 10); // month is 0-indexed in JavaScript Date, so subtract 1 35 | const day = Number.parseInt(match[3], 10); 36 | 37 | if (month === 2) { 38 | if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) { 39 | return day <= 29; 40 | } 41 | return day <= 28; 42 | } 43 | if (MONTHS_30.has(month)) { 44 | return day <= 30; 45 | } 46 | if (MONTHS_31.has(month)) { 47 | return day <= 31; 48 | } 49 | return false; 50 | }) 51 | 52 | .on("cycle", (e: Benchmark.Event) => { 53 | console.log(`${datetimeValidationSuite.name!}: ${e.target}`); 54 | }); 55 | 56 | export default { 57 | suites: [datetimeValidationSuite], 58 | }; 59 | -------------------------------------------------------------------------------- /deno/lib/benchmarks/datetime.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from "benchmark"; 2 | 3 | const datetimeValidationSuite = new Benchmark.Suite("datetime"); 4 | 5 | const DATA = "2021-01-01"; 6 | const MONTHS_31 = new Set([1, 3, 5, 7, 8, 10, 12]); 7 | const MONTHS_30 = new Set([4, 6, 9, 11]); 8 | 9 | const simpleDatetimeRegex = /^(\d{4})-(\d{2})-(\d{2})$/; 10 | const datetimeRegexNoLeapYearValidation = 11 | /^\d{4}-((0[13578]|10|12)-31|(0[13-9]|1[0-2])-30|(0[1-9]|1[0-2])-(0[1-9]|1\d|2\d))$/; 12 | const datetimeRegexWithLeapYearValidation = 13 | /^((\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\d|3[01])|(0[469]|11)-(0[1-9]|[12]\d|30)|(02)-(0[1-9]|1\d|2[0-8])))$/; 14 | 15 | datetimeValidationSuite 16 | .add("new Date()", () => { 17 | return !isNaN(new Date(DATA).getTime()); 18 | }) 19 | .add("regex (no validation)", () => { 20 | return simpleDatetimeRegex.test(DATA); 21 | }) 22 | .add("regex (no leap year)", () => { 23 | return datetimeRegexNoLeapYearValidation.test(DATA); 24 | }) 25 | .add("regex (w/ leap year)", () => { 26 | return datetimeRegexWithLeapYearValidation.test(DATA); 27 | }) 28 | .add("capture groups + code", () => { 29 | const match = DATA.match(simpleDatetimeRegex); 30 | if (!match) return false; 31 | 32 | // Extract year, month, and day from the capture groups 33 | const year = Number.parseInt(match[1], 10); 34 | const month = Number.parseInt(match[2], 10); // month is 0-indexed in JavaScript Date, so subtract 1 35 | const day = Number.parseInt(match[3], 10); 36 | 37 | if (month === 2) { 38 | if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) { 39 | return day <= 29; 40 | } 41 | return day <= 28; 42 | } 43 | if (MONTHS_30.has(month)) { 44 | return day <= 30; 45 | } 46 | if (MONTHS_31.has(month)) { 47 | return day <= 31; 48 | } 49 | return false; 50 | }) 51 | 52 | .on("cycle", (e: Benchmark.Event) => { 53 | console.log(`${datetimeValidationSuite.name!}: ${e.target}`); 54 | }); 55 | 56 | export default { 57 | suites: [datetimeValidationSuite], 58 | }; 59 | -------------------------------------------------------------------------------- /src/__tests__/bigint.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | const gtFive = z.bigint().gt(BigInt(5)); 7 | const gteFive = z.bigint().gte(BigInt(5)); 8 | const ltFive = z.bigint().lt(BigInt(5)); 9 | const lteFive = z.bigint().lte(BigInt(5)); 10 | const positive = z.bigint().positive(); 11 | const negative = z.bigint().negative(); 12 | const nonnegative = z.bigint().nonnegative(); 13 | const nonpositive = z.bigint().nonpositive(); 14 | const multipleOfFive = z.bigint().multipleOf(BigInt(5)); 15 | 16 | test("passing validations", () => { 17 | z.bigint().parse(BigInt(1)); 18 | z.bigint().parse(BigInt(0)); 19 | z.bigint().parse(BigInt(-1)); 20 | gtFive.parse(BigInt(6)); 21 | gteFive.parse(BigInt(5)); 22 | gteFive.parse(BigInt(6)); 23 | ltFive.parse(BigInt(4)); 24 | lteFive.parse(BigInt(5)); 25 | lteFive.parse(BigInt(4)); 26 | positive.parse(BigInt(3)); 27 | negative.parse(BigInt(-2)); 28 | nonnegative.parse(BigInt(0)); 29 | nonnegative.parse(BigInt(7)); 30 | nonpositive.parse(BigInt(0)); 31 | nonpositive.parse(BigInt(-12)); 32 | multipleOfFive.parse(BigInt(15)); 33 | }); 34 | 35 | test("failing validations", () => { 36 | expect(() => gtFive.parse(BigInt(5))).toThrow(); 37 | expect(() => gteFive.parse(BigInt(4))).toThrow(); 38 | expect(() => ltFive.parse(BigInt(5))).toThrow(); 39 | expect(() => lteFive.parse(BigInt(6))).toThrow(); 40 | expect(() => positive.parse(BigInt(0))).toThrow(); 41 | expect(() => positive.parse(BigInt(-2))).toThrow(); 42 | expect(() => negative.parse(BigInt(0))).toThrow(); 43 | expect(() => negative.parse(BigInt(3))).toThrow(); 44 | expect(() => nonnegative.parse(BigInt(-1))).toThrow(); 45 | expect(() => nonpositive.parse(BigInt(1))).toThrow(); 46 | expect(() => multipleOfFive.parse(BigInt(13))).toThrow(); 47 | }); 48 | 49 | test("min max getters", () => { 50 | expect(z.bigint().min(BigInt(5)).minValue).toEqual(BigInt(5)); 51 | expect(z.bigint().min(BigInt(5)).min(BigInt(10)).minValue).toEqual( 52 | BigInt(10) 53 | ); 54 | 55 | expect(z.bigint().max(BigInt(5)).maxValue).toEqual(BigInt(5)); 56 | expect(z.bigint().max(BigInt(5)).max(BigInt(1)).maxValue).toEqual(BigInt(1)); 57 | }); 58 | -------------------------------------------------------------------------------- /src/__tests__/array.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | const minTwo = z.string().array().min(2); 8 | const maxTwo = z.string().array().max(2); 9 | const justTwo = z.string().array().length(2); 10 | const intNum = z.string().array().nonempty(); 11 | const nonEmptyMax = z.string().array().nonempty().max(2); 12 | 13 | type t1 = z.infer; 14 | util.assertEqual<[string, ...string[]], t1>(true); 15 | 16 | type t2 = z.infer; 17 | util.assertEqual(true); 18 | 19 | test("passing validations", () => { 20 | minTwo.parse(["a", "a"]); 21 | minTwo.parse(["a", "a", "a"]); 22 | maxTwo.parse(["a", "a"]); 23 | maxTwo.parse(["a"]); 24 | justTwo.parse(["a", "a"]); 25 | intNum.parse(["a"]); 26 | nonEmptyMax.parse(["a"]); 27 | }); 28 | 29 | test("failing validations", () => { 30 | expect(() => minTwo.parse(["a"])).toThrow(); 31 | expect(() => maxTwo.parse(["a", "a", "a"])).toThrow(); 32 | expect(() => justTwo.parse(["a"])).toThrow(); 33 | expect(() => justTwo.parse(["a", "a", "a"])).toThrow(); 34 | expect(() => intNum.parse([])).toThrow(); 35 | expect(() => nonEmptyMax.parse([])).toThrow(); 36 | expect(() => nonEmptyMax.parse(["a", "a", "a"])).toThrow(); 37 | }); 38 | 39 | test("parse empty array in nonempty", () => { 40 | expect(() => 41 | z 42 | .array(z.string()) 43 | .nonempty() 44 | .parse([] as any) 45 | ).toThrow(); 46 | }); 47 | 48 | test("get element", () => { 49 | justTwo.element.parse("asdf"); 50 | expect(() => justTwo.element.parse(12)).toThrow(); 51 | }); 52 | 53 | test("continue parsing despite array size error", () => { 54 | const schema = z.object({ 55 | people: z.string().array().min(2), 56 | }); 57 | 58 | const result = schema.safeParse({ 59 | people: [123], 60 | }); 61 | expect(result.success).toEqual(false); 62 | if (!result.success) { 63 | expect(result.error.issues.length).toEqual(2); 64 | } 65 | }); 66 | 67 | test("parse should fail given sparse array", () => { 68 | const schema = z.array(z.string()).nonempty().min(1).max(3); 69 | 70 | expect(() => schema.parse(new Array(3))).toThrow(); 71 | }); 72 | -------------------------------------------------------------------------------- /deno/lib/__tests__/bigint.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | const gtFive = z.bigint().gt(BigInt(5)); 8 | const gteFive = z.bigint().gte(BigInt(5)); 9 | const ltFive = z.bigint().lt(BigInt(5)); 10 | const lteFive = z.bigint().lte(BigInt(5)); 11 | const positive = z.bigint().positive(); 12 | const negative = z.bigint().negative(); 13 | const nonnegative = z.bigint().nonnegative(); 14 | const nonpositive = z.bigint().nonpositive(); 15 | const multipleOfFive = z.bigint().multipleOf(BigInt(5)); 16 | 17 | test("passing validations", () => { 18 | z.bigint().parse(BigInt(1)); 19 | z.bigint().parse(BigInt(0)); 20 | z.bigint().parse(BigInt(-1)); 21 | gtFive.parse(BigInt(6)); 22 | gteFive.parse(BigInt(5)); 23 | gteFive.parse(BigInt(6)); 24 | ltFive.parse(BigInt(4)); 25 | lteFive.parse(BigInt(5)); 26 | lteFive.parse(BigInt(4)); 27 | positive.parse(BigInt(3)); 28 | negative.parse(BigInt(-2)); 29 | nonnegative.parse(BigInt(0)); 30 | nonnegative.parse(BigInt(7)); 31 | nonpositive.parse(BigInt(0)); 32 | nonpositive.parse(BigInt(-12)); 33 | multipleOfFive.parse(BigInt(15)); 34 | }); 35 | 36 | test("failing validations", () => { 37 | expect(() => gtFive.parse(BigInt(5))).toThrow(); 38 | expect(() => gteFive.parse(BigInt(4))).toThrow(); 39 | expect(() => ltFive.parse(BigInt(5))).toThrow(); 40 | expect(() => lteFive.parse(BigInt(6))).toThrow(); 41 | expect(() => positive.parse(BigInt(0))).toThrow(); 42 | expect(() => positive.parse(BigInt(-2))).toThrow(); 43 | expect(() => negative.parse(BigInt(0))).toThrow(); 44 | expect(() => negative.parse(BigInt(3))).toThrow(); 45 | expect(() => nonnegative.parse(BigInt(-1))).toThrow(); 46 | expect(() => nonpositive.parse(BigInt(1))).toThrow(); 47 | expect(() => multipleOfFive.parse(BigInt(13))).toThrow(); 48 | }); 49 | 50 | test("min max getters", () => { 51 | expect(z.bigint().min(BigInt(5)).minValue).toEqual(BigInt(5)); 52 | expect(z.bigint().min(BigInt(5)).min(BigInt(10)).minValue).toEqual( 53 | BigInt(10) 54 | ); 55 | 56 | expect(z.bigint().max(BigInt(5)).maxValue).toEqual(BigInt(5)); 57 | expect(z.bigint().max(BigInt(5)).max(BigInt(1)).maxValue).toEqual(BigInt(1)); 58 | }); 59 | -------------------------------------------------------------------------------- /deno/lib/__tests__/array.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | const minTwo = z.string().array().min(2); 9 | const maxTwo = z.string().array().max(2); 10 | const justTwo = z.string().array().length(2); 11 | const intNum = z.string().array().nonempty(); 12 | const nonEmptyMax = z.string().array().nonempty().max(2); 13 | 14 | type t1 = z.infer; 15 | util.assertEqual<[string, ...string[]], t1>(true); 16 | 17 | type t2 = z.infer; 18 | util.assertEqual(true); 19 | 20 | test("passing validations", () => { 21 | minTwo.parse(["a", "a"]); 22 | minTwo.parse(["a", "a", "a"]); 23 | maxTwo.parse(["a", "a"]); 24 | maxTwo.parse(["a"]); 25 | justTwo.parse(["a", "a"]); 26 | intNum.parse(["a"]); 27 | nonEmptyMax.parse(["a"]); 28 | }); 29 | 30 | test("failing validations", () => { 31 | expect(() => minTwo.parse(["a"])).toThrow(); 32 | expect(() => maxTwo.parse(["a", "a", "a"])).toThrow(); 33 | expect(() => justTwo.parse(["a"])).toThrow(); 34 | expect(() => justTwo.parse(["a", "a", "a"])).toThrow(); 35 | expect(() => intNum.parse([])).toThrow(); 36 | expect(() => nonEmptyMax.parse([])).toThrow(); 37 | expect(() => nonEmptyMax.parse(["a", "a", "a"])).toThrow(); 38 | }); 39 | 40 | test("parse empty array in nonempty", () => { 41 | expect(() => 42 | z 43 | .array(z.string()) 44 | .nonempty() 45 | .parse([] as any) 46 | ).toThrow(); 47 | }); 48 | 49 | test("get element", () => { 50 | justTwo.element.parse("asdf"); 51 | expect(() => justTwo.element.parse(12)).toThrow(); 52 | }); 53 | 54 | test("continue parsing despite array size error", () => { 55 | const schema = z.object({ 56 | people: z.string().array().min(2), 57 | }); 58 | 59 | const result = schema.safeParse({ 60 | people: [123], 61 | }); 62 | expect(result.success).toEqual(false); 63 | if (!result.success) { 64 | expect(result.error.issues.length).toEqual(2); 65 | } 66 | }); 67 | 68 | test("parse should fail given sparse array", () => { 69 | const schema = z.array(z.string()).nonempty().min(1).max(3); 70 | 71 | expect(() => schema.parse(new Array(3))).toThrow(); 72 | }); 73 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true, node: true }, 3 | root: true, 4 | parser: "@typescript-eslint/parser", 5 | plugins: [ 6 | "@typescript-eslint", 7 | "import", 8 | "simple-import-sort", 9 | "unused-imports", 10 | "ban", 11 | ], 12 | extends: [ 13 | "eslint:recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "prettier", 16 | ], 17 | rules: { 18 | "import/order": 0, // turn off in favor of eslint-plugin-simple-import-sort 19 | "import/no-unresolved": 0, 20 | "import/no-duplicates": 1, 21 | 22 | /** 23 | * eslint-plugin-simple-import-sort @see https://github.com/lydell/eslint-plugin-simple-import-sort 24 | */ 25 | "sort-imports": 0, // we use eslint-plugin-import instead 26 | "simple-import-sort/imports": 1, 27 | "simple-import-sort/exports": 1, 28 | 29 | /** 30 | * @typescript-eslint/eslint-plugin @see https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin 31 | */ 32 | "@typescript-eslint/no-namespace": "off", 33 | "@typescript-eslint/explicit-module-boundary-types": "off", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "@typescript-eslint/ban-types": "off", 36 | "@typescript-eslint/no-unused-vars": "off", 37 | "@typescript-eslint/no-empty-function": "off", 38 | "@typescript-eslint/ban-ts-comment": "off", 39 | "@typescript-eslint/no-non-null-assertion": "off", 40 | "@typescript-eslint/no-empty-interface": "off", 41 | /** 42 | * ESLint core rules @see https://eslint.org/docs/rules/ 43 | */ 44 | "no-case-declarations": "off", 45 | "no-empty": "off", 46 | "no-useless-escape": "off", 47 | "no-control-regex": "off", 48 | 49 | "ban/ban": [ 50 | 2, 51 | { 52 | name: ["Object", "keys"], 53 | message: 54 | "Object.keys() is not supported in legacy browsers, use objectKeys()", 55 | }, 56 | { 57 | name: ["Object", "setPrototypeOf"], 58 | message: "Object.setPrototypeOf() is not supported in legacy browsers", 59 | }, 60 | { 61 | name: ["Number", "isNaN"], 62 | message: "Number.isNaN() is not supported in legacy browsers", 63 | }, 64 | { 65 | name: ["Number", "isInteger"], 66 | message: "Number.isInteger() is not supported in legacy browsers", 67 | }, 68 | ], 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/__tests__/nativeEnum.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("nativeEnum test with consts", () => { 8 | const Fruits: { Apple: "apple"; Banana: "banana" } = { 9 | Apple: "apple", 10 | Banana: "banana", 11 | }; 12 | const fruitEnum = z.nativeEnum(Fruits); 13 | type fruitEnum = z.infer; 14 | fruitEnum.parse("apple"); 15 | fruitEnum.parse("banana"); 16 | fruitEnum.parse(Fruits.Apple); 17 | fruitEnum.parse(Fruits.Banana); 18 | util.assertEqual(true); 19 | }); 20 | 21 | test("nativeEnum test with real enum", () => { 22 | enum Fruits { 23 | Apple = "apple", 24 | Banana = "banana", 25 | } 26 | // @ts-ignore 27 | const fruitEnum = z.nativeEnum(Fruits); 28 | type fruitEnum = z.infer; 29 | fruitEnum.parse("apple"); 30 | fruitEnum.parse("banana"); 31 | fruitEnum.parse(Fruits.Apple); 32 | fruitEnum.parse(Fruits.Banana); 33 | util.assertIs(true); 34 | }); 35 | 36 | test("nativeEnum test with const with numeric keys", () => { 37 | const FruitValues = { 38 | Apple: 10, 39 | Banana: 20, 40 | // @ts-ignore 41 | } as const; 42 | const fruitEnum = z.nativeEnum(FruitValues); 43 | type fruitEnum = z.infer; 44 | fruitEnum.parse(10); 45 | fruitEnum.parse(20); 46 | fruitEnum.parse(FruitValues.Apple); 47 | fruitEnum.parse(FruitValues.Banana); 48 | util.assertEqual(true); 49 | }); 50 | 51 | test("from enum", () => { 52 | enum Fruits { 53 | Cantaloupe, 54 | Apple = "apple", 55 | Banana = "banana", 56 | } 57 | 58 | const FruitEnum = z.nativeEnum(Fruits as any); 59 | type FruitEnum = z.infer; 60 | FruitEnum.parse(Fruits.Cantaloupe); 61 | FruitEnum.parse(Fruits.Apple); 62 | FruitEnum.parse("apple"); 63 | FruitEnum.parse(0); 64 | expect(() => FruitEnum.parse(1)).toThrow(); 65 | expect(() => FruitEnum.parse("Apple")).toThrow(); 66 | expect(() => FruitEnum.parse("Cantaloupe")).toThrow(); 67 | }); 68 | 69 | test("from const", () => { 70 | const Greek = { 71 | Alpha: "a", 72 | Beta: "b", 73 | Gamma: 3, 74 | // @ts-ignore 75 | } as const; 76 | 77 | const GreekEnum = z.nativeEnum(Greek); 78 | type GreekEnum = z.infer; 79 | GreekEnum.parse("a"); 80 | GreekEnum.parse("b"); 81 | GreekEnum.parse(3); 82 | expect(() => GreekEnum.parse("v")).toThrow(); 83 | expect(() => GreekEnum.parse("Alpha")).toThrow(); 84 | expect(() => GreekEnum.parse(2)).toThrow(); 85 | 86 | expect(GreekEnum.enum.Alpha).toEqual("a"); 87 | }); 88 | -------------------------------------------------------------------------------- /src/__tests__/firstparty.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("first party switch", () => { 8 | const myType = z.string() as z.ZodFirstPartySchemaTypes; 9 | const def = myType._def; 10 | 11 | switch (def.typeName) { 12 | case z.ZodFirstPartyTypeKind.ZodString: 13 | break; 14 | case z.ZodFirstPartyTypeKind.ZodNumber: 15 | break; 16 | case z.ZodFirstPartyTypeKind.ZodNaN: 17 | break; 18 | case z.ZodFirstPartyTypeKind.ZodBigInt: 19 | break; 20 | case z.ZodFirstPartyTypeKind.ZodBoolean: 21 | break; 22 | case z.ZodFirstPartyTypeKind.ZodDate: 23 | break; 24 | case z.ZodFirstPartyTypeKind.ZodUndefined: 25 | break; 26 | case z.ZodFirstPartyTypeKind.ZodNull: 27 | break; 28 | case z.ZodFirstPartyTypeKind.ZodAny: 29 | break; 30 | case z.ZodFirstPartyTypeKind.ZodUnknown: 31 | break; 32 | case z.ZodFirstPartyTypeKind.ZodNever: 33 | break; 34 | case z.ZodFirstPartyTypeKind.ZodVoid: 35 | break; 36 | case z.ZodFirstPartyTypeKind.ZodArray: 37 | break; 38 | case z.ZodFirstPartyTypeKind.ZodObject: 39 | break; 40 | case z.ZodFirstPartyTypeKind.ZodUnion: 41 | break; 42 | case z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion: 43 | break; 44 | case z.ZodFirstPartyTypeKind.ZodIntersection: 45 | break; 46 | case z.ZodFirstPartyTypeKind.ZodTuple: 47 | break; 48 | case z.ZodFirstPartyTypeKind.ZodRecord: 49 | break; 50 | case z.ZodFirstPartyTypeKind.ZodMap: 51 | break; 52 | case z.ZodFirstPartyTypeKind.ZodSet: 53 | break; 54 | case z.ZodFirstPartyTypeKind.ZodFunction: 55 | break; 56 | case z.ZodFirstPartyTypeKind.ZodLazy: 57 | break; 58 | case z.ZodFirstPartyTypeKind.ZodLiteral: 59 | break; 60 | case z.ZodFirstPartyTypeKind.ZodEnum: 61 | break; 62 | case z.ZodFirstPartyTypeKind.ZodEffects: 63 | break; 64 | case z.ZodFirstPartyTypeKind.ZodNativeEnum: 65 | break; 66 | case z.ZodFirstPartyTypeKind.ZodOptional: 67 | break; 68 | case z.ZodFirstPartyTypeKind.ZodNullable: 69 | break; 70 | case z.ZodFirstPartyTypeKind.ZodDefault: 71 | break; 72 | case z.ZodFirstPartyTypeKind.ZodCatch: 73 | break; 74 | case z.ZodFirstPartyTypeKind.ZodPromise: 75 | break; 76 | case z.ZodFirstPartyTypeKind.ZodBranded: 77 | break; 78 | case z.ZodFirstPartyTypeKind.ZodPipeline: 79 | break; 80 | case z.ZodFirstPartyTypeKind.ZodSymbol: 81 | break; 82 | case z.ZodFirstPartyTypeKind.ZodReadonly: 83 | break; 84 | default: 85 | util.assertNever(def); 86 | } 87 | }); 88 | -------------------------------------------------------------------------------- /deno/lib/__tests__/nativeEnum.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("nativeEnum test with consts", () => { 9 | const Fruits: { Apple: "apple"; Banana: "banana" } = { 10 | Apple: "apple", 11 | Banana: "banana", 12 | }; 13 | const fruitEnum = z.nativeEnum(Fruits); 14 | type fruitEnum = z.infer; 15 | fruitEnum.parse("apple"); 16 | fruitEnum.parse("banana"); 17 | fruitEnum.parse(Fruits.Apple); 18 | fruitEnum.parse(Fruits.Banana); 19 | util.assertEqual(true); 20 | }); 21 | 22 | test("nativeEnum test with real enum", () => { 23 | enum Fruits { 24 | Apple = "apple", 25 | Banana = "banana", 26 | } 27 | // @ts-ignore 28 | const fruitEnum = z.nativeEnum(Fruits); 29 | type fruitEnum = z.infer; 30 | fruitEnum.parse("apple"); 31 | fruitEnum.parse("banana"); 32 | fruitEnum.parse(Fruits.Apple); 33 | fruitEnum.parse(Fruits.Banana); 34 | util.assertIs(true); 35 | }); 36 | 37 | test("nativeEnum test with const with numeric keys", () => { 38 | const FruitValues = { 39 | Apple: 10, 40 | Banana: 20, 41 | // @ts-ignore 42 | } as const; 43 | const fruitEnum = z.nativeEnum(FruitValues); 44 | type fruitEnum = z.infer; 45 | fruitEnum.parse(10); 46 | fruitEnum.parse(20); 47 | fruitEnum.parse(FruitValues.Apple); 48 | fruitEnum.parse(FruitValues.Banana); 49 | util.assertEqual(true); 50 | }); 51 | 52 | test("from enum", () => { 53 | enum Fruits { 54 | Cantaloupe, 55 | Apple = "apple", 56 | Banana = "banana", 57 | } 58 | 59 | const FruitEnum = z.nativeEnum(Fruits as any); 60 | type FruitEnum = z.infer; 61 | FruitEnum.parse(Fruits.Cantaloupe); 62 | FruitEnum.parse(Fruits.Apple); 63 | FruitEnum.parse("apple"); 64 | FruitEnum.parse(0); 65 | expect(() => FruitEnum.parse(1)).toThrow(); 66 | expect(() => FruitEnum.parse("Apple")).toThrow(); 67 | expect(() => FruitEnum.parse("Cantaloupe")).toThrow(); 68 | }); 69 | 70 | test("from const", () => { 71 | const Greek = { 72 | Alpha: "a", 73 | Beta: "b", 74 | Gamma: 3, 75 | // @ts-ignore 76 | } as const; 77 | 78 | const GreekEnum = z.nativeEnum(Greek); 79 | type GreekEnum = z.infer; 80 | GreekEnum.parse("a"); 81 | GreekEnum.parse("b"); 82 | GreekEnum.parse(3); 83 | expect(() => GreekEnum.parse("v")).toThrow(); 84 | expect(() => GreekEnum.parse("Alpha")).toThrow(); 85 | expect(() => GreekEnum.parse(2)).toThrow(); 86 | 87 | expect(GreekEnum.enum.Alpha).toEqual("a"); 88 | }); 89 | -------------------------------------------------------------------------------- /deno/lib/__tests__/firstparty.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("first party switch", () => { 9 | const myType = z.string() as z.ZodFirstPartySchemaTypes; 10 | const def = myType._def; 11 | 12 | switch (def.typeName) { 13 | case z.ZodFirstPartyTypeKind.ZodString: 14 | break; 15 | case z.ZodFirstPartyTypeKind.ZodNumber: 16 | break; 17 | case z.ZodFirstPartyTypeKind.ZodNaN: 18 | break; 19 | case z.ZodFirstPartyTypeKind.ZodBigInt: 20 | break; 21 | case z.ZodFirstPartyTypeKind.ZodBoolean: 22 | break; 23 | case z.ZodFirstPartyTypeKind.ZodDate: 24 | break; 25 | case z.ZodFirstPartyTypeKind.ZodUndefined: 26 | break; 27 | case z.ZodFirstPartyTypeKind.ZodNull: 28 | break; 29 | case z.ZodFirstPartyTypeKind.ZodAny: 30 | break; 31 | case z.ZodFirstPartyTypeKind.ZodUnknown: 32 | break; 33 | case z.ZodFirstPartyTypeKind.ZodNever: 34 | break; 35 | case z.ZodFirstPartyTypeKind.ZodVoid: 36 | break; 37 | case z.ZodFirstPartyTypeKind.ZodArray: 38 | break; 39 | case z.ZodFirstPartyTypeKind.ZodObject: 40 | break; 41 | case z.ZodFirstPartyTypeKind.ZodUnion: 42 | break; 43 | case z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion: 44 | break; 45 | case z.ZodFirstPartyTypeKind.ZodIntersection: 46 | break; 47 | case z.ZodFirstPartyTypeKind.ZodTuple: 48 | break; 49 | case z.ZodFirstPartyTypeKind.ZodRecord: 50 | break; 51 | case z.ZodFirstPartyTypeKind.ZodMap: 52 | break; 53 | case z.ZodFirstPartyTypeKind.ZodSet: 54 | break; 55 | case z.ZodFirstPartyTypeKind.ZodFunction: 56 | break; 57 | case z.ZodFirstPartyTypeKind.ZodLazy: 58 | break; 59 | case z.ZodFirstPartyTypeKind.ZodLiteral: 60 | break; 61 | case z.ZodFirstPartyTypeKind.ZodEnum: 62 | break; 63 | case z.ZodFirstPartyTypeKind.ZodEffects: 64 | break; 65 | case z.ZodFirstPartyTypeKind.ZodNativeEnum: 66 | break; 67 | case z.ZodFirstPartyTypeKind.ZodOptional: 68 | break; 69 | case z.ZodFirstPartyTypeKind.ZodNullable: 70 | break; 71 | case z.ZodFirstPartyTypeKind.ZodDefault: 72 | break; 73 | case z.ZodFirstPartyTypeKind.ZodCatch: 74 | break; 75 | case z.ZodFirstPartyTypeKind.ZodPromise: 76 | break; 77 | case z.ZodFirstPartyTypeKind.ZodBranded: 78 | break; 79 | case z.ZodFirstPartyTypeKind.ZodPipeline: 80 | break; 81 | case z.ZodFirstPartyTypeKind.ZodSymbol: 82 | break; 83 | case z.ZodFirstPartyTypeKind.ZodReadonly: 84 | break; 85 | default: 86 | util.assertNever(def); 87 | } 88 | }); 89 | -------------------------------------------------------------------------------- /src/__tests__/standard-schema.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | import type { StandardSchemaV1 } from "@standard-schema/spec"; 4 | 5 | import { util } from "../helpers/util"; 6 | import * as z from "../index"; 7 | 8 | test("assignability", () => { 9 | const _s1: StandardSchemaV1 = z.string(); 10 | const _s2: StandardSchemaV1 = z.string(); 11 | const _s3: StandardSchemaV1 = z.string(); 12 | const _s4: StandardSchemaV1 = z.string(); 13 | [_s1, _s2, _s3, _s4]; 14 | }); 15 | 16 | test("type inference", () => { 17 | const stringToNumber = z.string().transform((x) => x.length); 18 | type input = StandardSchemaV1.InferInput; 19 | util.assertEqual(true); 20 | type output = StandardSchemaV1.InferOutput; 21 | util.assertEqual(true); 22 | }); 23 | 24 | test("valid parse", () => { 25 | const schema = z.string(); 26 | const result = schema["~standard"]["validate"]("hello"); 27 | if (result instanceof Promise) { 28 | throw new Error("Expected sync result"); 29 | } 30 | expect(result.issues).toEqual(undefined); 31 | if (result.issues) { 32 | throw new Error("Expected no issues"); 33 | } else { 34 | expect(result.value).toEqual("hello"); 35 | } 36 | }); 37 | 38 | test("invalid parse", () => { 39 | const schema = z.string(); 40 | const result = schema["~standard"]["validate"](1234); 41 | if (result instanceof Promise) { 42 | throw new Error("Expected sync result"); 43 | } 44 | expect(result.issues).toBeDefined(); 45 | if (!result.issues) { 46 | throw new Error("Expected issues"); 47 | } 48 | expect(result.issues.length).toEqual(1); 49 | expect(result.issues[0].path).toEqual([]); 50 | }); 51 | 52 | test("valid parse async", async () => { 53 | const schema = z.string().refine(async () => true); 54 | const _result = schema["~standard"]["validate"]("hello"); 55 | if (_result instanceof Promise) { 56 | const result = await _result; 57 | expect(result.issues).toEqual(undefined); 58 | if (result.issues) { 59 | throw new Error("Expected no issues"); 60 | } else { 61 | expect(result.value).toEqual("hello"); 62 | } 63 | } else { 64 | throw new Error("Expected async result"); 65 | } 66 | }); 67 | 68 | test("invalid parse async", async () => { 69 | const schema = z.string().refine(async () => false); 70 | const _result = schema["~standard"]["validate"]("hello"); 71 | if (_result instanceof Promise) { 72 | const result = await _result; 73 | expect(result.issues).toBeDefined(); 74 | if (!result.issues) { 75 | throw new Error("Expected issues"); 76 | } 77 | expect(result.issues.length).toEqual(1); 78 | expect(result.issues[0].path).toEqual([]); 79 | } else { 80 | throw new Error("Expected async result"); 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /src/helpers/partialUtil.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ZodArray, 3 | ZodNullable, 4 | ZodObject, 5 | ZodOptional, 6 | ZodRawShape, 7 | ZodTuple, 8 | ZodTupleItems, 9 | ZodTypeAny, 10 | } from "../index"; 11 | 12 | export namespace partialUtil { 13 | // export type DeepPartial = T extends AnyZodObject 14 | // ? ZodObject< 15 | // { [k in keyof T["_shape"]]: InternalDeepPartial }, 16 | // T["_unknownKeys"], 17 | // T["_catchall"] 18 | // > 19 | // : T extends ZodArray 20 | // ? ZodArray, Card> 21 | // : ZodOptional; 22 | 23 | // { 24 | // // optional: T extends ZodOptional ? T : ZodOptional; 25 | // // array: T extends ZodArray ? ZodArray> : never; 26 | // object: T extends AnyZodObject 27 | // ? ZodObject< 28 | // { [k in keyof T["_shape"]]: DeepPartial }, 29 | // T["_unknownKeys"], 30 | // T["_catchall"] 31 | // > 32 | // : never; 33 | // rest: ReturnType; // ZodOptional; 34 | // }[T extends AnyZodObject 35 | // ? "object" // T extends ZodOptional // ? 'optional' // : 36 | // : "rest"]; 37 | 38 | export type DeepPartial = 39 | T extends ZodObject 40 | ? ZodObject< 41 | { [k in keyof T["shape"]]: ZodOptional> }, 42 | T["_def"]["unknownKeys"], 43 | T["_def"]["catchall"] 44 | > 45 | : T extends ZodArray 46 | ? ZodArray, Card> 47 | : T extends ZodOptional 48 | ? ZodOptional> 49 | : T extends ZodNullable 50 | ? ZodNullable> 51 | : T extends ZodTuple 52 | ? { 53 | [k in keyof Items]: Items[k] extends ZodTypeAny 54 | ? DeepPartial 55 | : never; 56 | } extends infer PI 57 | ? PI extends ZodTupleItems 58 | ? ZodTuple 59 | : never 60 | : never 61 | : T; 62 | // { 63 | // // optional: T extends ZodOptional ? T : ZodOptional; 64 | // // array: T extends ZodArray ? ZodArray> : never; 65 | // object: T extends ZodObject 66 | // ? ZodOptional< 67 | // ZodObject< 68 | // { [k in keyof Shape]: DeepPartial }, 69 | // Params, 70 | // Catchall 71 | // > 72 | // > 73 | // : never; 74 | // rest: ReturnType; 75 | // }[T extends ZodObject 76 | // ? "object" // T extends ZodOptional // ? 'optional' // : 77 | // : "rest"]; 78 | } 79 | -------------------------------------------------------------------------------- /deno/lib/helpers/partialUtil.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ZodArray, 3 | ZodNullable, 4 | ZodObject, 5 | ZodOptional, 6 | ZodRawShape, 7 | ZodTuple, 8 | ZodTupleItems, 9 | ZodTypeAny, 10 | } from "../index.ts"; 11 | 12 | export namespace partialUtil { 13 | // export type DeepPartial = T extends AnyZodObject 14 | // ? ZodObject< 15 | // { [k in keyof T["_shape"]]: InternalDeepPartial }, 16 | // T["_unknownKeys"], 17 | // T["_catchall"] 18 | // > 19 | // : T extends ZodArray 20 | // ? ZodArray, Card> 21 | // : ZodOptional; 22 | 23 | // { 24 | // // optional: T extends ZodOptional ? T : ZodOptional; 25 | // // array: T extends ZodArray ? ZodArray> : never; 26 | // object: T extends AnyZodObject 27 | // ? ZodObject< 28 | // { [k in keyof T["_shape"]]: DeepPartial }, 29 | // T["_unknownKeys"], 30 | // T["_catchall"] 31 | // > 32 | // : never; 33 | // rest: ReturnType; // ZodOptional; 34 | // }[T extends AnyZodObject 35 | // ? "object" // T extends ZodOptional // ? 'optional' // : 36 | // : "rest"]; 37 | 38 | export type DeepPartial = 39 | T extends ZodObject 40 | ? ZodObject< 41 | { [k in keyof T["shape"]]: ZodOptional> }, 42 | T["_def"]["unknownKeys"], 43 | T["_def"]["catchall"] 44 | > 45 | : T extends ZodArray 46 | ? ZodArray, Card> 47 | : T extends ZodOptional 48 | ? ZodOptional> 49 | : T extends ZodNullable 50 | ? ZodNullable> 51 | : T extends ZodTuple 52 | ? { 53 | [k in keyof Items]: Items[k] extends ZodTypeAny 54 | ? DeepPartial 55 | : never; 56 | } extends infer PI 57 | ? PI extends ZodTupleItems 58 | ? ZodTuple 59 | : never 60 | : never 61 | : T; 62 | // { 63 | // // optional: T extends ZodOptional ? T : ZodOptional; 64 | // // array: T extends ZodArray ? ZodArray> : never; 65 | // object: T extends ZodObject 66 | // ? ZodOptional< 67 | // ZodObject< 68 | // { [k in keyof Shape]: DeepPartial }, 69 | // Params, 70 | // Catchall 71 | // > 72 | // > 73 | // : never; 74 | // rest: ReturnType; 75 | // }[T extends ZodObject 76 | // ? "object" // T extends ZodOptional // ? 'optional' // : 77 | // : "rest"]; 78 | } 79 | -------------------------------------------------------------------------------- /src/__tests__/promise.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | const promSchema = z.promise( 8 | z.object({ 9 | name: z.string(), 10 | age: z.number(), 11 | }) 12 | ); 13 | 14 | test("promise inference", () => { 15 | type promSchemaType = z.infer; 16 | util.assertEqual>( 17 | true 18 | ); 19 | }); 20 | 21 | test("promise parsing success", async () => { 22 | const pr = promSchema.parse(Promise.resolve({ name: "Bobby", age: 10 })); 23 | expect(pr).toBeInstanceOf(Promise); 24 | const result = await pr; 25 | expect(typeof result).toBe("object"); 26 | expect(typeof result.age).toBe("number"); 27 | expect(typeof result.name).toBe("string"); 28 | }); 29 | 30 | test("promise parsing success 2", () => { 31 | const fakePromise = { 32 | then() { 33 | return this; 34 | }, 35 | catch() { 36 | return this; 37 | }, 38 | }; 39 | promSchema.parse(fakePromise); 40 | }); 41 | 42 | test("promise parsing fail", async () => { 43 | const bad = promSchema.parse(Promise.resolve({ name: "Bobby", age: "10" })); 44 | // return await expect(bad).resolves.toBe({ name: 'Bobby', age: '10' }); 45 | return await expect(bad).rejects.toBeInstanceOf(z.ZodError); 46 | // done(); 47 | }); 48 | 49 | test("promise parsing fail 2", async () => { 50 | const failPromise = promSchema.parse( 51 | Promise.resolve({ name: "Bobby", age: "10" }) 52 | ); 53 | await expect(failPromise).rejects.toBeInstanceOf(z.ZodError); 54 | // done();/z 55 | }); 56 | 57 | test("promise parsing fail", () => { 58 | const bad = () => promSchema.parse({ then: () => {}, catch: {} }); 59 | expect(bad).toThrow(); 60 | }); 61 | 62 | // test('sync promise parsing', () => { 63 | // expect(() => z.promise(z.string()).parse(Promise.resolve('asfd'))).toThrow(); 64 | // }); 65 | 66 | const asyncFunction = z.function(z.tuple([]), promSchema); 67 | 68 | test("async function pass", async () => { 69 | const validatedFunction = asyncFunction.implement(async () => { 70 | return { name: "jimmy", age: 14 }; 71 | }); 72 | await expect(validatedFunction()).resolves.toEqual({ 73 | name: "jimmy", 74 | age: 14, 75 | }); 76 | }); 77 | 78 | test("async function fail", async () => { 79 | const validatedFunction = asyncFunction.implement(() => { 80 | return Promise.resolve("asdf" as any); 81 | }); 82 | await expect(validatedFunction()).rejects.toBeInstanceOf(z.ZodError); 83 | }); 84 | 85 | test("async promise parsing", () => { 86 | const res = z.promise(z.number()).parseAsync(Promise.resolve(12)); 87 | expect(res).toBeInstanceOf(Promise); 88 | }); 89 | 90 | test("resolves", () => { 91 | const foo = z.literal("foo"); 92 | const res = z.promise(foo); 93 | expect(res.unwrap()).toEqual(foo); 94 | }); 95 | -------------------------------------------------------------------------------- /deno/lib/__tests__/standard-schema.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | import type { StandardSchemaV1 } from "@standard-schema/spec"; 5 | 6 | import { util } from "../helpers/util.ts"; 7 | import * as z from "../index.ts"; 8 | 9 | test("assignability", () => { 10 | const _s1: StandardSchemaV1 = z.string(); 11 | const _s2: StandardSchemaV1 = z.string(); 12 | const _s3: StandardSchemaV1 = z.string(); 13 | const _s4: StandardSchemaV1 = z.string(); 14 | [_s1, _s2, _s3, _s4]; 15 | }); 16 | 17 | test("type inference", () => { 18 | const stringToNumber = z.string().transform((x) => x.length); 19 | type input = StandardSchemaV1.InferInput; 20 | util.assertEqual(true); 21 | type output = StandardSchemaV1.InferOutput; 22 | util.assertEqual(true); 23 | }); 24 | 25 | test("valid parse", () => { 26 | const schema = z.string(); 27 | const result = schema["~standard"]["validate"]("hello"); 28 | if (result instanceof Promise) { 29 | throw new Error("Expected sync result"); 30 | } 31 | expect(result.issues).toEqual(undefined); 32 | if (result.issues) { 33 | throw new Error("Expected no issues"); 34 | } else { 35 | expect(result.value).toEqual("hello"); 36 | } 37 | }); 38 | 39 | test("invalid parse", () => { 40 | const schema = z.string(); 41 | const result = schema["~standard"]["validate"](1234); 42 | if (result instanceof Promise) { 43 | throw new Error("Expected sync result"); 44 | } 45 | expect(result.issues).toBeDefined(); 46 | if (!result.issues) { 47 | throw new Error("Expected issues"); 48 | } 49 | expect(result.issues.length).toEqual(1); 50 | expect(result.issues[0].path).toEqual([]); 51 | }); 52 | 53 | test("valid parse async", async () => { 54 | const schema = z.string().refine(async () => true); 55 | const _result = schema["~standard"]["validate"]("hello"); 56 | if (_result instanceof Promise) { 57 | const result = await _result; 58 | expect(result.issues).toEqual(undefined); 59 | if (result.issues) { 60 | throw new Error("Expected no issues"); 61 | } else { 62 | expect(result.value).toEqual("hello"); 63 | } 64 | } else { 65 | throw new Error("Expected async result"); 66 | } 67 | }); 68 | 69 | test("invalid parse async", async () => { 70 | const schema = z.string().refine(async () => false); 71 | const _result = schema["~standard"]["validate"]("hello"); 72 | if (_result instanceof Promise) { 73 | const result = await _result; 74 | expect(result.issues).toBeDefined(); 75 | if (!result.issues) { 76 | throw new Error("Expected issues"); 77 | } 78 | expect(result.issues.length).toEqual(1); 79 | expect(result.issues[0].path).toEqual([]); 80 | } else { 81 | throw new Error("Expected async result"); 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /deno/lib/__tests__/promise.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | const promSchema = z.promise( 9 | z.object({ 10 | name: z.string(), 11 | age: z.number(), 12 | }) 13 | ); 14 | 15 | test("promise inference", () => { 16 | type promSchemaType = z.infer; 17 | util.assertEqual>( 18 | true 19 | ); 20 | }); 21 | 22 | test("promise parsing success", async () => { 23 | const pr = promSchema.parse(Promise.resolve({ name: "Bobby", age: 10 })); 24 | expect(pr).toBeInstanceOf(Promise); 25 | const result = await pr; 26 | expect(typeof result).toBe("object"); 27 | expect(typeof result.age).toBe("number"); 28 | expect(typeof result.name).toBe("string"); 29 | }); 30 | 31 | test("promise parsing success 2", () => { 32 | const fakePromise = { 33 | then() { 34 | return this; 35 | }, 36 | catch() { 37 | return this; 38 | }, 39 | }; 40 | promSchema.parse(fakePromise); 41 | }); 42 | 43 | test("promise parsing fail", async () => { 44 | const bad = promSchema.parse(Promise.resolve({ name: "Bobby", age: "10" })); 45 | // return await expect(bad).resolves.toBe({ name: 'Bobby', age: '10' }); 46 | return await expect(bad).rejects.toBeInstanceOf(z.ZodError); 47 | // done(); 48 | }); 49 | 50 | test("promise parsing fail 2", async () => { 51 | const failPromise = promSchema.parse( 52 | Promise.resolve({ name: "Bobby", age: "10" }) 53 | ); 54 | await expect(failPromise).rejects.toBeInstanceOf(z.ZodError); 55 | // done();/z 56 | }); 57 | 58 | test("promise parsing fail", () => { 59 | const bad = () => promSchema.parse({ then: () => {}, catch: {} }); 60 | expect(bad).toThrow(); 61 | }); 62 | 63 | // test('sync promise parsing', () => { 64 | // expect(() => z.promise(z.string()).parse(Promise.resolve('asfd'))).toThrow(); 65 | // }); 66 | 67 | const asyncFunction = z.function(z.tuple([]), promSchema); 68 | 69 | test("async function pass", async () => { 70 | const validatedFunction = asyncFunction.implement(async () => { 71 | return { name: "jimmy", age: 14 }; 72 | }); 73 | await expect(validatedFunction()).resolves.toEqual({ 74 | name: "jimmy", 75 | age: 14, 76 | }); 77 | }); 78 | 79 | test("async function fail", async () => { 80 | const validatedFunction = asyncFunction.implement(() => { 81 | return Promise.resolve("asdf" as any); 82 | }); 83 | await expect(validatedFunction()).rejects.toBeInstanceOf(z.ZodError); 84 | }); 85 | 86 | test("async promise parsing", () => { 87 | const res = z.promise(z.number()).parseAsync(Promise.resolve(12)); 88 | expect(res).toBeInstanceOf(Promise); 89 | }); 90 | 91 | test("resolves", () => { 92 | const foo = z.literal("foo"); 93 | const res = z.promise(foo); 94 | expect(res.unwrap()).toEqual(foo); 95 | }); 96 | -------------------------------------------------------------------------------- /src/__tests__/enum.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | test("create enum", () => { 8 | const MyEnum = z.enum(["Red", "Green", "Blue"]); 9 | expect(MyEnum.Values.Red).toEqual("Red"); 10 | expect(MyEnum.Enum.Red).toEqual("Red"); 11 | expect(MyEnum.enum.Red).toEqual("Red"); 12 | }); 13 | 14 | test("infer enum", () => { 15 | const MyEnum = z.enum(["Red", "Green", "Blue"]); 16 | type MyEnum = z.infer; 17 | util.assertEqual(true); 18 | }); 19 | 20 | test("get options", () => { 21 | expect(z.enum(["tuna", "trout"]).options).toEqual(["tuna", "trout"]); 22 | }); 23 | 24 | test("readonly enum", () => { 25 | const HTTP_SUCCESS = ["200", "201"] as const; 26 | const arg = z.enum(HTTP_SUCCESS); 27 | type arg = z.infer; 28 | util.assertEqual(true); 29 | 30 | arg.parse("201"); 31 | expect(() => arg.parse("202")).toThrow(); 32 | }); 33 | 34 | test("error params", () => { 35 | const result = z 36 | .enum(["test"], { required_error: "REQUIRED" }) 37 | .safeParse(undefined); 38 | expect(result.success).toEqual(false); 39 | if (!result.success) { 40 | expect(result.error.issues[0].message).toEqual("REQUIRED"); 41 | } 42 | }); 43 | 44 | test("extract/exclude", () => { 45 | const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const; 46 | const FoodEnum = z.enum(foods); 47 | const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]); 48 | const UnhealthyEnum = FoodEnum.exclude(["Salad"]); 49 | const EmptyFoodEnum = FoodEnum.exclude(foods); 50 | 51 | util.assertEqual, "Pasta" | "Pizza">(true); 52 | util.assertEqual< 53 | z.infer, 54 | "Pasta" | "Pizza" | "Tacos" | "Burgers" 55 | >(true); 56 | // @ts-expect-error TS2344 57 | util.assertEqual>(true); 58 | util.assertEqual, never>(true); 59 | }); 60 | 61 | test("error map in extract/exclude", () => { 62 | const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const; 63 | const FoodEnum = z.enum(foods, { 64 | errorMap: () => ({ message: "This is not food!" }), 65 | }); 66 | const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]); 67 | const foodsError = FoodEnum.safeParse("Cucumbers"); 68 | const italianError = ItalianEnum.safeParse("Tacos"); 69 | if (!foodsError.success && !italianError.success) { 70 | expect(foodsError.error.issues[0].message).toEqual( 71 | italianError.error.issues[0].message 72 | ); 73 | } 74 | 75 | const UnhealthyEnum = FoodEnum.exclude(["Salad"], { 76 | errorMap: () => ({ message: "This is not healthy food!" }), 77 | }); 78 | const unhealthyError = UnhealthyEnum.safeParse("Salad"); 79 | if (!unhealthyError.success) { 80 | expect(unhealthyError.error.issues[0].message).toEqual( 81 | "This is not healthy food!" 82 | ); 83 | } 84 | }); 85 | 86 | test("readonly in ZodEnumDef", () => { 87 | let _t!: z.ZodEnumDef; 88 | _t; 89 | }); 90 | -------------------------------------------------------------------------------- /deno/lib/__tests__/enum.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | test("create enum", () => { 9 | const MyEnum = z.enum(["Red", "Green", "Blue"]); 10 | expect(MyEnum.Values.Red).toEqual("Red"); 11 | expect(MyEnum.Enum.Red).toEqual("Red"); 12 | expect(MyEnum.enum.Red).toEqual("Red"); 13 | }); 14 | 15 | test("infer enum", () => { 16 | const MyEnum = z.enum(["Red", "Green", "Blue"]); 17 | type MyEnum = z.infer; 18 | util.assertEqual(true); 19 | }); 20 | 21 | test("get options", () => { 22 | expect(z.enum(["tuna", "trout"]).options).toEqual(["tuna", "trout"]); 23 | }); 24 | 25 | test("readonly enum", () => { 26 | const HTTP_SUCCESS = ["200", "201"] as const; 27 | const arg = z.enum(HTTP_SUCCESS); 28 | type arg = z.infer; 29 | util.assertEqual(true); 30 | 31 | arg.parse("201"); 32 | expect(() => arg.parse("202")).toThrow(); 33 | }); 34 | 35 | test("error params", () => { 36 | const result = z 37 | .enum(["test"], { required_error: "REQUIRED" }) 38 | .safeParse(undefined); 39 | expect(result.success).toEqual(false); 40 | if (!result.success) { 41 | expect(result.error.issues[0].message).toEqual("REQUIRED"); 42 | } 43 | }); 44 | 45 | test("extract/exclude", () => { 46 | const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const; 47 | const FoodEnum = z.enum(foods); 48 | const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]); 49 | const UnhealthyEnum = FoodEnum.exclude(["Salad"]); 50 | const EmptyFoodEnum = FoodEnum.exclude(foods); 51 | 52 | util.assertEqual, "Pasta" | "Pizza">(true); 53 | util.assertEqual< 54 | z.infer, 55 | "Pasta" | "Pizza" | "Tacos" | "Burgers" 56 | >(true); 57 | // @ts-expect-error TS2344 58 | util.assertEqual>(true); 59 | util.assertEqual, never>(true); 60 | }); 61 | 62 | test("error map in extract/exclude", () => { 63 | const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const; 64 | const FoodEnum = z.enum(foods, { 65 | errorMap: () => ({ message: "This is not food!" }), 66 | }); 67 | const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]); 68 | const foodsError = FoodEnum.safeParse("Cucumbers"); 69 | const italianError = ItalianEnum.safeParse("Tacos"); 70 | if (!foodsError.success && !italianError.success) { 71 | expect(foodsError.error.issues[0].message).toEqual( 72 | italianError.error.issues[0].message 73 | ); 74 | } 75 | 76 | const UnhealthyEnum = FoodEnum.exclude(["Salad"], { 77 | errorMap: () => ({ message: "This is not healthy food!" }), 78 | }); 79 | const unhealthyError = UnhealthyEnum.safeParse("Salad"); 80 | if (!unhealthyError.success) { 81 | expect(unhealthyError.error.issues[0].message).toEqual( 82 | "This is not healthy food!" 83 | ); 84 | } 85 | }); 86 | 87 | test("readonly in ZodEnumDef", () => { 88 | let _t!: z.ZodEnumDef; 89 | _t; 90 | }); 91 | -------------------------------------------------------------------------------- /src/__tests__/tuple.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | import { ZodError } from "../ZodError"; 7 | 8 | const testTuple = z.tuple([ 9 | z.string(), 10 | z.object({ name: z.literal("Rudy") }), 11 | z.array(z.literal("blue")), 12 | ]); 13 | const testData = ["asdf", { name: "Rudy" }, ["blue"]]; 14 | const badData = [123, { name: "Rudy2" }, ["blue", "red"]]; 15 | 16 | test("tuple inference", () => { 17 | const args1 = z.tuple([z.string()]); 18 | const returns1 = z.number(); 19 | const func1 = z.function(args1, returns1); 20 | type func1 = z.TypeOf; 21 | util.assertEqual number>(true); 22 | }); 23 | 24 | test("successful validation", () => { 25 | const val = testTuple.parse(testData); 26 | expect(val).toEqual(["asdf", { name: "Rudy" }, ["blue"]]); 27 | }); 28 | 29 | test("successful async validation", async () => { 30 | const val = await testTuple.parseAsync(testData); 31 | return expect(val).toEqual(testData); 32 | }); 33 | 34 | test("failed validation", () => { 35 | const checker = () => { 36 | testTuple.parse([123, { name: "Rudy2" }, ["blue", "red"]] as any); 37 | }; 38 | try { 39 | checker(); 40 | } catch (err) { 41 | if (err instanceof ZodError) { 42 | expect(err.issues.length).toEqual(3); 43 | } 44 | } 45 | }); 46 | 47 | test("failed async validation", async () => { 48 | const res = await testTuple.safeParse(badData); 49 | expect(res.success).toEqual(false); 50 | if (!res.success) { 51 | expect(res.error.issues.length).toEqual(3); 52 | } 53 | // try { 54 | // checker(); 55 | // } catch (err) { 56 | // if (err instanceof ZodError) { 57 | // expect(err.issues.length).toEqual(3); 58 | // } 59 | // } 60 | }); 61 | 62 | test("tuple with transformers", () => { 63 | const stringToNumber = z.string().transform((val) => val.length); 64 | const val = z.tuple([stringToNumber]); 65 | 66 | type t1 = z.input; 67 | util.assertEqual(true); 68 | type t2 = z.output; 69 | util.assertEqual(true); 70 | expect(val.parse(["1234"])).toEqual([4]); 71 | }); 72 | 73 | test("tuple with rest schema", () => { 74 | const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean()); 75 | expect(myTuple.parse(["asdf", 1234, true, false, true])).toEqual([ 76 | "asdf", 77 | 1234, 78 | true, 79 | false, 80 | true, 81 | ]); 82 | 83 | expect(myTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]); 84 | 85 | expect(() => myTuple.parse(["asdf", 1234, "asdf"])).toThrow(); 86 | type t1 = z.output; 87 | 88 | util.assertEqual(true); 89 | }); 90 | 91 | test("parse should fail given sparse array as tuple", () => { 92 | expect(() => testTuple.parse(new Array(3))).toThrow(); 93 | }); 94 | 95 | // test('tuple with optional elements', () => { 96 | // const result = z 97 | // .tuple([z.string(), z.number().optional()]) 98 | // .safeParse(['asdf']); 99 | // expect(result).toEqual(['asdf']); 100 | // }); 101 | -------------------------------------------------------------------------------- /deno/lib/__tests__/tuple.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | import { ZodError } from "../ZodError.ts"; 8 | 9 | const testTuple = z.tuple([ 10 | z.string(), 11 | z.object({ name: z.literal("Rudy") }), 12 | z.array(z.literal("blue")), 13 | ]); 14 | const testData = ["asdf", { name: "Rudy" }, ["blue"]]; 15 | const badData = [123, { name: "Rudy2" }, ["blue", "red"]]; 16 | 17 | test("tuple inference", () => { 18 | const args1 = z.tuple([z.string()]); 19 | const returns1 = z.number(); 20 | const func1 = z.function(args1, returns1); 21 | type func1 = z.TypeOf; 22 | util.assertEqual number>(true); 23 | }); 24 | 25 | test("successful validation", () => { 26 | const val = testTuple.parse(testData); 27 | expect(val).toEqual(["asdf", { name: "Rudy" }, ["blue"]]); 28 | }); 29 | 30 | test("successful async validation", async () => { 31 | const val = await testTuple.parseAsync(testData); 32 | return expect(val).toEqual(testData); 33 | }); 34 | 35 | test("failed validation", () => { 36 | const checker = () => { 37 | testTuple.parse([123, { name: "Rudy2" }, ["blue", "red"]] as any); 38 | }; 39 | try { 40 | checker(); 41 | } catch (err) { 42 | if (err instanceof ZodError) { 43 | expect(err.issues.length).toEqual(3); 44 | } 45 | } 46 | }); 47 | 48 | test("failed async validation", async () => { 49 | const res = await testTuple.safeParse(badData); 50 | expect(res.success).toEqual(false); 51 | if (!res.success) { 52 | expect(res.error.issues.length).toEqual(3); 53 | } 54 | // try { 55 | // checker(); 56 | // } catch (err) { 57 | // if (err instanceof ZodError) { 58 | // expect(err.issues.length).toEqual(3); 59 | // } 60 | // } 61 | }); 62 | 63 | test("tuple with transformers", () => { 64 | const stringToNumber = z.string().transform((val) => val.length); 65 | const val = z.tuple([stringToNumber]); 66 | 67 | type t1 = z.input; 68 | util.assertEqual(true); 69 | type t2 = z.output; 70 | util.assertEqual(true); 71 | expect(val.parse(["1234"])).toEqual([4]); 72 | }); 73 | 74 | test("tuple with rest schema", () => { 75 | const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean()); 76 | expect(myTuple.parse(["asdf", 1234, true, false, true])).toEqual([ 77 | "asdf", 78 | 1234, 79 | true, 80 | false, 81 | true, 82 | ]); 83 | 84 | expect(myTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]); 85 | 86 | expect(() => myTuple.parse(["asdf", 1234, "asdf"])).toThrow(); 87 | type t1 = z.output; 88 | 89 | util.assertEqual(true); 90 | }); 91 | 92 | test("parse should fail given sparse array as tuple", () => { 93 | expect(() => testTuple.parse(new Array(3))).toThrow(); 94 | }); 95 | 96 | // test('tuple with optional elements', () => { 97 | // const result = z 98 | // .tuple([z.string(), z.number().optional()]) 99 | // .safeParse(['asdf']); 100 | // expect(result).toEqual(['asdf']); 101 | // }); 102 | -------------------------------------------------------------------------------- /src/standard-schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The Standard Schema interface. 3 | */ 4 | export type StandardSchemaV1 = { 5 | /** 6 | * The Standard Schema properties. 7 | */ 8 | readonly "~standard": StandardSchemaV1.Props; 9 | }; 10 | 11 | export declare namespace StandardSchemaV1 { 12 | /** 13 | * The Standard Schema properties interface. 14 | */ 15 | export interface Props { 16 | /** 17 | * The version number of the standard. 18 | */ 19 | readonly version: 1; 20 | /** 21 | * The vendor name of the schema library. 22 | */ 23 | readonly vendor: string; 24 | /** 25 | * Validates unknown input values. 26 | */ 27 | readonly validate: ( 28 | value: unknown 29 | ) => Result | Promise>; 30 | /** 31 | * Inferred types associated with the schema. 32 | */ 33 | readonly types?: Types | undefined; 34 | } 35 | 36 | /** 37 | * The result interface of the validate function. 38 | */ 39 | export type Result = SuccessResult | FailureResult; 40 | 41 | /** 42 | * The result interface if validation succeeds. 43 | */ 44 | export interface SuccessResult { 45 | /** 46 | * The typed output value. 47 | */ 48 | readonly value: Output; 49 | /** 50 | * The non-existent issues. 51 | */ 52 | readonly issues?: undefined; 53 | } 54 | 55 | /** 56 | * The result interface if validation fails. 57 | */ 58 | export interface FailureResult { 59 | /** 60 | * The issues of failed validation. 61 | */ 62 | readonly issues: ReadonlyArray; 63 | } 64 | 65 | /** 66 | * The issue interface of the failure output. 67 | */ 68 | export interface Issue { 69 | /** 70 | * The error message of the issue. 71 | */ 72 | readonly message: string; 73 | /** 74 | * The path of the issue, if any. 75 | */ 76 | readonly path?: ReadonlyArray | undefined; 77 | } 78 | 79 | /** 80 | * The path segment interface of the issue. 81 | */ 82 | export interface PathSegment { 83 | /** 84 | * The key representing a path segment. 85 | */ 86 | readonly key: PropertyKey; 87 | } 88 | 89 | /** 90 | * The Standard Schema types interface. 91 | */ 92 | export interface Types { 93 | /** 94 | * The input type of the schema. 95 | */ 96 | readonly input: Input; 97 | /** 98 | * The output type of the schema. 99 | */ 100 | readonly output: Output; 101 | } 102 | 103 | /** 104 | * Infers the input type of a Standard Schema. 105 | */ 106 | export type InferInput = NonNullable< 107 | Schema["~standard"]["types"] 108 | >["input"]; 109 | 110 | /** 111 | * Infers the output type of a Standard Schema. 112 | */ 113 | export type InferOutput = NonNullable< 114 | Schema["~standard"]["types"] 115 | >["output"]; 116 | 117 | // biome-ignore lint/complexity/noUselessEmptyExport: needed for granular visibility control of TS namespace 118 | export {}; 119 | } 120 | -------------------------------------------------------------------------------- /deno/lib/standard-schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The Standard Schema interface. 3 | */ 4 | export type StandardSchemaV1 = { 5 | /** 6 | * The Standard Schema properties. 7 | */ 8 | readonly "~standard": StandardSchemaV1.Props; 9 | }; 10 | 11 | export declare namespace StandardSchemaV1 { 12 | /** 13 | * The Standard Schema properties interface. 14 | */ 15 | export interface Props { 16 | /** 17 | * The version number of the standard. 18 | */ 19 | readonly version: 1; 20 | /** 21 | * The vendor name of the schema library. 22 | */ 23 | readonly vendor: string; 24 | /** 25 | * Validates unknown input values. 26 | */ 27 | readonly validate: ( 28 | value: unknown 29 | ) => Result | Promise>; 30 | /** 31 | * Inferred types associated with the schema. 32 | */ 33 | readonly types?: Types | undefined; 34 | } 35 | 36 | /** 37 | * The result interface of the validate function. 38 | */ 39 | export type Result = SuccessResult | FailureResult; 40 | 41 | /** 42 | * The result interface if validation succeeds. 43 | */ 44 | export interface SuccessResult { 45 | /** 46 | * The typed output value. 47 | */ 48 | readonly value: Output; 49 | /** 50 | * The non-existent issues. 51 | */ 52 | readonly issues?: undefined; 53 | } 54 | 55 | /** 56 | * The result interface if validation fails. 57 | */ 58 | export interface FailureResult { 59 | /** 60 | * The issues of failed validation. 61 | */ 62 | readonly issues: ReadonlyArray; 63 | } 64 | 65 | /** 66 | * The issue interface of the failure output. 67 | */ 68 | export interface Issue { 69 | /** 70 | * The error message of the issue. 71 | */ 72 | readonly message: string; 73 | /** 74 | * The path of the issue, if any. 75 | */ 76 | readonly path?: ReadonlyArray | undefined; 77 | } 78 | 79 | /** 80 | * The path segment interface of the issue. 81 | */ 82 | export interface PathSegment { 83 | /** 84 | * The key representing a path segment. 85 | */ 86 | readonly key: PropertyKey; 87 | } 88 | 89 | /** 90 | * The Standard Schema types interface. 91 | */ 92 | export interface Types { 93 | /** 94 | * The input type of the schema. 95 | */ 96 | readonly input: Input; 97 | /** 98 | * The output type of the schema. 99 | */ 100 | readonly output: Output; 101 | } 102 | 103 | /** 104 | * Infers the input type of a Standard Schema. 105 | */ 106 | export type InferInput = NonNullable< 107 | Schema["~standard"]["types"] 108 | >["input"]; 109 | 110 | /** 111 | * Infers the output type of a Standard Schema. 112 | */ 113 | export type InferOutput = NonNullable< 114 | Schema["~standard"]["types"] 115 | >["output"]; 116 | 117 | // biome-ignore lint/complexity/noUselessEmptyExport: needed for granular visibility control of TS namespace 118 | export {}; 119 | } 120 | -------------------------------------------------------------------------------- /deno-build.mjs: -------------------------------------------------------------------------------- 1 | // This script expects to be run via `yarn build:deno`. 2 | // 3 | // Although this script generates code for use in Deno, this script itself is 4 | // written for Node so that contributors do not need to install Deno to build. 5 | // 6 | // @ts-check 7 | 8 | import { 9 | mkdirSync, 10 | readdirSync, 11 | readFileSync, 12 | statSync, 13 | writeFileSync, 14 | } from "fs"; 15 | import { dirname } from "path"; 16 | 17 | // Node's path.join() normalize explicitly-relative paths like "./index.ts" to 18 | // paths like "index.ts" which don't work as relative ES imports, so we do this. 19 | const join = (/** @type string[] */ ...parts) => 20 | parts.join("/").replace(/\/\//g, "/"); 21 | 22 | const projectRoot = process.cwd(); 23 | const nodeSrcRoot = join(projectRoot, "src"); 24 | const denoLibRoot = join(projectRoot, "deno", "lib"); 25 | 26 | const skipList = [ 27 | join(nodeSrcRoot, "__tests__", "object-in-es5-env.test.ts"), 28 | join(nodeSrcRoot, "__tests__", "language-server.test.ts"), 29 | join(nodeSrcRoot, "__tests__", "language-server.source.ts"), 30 | ]; 31 | const walkAndBuild = (/** @type string */ dir) => { 32 | for (const entry of readdirSync(join(nodeSrcRoot, dir), { 33 | withFileTypes: true, 34 | encoding: "utf-8", 35 | })) { 36 | if (entry.isDirectory()) { 37 | walkAndBuild(join(dir, entry.name)); 38 | } else if (entry.isFile() && entry.name.endsWith(".ts")) { 39 | const nodePath = join(nodeSrcRoot, dir, entry.name); 40 | const denoPath = join(denoLibRoot, dir, entry.name); 41 | 42 | if (skipList.includes(nodePath)) { 43 | // console.log(`Skipping ${nodePath}`); 44 | continue; 45 | } 46 | 47 | const nodeSource = readFileSync(nodePath, { encoding: "utf-8" }); 48 | 49 | const denoSource = nodeSource.replace( 50 | /^(?:import|export)[\s\S]*?from\s*['"]([^'"]*)['"];$/gm, 51 | (line, target) => { 52 | if (target === "@jest/globals") { 53 | return `import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";\nconst test = Deno.test;`; 54 | } 55 | 56 | const targetNodePath = join(dirname(nodePath), target); 57 | const targetNodePathIfFile = targetNodePath + ".ts"; 58 | const targetNodePathIfDir = join(targetNodePath, "index.ts"); 59 | 60 | try { 61 | if (statSync(targetNodePathIfFile)?.isFile()) { 62 | return line.replace(target, target + ".ts"); 63 | } 64 | } catch (error) { 65 | if (error?.code !== "ENOENT") { 66 | throw error; 67 | } 68 | } 69 | 70 | try { 71 | if (statSync(targetNodePathIfDir)?.isFile()) { 72 | return line.replace(target, join(target, "index.ts")); 73 | } 74 | } catch (error) { 75 | if (error?.code !== "ENOENT") { 76 | throw error; 77 | } 78 | } 79 | 80 | // console.warn(`Skipping non-resolvable import:\n ${line}`); 81 | return line; 82 | } 83 | ); 84 | 85 | mkdirSync(dirname(denoPath), { recursive: true }); 86 | writeFileSync(denoPath, denoSource, { encoding: "utf-8" }); 87 | } 88 | } 89 | }; 90 | 91 | walkAndBuild(""); 92 | 93 | writeFileSync( 94 | join(denoLibRoot, "mod.ts"), 95 | `export * from "./index.ts";\nexport { default as default } from "./index.ts";\n`, 96 | { 97 | encoding: "utf-8", 98 | } 99 | ); 100 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | > ⚠ The default branch has just been switched to `main` from `master` (as of May 15th, 2024). Follow the following instructions to update your local fork. 4 | > 5 | > ```sh 6 | > git branch -m master main # rename local branch 7 | > git fetch origin # fetch from remote 8 | > git branch -u origin/main main # set upstream 9 | > git remote set-head origin -a # update remote 10 | > ``` 11 | 12 | When it comes to open source, there are different ways you can contribute, all 13 | of which are valuable. Here's few guidelines that should help you as you prepare 14 | your contribution. 15 | 16 | ## Initial steps 17 | 18 | Before you start working on a contribution, create an issue describing what you want to build. It's possible someone else is already working on something similar, or perhaps there is a reason that feature isn't implemented. The maintainers will point you in the right direction. 19 | 20 | 30 | 31 | ## Development 32 | 33 | The following steps will get you setup to contribute changes to this repo: 34 | 35 | 1. Fork this repo. 36 | 37 | 2. Clone your forked repo: `git clone git@github.com:{your_username}/zod.git` 38 | 39 | 3. Run `yarn` to install dependencies. 40 | 41 | 4. Start playing with the code! You can do some simple experimentation in [`playground.ts`](playground.ts) (see `yarn play` below) or start implementing a feature right away. 42 | 43 | ## Alternative: VSCode Dev Container setup 44 | 45 | For an officially supported isolated dev environment that automatically installs dependencies for you: 46 | 47 | 1. `F1` in VSCode and start typing `Dev Containers: Clone Repository in Named Container Volume` to run the command. 48 | 2. For the repo, paste `git@github.com:{your_username}/zod.git` if you're using ssh. 49 | 3. Click `Create a new volume...` and name it `zod` and the folder name as `zod`. 50 | 51 | Note: if you can't see `Dev Containers` in the `F1` menu, follow [this guide](https://code.visualstudio.com/docs/devcontainers/tutorial) to install the needed extension. 52 | In the OSS version of VSCode the extension may be not available. 53 | 54 | ### Commands 55 | 56 | **`yarn build`** 57 | 58 | - deletes `lib` and re-compiles `src` to `lib` 59 | 60 | **`yarn test`** 61 | 62 | - runs all Jest tests and generates coverage badge 63 | 64 | **`yarn test enum`** 65 | 66 | - runs a single test file (e.g. `enum.test.ts`) 67 | 68 | **`yarn play`** 69 | 70 | - executes [`playground.ts`](playground.ts), watches for changes. useful for experimentation 71 | 72 | ### Tests 73 | 74 | Zod uses Jest for testing. After implementing your contribution, write tests for it. Just create a new file under `src/__tests__` or add additional tests to the appropriate existing file. 75 | 76 | Before submitting your PR, run `yarn test` to make sure there are no (unintended) breaking changes. 77 | 78 | ### Documentation 79 | 80 | The Zod documentation lives in the README.md. Be sure to document any API changes you implement. 81 | 82 | ## License 83 | 84 | By contributing your code to the zod GitHub repository, you agree to 85 | license your contribution under the MIT license. 86 | -------------------------------------------------------------------------------- /src/__tests__/intersection.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import * as z from "../index"; 5 | 6 | test("object intersection", () => { 7 | const BaseTeacher = z.object({ 8 | subjects: z.array(z.string()), 9 | }); 10 | const HasID = z.object({ id: z.string() }); 11 | 12 | const Teacher = z.intersection(BaseTeacher.passthrough(), HasID); // BaseTeacher.merge(HasID); 13 | const data = { 14 | subjects: ["math"], 15 | id: "asdfasdf", 16 | }; 17 | expect(Teacher.parse(data)).toEqual(data); 18 | expect(() => Teacher.parse({ subject: data.subjects })).toThrow(); 19 | expect(Teacher.parse({ ...data, extra: 12 })).toEqual({ ...data, extra: 12 }); 20 | 21 | expect(() => 22 | z.intersection(BaseTeacher.strict(), HasID).parse({ ...data, extra: 12 }) 23 | ).toThrow(); 24 | }); 25 | 26 | test("deep intersection", () => { 27 | const Animal = z.object({ 28 | properties: z.object({ 29 | is_animal: z.boolean(), 30 | }), 31 | }); 32 | const Cat = z 33 | .object({ 34 | properties: z.object({ 35 | jumped: z.boolean(), 36 | }), 37 | }) 38 | .and(Animal); 39 | 40 | type Cat = z.infer; 41 | // const cat:Cat = 'asdf' as any; 42 | const cat = Cat.parse({ properties: { is_animal: true, jumped: true } }); 43 | expect(cat.properties).toEqual({ is_animal: true, jumped: true }); 44 | }); 45 | 46 | test("deep intersection of arrays", async () => { 47 | const Author = z.object({ 48 | posts: z.array( 49 | z.object({ 50 | post_id: z.number(), 51 | }) 52 | ), 53 | }); 54 | const Registry = z 55 | .object({ 56 | posts: z.array( 57 | z.object({ 58 | title: z.string(), 59 | }) 60 | ), 61 | }) 62 | .and(Author); 63 | 64 | const posts = [ 65 | { post_id: 1, title: "Novels" }, 66 | { post_id: 2, title: "Fairy tales" }, 67 | ]; 68 | const cat = Registry.parse({ posts }); 69 | expect(cat.posts).toEqual(posts); 70 | const asyncCat = await Registry.parseAsync({ posts }); 71 | expect(asyncCat.posts).toEqual(posts); 72 | }); 73 | 74 | test("invalid intersection types", async () => { 75 | const numberIntersection = z.intersection( 76 | z.number(), 77 | z.number().transform((x) => x + 1) 78 | ); 79 | 80 | const syncResult = numberIntersection.safeParse(1234); 81 | expect(syncResult.success).toEqual(false); 82 | if (!syncResult.success) { 83 | expect(syncResult.error.issues[0].code).toEqual( 84 | z.ZodIssueCode.invalid_intersection_types 85 | ); 86 | } 87 | 88 | const asyncResult = await numberIntersection.spa(1234); 89 | expect(asyncResult.success).toEqual(false); 90 | if (!asyncResult.success) { 91 | expect(asyncResult.error.issues[0].code).toEqual( 92 | z.ZodIssueCode.invalid_intersection_types 93 | ); 94 | } 95 | }); 96 | 97 | test("invalid array merge", async () => { 98 | const stringArrInt = z.intersection( 99 | z.string().array(), 100 | z 101 | .string() 102 | .array() 103 | .transform((val) => [...val, "asdf"]) 104 | ); 105 | const syncResult = stringArrInt.safeParse(["asdf", "qwer"]); 106 | expect(syncResult.success).toEqual(false); 107 | if (!syncResult.success) { 108 | expect(syncResult.error.issues[0].code).toEqual( 109 | z.ZodIssueCode.invalid_intersection_types 110 | ); 111 | } 112 | 113 | const asyncResult = await stringArrInt.spa(["asdf", "qwer"]); 114 | expect(asyncResult.success).toEqual(false); 115 | if (!asyncResult.success) { 116 | expect(asyncResult.error.issues[0].code).toEqual( 117 | z.ZodIssueCode.invalid_intersection_types 118 | ); 119 | } 120 | }); 121 | -------------------------------------------------------------------------------- /deno/lib/__tests__/intersection.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import * as z from "../index.ts"; 6 | 7 | test("object intersection", () => { 8 | const BaseTeacher = z.object({ 9 | subjects: z.array(z.string()), 10 | }); 11 | const HasID = z.object({ id: z.string() }); 12 | 13 | const Teacher = z.intersection(BaseTeacher.passthrough(), HasID); // BaseTeacher.merge(HasID); 14 | const data = { 15 | subjects: ["math"], 16 | id: "asdfasdf", 17 | }; 18 | expect(Teacher.parse(data)).toEqual(data); 19 | expect(() => Teacher.parse({ subject: data.subjects })).toThrow(); 20 | expect(Teacher.parse({ ...data, extra: 12 })).toEqual({ ...data, extra: 12 }); 21 | 22 | expect(() => 23 | z.intersection(BaseTeacher.strict(), HasID).parse({ ...data, extra: 12 }) 24 | ).toThrow(); 25 | }); 26 | 27 | test("deep intersection", () => { 28 | const Animal = z.object({ 29 | properties: z.object({ 30 | is_animal: z.boolean(), 31 | }), 32 | }); 33 | const Cat = z 34 | .object({ 35 | properties: z.object({ 36 | jumped: z.boolean(), 37 | }), 38 | }) 39 | .and(Animal); 40 | 41 | type Cat = z.infer; 42 | // const cat:Cat = 'asdf' as any; 43 | const cat = Cat.parse({ properties: { is_animal: true, jumped: true } }); 44 | expect(cat.properties).toEqual({ is_animal: true, jumped: true }); 45 | }); 46 | 47 | test("deep intersection of arrays", async () => { 48 | const Author = z.object({ 49 | posts: z.array( 50 | z.object({ 51 | post_id: z.number(), 52 | }) 53 | ), 54 | }); 55 | const Registry = z 56 | .object({ 57 | posts: z.array( 58 | z.object({ 59 | title: z.string(), 60 | }) 61 | ), 62 | }) 63 | .and(Author); 64 | 65 | const posts = [ 66 | { post_id: 1, title: "Novels" }, 67 | { post_id: 2, title: "Fairy tales" }, 68 | ]; 69 | const cat = Registry.parse({ posts }); 70 | expect(cat.posts).toEqual(posts); 71 | const asyncCat = await Registry.parseAsync({ posts }); 72 | expect(asyncCat.posts).toEqual(posts); 73 | }); 74 | 75 | test("invalid intersection types", async () => { 76 | const numberIntersection = z.intersection( 77 | z.number(), 78 | z.number().transform((x) => x + 1) 79 | ); 80 | 81 | const syncResult = numberIntersection.safeParse(1234); 82 | expect(syncResult.success).toEqual(false); 83 | if (!syncResult.success) { 84 | expect(syncResult.error.issues[0].code).toEqual( 85 | z.ZodIssueCode.invalid_intersection_types 86 | ); 87 | } 88 | 89 | const asyncResult = await numberIntersection.spa(1234); 90 | expect(asyncResult.success).toEqual(false); 91 | if (!asyncResult.success) { 92 | expect(asyncResult.error.issues[0].code).toEqual( 93 | z.ZodIssueCode.invalid_intersection_types 94 | ); 95 | } 96 | }); 97 | 98 | test("invalid array merge", async () => { 99 | const stringArrInt = z.intersection( 100 | z.string().array(), 101 | z 102 | .string() 103 | .array() 104 | .transform((val) => [...val, "asdf"]) 105 | ); 106 | const syncResult = stringArrInt.safeParse(["asdf", "qwer"]); 107 | expect(syncResult.success).toEqual(false); 108 | if (!syncResult.success) { 109 | expect(syncResult.error.issues[0].code).toEqual( 110 | z.ZodIssueCode.invalid_intersection_types 111 | ); 112 | } 113 | 114 | const asyncResult = await stringArrInt.spa(["asdf", "qwer"]); 115 | expect(asyncResult.success).toEqual(false); 116 | if (!asyncResult.success) { 117 | expect(asyncResult.error.issues[0].code).toEqual( 118 | z.ZodIssueCode.invalid_intersection_types 119 | ); 120 | } 121 | }); 122 | -------------------------------------------------------------------------------- /src/__tests__/map.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | import { ZodIssueCode } from "../index"; 7 | 8 | const stringMap = z.map(z.string(), z.string()); 9 | type stringMap = z.infer; 10 | 11 | test("type inference", () => { 12 | util.assertEqual>(true); 13 | }); 14 | 15 | test("valid parse", () => { 16 | const result = stringMap.safeParse( 17 | new Map([ 18 | ["first", "foo"], 19 | ["second", "bar"], 20 | ]) 21 | ); 22 | expect(result.success).toEqual(true); 23 | if (result.success) { 24 | expect(result.data.has("first")).toEqual(true); 25 | expect(result.data.has("second")).toEqual(true); 26 | expect(result.data.get("first")).toEqual("foo"); 27 | expect(result.data.get("second")).toEqual("bar"); 28 | } 29 | }); 30 | 31 | test("valid parse async", async () => { 32 | const result = await stringMap.spa( 33 | new Map([ 34 | ["first", "foo"], 35 | ["second", "bar"], 36 | ]) 37 | ); 38 | expect(result.success).toEqual(true); 39 | if (result.success) { 40 | expect(result.data.has("first")).toEqual(true); 41 | expect(result.data.has("second")).toEqual(true); 42 | expect(result.data.get("first")).toEqual("foo"); 43 | expect(result.data.get("second")).toEqual("bar"); 44 | } 45 | }); 46 | 47 | test("throws when a Set is given", () => { 48 | const result = stringMap.safeParse(new Set([])); 49 | expect(result.success).toEqual(false); 50 | if (result.success === false) { 51 | expect(result.error.issues.length).toEqual(1); 52 | expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type); 53 | } 54 | }); 55 | 56 | test("throws when the given map has invalid key and invalid input", () => { 57 | const result = stringMap.safeParse(new Map([[42, Symbol()]])); 58 | expect(result.success).toEqual(false); 59 | if (result.success === false) { 60 | expect(result.error.issues.length).toEqual(2); 61 | expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type); 62 | expect(result.error.issues[0].path).toEqual([0, "key"]); 63 | expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type); 64 | expect(result.error.issues[1].path).toEqual([0, "value"]); 65 | } 66 | }); 67 | 68 | test("throws when the given map has multiple invalid entries", () => { 69 | // const result = stringMap.safeParse(new Map([[42, Symbol()]])); 70 | 71 | const result = stringMap.safeParse( 72 | new Map([ 73 | [1, "foo"], 74 | ["bar", 2], 75 | ] as [any, any][]) as Map 76 | ); 77 | 78 | // const result = stringMap.safeParse(new Map([[42, Symbol()]])); 79 | expect(result.success).toEqual(false); 80 | if (result.success === false) { 81 | expect(result.error.issues.length).toEqual(2); 82 | expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type); 83 | expect(result.error.issues[0].path).toEqual([0, "key"]); 84 | expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type); 85 | expect(result.error.issues[1].path).toEqual([1, "value"]); 86 | } 87 | }); 88 | 89 | test("dirty", async () => { 90 | const map = z.map( 91 | z.string().refine((val) => val === val.toUpperCase(), { 92 | message: "Keys must be uppercase", 93 | }), 94 | z.string() 95 | ); 96 | const result = await map.spa( 97 | new Map([ 98 | ["first", "foo"], 99 | ["second", "bar"], 100 | ]) 101 | ); 102 | expect(result.success).toEqual(false); 103 | if (!result.success) { 104 | expect(result.error.issues.length).toEqual(2); 105 | expect(result.error.issues[0].code).toEqual(z.ZodIssueCode.custom); 106 | expect(result.error.issues[0].message).toEqual("Keys must be uppercase"); 107 | expect(result.error.issues[1].code).toEqual(z.ZodIssueCode.custom); 108 | expect(result.error.issues[1].message).toEqual("Keys must be uppercase"); 109 | } 110 | }); 111 | -------------------------------------------------------------------------------- /src/__tests__/default.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { z } from ".."; 5 | import { util } from "../helpers/util"; 6 | 7 | test("basic defaults", () => { 8 | expect(z.string().default("default").parse(undefined)).toBe("default"); 9 | }); 10 | 11 | test("default with transform", () => { 12 | const stringWithDefault = z 13 | .string() 14 | .transform((val) => val.toUpperCase()) 15 | .default("default"); 16 | expect(stringWithDefault.parse(undefined)).toBe("DEFAULT"); 17 | expect(stringWithDefault).toBeInstanceOf(z.ZodDefault); 18 | expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodEffects); 19 | expect(stringWithDefault._def.innerType._def.schema).toBeInstanceOf( 20 | z.ZodSchema 21 | ); 22 | 23 | type inp = z.input; 24 | util.assertEqual(true); 25 | type out = z.output; 26 | util.assertEqual(true); 27 | }); 28 | 29 | test("default on existing optional", () => { 30 | const stringWithDefault = z.string().optional().default("asdf"); 31 | expect(stringWithDefault.parse(undefined)).toBe("asdf"); 32 | expect(stringWithDefault).toBeInstanceOf(z.ZodDefault); 33 | expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodOptional); 34 | expect(stringWithDefault._def.innerType._def.innerType).toBeInstanceOf( 35 | z.ZodString 36 | ); 37 | 38 | type inp = z.input; 39 | util.assertEqual(true); 40 | type out = z.output; 41 | util.assertEqual(true); 42 | }); 43 | 44 | test("optional on default", () => { 45 | const stringWithDefault = z.string().default("asdf").optional(); 46 | 47 | type inp = z.input; 48 | util.assertEqual(true); 49 | type out = z.output; 50 | util.assertEqual(true); 51 | }); 52 | 53 | test("complex chain example", () => { 54 | const complex = z 55 | .string() 56 | .default("asdf") 57 | .transform((val) => val.toUpperCase()) 58 | .default("qwer") 59 | .removeDefault() 60 | .optional() 61 | .default("asdfasdf"); 62 | 63 | expect(complex.parse(undefined)).toBe("ASDFASDF"); 64 | }); 65 | 66 | test("removeDefault", () => { 67 | const stringWithRemovedDefault = z.string().default("asdf").removeDefault(); 68 | 69 | type out = z.output; 70 | util.assertEqual(true); 71 | }); 72 | 73 | test("nested", () => { 74 | const inner = z.string().default("asdf"); 75 | const outer = z.object({ inner }).default({ 76 | inner: undefined, 77 | }); 78 | type input = z.input; 79 | util.assertEqual(true); 80 | type out = z.output; 81 | util.assertEqual(true); 82 | expect(outer.parse(undefined)).toEqual({ inner: "asdf" }); 83 | expect(outer.parse({})).toEqual({ inner: "asdf" }); 84 | expect(outer.parse({ inner: undefined })).toEqual({ inner: "asdf" }); 85 | }); 86 | 87 | test("chained defaults", () => { 88 | const stringWithDefault = z.string().default("inner").default("outer"); 89 | const result = stringWithDefault.parse(undefined); 90 | expect(result).toEqual("outer"); 91 | }); 92 | 93 | test("factory", () => { 94 | expect( 95 | z.ZodDefault.create(z.string(), { default: "asdf" }).parse(undefined) 96 | ).toEqual("asdf"); 97 | }); 98 | 99 | test("native enum", () => { 100 | enum Fruits { 101 | apple = "apple", 102 | orange = "orange", 103 | } 104 | 105 | const schema = z.object({ 106 | fruit: z.nativeEnum(Fruits).default(Fruits.apple), 107 | }); 108 | 109 | expect(schema.parse({})).toEqual({ fruit: Fruits.apple }); 110 | }); 111 | 112 | test("enum", () => { 113 | const schema = z.object({ 114 | fruit: z.enum(["apple", "orange"]).default("apple"), 115 | }); 116 | 117 | expect(schema.parse({})).toEqual({ fruit: "apple" }); 118 | }); 119 | -------------------------------------------------------------------------------- /deno/lib/__tests__/map.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | import { ZodIssueCode } from "../index.ts"; 8 | 9 | const stringMap = z.map(z.string(), z.string()); 10 | type stringMap = z.infer; 11 | 12 | test("type inference", () => { 13 | util.assertEqual>(true); 14 | }); 15 | 16 | test("valid parse", () => { 17 | const result = stringMap.safeParse( 18 | new Map([ 19 | ["first", "foo"], 20 | ["second", "bar"], 21 | ]) 22 | ); 23 | expect(result.success).toEqual(true); 24 | if (result.success) { 25 | expect(result.data.has("first")).toEqual(true); 26 | expect(result.data.has("second")).toEqual(true); 27 | expect(result.data.get("first")).toEqual("foo"); 28 | expect(result.data.get("second")).toEqual("bar"); 29 | } 30 | }); 31 | 32 | test("valid parse async", async () => { 33 | const result = await stringMap.spa( 34 | new Map([ 35 | ["first", "foo"], 36 | ["second", "bar"], 37 | ]) 38 | ); 39 | expect(result.success).toEqual(true); 40 | if (result.success) { 41 | expect(result.data.has("first")).toEqual(true); 42 | expect(result.data.has("second")).toEqual(true); 43 | expect(result.data.get("first")).toEqual("foo"); 44 | expect(result.data.get("second")).toEqual("bar"); 45 | } 46 | }); 47 | 48 | test("throws when a Set is given", () => { 49 | const result = stringMap.safeParse(new Set([])); 50 | expect(result.success).toEqual(false); 51 | if (result.success === false) { 52 | expect(result.error.issues.length).toEqual(1); 53 | expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type); 54 | } 55 | }); 56 | 57 | test("throws when the given map has invalid key and invalid input", () => { 58 | const result = stringMap.safeParse(new Map([[42, Symbol()]])); 59 | expect(result.success).toEqual(false); 60 | if (result.success === false) { 61 | expect(result.error.issues.length).toEqual(2); 62 | expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type); 63 | expect(result.error.issues[0].path).toEqual([0, "key"]); 64 | expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type); 65 | expect(result.error.issues[1].path).toEqual([0, "value"]); 66 | } 67 | }); 68 | 69 | test("throws when the given map has multiple invalid entries", () => { 70 | // const result = stringMap.safeParse(new Map([[42, Symbol()]])); 71 | 72 | const result = stringMap.safeParse( 73 | new Map([ 74 | [1, "foo"], 75 | ["bar", 2], 76 | ] as [any, any][]) as Map 77 | ); 78 | 79 | // const result = stringMap.safeParse(new Map([[42, Symbol()]])); 80 | expect(result.success).toEqual(false); 81 | if (result.success === false) { 82 | expect(result.error.issues.length).toEqual(2); 83 | expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type); 84 | expect(result.error.issues[0].path).toEqual([0, "key"]); 85 | expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type); 86 | expect(result.error.issues[1].path).toEqual([1, "value"]); 87 | } 88 | }); 89 | 90 | test("dirty", async () => { 91 | const map = z.map( 92 | z.string().refine((val) => val === val.toUpperCase(), { 93 | message: "Keys must be uppercase", 94 | }), 95 | z.string() 96 | ); 97 | const result = await map.spa( 98 | new Map([ 99 | ["first", "foo"], 100 | ["second", "bar"], 101 | ]) 102 | ); 103 | expect(result.success).toEqual(false); 104 | if (!result.success) { 105 | expect(result.error.issues.length).toEqual(2); 106 | expect(result.error.issues[0].code).toEqual(z.ZodIssueCode.custom); 107 | expect(result.error.issues[0].message).toEqual("Keys must be uppercase"); 108 | expect(result.error.issues[1].code).toEqual(z.ZodIssueCode.custom); 109 | expect(result.error.issues[1].message).toEqual("Keys must be uppercase"); 110 | } 111 | }); 112 | -------------------------------------------------------------------------------- /src/__tests__/pickomit.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect, test } from "@jest/globals"; 3 | 4 | import { util } from "../helpers/util"; 5 | import * as z from "../index"; 6 | 7 | const fish = z.object({ 8 | name: z.string(), 9 | age: z.number(), 10 | nested: z.object({}), 11 | }); 12 | 13 | test("pick type inference", () => { 14 | const nameonlyFish = fish.pick({ name: true }); 15 | type nameonlyFish = z.infer; 16 | util.assertEqual(true); 17 | }); 18 | 19 | test("pick parse - success", () => { 20 | const nameonlyFish = fish.pick({ name: true }); 21 | nameonlyFish.parse({ name: "bob" }); 22 | 23 | // @ts-expect-error checking runtime picks `name` only. 24 | const anotherNameonlyFish = fish.pick({ name: true, age: false }); 25 | anotherNameonlyFish.parse({ name: "bob" }); 26 | }); 27 | 28 | test("pick parse - fail", () => { 29 | fish.pick({ name: true }).parse({ name: "12" } as any); 30 | fish.pick({ name: true }).parse({ name: "bob", age: 12 } as any); 31 | fish.pick({ age: true }).parse({ age: 12 } as any); 32 | 33 | const nameonlyFish = fish.pick({ name: true }).strict(); 34 | const bad1 = () => nameonlyFish.parse({ name: 12 } as any); 35 | const bad2 = () => nameonlyFish.parse({ name: "bob", age: 12 } as any); 36 | const bad3 = () => nameonlyFish.parse({ age: 12 } as any); 37 | 38 | // @ts-expect-error checking runtime picks `name` only. 39 | const anotherNameonlyFish = fish.pick({ name: true, age: false }).strict(); 40 | const bad4 = () => anotherNameonlyFish.parse({ name: "bob", age: 12 } as any); 41 | 42 | expect(bad1).toThrow(); 43 | expect(bad2).toThrow(); 44 | expect(bad3).toThrow(); 45 | expect(bad4).toThrow(); 46 | }); 47 | 48 | test("omit type inference", () => { 49 | const nonameFish = fish.omit({ name: true }); 50 | type nonameFish = z.infer; 51 | util.assertEqual(true); 52 | }); 53 | 54 | test("omit parse - success", () => { 55 | const nonameFish = fish.omit({ name: true }); 56 | nonameFish.parse({ age: 12, nested: {} }); 57 | 58 | // @ts-expect-error checking runtime omits `name` only. 59 | const anotherNonameFish = fish.omit({ name: true, age: false }); 60 | anotherNonameFish.parse({ age: 12, nested: {} }); 61 | }); 62 | 63 | test("omit parse - fail", () => { 64 | const nonameFish = fish.omit({ name: true }); 65 | const bad1 = () => nonameFish.parse({ name: 12 } as any); 66 | const bad2 = () => nonameFish.parse({ age: 12 } as any); 67 | const bad3 = () => nonameFish.parse({} as any); 68 | 69 | // @ts-expect-error checking runtime omits `name` only. 70 | const anotherNonameFish = fish.omit({ name: true, age: false }); 71 | const bad4 = () => anotherNonameFish.parse({ nested: {} } as any); 72 | 73 | expect(bad1).toThrow(); 74 | expect(bad2).toThrow(); 75 | expect(bad3).toThrow(); 76 | expect(bad4).toThrow(); 77 | }); 78 | 79 | test("nonstrict inference", () => { 80 | const laxfish = fish.pick({ name: true }).catchall(z.any()); 81 | type laxfish = z.infer; 82 | util.assertEqual(true); 83 | }); 84 | 85 | test("nonstrict parsing - pass", () => { 86 | const laxfish = fish.passthrough().pick({ name: true }); 87 | laxfish.parse({ name: "asdf", whatever: "asdf" }); 88 | laxfish.parse({ name: "asdf", age: 12, nested: {} }); 89 | }); 90 | 91 | test("nonstrict parsing - fail", () => { 92 | const laxfish = fish.passthrough().pick({ name: true }); 93 | const bad = () => laxfish.parse({ whatever: "asdf" } as any); 94 | expect(bad).toThrow(); 95 | }); 96 | 97 | test("pick/omit/required/partial - do not allow unknown keys", () => { 98 | const schema = z.object({ 99 | name: z.string(), 100 | age: z.number(), 101 | }); 102 | 103 | // @ts-expect-error 104 | schema.pick({ $unknown: true }); 105 | // @ts-expect-error 106 | schema.omit({ $unknown: true }); 107 | // @ts-expect-error 108 | schema.required({ $unknown: true }); 109 | // @ts-expect-error 110 | schema.partial({ $unknown: true }); 111 | }); 112 | -------------------------------------------------------------------------------- /deno/lib/__tests__/default.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { z } from "../index.ts"; 6 | import { util } from "../helpers/util.ts"; 7 | 8 | test("basic defaults", () => { 9 | expect(z.string().default("default").parse(undefined)).toBe("default"); 10 | }); 11 | 12 | test("default with transform", () => { 13 | const stringWithDefault = z 14 | .string() 15 | .transform((val) => val.toUpperCase()) 16 | .default("default"); 17 | expect(stringWithDefault.parse(undefined)).toBe("DEFAULT"); 18 | expect(stringWithDefault).toBeInstanceOf(z.ZodDefault); 19 | expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodEffects); 20 | expect(stringWithDefault._def.innerType._def.schema).toBeInstanceOf( 21 | z.ZodSchema 22 | ); 23 | 24 | type inp = z.input; 25 | util.assertEqual(true); 26 | type out = z.output; 27 | util.assertEqual(true); 28 | }); 29 | 30 | test("default on existing optional", () => { 31 | const stringWithDefault = z.string().optional().default("asdf"); 32 | expect(stringWithDefault.parse(undefined)).toBe("asdf"); 33 | expect(stringWithDefault).toBeInstanceOf(z.ZodDefault); 34 | expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodOptional); 35 | expect(stringWithDefault._def.innerType._def.innerType).toBeInstanceOf( 36 | z.ZodString 37 | ); 38 | 39 | type inp = z.input; 40 | util.assertEqual(true); 41 | type out = z.output; 42 | util.assertEqual(true); 43 | }); 44 | 45 | test("optional on default", () => { 46 | const stringWithDefault = z.string().default("asdf").optional(); 47 | 48 | type inp = z.input; 49 | util.assertEqual(true); 50 | type out = z.output; 51 | util.assertEqual(true); 52 | }); 53 | 54 | test("complex chain example", () => { 55 | const complex = z 56 | .string() 57 | .default("asdf") 58 | .transform((val) => val.toUpperCase()) 59 | .default("qwer") 60 | .removeDefault() 61 | .optional() 62 | .default("asdfasdf"); 63 | 64 | expect(complex.parse(undefined)).toBe("ASDFASDF"); 65 | }); 66 | 67 | test("removeDefault", () => { 68 | const stringWithRemovedDefault = z.string().default("asdf").removeDefault(); 69 | 70 | type out = z.output; 71 | util.assertEqual(true); 72 | }); 73 | 74 | test("nested", () => { 75 | const inner = z.string().default("asdf"); 76 | const outer = z.object({ inner }).default({ 77 | inner: undefined, 78 | }); 79 | type input = z.input; 80 | util.assertEqual(true); 81 | type out = z.output; 82 | util.assertEqual(true); 83 | expect(outer.parse(undefined)).toEqual({ inner: "asdf" }); 84 | expect(outer.parse({})).toEqual({ inner: "asdf" }); 85 | expect(outer.parse({ inner: undefined })).toEqual({ inner: "asdf" }); 86 | }); 87 | 88 | test("chained defaults", () => { 89 | const stringWithDefault = z.string().default("inner").default("outer"); 90 | const result = stringWithDefault.parse(undefined); 91 | expect(result).toEqual("outer"); 92 | }); 93 | 94 | test("factory", () => { 95 | expect( 96 | z.ZodDefault.create(z.string(), { default: "asdf" }).parse(undefined) 97 | ).toEqual("asdf"); 98 | }); 99 | 100 | test("native enum", () => { 101 | enum Fruits { 102 | apple = "apple", 103 | orange = "orange", 104 | } 105 | 106 | const schema = z.object({ 107 | fruit: z.nativeEnum(Fruits).default(Fruits.apple), 108 | }); 109 | 110 | expect(schema.parse({})).toEqual({ fruit: Fruits.apple }); 111 | }); 112 | 113 | test("enum", () => { 114 | const schema = z.object({ 115 | fruit: z.enum(["apple", "orange"]).default("apple"), 116 | }); 117 | 118 | expect(schema.parse({})).toEqual({ fruit: "apple" }); 119 | }); 120 | -------------------------------------------------------------------------------- /deno/lib/__tests__/pickomit.test.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore TS6133 2 | import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts"; 3 | const test = Deno.test; 4 | 5 | import { util } from "../helpers/util.ts"; 6 | import * as z from "../index.ts"; 7 | 8 | const fish = z.object({ 9 | name: z.string(), 10 | age: z.number(), 11 | nested: z.object({}), 12 | }); 13 | 14 | test("pick type inference", () => { 15 | const nameonlyFish = fish.pick({ name: true }); 16 | type nameonlyFish = z.infer; 17 | util.assertEqual(true); 18 | }); 19 | 20 | test("pick parse - success", () => { 21 | const nameonlyFish = fish.pick({ name: true }); 22 | nameonlyFish.parse({ name: "bob" }); 23 | 24 | // @ts-expect-error checking runtime picks `name` only. 25 | const anotherNameonlyFish = fish.pick({ name: true, age: false }); 26 | anotherNameonlyFish.parse({ name: "bob" }); 27 | }); 28 | 29 | test("pick parse - fail", () => { 30 | fish.pick({ name: true }).parse({ name: "12" } as any); 31 | fish.pick({ name: true }).parse({ name: "bob", age: 12 } as any); 32 | fish.pick({ age: true }).parse({ age: 12 } as any); 33 | 34 | const nameonlyFish = fish.pick({ name: true }).strict(); 35 | const bad1 = () => nameonlyFish.parse({ name: 12 } as any); 36 | const bad2 = () => nameonlyFish.parse({ name: "bob", age: 12 } as any); 37 | const bad3 = () => nameonlyFish.parse({ age: 12 } as any); 38 | 39 | // @ts-expect-error checking runtime picks `name` only. 40 | const anotherNameonlyFish = fish.pick({ name: true, age: false }).strict(); 41 | const bad4 = () => anotherNameonlyFish.parse({ name: "bob", age: 12 } as any); 42 | 43 | expect(bad1).toThrow(); 44 | expect(bad2).toThrow(); 45 | expect(bad3).toThrow(); 46 | expect(bad4).toThrow(); 47 | }); 48 | 49 | test("omit type inference", () => { 50 | const nonameFish = fish.omit({ name: true }); 51 | type nonameFish = z.infer; 52 | util.assertEqual(true); 53 | }); 54 | 55 | test("omit parse - success", () => { 56 | const nonameFish = fish.omit({ name: true }); 57 | nonameFish.parse({ age: 12, nested: {} }); 58 | 59 | // @ts-expect-error checking runtime omits `name` only. 60 | const anotherNonameFish = fish.omit({ name: true, age: false }); 61 | anotherNonameFish.parse({ age: 12, nested: {} }); 62 | }); 63 | 64 | test("omit parse - fail", () => { 65 | const nonameFish = fish.omit({ name: true }); 66 | const bad1 = () => nonameFish.parse({ name: 12 } as any); 67 | const bad2 = () => nonameFish.parse({ age: 12 } as any); 68 | const bad3 = () => nonameFish.parse({} as any); 69 | 70 | // @ts-expect-error checking runtime omits `name` only. 71 | const anotherNonameFish = fish.omit({ name: true, age: false }); 72 | const bad4 = () => anotherNonameFish.parse({ nested: {} } as any); 73 | 74 | expect(bad1).toThrow(); 75 | expect(bad2).toThrow(); 76 | expect(bad3).toThrow(); 77 | expect(bad4).toThrow(); 78 | }); 79 | 80 | test("nonstrict inference", () => { 81 | const laxfish = fish.pick({ name: true }).catchall(z.any()); 82 | type laxfish = z.infer; 83 | util.assertEqual(true); 84 | }); 85 | 86 | test("nonstrict parsing - pass", () => { 87 | const laxfish = fish.passthrough().pick({ name: true }); 88 | laxfish.parse({ name: "asdf", whatever: "asdf" }); 89 | laxfish.parse({ name: "asdf", age: 12, nested: {} }); 90 | }); 91 | 92 | test("nonstrict parsing - fail", () => { 93 | const laxfish = fish.passthrough().pick({ name: true }); 94 | const bad = () => laxfish.parse({ whatever: "asdf" } as any); 95 | expect(bad).toThrow(); 96 | }); 97 | 98 | test("pick/omit/required/partial - do not allow unknown keys", () => { 99 | const schema = z.object({ 100 | name: z.string(), 101 | age: z.number(), 102 | }); 103 | 104 | // @ts-expect-error 105 | schema.pick({ $unknown: true }); 106 | // @ts-expect-error 107 | schema.omit({ $unknown: true }); 108 | // @ts-expect-error 109 | schema.required({ $unknown: true }); 110 | // @ts-expect-error 111 | schema.partial({ $unknown: true }); 112 | }); 113 | --------------------------------------------------------------------------------