forked from WikiDeck/wikideck
v2
This commit is contained in:
+56
@@ -0,0 +1,56 @@
|
||||
// Side-effect-only: installs the post-processor on `globalConfig`. Listed in `package.json`'s `sideEffects`, and nothing else references it, so apps that never import it drop the compiler.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// import "zod/compile";
|
||||
// import * as z from "zod";
|
||||
// // every schema constructed below this is compiled on first parse
|
||||
//
|
||||
// Module evaluation order matters: schemas constructed in modules that evaluate before this import will not be compiled. Place this import in the app entry point, before any module that constructs schemas at top level.
|
||||
//
|
||||
// Failure handling: if the compiler refuses a schema (async refinement, unsupported feature, etc.) the shim permanently restores the runtime `_zod.run` for that schema. The schema continues to work via the regular runtime parser — no observable difference to the caller.
|
||||
|
||||
import { compile } from "./v4/core/compile.js";
|
||||
import * as core from "./v4/core/index.js";
|
||||
|
||||
let compiling = false;
|
||||
|
||||
core.globalConfig.postProcessor = (inst: any) => {
|
||||
if (compiling) return;
|
||||
const originalRun = inst._zod?.run;
|
||||
if (typeof originalRun !== "function") return;
|
||||
|
||||
const shim = (payload: any, ctx: any): any => {
|
||||
// Bypass the fast path for any non-forward / non-sync / check-skipping call. The runtime owns those contracts.
|
||||
if (ctx?.async || ctx?.direction === "backward" || ctx?.skipChecks) {
|
||||
return originalRun(payload, ctx);
|
||||
}
|
||||
|
||||
compiling = true;
|
||||
try {
|
||||
// Respect the jitless config: it exists precisely so CSP/no-eval environments never reach `new Function`. Global mode must not bypass it (explicit z.compile calls remain an explicit opt-in).
|
||||
if (core.globalConfig.jitless) {
|
||||
inst._zod.run = originalRun;
|
||||
return originalRun(payload, ctx);
|
||||
}
|
||||
// Strict: the shim owns its own fallback below, and a non-strict compile would hand back `inst` — whose run is this shim — and reinstall it on itself.
|
||||
const compiled = compile(inst, { strict: true });
|
||||
// Only the run wrapper. Copying the compiled parse/safeParse closures would make their fallback re-enter this instance and run user callbacks a third time.
|
||||
inst._zod.run = compiled._zod.run;
|
||||
inst._zod.bag.fallbackRun = compiled._zod.bag.fallbackRun;
|
||||
inst._zod.bag.validator = compiled._zod.bag.validator;
|
||||
} catch {
|
||||
// Permanent fallback for unsupported schemas.
|
||||
inst._zod.run = originalRun;
|
||||
} finally {
|
||||
compiling = false;
|
||||
}
|
||||
|
||||
return inst._zod.run(payload, ctx);
|
||||
};
|
||||
|
||||
// Expose the pre-shim runtime so `compile()` invoked elsewhere can unwrap past the shim and capture the source-of-truth runtime. Without this, a user calling `z.compile(s)` after global mode is enabled would capture the shim itself, which would feed the wrapper into itself on fallback.
|
||||
(shim as any).__originalRun = originalRun;
|
||||
|
||||
inst._zod.run = shim;
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import * as z from "./v4/classic/external.js";
|
||||
export * from "./v4/classic/external.js";
|
||||
// Aliasing the binding, instead of `export default z`, keeps the default export a
|
||||
// re-export of `z` rather than a fresh namespace value. Rollup and Webpack can then
|
||||
// shake `import z from "zod"` as they already shake `import { z } from "zod"` — without
|
||||
// this they pull in every locale. See #6050.
|
||||
export { z, z as default };
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * from "../v4/locales/index.js";
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import * as z from "../v4/mini/external.js";
|
||||
export * from "../v4/mini/external.js";
|
||||
export { z };
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
import type { Primitive } from "./helpers/typeAliases.js";
|
||||
import { util, type ZodParsedType } from "./helpers/util.js";
|
||||
import type { TypeOf, ZodType } from "./index.js";
|
||||
|
||||
type allKeys<T> = T extends any ? keyof T : never;
|
||||
|
||||
export type inferFlattenedErrors<T extends ZodType<any, any, any>, U = string> = typeToFlattenedError<TypeOf<T>, U>;
|
||||
export type typeToFlattenedError<T, U = string> = {
|
||||
formErrors: U[];
|
||||
fieldErrors: {
|
||||
[P in allKeys<T>]?: U[];
|
||||
};
|
||||
};
|
||||
|
||||
export const ZodIssueCode = util.arrayToEnum([
|
||||
"invalid_type",
|
||||
"invalid_literal",
|
||||
"custom",
|
||||
"invalid_union",
|
||||
"invalid_union_discriminator",
|
||||
"invalid_enum_value",
|
||||
"unrecognized_keys",
|
||||
"invalid_arguments",
|
||||
"invalid_return_type",
|
||||
"invalid_date",
|
||||
"invalid_string",
|
||||
"too_small",
|
||||
"too_big",
|
||||
"invalid_intersection_types",
|
||||
"not_multiple_of",
|
||||
"not_finite",
|
||||
]);
|
||||
|
||||
export type ZodIssueCode = keyof typeof ZodIssueCode;
|
||||
|
||||
export type ZodIssueBase = {
|
||||
path: (string | number)[];
|
||||
message?: string | undefined;
|
||||
};
|
||||
|
||||
export interface ZodInvalidTypeIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_type;
|
||||
expected: ZodParsedType;
|
||||
received: ZodParsedType;
|
||||
}
|
||||
|
||||
export interface ZodInvalidLiteralIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_literal;
|
||||
expected: unknown;
|
||||
received: unknown;
|
||||
}
|
||||
|
||||
export interface ZodUnrecognizedKeysIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.unrecognized_keys;
|
||||
keys: string[];
|
||||
}
|
||||
|
||||
export interface ZodInvalidUnionIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_union;
|
||||
unionErrors: ZodError[];
|
||||
}
|
||||
|
||||
export interface ZodInvalidUnionDiscriminatorIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_union_discriminator;
|
||||
options: Primitive[];
|
||||
}
|
||||
|
||||
export interface ZodInvalidEnumValueIssue extends ZodIssueBase {
|
||||
received: string | number;
|
||||
code: typeof ZodIssueCode.invalid_enum_value;
|
||||
options: (string | number)[];
|
||||
}
|
||||
|
||||
export interface ZodInvalidArgumentsIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_arguments;
|
||||
argumentsError: ZodError;
|
||||
}
|
||||
|
||||
export interface ZodInvalidReturnTypeIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_return_type;
|
||||
returnTypeError: ZodError;
|
||||
}
|
||||
|
||||
export interface ZodInvalidDateIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_date;
|
||||
}
|
||||
|
||||
export type StringValidation =
|
||||
| "email"
|
||||
| "url"
|
||||
| "emoji"
|
||||
| "uuid"
|
||||
| "nanoid"
|
||||
| "regex"
|
||||
| "cuid"
|
||||
| "cuid2"
|
||||
| "ulid"
|
||||
| "datetime"
|
||||
| "date"
|
||||
| "time"
|
||||
| "duration"
|
||||
| "ip"
|
||||
| "cidr"
|
||||
| "base64"
|
||||
| "jwt"
|
||||
| "base64url"
|
||||
| { includes: string; position?: number | undefined }
|
||||
| { startsWith: string }
|
||||
| { endsWith: string };
|
||||
|
||||
export interface ZodInvalidStringIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_string;
|
||||
validation: StringValidation;
|
||||
}
|
||||
|
||||
export interface ZodTooSmallIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.too_small;
|
||||
minimum: number | bigint;
|
||||
inclusive: boolean;
|
||||
exact?: boolean;
|
||||
type: "array" | "string" | "number" | "set" | "date" | "bigint";
|
||||
}
|
||||
|
||||
export interface ZodTooBigIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.too_big;
|
||||
maximum: number | bigint;
|
||||
inclusive: boolean;
|
||||
exact?: boolean;
|
||||
type: "array" | "string" | "number" | "set" | "date" | "bigint";
|
||||
}
|
||||
|
||||
export interface ZodInvalidIntersectionTypesIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.invalid_intersection_types;
|
||||
}
|
||||
|
||||
export interface ZodNotMultipleOfIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.not_multiple_of;
|
||||
multipleOf: number | bigint;
|
||||
}
|
||||
|
||||
export interface ZodNotFiniteIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.not_finite;
|
||||
}
|
||||
|
||||
export interface ZodCustomIssue extends ZodIssueBase {
|
||||
code: typeof ZodIssueCode.custom;
|
||||
params?: { [k: string]: any };
|
||||
}
|
||||
|
||||
export type DenormalizedError = { [k: string]: DenormalizedError | string[] };
|
||||
|
||||
export type ZodIssueOptionalMessage =
|
||||
| ZodInvalidTypeIssue
|
||||
| ZodInvalidLiteralIssue
|
||||
| ZodUnrecognizedKeysIssue
|
||||
| ZodInvalidUnionIssue
|
||||
| ZodInvalidUnionDiscriminatorIssue
|
||||
| ZodInvalidEnumValueIssue
|
||||
| ZodInvalidArgumentsIssue
|
||||
| ZodInvalidReturnTypeIssue
|
||||
| ZodInvalidDateIssue
|
||||
| ZodInvalidStringIssue
|
||||
| ZodTooSmallIssue
|
||||
| ZodTooBigIssue
|
||||
| ZodInvalidIntersectionTypesIssue
|
||||
| ZodNotMultipleOfIssue
|
||||
| ZodNotFiniteIssue
|
||||
| ZodCustomIssue;
|
||||
|
||||
export type ZodIssue = ZodIssueOptionalMessage & {
|
||||
fatal?: boolean | undefined;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const quotelessJson = (obj: any) => {
|
||||
const json = JSON.stringify(obj, null, 2);
|
||||
return json.replace(/"([^"]+)":/g, "$1:");
|
||||
};
|
||||
|
||||
type recursiveZodFormattedError<T> = T extends [any, ...any[]]
|
||||
? { [K in keyof T]?: ZodFormattedError<T[K]> }
|
||||
: T extends any[]
|
||||
? { [k: number]: ZodFormattedError<T[number]> }
|
||||
: T extends object
|
||||
? { [K in keyof T]?: ZodFormattedError<T[K]> }
|
||||
: unknown;
|
||||
|
||||
export type ZodFormattedError<T, U = string> = {
|
||||
_errors: U[];
|
||||
} & recursiveZodFormattedError<NonNullable<T>>;
|
||||
|
||||
export type inferFormattedError<T extends ZodType<any, any, any>, U = string> = ZodFormattedError<TypeOf<T>, U>;
|
||||
|
||||
export class ZodError<T = any> extends Error {
|
||||
issues: ZodIssue[] = [];
|
||||
|
||||
get errors() {
|
||||
return this.issues;
|
||||
}
|
||||
|
||||
constructor(issues: ZodIssue[]) {
|
||||
super();
|
||||
|
||||
const actualProto = new.target.prototype;
|
||||
if (Object.setPrototypeOf) {
|
||||
// eslint-disable-next-line ban/ban
|
||||
Object.setPrototypeOf(this, actualProto);
|
||||
} else {
|
||||
(this as any).__proto__ = actualProto;
|
||||
}
|
||||
this.name = "ZodError";
|
||||
this.issues = issues;
|
||||
}
|
||||
|
||||
format(): ZodFormattedError<T>;
|
||||
format<U>(mapper: (issue: ZodIssue) => U): ZodFormattedError<T, U>;
|
||||
format(_mapper?: any) {
|
||||
const mapper: (issue: ZodIssue) => any =
|
||||
_mapper ||
|
||||
function (issue: ZodIssue) {
|
||||
return issue.message;
|
||||
};
|
||||
const fieldErrors: ZodFormattedError<T> = { _errors: [] } as any;
|
||||
const processError = (error: ZodError) => {
|
||||
for (const issue of error.issues) {
|
||||
if (issue.code === "invalid_union") {
|
||||
issue.unionErrors.map(processError);
|
||||
} else if (issue.code === "invalid_return_type") {
|
||||
processError(issue.returnTypeError);
|
||||
} else if (issue.code === "invalid_arguments") {
|
||||
processError(issue.argumentsError);
|
||||
} else if (issue.path.length === 0) {
|
||||
(fieldErrors as any)._errors.push(mapper(issue));
|
||||
} else {
|
||||
let curr: any = fieldErrors;
|
||||
let i = 0;
|
||||
while (i < issue.path.length) {
|
||||
const el = issue.path[i]!;
|
||||
const terminal = i === issue.path.length - 1;
|
||||
|
||||
// `_errors` is reserved by this legacy format, so merge a matching path segment into the current node instead of treating its array as a child.
|
||||
if (el === "_errors") {
|
||||
if (terminal) curr._errors.push(mapper(issue));
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create `el` as an own data property. A segment naming an inherited member ("toString", "constructor") would otherwise read through to the prototype, and assigning "__proto__" would hit the setter and walk into Object.prototype.
|
||||
if (!Object.prototype.hasOwnProperty.call(curr, el)) {
|
||||
if (el === "__proto__") {
|
||||
Object.defineProperty(curr, el, {
|
||||
value: { _errors: [] },
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
} else {
|
||||
curr[el] = { _errors: [] };
|
||||
}
|
||||
}
|
||||
|
||||
curr = curr[el];
|
||||
if (terminal) {
|
||||
curr._errors.push(mapper(issue));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
processError(this);
|
||||
return fieldErrors;
|
||||
}
|
||||
|
||||
static create = (issues: ZodIssue[]) => {
|
||||
const error = new ZodError(issues);
|
||||
return error;
|
||||
};
|
||||
|
||||
static assert(value: unknown): asserts value is ZodError {
|
||||
if (!(value instanceof ZodError)) {
|
||||
throw new Error(`Not a ZodError: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
override toString() {
|
||||
return this.message;
|
||||
}
|
||||
override get message() {
|
||||
return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);
|
||||
}
|
||||
|
||||
get isEmpty(): boolean {
|
||||
return this.issues.length === 0;
|
||||
}
|
||||
|
||||
addIssue = (sub: ZodIssue) => {
|
||||
this.issues = [...this.issues, sub];
|
||||
};
|
||||
|
||||
addIssues = (subs: ZodIssue[] = []) => {
|
||||
this.issues = [...this.issues, ...subs];
|
||||
};
|
||||
|
||||
flatten(): typeToFlattenedError<T>;
|
||||
flatten<U>(mapper?: (issue: ZodIssue) => U): typeToFlattenedError<T, U>;
|
||||
flatten<U = string>(mapper: (issue: ZodIssue) => U = (issue: ZodIssue) => issue.message as any): any {
|
||||
const fieldErrors: any = Object.create(null);
|
||||
const formErrors: U[] = [];
|
||||
for (const sub of this.issues) {
|
||||
if (sub.path.length > 0) {
|
||||
const firstEl = sub.path[0]!;
|
||||
fieldErrors[firstEl] = fieldErrors[firstEl] || [];
|
||||
fieldErrors[firstEl].push(mapper(sub));
|
||||
} else {
|
||||
formErrors.push(mapper(sub));
|
||||
}
|
||||
}
|
||||
return { formErrors, fieldErrors };
|
||||
}
|
||||
|
||||
get formErrors() {
|
||||
return this.flatten();
|
||||
}
|
||||
}
|
||||
|
||||
type stripPath<T extends object> = T extends any ? util.OmitKeys<T, "path"> : never;
|
||||
|
||||
export type IssueData = stripPath<ZodIssueOptionalMessage> & {
|
||||
path?: (string | number)[];
|
||||
fatal?: boolean | undefined;
|
||||
};
|
||||
|
||||
export type ErrorMapCtx = {
|
||||
defaultError: string;
|
||||
data: any;
|
||||
};
|
||||
|
||||
export type ZodErrorMap = (issue: ZodIssueOptionalMessage, _ctx: ErrorMapCtx) => { message: string };
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
const datetimeValidationSuite = new Benchmark.Suite("datetime");
|
||||
|
||||
const DATA = "2021-01-01";
|
||||
const MONTHS_31 = new Set([1, 3, 5, 7, 8, 10, 12]);
|
||||
const MONTHS_30 = new Set([4, 6, 9, 11]);
|
||||
|
||||
const simpleDatetimeRegex = /^(\d{4})-(\d{2})-(\d{2})$/;
|
||||
const datetimeRegexNoLeapYearValidation =
|
||||
/^\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))$/;
|
||||
const datetimeRegexWithLeapYearValidation =
|
||||
/^((\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])))$/;
|
||||
|
||||
datetimeValidationSuite
|
||||
.add("new Date()", () => {
|
||||
return !Number.isNaN(new Date(DATA).getTime());
|
||||
})
|
||||
.add("regex (no validation)", () => {
|
||||
return simpleDatetimeRegex.test(DATA);
|
||||
})
|
||||
.add("regex (no leap year)", () => {
|
||||
return datetimeRegexNoLeapYearValidation.test(DATA);
|
||||
})
|
||||
.add("regex (w/ leap year)", () => {
|
||||
return datetimeRegexWithLeapYearValidation.test(DATA);
|
||||
})
|
||||
.add("capture groups + code", () => {
|
||||
const match = DATA.match(simpleDatetimeRegex);
|
||||
if (!match) return false;
|
||||
|
||||
// Extract year, month, and day from the capture groups
|
||||
const year = Number.parseInt(match[1], 10);
|
||||
const month = Number.parseInt(match[2], 10); // month is 0-indexed in JavaScript Date, so subtract 1
|
||||
const day = Number.parseInt(match[3], 10);
|
||||
|
||||
if (month === 2) {
|
||||
if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
|
||||
return day <= 29;
|
||||
}
|
||||
return day <= 28;
|
||||
}
|
||||
if (MONTHS_30.has(month)) {
|
||||
return day <= 30;
|
||||
}
|
||||
if (MONTHS_31.has(month)) {
|
||||
return day <= 31;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${datetimeValidationSuite.name!}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [datetimeValidationSuite],
|
||||
};
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
const doubleSuite = new Benchmark.Suite("z.discriminatedUnion: double");
|
||||
const manySuite = new Benchmark.Suite("z.discriminatedUnion: many");
|
||||
|
||||
const aSchema = z.object({
|
||||
type: z.literal("a"),
|
||||
});
|
||||
const objA = {
|
||||
type: "a",
|
||||
};
|
||||
|
||||
const bSchema = z.object({
|
||||
type: z.literal("b"),
|
||||
});
|
||||
const objB = {
|
||||
type: "b",
|
||||
};
|
||||
|
||||
const cSchema = z.object({
|
||||
type: z.literal("c"),
|
||||
});
|
||||
const objC = {
|
||||
type: "c",
|
||||
};
|
||||
|
||||
const dSchema = z.object({
|
||||
type: z.literal("d"),
|
||||
});
|
||||
|
||||
const double = z.discriminatedUnion("type", [aSchema, bSchema]);
|
||||
const many = z.discriminatedUnion("type", [aSchema, bSchema, cSchema, dSchema]);
|
||||
|
||||
doubleSuite
|
||||
.add("valid: a", () => {
|
||||
double.parse(objA);
|
||||
})
|
||||
.add("valid: b", () => {
|
||||
double.parse(objB);
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
double.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.add("invalid: wrong shape", () => {
|
||||
try {
|
||||
double.parse(objC);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(doubleSuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
manySuite
|
||||
.add("valid: a", () => {
|
||||
many.parse(objA);
|
||||
})
|
||||
.add("valid: c", () => {
|
||||
many.parse(objC);
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
many.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.add("invalid: wrong shape", () => {
|
||||
try {
|
||||
many.parse({ type: "unknown" });
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(manySuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [doubleSuite, manySuite],
|
||||
};
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import type Benchmark from "benchmark";
|
||||
|
||||
import datetimeBenchmarks from "./datetime.js";
|
||||
import discriminatedUnionBenchmarks from "./discriminatedUnion.js";
|
||||
import ipv4Benchmarks from "./ipv4.js";
|
||||
import objectBenchmarks from "./object.js";
|
||||
import primitiveBenchmarks from "./primitives.js";
|
||||
import realworld from "./realworld.js";
|
||||
import stringBenchmarks from "./string.js";
|
||||
import unionBenchmarks from "./union.js";
|
||||
|
||||
const argv = process.argv.slice(2);
|
||||
let suites: Benchmark.Suite[] = [];
|
||||
|
||||
if (!argv.length) {
|
||||
suites = [
|
||||
...realworld.suites,
|
||||
...primitiveBenchmarks.suites,
|
||||
...stringBenchmarks.suites,
|
||||
...objectBenchmarks.suites,
|
||||
...unionBenchmarks.suites,
|
||||
...discriminatedUnionBenchmarks.suites,
|
||||
];
|
||||
} else {
|
||||
if (argv.includes("--realworld")) {
|
||||
suites.push(...realworld.suites);
|
||||
}
|
||||
if (argv.includes("--primitives")) {
|
||||
suites.push(...primitiveBenchmarks.suites);
|
||||
}
|
||||
if (argv.includes("--string")) {
|
||||
suites.push(...stringBenchmarks.suites);
|
||||
}
|
||||
if (argv.includes("--object")) {
|
||||
suites.push(...objectBenchmarks.suites);
|
||||
}
|
||||
if (argv.includes("--union")) {
|
||||
suites.push(...unionBenchmarks.suites);
|
||||
}
|
||||
if (argv.includes("--discriminatedUnion")) {
|
||||
suites.push(...datetimeBenchmarks.suites);
|
||||
}
|
||||
if (argv.includes("--datetime")) {
|
||||
suites.push(...datetimeBenchmarks.suites);
|
||||
}
|
||||
if (argv.includes("--ipv4")) {
|
||||
suites.push(...ipv4Benchmarks.suites);
|
||||
}
|
||||
}
|
||||
|
||||
for (const suite of suites) {
|
||||
suite.run({});
|
||||
}
|
||||
|
||||
// exit on Ctrl-C
|
||||
process.on("SIGINT", function () {
|
||||
console.log("Exiting...");
|
||||
process.exit();
|
||||
});
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
const suite = new Benchmark.Suite("ipv4");
|
||||
|
||||
const DATA = "127.0.0.1";
|
||||
const ipv4RegexA =
|
||||
/^(((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}))$/;
|
||||
const ipv4RegexB =
|
||||
/^(?:(?:(?=(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)$/;
|
||||
const ipv4RegexC = /^(?:(?: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)$/;
|
||||
const ipv4RegexD = /^(\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}$/;
|
||||
const ipv4RegexE = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.){3}(25[0-5]|(2[0-4]|1\d|[1-9]|)\d)$/;
|
||||
const ipv4RegexF = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||
const ipv4RegexG = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)(\.(?!$)|$)){4}$/;
|
||||
const ipv4RegexH = /^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\.(?!$)|$)){4}$/;
|
||||
const ipv4RegexI =
|
||||
/^(?:(?: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])$/;
|
||||
|
||||
suite
|
||||
.add("A", () => {
|
||||
return ipv4RegexA.test(DATA);
|
||||
})
|
||||
.add("B", () => {
|
||||
return ipv4RegexB.test(DATA);
|
||||
})
|
||||
.add("C", () => {
|
||||
return ipv4RegexC.test(DATA);
|
||||
})
|
||||
.add("D", () => {
|
||||
return ipv4RegexD.test(DATA);
|
||||
})
|
||||
.add("E", () => {
|
||||
return ipv4RegexE.test(DATA);
|
||||
})
|
||||
.add("F", () => {
|
||||
return ipv4RegexF.test(DATA);
|
||||
})
|
||||
.add("G", () => {
|
||||
return ipv4RegexG.test(DATA);
|
||||
})
|
||||
.add("H", () => {
|
||||
return ipv4RegexH.test(DATA);
|
||||
})
|
||||
.add("I", () => {
|
||||
return ipv4RegexI.test(DATA);
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${suite.name!}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [suite],
|
||||
};
|
||||
|
||||
if (require.main === module) {
|
||||
suite.run();
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
const emptySuite = new Benchmark.Suite("z.object: empty");
|
||||
const shortSuite = new Benchmark.Suite("z.object: short");
|
||||
const longSuite = new Benchmark.Suite("z.object: long");
|
||||
|
||||
const empty = z.object({});
|
||||
const short = z.object({
|
||||
string: z.string(),
|
||||
});
|
||||
const long = z.object({
|
||||
string: z.string(),
|
||||
number: z.number(),
|
||||
boolean: z.boolean(),
|
||||
});
|
||||
|
||||
emptySuite
|
||||
.add("valid", () => {
|
||||
empty.parse({});
|
||||
})
|
||||
.add("valid: extra keys", () => {
|
||||
empty.parse({ string: "string" });
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
empty.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(emptySuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
shortSuite
|
||||
.add("valid", () => {
|
||||
short.parse({ string: "string" });
|
||||
})
|
||||
.add("valid: extra keys", () => {
|
||||
short.parse({ string: "string", number: 42 });
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
short.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(shortSuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
longSuite
|
||||
.add("valid", () => {
|
||||
long.parse({ string: "string", number: 42, boolean: true });
|
||||
})
|
||||
.add("valid: extra keys", () => {
|
||||
long.parse({ string: "string", number: 42, boolean: true, list: [] });
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
long.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(longSuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [emptySuite, shortSuite, longSuite],
|
||||
};
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
import { Mocker } from "../tests/Mocker.js";
|
||||
|
||||
const val = new Mocker();
|
||||
|
||||
const enumSuite = new Benchmark.Suite("z.enum");
|
||||
const enumSchema = z.enum(["a", "b", "c"]);
|
||||
|
||||
enumSuite
|
||||
.add("valid", () => {
|
||||
enumSchema.parse("a");
|
||||
})
|
||||
.add("invalid", () => {
|
||||
try {
|
||||
enumSchema.parse("x");
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`z.enum: ${e.target}`);
|
||||
});
|
||||
|
||||
const longEnumSuite = new Benchmark.Suite("long z.enum");
|
||||
const longEnumSchema = z.enum([
|
||||
"one",
|
||||
"two",
|
||||
"three",
|
||||
"four",
|
||||
"five",
|
||||
"six",
|
||||
"seven",
|
||||
"eight",
|
||||
"nine",
|
||||
"ten",
|
||||
"eleven",
|
||||
"twelve",
|
||||
"thirteen",
|
||||
"fourteen",
|
||||
"fifteen",
|
||||
"sixteen",
|
||||
"seventeen",
|
||||
]);
|
||||
|
||||
longEnumSuite
|
||||
.add("valid", () => {
|
||||
longEnumSchema.parse("five");
|
||||
})
|
||||
.add("invalid", () => {
|
||||
try {
|
||||
longEnumSchema.parse("invalid");
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`long z.enum: ${e.target}`);
|
||||
});
|
||||
|
||||
const undefinedSuite = new Benchmark.Suite("z.undefined");
|
||||
const undefinedSchema = z.undefined();
|
||||
|
||||
undefinedSuite
|
||||
.add("valid", () => {
|
||||
undefinedSchema.parse(undefined);
|
||||
})
|
||||
.add("invalid", () => {
|
||||
try {
|
||||
undefinedSchema.parse(1);
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`z.undefined: ${e.target}`);
|
||||
});
|
||||
|
||||
const literalSuite = new Benchmark.Suite("z.literal");
|
||||
const short = "short";
|
||||
const bad = "bad";
|
||||
const literalSchema = z.literal("short");
|
||||
|
||||
literalSuite
|
||||
.add("valid", () => {
|
||||
literalSchema.parse(short);
|
||||
})
|
||||
.add("invalid", () => {
|
||||
try {
|
||||
literalSchema.parse(bad);
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`z.literal: ${e.target}`);
|
||||
});
|
||||
|
||||
const numberSuite = new Benchmark.Suite("z.number");
|
||||
const numberSchema = z.number().int();
|
||||
|
||||
numberSuite
|
||||
.add("valid", () => {
|
||||
numberSchema.parse(1);
|
||||
})
|
||||
.add("invalid type", () => {
|
||||
try {
|
||||
numberSchema.parse("bad");
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.add("invalid number", () => {
|
||||
try {
|
||||
numberSchema.parse(0.5);
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`z.number: ${e.target}`);
|
||||
});
|
||||
|
||||
const dateSuite = new Benchmark.Suite("z.date");
|
||||
|
||||
const plainDate = z.date();
|
||||
const minMaxDate = z.date().min(new Date("2021-01-01")).max(new Date("2030-01-01"));
|
||||
|
||||
dateSuite
|
||||
.add("valid", () => {
|
||||
plainDate.parse(new Date());
|
||||
})
|
||||
.add("invalid", () => {
|
||||
try {
|
||||
plainDate.parse(1);
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.add("valid min and max", () => {
|
||||
minMaxDate.parse(new Date("2023-01-01"));
|
||||
})
|
||||
.add("invalid min", () => {
|
||||
try {
|
||||
minMaxDate.parse(new Date("2019-01-01"));
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.add("invalid max", () => {
|
||||
try {
|
||||
minMaxDate.parse(new Date("2031-01-01"));
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`z.date: ${e.target}`);
|
||||
});
|
||||
|
||||
const symbolSuite = new Benchmark.Suite("z.symbol");
|
||||
const symbolSchema = z.symbol();
|
||||
|
||||
symbolSuite
|
||||
.add("valid", () => {
|
||||
symbolSchema.parse(val.symbol);
|
||||
})
|
||||
.add("invalid", () => {
|
||||
try {
|
||||
symbolSchema.parse(1);
|
||||
} catch (_e: any) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`z.symbol: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [enumSuite, longEnumSuite, undefinedSuite, literalSuite, numberSuite, dateSuite, symbolSuite],
|
||||
};
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
const shortSuite = new Benchmark.Suite("realworld");
|
||||
|
||||
const People = z.array(
|
||||
z.object({
|
||||
type: z.literal("person"),
|
||||
hair: z.enum(["blue", "brown"]),
|
||||
active: z.boolean(),
|
||||
name: z.string(),
|
||||
age: z.number().int(),
|
||||
hobbies: z.array(z.string()),
|
||||
address: z.object({
|
||||
street: z.string(),
|
||||
zip: z.string(),
|
||||
country: z.string(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
let i = 0;
|
||||
|
||||
function num() {
|
||||
return ++i;
|
||||
}
|
||||
|
||||
function str() {
|
||||
return (++i % 100).toString(16);
|
||||
}
|
||||
|
||||
function array<T>(fn: () => T): T[] {
|
||||
return Array.from({ length: ++i % 10 }, () => fn());
|
||||
}
|
||||
|
||||
const people = Array.from({ length: 100 }, () => {
|
||||
return {
|
||||
type: "person",
|
||||
hair: i % 2 ? "blue" : "brown",
|
||||
active: !!(i % 2),
|
||||
name: str(),
|
||||
age: num(),
|
||||
hobbies: array(str),
|
||||
address: {
|
||||
street: str(),
|
||||
zip: str(),
|
||||
country: str(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
shortSuite
|
||||
.add("valid", () => {
|
||||
People.parse(people);
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(shortSuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [shortSuite],
|
||||
};
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
const SUITE_NAME = "z.string";
|
||||
const suite = new Benchmark.Suite(SUITE_NAME);
|
||||
|
||||
const empty = "";
|
||||
const short = "short";
|
||||
const long = "long".repeat(256);
|
||||
const manual = (str: unknown) => {
|
||||
if (typeof str !== "string") {
|
||||
throw new Error("Not a string");
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
const stringSchema = z.string();
|
||||
const optionalStringSchema = z.string().optional();
|
||||
const optionalNullableStringSchema = z.string().optional().nullable();
|
||||
|
||||
suite
|
||||
.add("empty string", () => {
|
||||
stringSchema.parse(empty);
|
||||
})
|
||||
.add("short string", () => {
|
||||
stringSchema.parse(short);
|
||||
})
|
||||
.add("long string", () => {
|
||||
stringSchema.parse(long);
|
||||
})
|
||||
.add("optional string", () => {
|
||||
optionalStringSchema.parse(long);
|
||||
})
|
||||
.add("nullable string", () => {
|
||||
optionalNullableStringSchema.parse(long);
|
||||
})
|
||||
.add("nullable (null) string", () => {
|
||||
optionalNullableStringSchema.parse(null);
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
stringSchema.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.add("manual parser: long", () => {
|
||||
manual(long);
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${SUITE_NAME}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [suite],
|
||||
};
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import Benchmark from "benchmark";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
const doubleSuite = new Benchmark.Suite("z.union: double");
|
||||
const manySuite = new Benchmark.Suite("z.union: many");
|
||||
|
||||
const aSchema = z.object({
|
||||
type: z.literal("a"),
|
||||
});
|
||||
const objA = {
|
||||
type: "a",
|
||||
};
|
||||
|
||||
const bSchema = z.object({
|
||||
type: z.literal("b"),
|
||||
});
|
||||
const objB = {
|
||||
type: "b",
|
||||
};
|
||||
|
||||
const cSchema = z.object({
|
||||
type: z.literal("c"),
|
||||
});
|
||||
const objC = {
|
||||
type: "c",
|
||||
};
|
||||
|
||||
const dSchema = z.object({
|
||||
type: z.literal("d"),
|
||||
});
|
||||
|
||||
const double = z.union([aSchema, bSchema]);
|
||||
const many = z.union([aSchema, bSchema, cSchema, dSchema]);
|
||||
|
||||
doubleSuite
|
||||
.add("valid: a", () => {
|
||||
double.parse(objA);
|
||||
})
|
||||
.add("valid: b", () => {
|
||||
double.parse(objB);
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
double.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.add("invalid: wrong shape", () => {
|
||||
try {
|
||||
double.parse(objC);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(doubleSuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
manySuite
|
||||
.add("valid: a", () => {
|
||||
many.parse(objA);
|
||||
})
|
||||
.add("valid: c", () => {
|
||||
many.parse(objC);
|
||||
})
|
||||
.add("invalid: null", () => {
|
||||
try {
|
||||
many.parse(null);
|
||||
} catch (_err) {}
|
||||
})
|
||||
.add("invalid: wrong shape", () => {
|
||||
try {
|
||||
many.parse({ type: "unknown" });
|
||||
} catch (_err) {}
|
||||
})
|
||||
.on("cycle", (e: Benchmark.Event) => {
|
||||
console.log(`${(manySuite as any).name}: ${e.target}`);
|
||||
});
|
||||
|
||||
export default {
|
||||
suites: [doubleSuite, manySuite],
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import type { ZodErrorMap } from "./ZodError.js";
|
||||
import defaultErrorMap from "./locales/en.js";
|
||||
|
||||
let overrideErrorMap = defaultErrorMap;
|
||||
export { defaultErrorMap };
|
||||
|
||||
export function setErrorMap(map: ZodErrorMap) {
|
||||
overrideErrorMap = map;
|
||||
}
|
||||
|
||||
export function getErrorMap() {
|
||||
return overrideErrorMap;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export * from "./errors.js";
|
||||
export * from "./helpers/parseUtil.js";
|
||||
export * from "./helpers/typeAliases.js";
|
||||
export * from "./helpers/util.js";
|
||||
export * from "./types.js";
|
||||
export * from "./ZodError.js";
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
export namespace enumUtil {
|
||||
type UnionToIntersectionFn<T> = (T extends unknown ? (k: () => T) => void : never) extends (
|
||||
k: infer Intersection
|
||||
) => void
|
||||
? Intersection
|
||||
: never;
|
||||
|
||||
type GetUnionLast<T> = UnionToIntersectionFn<T> extends () => infer Last ? Last : never;
|
||||
|
||||
type UnionToTuple<T, Tuple extends unknown[] = []> = [T] extends [never]
|
||||
? Tuple
|
||||
: UnionToTuple<Exclude<T, GetUnionLast<T>>, [GetUnionLast<T>, ...Tuple]>;
|
||||
|
||||
type CastToStringTuple<T> = T extends [string, ...string[]] ? T : never;
|
||||
|
||||
export type UnionToTupleString<T> = CastToStringTuple<UnionToTuple<T>>;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export namespace errorUtil {
|
||||
export type ErrMessage = string | { message?: string | undefined };
|
||||
export const errToObj = (message?: ErrMessage): { message?: string | undefined } =>
|
||||
typeof message === "string" ? { message } : message || {};
|
||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: renaming churns 31 v3 call sites
|
||||
export const toString = (message?: ErrMessage): string | undefined =>
|
||||
typeof message === "string" ? message : message?.message;
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
import type { IssueData, ZodErrorMap, ZodIssue } from "../ZodError.js";
|
||||
import { getErrorMap } from "../errors.js";
|
||||
import defaultErrorMap from "../locales/en.js";
|
||||
import type { ZodParsedType } from "./util.js";
|
||||
|
||||
export const makeIssue = (params: {
|
||||
data: any;
|
||||
path: (string | number)[];
|
||||
errorMaps: ZodErrorMap[];
|
||||
issueData: IssueData;
|
||||
}): ZodIssue => {
|
||||
const { data, path, errorMaps, issueData } = params;
|
||||
const fullPath = [...path, ...(issueData.path || [])];
|
||||
const fullIssue = {
|
||||
...issueData,
|
||||
path: fullPath,
|
||||
};
|
||||
|
||||
if (issueData.message !== undefined) {
|
||||
return {
|
||||
...issueData,
|
||||
path: fullPath,
|
||||
message: issueData.message,
|
||||
};
|
||||
}
|
||||
|
||||
let errorMessage = "";
|
||||
const maps = errorMaps
|
||||
.filter((m) => !!m)
|
||||
.slice()
|
||||
.reverse();
|
||||
for (const map of maps) {
|
||||
errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
|
||||
}
|
||||
|
||||
return {
|
||||
...issueData,
|
||||
path: fullPath,
|
||||
message: errorMessage,
|
||||
};
|
||||
};
|
||||
|
||||
export type ParseParams = {
|
||||
path: (string | number)[];
|
||||
errorMap: ZodErrorMap;
|
||||
async: boolean;
|
||||
};
|
||||
|
||||
export type ParsePathComponent = string | number;
|
||||
export type ParsePath = ParsePathComponent[];
|
||||
export const EMPTY_PATH: ParsePath = [];
|
||||
|
||||
export interface ParseContext {
|
||||
readonly common: {
|
||||
readonly issues: ZodIssue[];
|
||||
readonly contextualErrorMap?: ZodErrorMap | undefined;
|
||||
readonly async: boolean;
|
||||
};
|
||||
readonly path: ParsePath;
|
||||
readonly schemaErrorMap?: ZodErrorMap | undefined;
|
||||
readonly parent: ParseContext | null;
|
||||
readonly data: any;
|
||||
readonly parsedType: ZodParsedType;
|
||||
}
|
||||
|
||||
export type ParseInput = {
|
||||
data: any;
|
||||
path: (string | number)[];
|
||||
parent: ParseContext;
|
||||
};
|
||||
|
||||
export function addIssueToContext(ctx: ParseContext, issueData: IssueData): void {
|
||||
const overrideMap = getErrorMap();
|
||||
const issue = makeIssue({
|
||||
issueData: issueData,
|
||||
data: ctx.data,
|
||||
path: ctx.path,
|
||||
errorMaps: [
|
||||
ctx.common.contextualErrorMap, // contextual error map is first priority
|
||||
ctx.schemaErrorMap, // then schema-bound map if available
|
||||
overrideMap, // then global override map
|
||||
overrideMap === defaultErrorMap ? undefined : defaultErrorMap, // then global default map
|
||||
].filter((x) => !!x),
|
||||
});
|
||||
ctx.common.issues.push(issue);
|
||||
}
|
||||
|
||||
export type ObjectPair = {
|
||||
key: SyncParseReturnType<any>;
|
||||
value: SyncParseReturnType<any>;
|
||||
};
|
||||
export class ParseStatus {
|
||||
value: "aborted" | "dirty" | "valid" = "valid";
|
||||
dirty(): void {
|
||||
if (this.value === "valid") this.value = "dirty";
|
||||
}
|
||||
abort(): void {
|
||||
if (this.value !== "aborted") this.value = "aborted";
|
||||
}
|
||||
|
||||
static mergeArray(status: ParseStatus, results: SyncParseReturnType<any>[]): SyncParseReturnType {
|
||||
const arrayValue: any[] = [];
|
||||
for (const s of results) {
|
||||
if (s.status === "aborted") return INVALID;
|
||||
if (s.status === "dirty") status.dirty();
|
||||
arrayValue.push(s.value);
|
||||
}
|
||||
|
||||
return { status: status.value, value: arrayValue };
|
||||
}
|
||||
|
||||
static async mergeObjectAsync(
|
||||
status: ParseStatus,
|
||||
pairs: { key: ParseReturnType<any>; value: ParseReturnType<any> }[]
|
||||
): Promise<SyncParseReturnType<any>> {
|
||||
const syncPairs: ObjectPair[] = [];
|
||||
for (const pair of pairs) {
|
||||
const key = await pair.key;
|
||||
const value = await pair.value;
|
||||
syncPairs.push({
|
||||
key,
|
||||
value,
|
||||
});
|
||||
}
|
||||
return ParseStatus.mergeObjectSync(status, syncPairs);
|
||||
}
|
||||
|
||||
static mergeObjectSync(
|
||||
status: ParseStatus,
|
||||
pairs: {
|
||||
key: SyncParseReturnType<any>;
|
||||
value: SyncParseReturnType<any>;
|
||||
alwaysSet?: boolean;
|
||||
}[]
|
||||
): SyncParseReturnType {
|
||||
const finalObject: any = {};
|
||||
for (const pair of pairs) {
|
||||
const { key, value } = pair;
|
||||
if (key.status === "aborted") return INVALID;
|
||||
if (value.status === "aborted") return INVALID;
|
||||
if (key.status === "dirty") status.dirty();
|
||||
if (value.status === "dirty") status.dirty();
|
||||
|
||||
if (key.value !== "__proto__" && (typeof value.value !== "undefined" || pair.alwaysSet)) {
|
||||
finalObject[key.value] = value.value;
|
||||
}
|
||||
}
|
||||
|
||||
return { status: status.value, value: finalObject };
|
||||
}
|
||||
}
|
||||
export interface ParseResult {
|
||||
status: "aborted" | "dirty" | "valid";
|
||||
data: any;
|
||||
}
|
||||
|
||||
export type INVALID = { status: "aborted" };
|
||||
export const INVALID: INVALID = Object.freeze({
|
||||
status: "aborted",
|
||||
});
|
||||
|
||||
export type DIRTY<T> = { status: "dirty"; value: T };
|
||||
export const DIRTY = <T>(value: T): DIRTY<T> => ({ status: "dirty", value });
|
||||
|
||||
export type OK<T> = { status: "valid"; value: T };
|
||||
export const OK = <T>(value: T): OK<T> => ({ status: "valid", value });
|
||||
|
||||
export type SyncParseReturnType<T = any> = OK<T> | DIRTY<T> | INVALID;
|
||||
export type AsyncParseReturnType<T> = Promise<SyncParseReturnType<T>>;
|
||||
export type ParseReturnType<T> = SyncParseReturnType<T> | AsyncParseReturnType<T>;
|
||||
|
||||
export const isAborted = (x: ParseReturnType<any>): x is INVALID => (x as any).status === "aborted";
|
||||
export const isDirty = <T>(x: ParseReturnType<T>): x is OK<T> | DIRTY<T> => (x as any).status === "dirty";
|
||||
export const isValid = <T>(x: ParseReturnType<T>): x is OK<T> => (x as any).status === "valid";
|
||||
export const isAsync = <T>(x: ParseReturnType<T>): x is AsyncParseReturnType<T> =>
|
||||
typeof Promise !== "undefined" && x instanceof Promise;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import type {
|
||||
ZodArray,
|
||||
ZodNullable,
|
||||
ZodObject,
|
||||
ZodOptional,
|
||||
ZodRawShape,
|
||||
ZodTuple,
|
||||
ZodTupleItems,
|
||||
ZodTypeAny,
|
||||
} from "../types.js";
|
||||
|
||||
export namespace partialUtil {
|
||||
export type DeepPartial<T extends ZodTypeAny> = T extends ZodObject<ZodRawShape>
|
||||
? ZodObject<
|
||||
{ [k in keyof T["shape"]]: ZodOptional<DeepPartial<T["shape"][k]>> },
|
||||
T["_def"]["unknownKeys"],
|
||||
T["_def"]["catchall"]
|
||||
>
|
||||
: T extends ZodArray<infer Type, infer Card>
|
||||
? ZodArray<DeepPartial<Type>, Card>
|
||||
: T extends ZodOptional<infer Type>
|
||||
? ZodOptional<DeepPartial<Type>>
|
||||
: T extends ZodNullable<infer Type>
|
||||
? ZodNullable<DeepPartial<Type>>
|
||||
: T extends ZodTuple<infer Items>
|
||||
? {
|
||||
[k in keyof Items]: Items[k] extends ZodTypeAny ? DeepPartial<Items[k]> : never;
|
||||
} extends infer PI
|
||||
? PI extends ZodTupleItems
|
||||
? ZodTuple<PI>
|
||||
: never
|
||||
: never
|
||||
: T;
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export type Primitive = string | number | symbol | bigint | boolean | null | undefined;
|
||||
export type Scalars = Primitive | Primitive[];
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
export namespace util {
|
||||
type AssertEqual<T, U> = (<V>() => V extends T ? 1 : 2) extends <V>() => V extends U ? 1 : 2 ? true : false;
|
||||
|
||||
export type isAny<T> = 0 extends 1 & T ? true : false;
|
||||
export const assertEqual = <A, B>(_: AssertEqual<A, B>): void => {};
|
||||
export function assertIs<T>(_arg: T): void {}
|
||||
export function assertNever(_x: never): never {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
|
||||
export type OmitKeys<T, K extends string> = Pick<T, Exclude<keyof T, K>>;
|
||||
export type MakePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||
export type Exactly<T, X> = T & Record<Exclude<keyof X, keyof T>, never>;
|
||||
export type InexactPartial<T> = { [k in keyof T]?: T[k] | undefined };
|
||||
export const arrayToEnum = <T extends string, U extends [T, ...T[]]>(items: U): { [k in U[number]]: k } => {
|
||||
const obj: any = {};
|
||||
for (const item of items) {
|
||||
obj[item] = item;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
export const getValidEnumValues = (obj: any): any[] => {
|
||||
const validKeys = objectKeys(obj).filter((k: any) => typeof obj[obj[k]] !== "number");
|
||||
const filtered: any = {};
|
||||
for (const k of validKeys) {
|
||||
filtered[k] = obj[k];
|
||||
}
|
||||
return objectValues(filtered);
|
||||
};
|
||||
|
||||
export const objectValues = (obj: any): any[] => {
|
||||
return objectKeys(obj).map(function (e) {
|
||||
return obj[e];
|
||||
});
|
||||
};
|
||||
|
||||
export const objectKeys: ObjectConstructor["keys"] =
|
||||
typeof Object.keys === "function" // eslint-disable-line ban/ban
|
||||
? (obj: any) => Object.keys(obj) // eslint-disable-line ban/ban
|
||||
: (object: any) => {
|
||||
const keys = [];
|
||||
for (const key in object) {
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
keys.push(key);
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
export const find = <T>(arr: T[], checker: (arg: T) => any): T | undefined => {
|
||||
for (const item of arr) {
|
||||
if (checker(item)) return item;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export type identity<T> = objectUtil.identity<T>;
|
||||
export type flatten<T> = objectUtil.flatten<T>;
|
||||
|
||||
export type noUndefined<T> = T extends undefined ? never : T;
|
||||
|
||||
export const isInteger: NumberConstructor["isInteger"] =
|
||||
typeof Number.isInteger === "function"
|
||||
? (val) => Number.isInteger(val) // eslint-disable-line ban/ban
|
||||
: (val) => typeof val === "number" && Number.isFinite(val) && Math.floor(val) === val;
|
||||
|
||||
export function joinValues<T extends any[]>(array: T, separator = " | "): string {
|
||||
return array.map((val) => (typeof val === "string" ? `'${val}'` : val)).join(separator);
|
||||
}
|
||||
|
||||
export const jsonStringifyReplacer = (_: string, value: any): any => {
|
||||
if (typeof value === "bigint") {
|
||||
return value.toString();
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace objectUtil {
|
||||
export type MergeShapes<U, V> =
|
||||
// fast path when there is no keys overlap
|
||||
keyof U & keyof V extends never
|
||||
? U & V
|
||||
: {
|
||||
[k in Exclude<keyof U, keyof V>]: U[k];
|
||||
} & V;
|
||||
|
||||
type optionalKeys<T extends object> = {
|
||||
[k in keyof T]: undefined extends T[k] ? k : never;
|
||||
}[keyof T];
|
||||
type requiredKeys<T extends object> = {
|
||||
[k in keyof T]: undefined extends T[k] ? never : k;
|
||||
}[keyof T];
|
||||
export type addQuestionMarks<T extends object, _O = any> = {
|
||||
[K in requiredKeys<T>]: T[K];
|
||||
} & {
|
||||
[K in optionalKeys<T>]?: T[K];
|
||||
} & { [k in keyof T]?: unknown };
|
||||
|
||||
export type identity<T> = T;
|
||||
export type flatten<T> = identity<{ [k in keyof T]: T[k] }>;
|
||||
|
||||
export type noNeverKeys<T> = {
|
||||
[k in keyof T]: [T[k]] extends [never] ? never : k;
|
||||
}[keyof T];
|
||||
|
||||
export type noNever<T> = identity<{
|
||||
[k in noNeverKeys<T>]: k extends keyof T ? T[k] : never;
|
||||
}>;
|
||||
|
||||
export const mergeShapes = <U, T>(first: U, second: T): T & U => {
|
||||
return {
|
||||
...first,
|
||||
...second, // second overwrites first
|
||||
};
|
||||
};
|
||||
|
||||
export type extendShape<A extends object, B extends object> = keyof A & keyof B extends never // fast path when there is no keys overlap
|
||||
? A & B
|
||||
: {
|
||||
[K in keyof A as K extends keyof B ? never : K]: A[K];
|
||||
} & {
|
||||
[K in keyof B]: B[K];
|
||||
};
|
||||
}
|
||||
|
||||
export const ZodParsedType: {
|
||||
string: "string";
|
||||
nan: "nan";
|
||||
number: "number";
|
||||
integer: "integer";
|
||||
float: "float";
|
||||
boolean: "boolean";
|
||||
date: "date";
|
||||
bigint: "bigint";
|
||||
symbol: "symbol";
|
||||
function: "function";
|
||||
undefined: "undefined";
|
||||
null: "null";
|
||||
array: "array";
|
||||
object: "object";
|
||||
unknown: "unknown";
|
||||
promise: "promise";
|
||||
void: "void";
|
||||
never: "never";
|
||||
map: "map";
|
||||
set: "set";
|
||||
} = util.arrayToEnum([
|
||||
"string",
|
||||
"nan",
|
||||
"number",
|
||||
"integer",
|
||||
"float",
|
||||
"boolean",
|
||||
"date",
|
||||
"bigint",
|
||||
"symbol",
|
||||
"function",
|
||||
"undefined",
|
||||
"null",
|
||||
"array",
|
||||
"object",
|
||||
"unknown",
|
||||
"promise",
|
||||
"void",
|
||||
"never",
|
||||
"map",
|
||||
"set",
|
||||
]);
|
||||
|
||||
export type ZodParsedType = keyof typeof ZodParsedType;
|
||||
|
||||
export const getParsedType = (data: any): ZodParsedType => {
|
||||
const t = typeof data;
|
||||
|
||||
switch (t) {
|
||||
case "undefined":
|
||||
return ZodParsedType.undefined;
|
||||
|
||||
case "string":
|
||||
return ZodParsedType.string;
|
||||
|
||||
case "number":
|
||||
return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number;
|
||||
|
||||
case "boolean":
|
||||
return ZodParsedType.boolean;
|
||||
|
||||
case "function":
|
||||
return ZodParsedType.function;
|
||||
|
||||
case "bigint":
|
||||
return ZodParsedType.bigint;
|
||||
|
||||
case "symbol":
|
||||
return ZodParsedType.symbol;
|
||||
|
||||
case "object":
|
||||
if (Array.isArray(data)) {
|
||||
return ZodParsedType.array;
|
||||
}
|
||||
if (data === null) {
|
||||
return ZodParsedType.null;
|
||||
}
|
||||
if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") {
|
||||
return ZodParsedType.promise;
|
||||
}
|
||||
if (typeof Map !== "undefined" && data instanceof Map) {
|
||||
return ZodParsedType.map;
|
||||
}
|
||||
if (typeof Set !== "undefined" && data instanceof Set) {
|
||||
return ZodParsedType.set;
|
||||
}
|
||||
if (typeof Date !== "undefined" && data instanceof Date) {
|
||||
return ZodParsedType.date;
|
||||
}
|
||||
return ZodParsedType.object;
|
||||
|
||||
default:
|
||||
return ZodParsedType.unknown;
|
||||
}
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import * as z from "./external.js";
|
||||
export * from "./external.js";
|
||||
export { z };
|
||||
export default z;
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import { type ZodErrorMap, ZodIssueCode } from "../ZodError.js";
|
||||
import { util, ZodParsedType } from "../helpers/util.js";
|
||||
|
||||
const errorMap: ZodErrorMap = (issue, _ctx) => {
|
||||
let message: string;
|
||||
switch (issue.code) {
|
||||
case ZodIssueCode.invalid_type:
|
||||
if (issue.received === ZodParsedType.undefined) {
|
||||
message = "Required";
|
||||
} else {
|
||||
message = `Expected ${issue.expected}, received ${issue.received}`;
|
||||
}
|
||||
break;
|
||||
case ZodIssueCode.invalid_literal:
|
||||
message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`;
|
||||
break;
|
||||
case ZodIssueCode.unrecognized_keys:
|
||||
message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_union:
|
||||
message = `Invalid input`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_union_discriminator:
|
||||
message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_enum_value:
|
||||
message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_arguments:
|
||||
message = `Invalid function arguments`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_return_type:
|
||||
message = `Invalid function return type`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_date:
|
||||
message = `Invalid date`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_string:
|
||||
if (typeof issue.validation === "object") {
|
||||
if ("includes" in issue.validation) {
|
||||
message = `Invalid input: must include "${issue.validation.includes}"`;
|
||||
|
||||
if (typeof issue.validation.position === "number") {
|
||||
message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`;
|
||||
}
|
||||
} else if ("startsWith" in issue.validation) {
|
||||
message = `Invalid input: must start with "${issue.validation.startsWith}"`;
|
||||
} else if ("endsWith" in issue.validation) {
|
||||
message = `Invalid input: must end with "${issue.validation.endsWith}"`;
|
||||
} else {
|
||||
util.assertNever(issue.validation);
|
||||
}
|
||||
} else if (issue.validation !== "regex") {
|
||||
message = `Invalid ${issue.validation}`;
|
||||
} else {
|
||||
message = "Invalid";
|
||||
}
|
||||
break;
|
||||
case ZodIssueCode.too_small:
|
||||
if (issue.type === "array")
|
||||
message = `Array must contain ${
|
||||
issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`
|
||||
} ${issue.minimum} element(s)`;
|
||||
else if (issue.type === "string")
|
||||
message = `String must contain ${
|
||||
issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`
|
||||
} ${issue.minimum} character(s)`;
|
||||
else if (issue.type === "number")
|
||||
message = `Number must be ${
|
||||
issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `
|
||||
}${issue.minimum}`;
|
||||
else if (issue.type === "bigint")
|
||||
message = `Number must be ${
|
||||
issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `
|
||||
}${issue.minimum}`;
|
||||
else if (issue.type === "date")
|
||||
message = `Date must be ${
|
||||
issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `
|
||||
}${new Date(Number(issue.minimum))}`;
|
||||
else message = "Invalid input";
|
||||
break;
|
||||
case ZodIssueCode.too_big:
|
||||
if (issue.type === "array")
|
||||
message = `Array must contain ${
|
||||
issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`
|
||||
} ${issue.maximum} element(s)`;
|
||||
else if (issue.type === "string")
|
||||
message = `String must contain ${
|
||||
issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`
|
||||
} ${issue.maximum} character(s)`;
|
||||
else if (issue.type === "number")
|
||||
message = `Number must be ${
|
||||
issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`
|
||||
} ${issue.maximum}`;
|
||||
else if (issue.type === "bigint")
|
||||
message = `BigInt must be ${
|
||||
issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`
|
||||
} ${issue.maximum}`;
|
||||
else if (issue.type === "date")
|
||||
message = `Date must be ${
|
||||
issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`
|
||||
} ${new Date(Number(issue.maximum))}`;
|
||||
else message = "Invalid input";
|
||||
break;
|
||||
case ZodIssueCode.custom:
|
||||
message = `Invalid input`;
|
||||
break;
|
||||
case ZodIssueCode.invalid_intersection_types:
|
||||
message = `Intersection results could not be merged`;
|
||||
break;
|
||||
case ZodIssueCode.not_multiple_of:
|
||||
message = `Number must be a multiple of ${issue.multipleOf}`;
|
||||
break;
|
||||
case ZodIssueCode.not_finite:
|
||||
message = "Number must be finite";
|
||||
break;
|
||||
default:
|
||||
message = _ctx.defaultError;
|
||||
util.assertNever(issue);
|
||||
}
|
||||
return { message };
|
||||
};
|
||||
|
||||
export default errorMap;
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* The Standard Schema interface.
|
||||
*/
|
||||
export type StandardSchemaV1<Input = unknown, Output = Input> = {
|
||||
/**
|
||||
* The Standard Schema properties.
|
||||
*/
|
||||
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
|
||||
};
|
||||
|
||||
export declare namespace StandardSchemaV1 {
|
||||
/**
|
||||
* The Standard Schema properties interface.
|
||||
*/
|
||||
export interface Props<Input = unknown, Output = Input> {
|
||||
/**
|
||||
* The version number of the standard.
|
||||
*/
|
||||
readonly version: 1;
|
||||
/**
|
||||
* The vendor name of the schema library.
|
||||
*/
|
||||
readonly vendor: string;
|
||||
/**
|
||||
* Validates unknown input values.
|
||||
*/
|
||||
readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
|
||||
/**
|
||||
* Inferred types associated with the schema.
|
||||
*/
|
||||
readonly types?: Types<Input, Output> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result interface of the validate function.
|
||||
*/
|
||||
export type Result<Output> = SuccessResult<Output> | FailureResult;
|
||||
|
||||
/**
|
||||
* The result interface if validation succeeds.
|
||||
*/
|
||||
export interface SuccessResult<Output> {
|
||||
/**
|
||||
* The typed output value.
|
||||
*/
|
||||
readonly value: Output;
|
||||
/**
|
||||
* The non-existent issues.
|
||||
*/
|
||||
readonly issues?: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result interface if validation fails.
|
||||
*/
|
||||
export interface FailureResult {
|
||||
/**
|
||||
* The issues of failed validation.
|
||||
*/
|
||||
readonly issues: ReadonlyArray<Issue>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The issue interface of the failure output.
|
||||
*/
|
||||
export interface Issue {
|
||||
/**
|
||||
* The error message of the issue.
|
||||
*/
|
||||
readonly message: string;
|
||||
/**
|
||||
* The path of the issue, if any.
|
||||
*/
|
||||
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path segment interface of the issue.
|
||||
*/
|
||||
export interface PathSegment {
|
||||
/**
|
||||
* The key representing a path segment.
|
||||
*/
|
||||
readonly key: PropertyKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Standard Schema types interface.
|
||||
*/
|
||||
export interface Types<Input = unknown, Output = Input> {
|
||||
/**
|
||||
* The input type of the schema.
|
||||
*/
|
||||
readonly input: Input;
|
||||
/**
|
||||
* The output type of the schema.
|
||||
*/
|
||||
readonly output: Output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers the input type of a Standard Schema.
|
||||
*/
|
||||
export type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["input"];
|
||||
|
||||
/**
|
||||
* Infers the output type of a Standard Schema.
|
||||
*/
|
||||
export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema["~standard"]["types"]>["output"];
|
||||
|
||||
// biome-ignore lint/complexity/noUselessEmptyExport: needed for granular visibility control of TS namespace
|
||||
export {};
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
function getRandomInt(max: number) {
|
||||
return Math.floor(Math.random() * Math.floor(max));
|
||||
}
|
||||
|
||||
const testSymbol = Symbol("test");
|
||||
|
||||
export class Mocker {
|
||||
pick = (...args: any[]): any => {
|
||||
return args[getRandomInt(args.length)];
|
||||
};
|
||||
|
||||
get string(): string {
|
||||
return Math.random().toString(36).substring(7);
|
||||
}
|
||||
get number(): number {
|
||||
return Math.random() * 100;
|
||||
}
|
||||
get bigint(): bigint {
|
||||
return BigInt(Math.floor(Math.random() * 10000));
|
||||
}
|
||||
get boolean(): boolean {
|
||||
return Math.random() < 0.5;
|
||||
}
|
||||
get date(): Date {
|
||||
return new Date(Math.floor(Date.now() * Math.random()));
|
||||
}
|
||||
get symbol(): symbol {
|
||||
return testSymbol;
|
||||
}
|
||||
get null(): null {
|
||||
return null;
|
||||
}
|
||||
get undefined(): undefined {
|
||||
return undefined;
|
||||
}
|
||||
get stringOptional(): string | undefined {
|
||||
return this.pick(this.string, this.undefined);
|
||||
}
|
||||
get stringNullable(): string | null {
|
||||
return this.pick(this.string, this.null);
|
||||
}
|
||||
get numberOptional(): number | undefined {
|
||||
return this.pick(this.number, this.undefined);
|
||||
}
|
||||
get numberNullable(): number | null {
|
||||
return this.pick(this.number, this.null);
|
||||
}
|
||||
get booleanOptional(): boolean | undefined {
|
||||
return this.pick(this.boolean, this.undefined);
|
||||
}
|
||||
get booleanNullable(): boolean | null {
|
||||
return this.pick(this.boolean, this.null);
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const Test = z.object({
|
||||
f1: z.number(),
|
||||
f2: z.string().optional(),
|
||||
f3: z.string().nullable(),
|
||||
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
|
||||
});
|
||||
type TestFlattenedErrors = z.inferFlattenedErrors<typeof Test, { message: string; code: number }>;
|
||||
type TestFormErrors = z.inferFlattenedErrors<typeof Test>;
|
||||
|
||||
test("default flattened errors type inference", () => {
|
||||
type TestTypeErrors = {
|
||||
formErrors: string[];
|
||||
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] };
|
||||
};
|
||||
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(true);
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test, { message: string }>, TestTypeErrors>(false);
|
||||
});
|
||||
|
||||
test("custom flattened errors type inference", () => {
|
||||
type ErrorType = { message: string; code: number };
|
||||
type TestTypeErrors = {
|
||||
formErrors: ErrorType[];
|
||||
fieldErrors: {
|
||||
[P in keyof z.TypeOf<typeof Test>]?: ErrorType[];
|
||||
};
|
||||
};
|
||||
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(false);
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test, { message: string; code: number }>, TestTypeErrors>(true);
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test, { message: string }>, TestTypeErrors>(false);
|
||||
});
|
||||
|
||||
test("form errors type inference", () => {
|
||||
type TestTypeErrors = {
|
||||
formErrors: string[];
|
||||
fieldErrors: { [P in keyof z.TypeOf<typeof Test>]?: string[] };
|
||||
};
|
||||
|
||||
util.assertEqual<z.inferFlattenedErrors<typeof Test>, TestTypeErrors>(true);
|
||||
});
|
||||
|
||||
test(".flatten() type assertion", () => {
|
||||
const parsed = Test.safeParse({}) as z.SafeParseError<void>;
|
||||
const validFlattenedErrors: TestFlattenedErrors = parsed.error.flatten(() => ({ message: "", code: 0 }));
|
||||
// @ts-expect-error should fail assertion between `TestFlattenedErrors` and unmapped `flatten()`.
|
||||
const invalidFlattenedErrors: TestFlattenedErrors = parsed.error.flatten();
|
||||
const validFormErrors: TestFormErrors = parsed.error.flatten();
|
||||
// @ts-expect-error should fail assertion between `TestFormErrors` and mapped `flatten()`.
|
||||
const invalidFormErrors: TestFormErrors = parsed.error.flatten(() => ({
|
||||
message: "string",
|
||||
code: 0,
|
||||
}));
|
||||
|
||||
[validFlattenedErrors, invalidFlattenedErrors, validFormErrors, invalidFormErrors];
|
||||
});
|
||||
|
||||
test(".formErrors type assertion", () => {
|
||||
const parsed = Test.safeParse({}) as z.SafeParseError<void>;
|
||||
const validFormErrors: TestFormErrors = parsed.error.formErrors;
|
||||
// @ts-expect-error should fail assertion between `TestFlattenedErrors` and `.formErrors`.
|
||||
const invalidFlattenedErrors: TestFlattenedErrors = parsed.error.formErrors;
|
||||
|
||||
[validFormErrors, invalidFlattenedErrors];
|
||||
});
|
||||
|
||||
test("all errors", () => {
|
||||
const propertySchema = z.string();
|
||||
const schema = z
|
||||
.object({
|
||||
a: propertySchema,
|
||||
b: propertySchema,
|
||||
})
|
||||
.refine(
|
||||
(val) => {
|
||||
return val.a === val.b;
|
||||
},
|
||||
{ message: "Must be equal" }
|
||||
);
|
||||
|
||||
try {
|
||||
schema.parse({
|
||||
a: "asdf",
|
||||
b: "qwer",
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
expect(error.flatten()).toEqual({
|
||||
formErrors: ["Must be equal"],
|
||||
fieldErrors: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
schema.parse({
|
||||
a: null,
|
||||
b: null,
|
||||
});
|
||||
} catch (_error) {
|
||||
const error = _error as z.ZodError;
|
||||
expect(error.flatten()).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: ["Expected string, received null"],
|
||||
b: ["Expected string, received null"],
|
||||
},
|
||||
});
|
||||
|
||||
expect(error.flatten((iss) => iss.message.toUpperCase())).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: ["EXPECTED STRING, RECEIVED NULL"],
|
||||
b: ["EXPECTED STRING, RECEIVED NULL"],
|
||||
},
|
||||
});
|
||||
// Test identity
|
||||
|
||||
expect(error.flatten((i: z.ZodIssue) => i)).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: [
|
||||
{
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
message: "Expected string, received null",
|
||||
path: ["a"],
|
||||
received: "null",
|
||||
},
|
||||
],
|
||||
b: [
|
||||
{
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
message: "Expected string, received null",
|
||||
path: ["b"],
|
||||
received: "null",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
// Test mapping
|
||||
expect(error.flatten((i: z.ZodIssue) => i.message.length)).toEqual({
|
||||
formErrors: [],
|
||||
fieldErrors: {
|
||||
a: ["Expected string, received null".length],
|
||||
b: ["Expected string, received null".length],
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("check any inference", () => {
|
||||
const t1 = z.any();
|
||||
t1.optional();
|
||||
t1.nullable();
|
||||
type t1 = z.infer<typeof t1>;
|
||||
util.assertEqual<t1, any>(true);
|
||||
});
|
||||
|
||||
test("check unknown inference", () => {
|
||||
const t1 = z.unknown();
|
||||
t1.optional();
|
||||
t1.nullable();
|
||||
type t1 = z.infer<typeof t1>;
|
||||
util.assertEqual<t1, unknown>(true);
|
||||
});
|
||||
|
||||
test("check never inference", () => {
|
||||
const t1 = z.never();
|
||||
expect(() => t1.parse(undefined)).toThrow();
|
||||
expect(() => t1.parse("asdf")).toThrow();
|
||||
expect(() => t1.parse(null)).toThrow();
|
||||
});
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const minTwo = z.string().array().min(2);
|
||||
const maxTwo = z.string().array().max(2);
|
||||
const justTwo = z.string().array().length(2);
|
||||
const intNum = z.string().array().nonempty();
|
||||
const nonEmptyMax = z.string().array().nonempty().max(2);
|
||||
|
||||
type t1 = z.infer<typeof nonEmptyMax>;
|
||||
util.assertEqual<[string, ...string[]], t1>(true);
|
||||
|
||||
type t2 = z.infer<typeof minTwo>;
|
||||
util.assertEqual<string[], t2>(true);
|
||||
|
||||
test("passing validations", () => {
|
||||
minTwo.parse(["a", "a"]);
|
||||
minTwo.parse(["a", "a", "a"]);
|
||||
maxTwo.parse(["a", "a"]);
|
||||
maxTwo.parse(["a"]);
|
||||
justTwo.parse(["a", "a"]);
|
||||
intNum.parse(["a"]);
|
||||
nonEmptyMax.parse(["a"]);
|
||||
});
|
||||
|
||||
test("failing validations", () => {
|
||||
expect(() => minTwo.parse(["a"])).toThrow();
|
||||
expect(() => maxTwo.parse(["a", "a", "a"])).toThrow();
|
||||
expect(() => justTwo.parse(["a"])).toThrow();
|
||||
expect(() => justTwo.parse(["a", "a", "a"])).toThrow();
|
||||
expect(() => intNum.parse([])).toThrow();
|
||||
expect(() => nonEmptyMax.parse([])).toThrow();
|
||||
expect(() => nonEmptyMax.parse(["a", "a", "a"])).toThrow();
|
||||
});
|
||||
|
||||
test("parse empty array in nonempty", () => {
|
||||
expect(() =>
|
||||
z
|
||||
.array(z.string())
|
||||
.nonempty()
|
||||
.parse([] as any)
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("get element", () => {
|
||||
justTwo.element.parse("asdf");
|
||||
expect(() => justTwo.element.parse(12)).toThrow();
|
||||
});
|
||||
|
||||
test("continue parsing despite array size error", () => {
|
||||
const schema = z.object({
|
||||
people: z.string().array().min(2),
|
||||
});
|
||||
|
||||
const result = schema.safeParse({
|
||||
people: [123],
|
||||
});
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues.length).toEqual(2);
|
||||
}
|
||||
});
|
||||
|
||||
test("parse should fail given sparse array", () => {
|
||||
const schema = z.array(z.string()).nonempty().min(1).max(3);
|
||||
|
||||
expect(() => schema.parse(new Array(3))).toThrow();
|
||||
});
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
/// string
|
||||
const stringSchema = z.string();
|
||||
|
||||
test("string async parse", async () => {
|
||||
const goodData = "XXX";
|
||||
const badData = 12;
|
||||
|
||||
const goodResult = await stringSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await stringSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// number
|
||||
const numberSchema = z.number();
|
||||
test("number async parse", async () => {
|
||||
const goodData = 1234.2353;
|
||||
const badData = "1234";
|
||||
|
||||
const goodResult = await numberSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await numberSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// bigInt
|
||||
const bigIntSchema = z.bigint();
|
||||
test("bigInt async parse", async () => {
|
||||
const goodData = BigInt(145);
|
||||
const badData = 134;
|
||||
|
||||
const goodResult = await bigIntSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await bigIntSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// boolean
|
||||
const booleanSchema = z.boolean();
|
||||
test("boolean async parse", async () => {
|
||||
const goodData = true;
|
||||
const badData = 1;
|
||||
|
||||
const goodResult = await booleanSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await booleanSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// date
|
||||
const dateSchema = z.date();
|
||||
test("date async parse", async () => {
|
||||
const goodData = new Date();
|
||||
const badData = new Date().toISOString();
|
||||
|
||||
const goodResult = await dateSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await dateSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// undefined
|
||||
const undefinedSchema = z.undefined();
|
||||
test("undefined async parse", async () => {
|
||||
const goodData = undefined;
|
||||
const badData = "XXX";
|
||||
|
||||
const goodResult = await undefinedSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(undefined);
|
||||
|
||||
const badResult = await undefinedSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// null
|
||||
const nullSchema = z.null();
|
||||
test("null async parse", async () => {
|
||||
const goodData = null;
|
||||
const badData = undefined;
|
||||
|
||||
const goodResult = await nullSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await nullSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// any
|
||||
const anySchema = z.any();
|
||||
test("any async parse", async () => {
|
||||
const goodData = [{}];
|
||||
// const badData = 'XXX';
|
||||
|
||||
const goodResult = await anySchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
// const badResult = await anySchema.safeParseAsync(badData);
|
||||
// expect(badResult.success).toBe(false);
|
||||
// if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// unknown
|
||||
const unknownSchema = z.unknown();
|
||||
test("unknown async parse", async () => {
|
||||
const goodData = ["asdf", 124, () => {}];
|
||||
// const badData = 'XXX';
|
||||
|
||||
const goodResult = await unknownSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
// const badResult = await unknownSchema.safeParseAsync(badData);
|
||||
// expect(badResult.success).toBe(false);
|
||||
// if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// void
|
||||
const voidSchema = z.void();
|
||||
test("void async parse", async () => {
|
||||
const goodData = undefined;
|
||||
const badData = 0;
|
||||
|
||||
const goodResult = await voidSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await voidSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// array
|
||||
const arraySchema = z.array(z.string());
|
||||
test("array async parse", async () => {
|
||||
const goodData = ["XXX"];
|
||||
const badData = "XXX";
|
||||
|
||||
const goodResult = await arraySchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await arraySchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// object
|
||||
const objectSchema = z.object({ string: z.string() });
|
||||
test("object async parse", async () => {
|
||||
const goodData = { string: "XXX" };
|
||||
const badData = { string: 12 };
|
||||
|
||||
const goodResult = await objectSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await objectSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// union
|
||||
const unionSchema = z.union([z.string(), z.undefined()]);
|
||||
test("union async parse", async () => {
|
||||
const goodData = undefined;
|
||||
const badData = null;
|
||||
|
||||
const goodResult = await unionSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await unionSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// record
|
||||
const recordSchema = z.record(z.object({}));
|
||||
test("record async parse", async () => {
|
||||
const goodData = { adsf: {}, asdf: {} };
|
||||
const badData = [{}];
|
||||
|
||||
const goodResult = await recordSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await recordSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// function
|
||||
const functionSchema = z.function();
|
||||
test("function async parse", async () => {
|
||||
const goodData = () => {};
|
||||
const badData = "XXX";
|
||||
|
||||
const goodResult = await functionSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(typeof goodResult.data).toEqual("function");
|
||||
|
||||
const badResult = await functionSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// literal
|
||||
const literalSchema = z.literal("asdf");
|
||||
test("literal async parse", async () => {
|
||||
const goodData = "asdf";
|
||||
const badData = "asdff";
|
||||
|
||||
const goodResult = await literalSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await literalSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// enum
|
||||
const enumSchema = z.enum(["fish", "whale"]);
|
||||
test("enum async parse", async () => {
|
||||
const goodData = "whale";
|
||||
const badData = "leopard";
|
||||
|
||||
const goodResult = await enumSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await enumSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// nativeEnum
|
||||
enum nativeEnumTest {
|
||||
asdf = "qwer",
|
||||
}
|
||||
// @ts-ignore
|
||||
const nativeEnumSchema = z.nativeEnum(nativeEnumTest);
|
||||
test("nativeEnum async parse", async () => {
|
||||
const goodData = nativeEnumTest.asdf;
|
||||
const badData = "asdf";
|
||||
|
||||
const goodResult = await nativeEnumSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) expect(goodResult.data).toEqual(goodData);
|
||||
|
||||
const badResult = await nativeEnumSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(false);
|
||||
if (!badResult.success) expect(badResult.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
/// promise
|
||||
const promiseSchema = z.promise(z.number());
|
||||
test("promise async parse good", async () => {
|
||||
const goodData = Promise.resolve(123);
|
||||
|
||||
const goodResult = await promiseSchema.safeParseAsync(goodData);
|
||||
expect(goodResult.success).toBe(true);
|
||||
if (goodResult.success) {
|
||||
expect(goodResult.data).toBeInstanceOf(Promise);
|
||||
const data = await goodResult.data;
|
||||
expect(data).toEqual(123);
|
||||
// expect(goodResult.data).resolves.toEqual(124);
|
||||
// return goodResult.data;
|
||||
} else {
|
||||
throw new Error("success should be true");
|
||||
}
|
||||
});
|
||||
|
||||
test("promise async parse bad", async () => {
|
||||
const badData = Promise.resolve("XXX");
|
||||
const badResult = await promiseSchema.safeParseAsync(badData);
|
||||
expect(badResult.success).toBe(true);
|
||||
if (badResult.success) {
|
||||
await expect(badResult.data).rejects.toBeInstanceOf(z.ZodError);
|
||||
} else {
|
||||
throw new Error("success should be true");
|
||||
}
|
||||
});
|
||||
|
||||
test("async validation non-empty strings", async () => {
|
||||
const base = z.object({
|
||||
hello: z.string().refine((x) => x && x.length > 0),
|
||||
foo: z.string().refine((x) => x && x.length > 0),
|
||||
});
|
||||
|
||||
const testval = { hello: "", foo: "" };
|
||||
const result1 = base.safeParse(testval);
|
||||
const result2 = base.safeParseAsync(testval);
|
||||
|
||||
const r1 = result1;
|
||||
await result2.then((r2) => {
|
||||
if (r1.success === false && r2.success === false) expect(r1.error.issues.length).toBe(r2.error.issues.length); // <--- r1 has length 2, r2 has length 1
|
||||
});
|
||||
});
|
||||
|
||||
test("async validation multiple errors 1", async () => {
|
||||
const base = z.object({
|
||||
hello: z.string(),
|
||||
foo: z.number(),
|
||||
});
|
||||
|
||||
const testval = { hello: 3, foo: "hello" };
|
||||
const result1 = base.safeParse(testval);
|
||||
const result2 = base.safeParseAsync(testval);
|
||||
|
||||
const r1 = result1;
|
||||
await result2.then((r2) => {
|
||||
if (r1.success === false && r2.success === false) expect(r2.error.issues.length).toBe(r1.error.issues.length);
|
||||
});
|
||||
});
|
||||
|
||||
test("async validation multiple errors 2", async () => {
|
||||
const base = (is_async?: boolean) =>
|
||||
z.object({
|
||||
hello: z.string(),
|
||||
foo: z.object({
|
||||
bar: z.number().refine(is_async ? async () => false : () => false),
|
||||
}),
|
||||
});
|
||||
|
||||
const testval = { hello: 3, foo: { bar: 4 } };
|
||||
const result1 = base().safeParse(testval);
|
||||
const result2 = base(true).safeParseAsync(testval);
|
||||
|
||||
const r1 = result1;
|
||||
await result2.then((r2) => {
|
||||
if (r1.success === false && r2.success === false) expect(r2.error.issues.length).toBe(r1.error.issues.length);
|
||||
});
|
||||
});
|
||||
|
||||
test("ensure early async failure prevents follow-up refinement checks", async () => {
|
||||
let count = 0;
|
||||
const base = z.object({
|
||||
hello: z.string(),
|
||||
foo: z
|
||||
.number()
|
||||
.refine(async () => {
|
||||
count++;
|
||||
return true;
|
||||
})
|
||||
.refine(async () => {
|
||||
count++;
|
||||
return true;
|
||||
}, "Good"),
|
||||
});
|
||||
|
||||
const testval = { hello: "bye", foo: 3 };
|
||||
const result = await base.safeParseAsync(testval);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toBe(1);
|
||||
expect(count).toBe(1);
|
||||
}
|
||||
|
||||
// await result.then((r) => {
|
||||
// if (r.success === false) expect(r.error.issues.length).toBe(1);
|
||||
// expect(count).toBe(2);
|
||||
// });
|
||||
});
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("parse async test", async () => {
|
||||
const schema1 = z.string().refine(async (_val) => false);
|
||||
expect(() => schema1.parse("asdf")).toThrow();
|
||||
|
||||
const schema2 = z.string().refine((_val) => Promise.resolve(true));
|
||||
return await expect(() => schema2.parse("asdf")).toThrow();
|
||||
});
|
||||
|
||||
test("parseAsync async test", async () => {
|
||||
const schema1 = z.string().refine(async (_val) => true);
|
||||
await schema1.parseAsync("asdf");
|
||||
|
||||
const schema2 = z.string().refine(async (_val) => false);
|
||||
return await expect(schema2.parseAsync("asdf")).rejects.toBeDefined();
|
||||
// expect(async () => await schema2.parseAsync('asdf')).toThrow();
|
||||
});
|
||||
|
||||
test("parseAsync async test", async () => {
|
||||
// expect.assertions(2);
|
||||
|
||||
const schema1 = z.string().refine((_val) => Promise.resolve(true));
|
||||
const v1 = await schema1.parseAsync("asdf");
|
||||
expect(v1).toEqual("asdf");
|
||||
|
||||
const schema2 = z.string().refine((_val) => Promise.resolve(false));
|
||||
await expect(schema2.parseAsync("asdf")).rejects.toBeDefined();
|
||||
|
||||
const schema3 = z.string().refine((_val) => Promise.resolve(true));
|
||||
await expect(schema3.parseAsync("asdf")).resolves.toEqual("asdf");
|
||||
return await expect(schema3.parseAsync("qwer")).resolves.toEqual("qwer");
|
||||
});
|
||||
|
||||
test("parseAsync async with value", async () => {
|
||||
const schema1 = z.string().refine(async (val) => {
|
||||
return val.length > 5;
|
||||
});
|
||||
await expect(schema1.parseAsync("asdf")).rejects.toBeDefined();
|
||||
|
||||
const v = await schema1.parseAsync("asdf123");
|
||||
return await expect(v).toEqual("asdf123");
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("type guard", () => {
|
||||
const stringToNumber = z.string().transform((arg) => arg.length);
|
||||
|
||||
const s1 = z.object({
|
||||
stringToNumber,
|
||||
});
|
||||
type t1 = z.input<typeof s1>;
|
||||
|
||||
const data = { stringToNumber: "asdf" };
|
||||
const parsed = s1.safeParse(data);
|
||||
if (parsed.success) {
|
||||
util.assertEqual<typeof data, t1>(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("test this binding", () => {
|
||||
const callback = (predicate: (val: string) => boolean) => {
|
||||
return predicate("hello");
|
||||
};
|
||||
|
||||
expect(callback((value) => z.string().safeParse(value).success)).toBe(true); // true
|
||||
expect(callback((value) => z.string().safeParse(value).success)).toBe(true); // true
|
||||
});
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const gtFive = z.bigint().gt(BigInt(5));
|
||||
const gteFive = z.bigint().gte(BigInt(5));
|
||||
const ltFive = z.bigint().lt(BigInt(5));
|
||||
const lteFive = z.bigint().lte(BigInt(5));
|
||||
const positive = z.bigint().positive();
|
||||
const negative = z.bigint().negative();
|
||||
const nonnegative = z.bigint().nonnegative();
|
||||
const nonpositive = z.bigint().nonpositive();
|
||||
const multipleOfFive = z.bigint().multipleOf(BigInt(5));
|
||||
|
||||
test("passing validations", () => {
|
||||
z.bigint().parse(BigInt(1));
|
||||
z.bigint().parse(BigInt(0));
|
||||
z.bigint().parse(BigInt(-1));
|
||||
gtFive.parse(BigInt(6));
|
||||
gteFive.parse(BigInt(5));
|
||||
gteFive.parse(BigInt(6));
|
||||
ltFive.parse(BigInt(4));
|
||||
lteFive.parse(BigInt(5));
|
||||
lteFive.parse(BigInt(4));
|
||||
positive.parse(BigInt(3));
|
||||
negative.parse(BigInt(-2));
|
||||
nonnegative.parse(BigInt(0));
|
||||
nonnegative.parse(BigInt(7));
|
||||
nonpositive.parse(BigInt(0));
|
||||
nonpositive.parse(BigInt(-12));
|
||||
multipleOfFive.parse(BigInt(15));
|
||||
});
|
||||
|
||||
test("failing validations", () => {
|
||||
expect(() => gtFive.parse(BigInt(5))).toThrow();
|
||||
expect(() => gteFive.parse(BigInt(4))).toThrow();
|
||||
expect(() => ltFive.parse(BigInt(5))).toThrow();
|
||||
expect(() => lteFive.parse(BigInt(6))).toThrow();
|
||||
expect(() => positive.parse(BigInt(0))).toThrow();
|
||||
expect(() => positive.parse(BigInt(-2))).toThrow();
|
||||
expect(() => negative.parse(BigInt(0))).toThrow();
|
||||
expect(() => negative.parse(BigInt(3))).toThrow();
|
||||
expect(() => nonnegative.parse(BigInt(-1))).toThrow();
|
||||
expect(() => nonpositive.parse(BigInt(1))).toThrow();
|
||||
expect(() => multipleOfFive.parse(BigInt(13))).toThrow();
|
||||
});
|
||||
|
||||
test("min max getters", () => {
|
||||
expect(z.bigint().min(BigInt(5)).minValue).toEqual(BigInt(5));
|
||||
expect(z.bigint().min(BigInt(5)).min(BigInt(10)).minValue).toEqual(BigInt(10));
|
||||
|
||||
expect(z.bigint().max(BigInt(5)).maxValue).toEqual(BigInt(5));
|
||||
expect(z.bigint().max(BigInt(5)).max(BigInt(1)).maxValue).toEqual(BigInt(1));
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("branded types", () => {
|
||||
const mySchema = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
})
|
||||
.brand<"superschema">();
|
||||
|
||||
// simple branding
|
||||
type MySchema = z.infer<typeof mySchema>;
|
||||
util.assertEqual<MySchema, { name: string } & { [z.BRAND]: { superschema: true } }>(true);
|
||||
|
||||
const doStuff = (arg: MySchema) => arg;
|
||||
doStuff(mySchema.parse({ name: "hello there" }));
|
||||
|
||||
// inheritance
|
||||
const extendedSchema = mySchema.brand<"subschema">();
|
||||
type ExtendedSchema = z.infer<typeof extendedSchema>;
|
||||
util.assertEqual<ExtendedSchema, { name: string } & z.BRAND<"superschema"> & z.BRAND<"subschema">>(true);
|
||||
|
||||
doStuff(extendedSchema.parse({ name: "hello again" }));
|
||||
|
||||
// number branding
|
||||
const numberSchema = z.number().brand<42>();
|
||||
type NumberSchema = z.infer<typeof numberSchema>;
|
||||
util.assertEqual<NumberSchema, number & { [z.BRAND]: { 42: true } }>(true);
|
||||
|
||||
// symbol branding
|
||||
const MyBrand: unique symbol = Symbol("hello");
|
||||
type MyBrand = typeof MyBrand;
|
||||
const symbolBrand = z.number().brand<"sup">().brand<typeof MyBrand>();
|
||||
type SymbolBrand = z.infer<typeof symbolBrand>;
|
||||
// number & { [z.BRAND]: { sup: true, [MyBrand]: true } }
|
||||
util.assertEqual<SymbolBrand, number & z.BRAND<"sup"> & z.BRAND<MyBrand>>(true);
|
||||
|
||||
// keeping brands out of input types
|
||||
const age = z.number().brand<"age">();
|
||||
|
||||
type Age = z.infer<typeof age>;
|
||||
type AgeInput = z.input<typeof age>;
|
||||
|
||||
util.assertEqual<AgeInput, Age>(false);
|
||||
util.assertEqual<number, AgeInput>(true);
|
||||
util.assertEqual<number & z.BRAND<"age">, Age>(true);
|
||||
|
||||
// @ts-expect-error
|
||||
doStuff({ name: "hello there!" });
|
||||
});
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("basic catch", () => {
|
||||
expect(z.string().catch("default").parse(undefined)).toBe("default");
|
||||
});
|
||||
|
||||
test("catch fn does not run when parsing succeeds", () => {
|
||||
let isCalled = false;
|
||||
const cb = () => {
|
||||
isCalled = true;
|
||||
return "asdf";
|
||||
};
|
||||
expect(z.string().catch(cb).parse("test")).toBe("test");
|
||||
expect(isCalled).toEqual(false);
|
||||
});
|
||||
|
||||
test("basic catch async", async () => {
|
||||
const result = await z.string().catch("default").parseAsync(1243);
|
||||
expect(result).toBe("default");
|
||||
});
|
||||
|
||||
test("catch replace wrong types", () => {
|
||||
expect(z.string().catch("default").parse(true)).toBe("default");
|
||||
expect(z.string().catch("default").parse(true)).toBe("default");
|
||||
expect(z.string().catch("default").parse(15)).toBe("default");
|
||||
expect(z.string().catch("default").parse([])).toBe("default");
|
||||
expect(z.string().catch("default").parse(new Map())).toBe("default");
|
||||
expect(z.string().catch("default").parse(new Set())).toBe("default");
|
||||
expect(z.string().catch("default").parse({})).toBe("default");
|
||||
});
|
||||
|
||||
test("catch with transform", () => {
|
||||
const stringWithDefault = z
|
||||
.string()
|
||||
.transform((val) => val.toUpperCase())
|
||||
.catch("default");
|
||||
expect(stringWithDefault.parse(undefined)).toBe("default");
|
||||
expect(stringWithDefault.parse(15)).toBe("default");
|
||||
expect(stringWithDefault).toBeInstanceOf(z.ZodCatch);
|
||||
expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodEffects);
|
||||
expect(stringWithDefault._def.innerType._def.schema).toBeInstanceOf(z.ZodSchema);
|
||||
|
||||
type inp = z.input<typeof stringWithDefault>;
|
||||
util.assertEqual<inp, unknown>(true);
|
||||
type out = z.output<typeof stringWithDefault>;
|
||||
util.assertEqual<out, string>(true);
|
||||
});
|
||||
|
||||
test("catch on existing optional", () => {
|
||||
const stringWithDefault = z.string().optional().catch("asdf");
|
||||
expect(stringWithDefault.parse(undefined)).toBe(undefined);
|
||||
expect(stringWithDefault.parse(15)).toBe("asdf");
|
||||
expect(stringWithDefault).toBeInstanceOf(z.ZodCatch);
|
||||
expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodOptional);
|
||||
expect(stringWithDefault._def.innerType._def.innerType).toBeInstanceOf(z.ZodString);
|
||||
|
||||
type inp = z.input<typeof stringWithDefault>;
|
||||
util.assertEqual<inp, unknown>(true);
|
||||
type out = z.output<typeof stringWithDefault>;
|
||||
util.assertEqual<out, string | undefined>(true);
|
||||
});
|
||||
|
||||
test("optional on catch", () => {
|
||||
const stringWithDefault = z.string().catch("asdf").optional();
|
||||
|
||||
type inp = z.input<typeof stringWithDefault>;
|
||||
util.assertEqual<inp, unknown>(true);
|
||||
type out = z.output<typeof stringWithDefault>;
|
||||
util.assertEqual<out, string | undefined>(true);
|
||||
});
|
||||
|
||||
test("complex chain example", () => {
|
||||
const complex = z
|
||||
.string()
|
||||
.catch("asdf")
|
||||
.transform((val) => val + "!")
|
||||
.transform((val) => val.toUpperCase())
|
||||
.catch("qwer")
|
||||
.removeCatch()
|
||||
.optional()
|
||||
.catch("asdfasdf");
|
||||
|
||||
expect(complex.parse("qwer")).toBe("QWER!");
|
||||
expect(complex.parse(15)).toBe("ASDF!");
|
||||
expect(complex.parse(true)).toBe("ASDF!");
|
||||
});
|
||||
|
||||
test("removeCatch", () => {
|
||||
const stringWithRemovedDefault = z.string().catch("asdf").removeCatch();
|
||||
|
||||
type out = z.output<typeof stringWithRemovedDefault>;
|
||||
util.assertEqual<out, string>(true);
|
||||
});
|
||||
|
||||
test("nested", () => {
|
||||
const inner = z.string().catch("asdf");
|
||||
const outer = z.object({ inner }).catch({
|
||||
inner: "asdf",
|
||||
});
|
||||
type input = z.input<typeof outer>;
|
||||
util.assertEqual<input, unknown>(true);
|
||||
type out = z.output<typeof outer>;
|
||||
util.assertEqual<out, { inner: string }>(true);
|
||||
expect(outer.parse(undefined)).toEqual({ inner: "asdf" });
|
||||
expect(outer.parse({})).toEqual({ inner: "asdf" });
|
||||
expect(outer.parse({ inner: undefined })).toEqual({ inner: "asdf" });
|
||||
});
|
||||
|
||||
test("chained catch", () => {
|
||||
const stringWithDefault = z.string().catch("inner").catch("outer");
|
||||
const result = stringWithDefault.parse(undefined);
|
||||
expect(result).toEqual("inner");
|
||||
const resultDiff = stringWithDefault.parse(5);
|
||||
expect(resultDiff).toEqual("inner");
|
||||
});
|
||||
|
||||
test("factory", () => {
|
||||
z.ZodCatch.create(z.string(), {
|
||||
catch: "asdf",
|
||||
}).parse(undefined);
|
||||
});
|
||||
|
||||
test("native enum", () => {
|
||||
enum Fruits {
|
||||
apple = "apple",
|
||||
orange = "orange",
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
fruit: z.nativeEnum(Fruits).catch(Fruits.apple),
|
||||
});
|
||||
|
||||
expect(schema.parse({})).toEqual({ fruit: Fruits.apple });
|
||||
expect(schema.parse({ fruit: 15 })).toEqual({ fruit: Fruits.apple });
|
||||
});
|
||||
|
||||
test("enum", () => {
|
||||
const schema = z.object({
|
||||
fruit: z.enum(["apple", "orange"]).catch("apple"),
|
||||
});
|
||||
|
||||
expect(schema.parse({})).toEqual({ fruit: "apple" });
|
||||
expect(schema.parse({ fruit: true })).toEqual({ fruit: "apple" });
|
||||
expect(schema.parse({ fruit: 15 })).toEqual({ fruit: "apple" });
|
||||
});
|
||||
|
||||
test("reported issues with nested usage", () => {
|
||||
const schema = z.object({
|
||||
string: z.string(),
|
||||
obj: z.object({
|
||||
sub: z.object({
|
||||
lit: z.literal("a"),
|
||||
subCatch: z.number().catch(23),
|
||||
}),
|
||||
midCatch: z.number().catch(42),
|
||||
}),
|
||||
number: z.number().catch(0),
|
||||
bool: z.boolean(),
|
||||
});
|
||||
|
||||
try {
|
||||
schema.parse({
|
||||
string: {},
|
||||
obj: {
|
||||
sub: {
|
||||
lit: "b",
|
||||
subCatch: "24",
|
||||
},
|
||||
midCatch: 444,
|
||||
},
|
||||
number: "",
|
||||
bool: "yes",
|
||||
});
|
||||
} catch (error) {
|
||||
const issues = (error as z.ZodError).issues;
|
||||
|
||||
expect(issues.length).toEqual(3);
|
||||
expect(issues[0].message).toMatch("string");
|
||||
expect(issues[1].message).toMatch("literal");
|
||||
expect(issues[2].message).toMatch("boolean");
|
||||
}
|
||||
});
|
||||
|
||||
test("catch error", () => {
|
||||
let catchError: z.ZodError | undefined = undefined;
|
||||
|
||||
const schema = z.object({
|
||||
age: z.number(),
|
||||
name: z.string().catch((ctx) => {
|
||||
catchError = ctx.error;
|
||||
|
||||
return "John Doe";
|
||||
}),
|
||||
});
|
||||
|
||||
const result = schema.safeParse({
|
||||
age: null,
|
||||
name: null,
|
||||
});
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
expect(!result.success && result.error.issues.length).toEqual(1);
|
||||
expect(!result.success && result.error.issues[0].message).toMatch("number");
|
||||
|
||||
expect(catchError).toBeInstanceOf(z.ZodError);
|
||||
expect(catchError !== undefined && (catchError as z.ZodError).issues.length).toEqual(1);
|
||||
expect(catchError !== undefined && (catchError as z.ZodError).issues[0].message).toMatch("string");
|
||||
});
|
||||
|
||||
test("ctx.input", () => {
|
||||
const schema = z.string().catch((ctx) => {
|
||||
return String(ctx.input);
|
||||
});
|
||||
|
||||
expect(schema.parse(123)).toEqual("123");
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("string coercion", () => {
|
||||
const schema = z.coerce.string();
|
||||
expect(schema.parse("sup")).toEqual("sup");
|
||||
expect(schema.parse("")).toEqual("");
|
||||
expect(schema.parse(12)).toEqual("12");
|
||||
expect(schema.parse(0)).toEqual("0");
|
||||
expect(schema.parse(-12)).toEqual("-12");
|
||||
expect(schema.parse(3.14)).toEqual("3.14");
|
||||
expect(schema.parse(BigInt(15))).toEqual("15");
|
||||
expect(schema.parse(Number.NaN)).toEqual("NaN");
|
||||
expect(schema.parse(Number.POSITIVE_INFINITY)).toEqual("Infinity");
|
||||
expect(schema.parse(Number.NEGATIVE_INFINITY)).toEqual("-Infinity");
|
||||
expect(schema.parse(true)).toEqual("true");
|
||||
expect(schema.parse(false)).toEqual("false");
|
||||
expect(schema.parse(null)).toEqual("null");
|
||||
expect(schema.parse(undefined)).toEqual("undefined");
|
||||
expect(schema.parse({ hello: "world!" })).toEqual("[object Object]");
|
||||
expect(schema.parse(["item", "another_item"])).toEqual("item,another_item");
|
||||
expect(schema.parse([])).toEqual("");
|
||||
expect(schema.parse(new Date("2022-01-01T00:00:00.000Z"))).toEqual(new Date("2022-01-01T00:00:00.000Z").toString());
|
||||
});
|
||||
|
||||
test("number coercion", () => {
|
||||
const schema = z.coerce.number();
|
||||
expect(schema.parse("12")).toEqual(12);
|
||||
expect(schema.parse("0")).toEqual(0);
|
||||
expect(schema.parse("-12")).toEqual(-12);
|
||||
expect(schema.parse("3.14")).toEqual(3.14);
|
||||
expect(schema.parse("")).toEqual(0);
|
||||
expect(() => schema.parse("NOT_A_NUMBER")).toThrow(); // z.ZodError
|
||||
expect(schema.parse(12)).toEqual(12);
|
||||
expect(schema.parse(0)).toEqual(0);
|
||||
expect(schema.parse(-12)).toEqual(-12);
|
||||
expect(schema.parse(3.14)).toEqual(3.14);
|
||||
expect(schema.parse(BigInt(15))).toEqual(15);
|
||||
expect(() => schema.parse(Number.NaN)).toThrow(); // z.ZodError
|
||||
expect(schema.parse(Number.POSITIVE_INFINITY)).toEqual(Number.POSITIVE_INFINITY);
|
||||
expect(schema.parse(Number.NEGATIVE_INFINITY)).toEqual(Number.NEGATIVE_INFINITY);
|
||||
expect(schema.parse(true)).toEqual(1);
|
||||
expect(schema.parse(false)).toEqual(0);
|
||||
expect(schema.parse(null)).toEqual(0);
|
||||
expect(() => schema.parse(undefined)).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse({ hello: "world!" })).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse(["item", "another_item"])).toThrow(); // z.ZodError
|
||||
expect(schema.parse([])).toEqual(0);
|
||||
expect(schema.parse(new Date(1670139203496))).toEqual(1670139203496);
|
||||
});
|
||||
|
||||
test("boolean coercion", () => {
|
||||
const schema = z.coerce.boolean();
|
||||
expect(schema.parse("true")).toEqual(true);
|
||||
expect(schema.parse("false")).toEqual(true);
|
||||
expect(schema.parse("0")).toEqual(true);
|
||||
expect(schema.parse("1")).toEqual(true);
|
||||
expect(schema.parse("")).toEqual(false);
|
||||
expect(schema.parse(1)).toEqual(true);
|
||||
expect(schema.parse(0)).toEqual(false);
|
||||
expect(schema.parse(-1)).toEqual(true);
|
||||
expect(schema.parse(3.14)).toEqual(true);
|
||||
expect(schema.parse(BigInt(15))).toEqual(true);
|
||||
expect(schema.parse(Number.NaN)).toEqual(false);
|
||||
expect(schema.parse(Number.POSITIVE_INFINITY)).toEqual(true);
|
||||
expect(schema.parse(Number.NEGATIVE_INFINITY)).toEqual(true);
|
||||
expect(schema.parse(true)).toEqual(true);
|
||||
expect(schema.parse(false)).toEqual(false);
|
||||
expect(schema.parse(null)).toEqual(false);
|
||||
expect(schema.parse(undefined)).toEqual(false);
|
||||
expect(schema.parse({ hello: "world!" })).toEqual(true);
|
||||
expect(schema.parse(["item", "another_item"])).toEqual(true);
|
||||
expect(schema.parse([])).toEqual(true);
|
||||
expect(schema.parse(new Date(1670139203496))).toEqual(true);
|
||||
});
|
||||
|
||||
test("bigint coercion", () => {
|
||||
const schema = z.coerce.bigint();
|
||||
expect(schema.parse("5")).toEqual(BigInt(5));
|
||||
expect(schema.parse("0")).toEqual(BigInt(0));
|
||||
expect(schema.parse("-5")).toEqual(BigInt(-5));
|
||||
expect(() => schema.parse("3.14")).toThrow(); // not a z.ZodError!
|
||||
expect(schema.parse("")).toEqual(BigInt(0));
|
||||
expect(() => schema.parse("NOT_A_NUMBER")).toThrow(); // not a z.ZodError!
|
||||
expect(schema.parse(5)).toEqual(BigInt(5));
|
||||
expect(schema.parse(0)).toEqual(BigInt(0));
|
||||
expect(schema.parse(-5)).toEqual(BigInt(-5));
|
||||
expect(() => schema.parse(3.14)).toThrow(); // not a z.ZodError!
|
||||
expect(schema.parse(BigInt(5))).toEqual(BigInt(5));
|
||||
expect(() => schema.parse(Number.NaN)).toThrow(); // not a z.ZodError!
|
||||
expect(() => schema.parse(Number.POSITIVE_INFINITY)).toThrow(); // not a z.ZodError!
|
||||
expect(() => schema.parse(Number.NEGATIVE_INFINITY)).toThrow(); // not a z.ZodError!
|
||||
expect(schema.parse(true)).toEqual(BigInt(1));
|
||||
expect(schema.parse(false)).toEqual(BigInt(0));
|
||||
expect(() => schema.parse(null)).toThrow(); // not a z.ZodError!
|
||||
expect(() => schema.parse(undefined)).toThrow(); // not a z.ZodError!
|
||||
expect(() => schema.parse({ hello: "world!" })).toThrow(); // not a z.ZodError!
|
||||
expect(() => schema.parse(["item", "another_item"])).toThrow(); // not a z.ZodError!
|
||||
expect(schema.parse([])).toEqual(BigInt(0));
|
||||
expect(schema.parse(new Date(1670139203496))).toEqual(BigInt(1670139203496));
|
||||
});
|
||||
|
||||
test("date coercion", () => {
|
||||
const schema = z.coerce.date();
|
||||
expect(schema.parse(new Date().toDateString())).toBeInstanceOf(Date);
|
||||
expect(schema.parse(new Date().toISOString())).toBeInstanceOf(Date);
|
||||
expect(schema.parse(new Date().toUTCString())).toBeInstanceOf(Date);
|
||||
expect(schema.parse("5")).toBeInstanceOf(Date);
|
||||
expect(schema.parse("2000-01-01")).toBeInstanceOf(Date);
|
||||
// expect(schema.parse("0")).toBeInstanceOf(Date);
|
||||
// expect(schema.parse("-5")).toBeInstanceOf(Date);
|
||||
// expect(schema.parse("3.14")).toBeInstanceOf(Date);
|
||||
expect(() => schema.parse("")).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse("NOT_A_DATE")).toThrow(); // z.ZodError
|
||||
expect(schema.parse(5)).toBeInstanceOf(Date);
|
||||
expect(schema.parse(0)).toBeInstanceOf(Date);
|
||||
expect(schema.parse(-5)).toBeInstanceOf(Date);
|
||||
expect(schema.parse(3.14)).toBeInstanceOf(Date);
|
||||
expect(() => schema.parse(BigInt(5))).toThrow(); // not a z.ZodError!
|
||||
expect(() => schema.parse(Number.NaN)).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse(Number.POSITIVE_INFINITY)).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse(Number.NEGATIVE_INFINITY)).toThrow(); // z.ZodError
|
||||
expect(schema.parse(true)).toBeInstanceOf(Date);
|
||||
expect(schema.parse(false)).toBeInstanceOf(Date);
|
||||
expect(schema.parse(null)).toBeInstanceOf(Date);
|
||||
expect(() => schema.parse(undefined)).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse({ hello: "world!" })).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse(["item", "another_item"])).toThrow(); // z.ZodError
|
||||
expect(() => schema.parse([])).toThrow(); // z.ZodError
|
||||
expect(schema.parse(new Date())).toBeInstanceOf(Date);
|
||||
});
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { expect, test } from "vitest";
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const crazySchema = z.object({
|
||||
tuple: z.tuple([
|
||||
z.string().nullable().optional(),
|
||||
z.number().nullable().optional(),
|
||||
z.boolean().nullable().optional(),
|
||||
z.null().nullable().optional(),
|
||||
z.undefined().nullable().optional(),
|
||||
z.literal("1234").nullable().optional(),
|
||||
]),
|
||||
merged: z
|
||||
.object({
|
||||
k1: z.string().optional(),
|
||||
})
|
||||
.merge(z.object({ k1: z.string().nullable(), k2: z.number() })),
|
||||
union: z.array(z.union([z.literal("asdf"), z.literal(12)])).nonempty(),
|
||||
array: z.array(z.number()),
|
||||
// sumTransformer: z.transformer(z.array(z.number()), z.number(), (arg) => {
|
||||
// return arg.reduce((a, b) => a + b, 0);
|
||||
// }),
|
||||
sumMinLength: z.array(z.number()).refine((arg) => arg.length > 5),
|
||||
intersection: z.intersection(z.object({ p1: z.string().optional() }), z.object({ p1: z.number().optional() })),
|
||||
enum: z.intersection(z.enum(["zero", "one"]), z.enum(["one", "two"])),
|
||||
nonstrict: z.object({ points: z.number() }).nonstrict(),
|
||||
numProm: z.promise(z.number()),
|
||||
lenfun: z.function(z.tuple([z.string()]), z.boolean()),
|
||||
});
|
||||
|
||||
// const asyncCrazySchema = crazySchema.extend({
|
||||
// // async_transform: z.transformer(
|
||||
// // z.array(z.number()),
|
||||
// // z.number(),
|
||||
// // async (arg) => {
|
||||
// // return arg.reduce((a, b) => a + b, 0);
|
||||
// // }
|
||||
// // ),
|
||||
// async_refine: z.array(z.number()).refine(async (arg) => arg.length > 5),
|
||||
// });
|
||||
|
||||
test("parse", () => {
|
||||
const input = {
|
||||
tuple: ["asdf", 1234, true, null, undefined, "1234"],
|
||||
merged: { k1: "asdf", k2: 12 },
|
||||
union: ["asdf", 12, "asdf", 12, "asdf", 12],
|
||||
array: [12, 15, 16],
|
||||
// sumTransformer: [12, 15, 16],
|
||||
sumMinLength: [12, 15, 16, 98, 24, 63],
|
||||
intersection: {},
|
||||
enum: "one",
|
||||
nonstrict: { points: 1234 },
|
||||
numProm: Promise.resolve(12),
|
||||
lenfun: (x: string) => x.length,
|
||||
};
|
||||
|
||||
const result = crazySchema.parse(input);
|
||||
|
||||
// Verify the parsed result structure
|
||||
expect(result.tuple).toEqual(input.tuple);
|
||||
expect(result.merged).toEqual(input.merged);
|
||||
expect(result.union).toEqual(input.union);
|
||||
expect(result.array).toEqual(input.array);
|
||||
expect(result.sumMinLength).toEqual(input.sumMinLength);
|
||||
expect(result.intersection).toEqual(input.intersection);
|
||||
expect(result.enum).toEqual(input.enum);
|
||||
expect(result.nonstrict).toEqual(input.nonstrict);
|
||||
expect(result.numProm).toBeInstanceOf(Promise);
|
||||
expect(typeof result.lenfun).toBe("function");
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("passing validations", () => {
|
||||
const example1 = z.custom<number>((x) => typeof x === "number");
|
||||
example1.parse(1234);
|
||||
expect(() => example1.parse({})).toThrow();
|
||||
});
|
||||
|
||||
test("string params", () => {
|
||||
const example1 = z.custom<number>((x) => typeof x !== "number", "customerr");
|
||||
const result = example1.safeParse(1234);
|
||||
expect(result.success).toEqual(false);
|
||||
// @ts-ignore
|
||||
expect(JSON.stringify(result.error).includes("customerr")).toEqual(true);
|
||||
});
|
||||
|
||||
test("async validations", async () => {
|
||||
const example1 = z.custom<number>(async (x) => {
|
||||
return typeof x === "number";
|
||||
});
|
||||
const r1 = await example1.safeParseAsync(1234);
|
||||
expect(r1.success).toEqual(true);
|
||||
expect(r1.data).toEqual(1234);
|
||||
|
||||
const r2 = await example1.safeParseAsync("asdf");
|
||||
expect(r2.success).toEqual(false);
|
||||
expect(r2.error!.issues.length).toEqual(1);
|
||||
});
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const beforeBenchmarkDate = new Date(2022, 10, 4);
|
||||
const benchmarkDate = new Date(2022, 10, 5);
|
||||
const afterBenchmarkDate = new Date(2022, 10, 6);
|
||||
|
||||
const minCheck = z.date().min(benchmarkDate);
|
||||
const maxCheck = z.date().max(benchmarkDate);
|
||||
|
||||
test("passing validations", () => {
|
||||
minCheck.parse(benchmarkDate);
|
||||
minCheck.parse(afterBenchmarkDate);
|
||||
|
||||
maxCheck.parse(benchmarkDate);
|
||||
maxCheck.parse(beforeBenchmarkDate);
|
||||
});
|
||||
|
||||
test("failing validations", () => {
|
||||
expect(() => minCheck.parse(beforeBenchmarkDate)).toThrow();
|
||||
expect(() => maxCheck.parse(afterBenchmarkDate)).toThrow();
|
||||
});
|
||||
|
||||
test("min max getters", () => {
|
||||
expect(minCheck.minDate).toEqual(benchmarkDate);
|
||||
expect(minCheck.min(afterBenchmarkDate).minDate).toEqual(afterBenchmarkDate);
|
||||
|
||||
expect(maxCheck.maxDate).toEqual(benchmarkDate);
|
||||
expect(maxCheck.max(beforeBenchmarkDate).maxDate).toEqual(beforeBenchmarkDate);
|
||||
});
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("test", () => {
|
||||
z;
|
||||
});
|
||||
|
||||
// const fish = z.object({
|
||||
// name: z.string(),
|
||||
// props: z.object({
|
||||
// color: z.string(),
|
||||
// numScales: z.number(),
|
||||
// }),
|
||||
// });
|
||||
|
||||
// const nonStrict = z
|
||||
// .object({
|
||||
// name: z.string(),
|
||||
// color: z.string(),
|
||||
// })
|
||||
// .nonstrict();
|
||||
|
||||
// test('object pick type', () => {
|
||||
// const modNonStrictFish = nonStrict.omit({ name: true });
|
||||
// modNonStrictFish.parse({ color: 'asdf' });
|
||||
|
||||
// const bad1 = () => fish.pick({ props: { unknown: true } } as any);
|
||||
// const bad2 = () => fish.omit({ name: true, props: { unknown: true } } as any);
|
||||
|
||||
// expect(bad1).toThrow();
|
||||
// expect(bad2).toThrow();
|
||||
// });
|
||||
|
||||
// test('f1', () => {
|
||||
// const f1 = fish.pick(true);
|
||||
// f1.parse({ name: 'a', props: { color: 'b', numScales: 3 } });
|
||||
// });
|
||||
// test('f2', () => {
|
||||
// const f2 = fish.pick({ props: true });
|
||||
// f2.parse({ props: { color: 'asdf', numScales: 1 } });
|
||||
// const badcheck2 = () => f2.parse({ name: 'a', props: { color: 'b', numScales: 3 } } as any);
|
||||
// expect(badcheck2).toThrow();
|
||||
// });
|
||||
// test('f3', () => {
|
||||
// const f3 = fish.pick({ props: { color: true } });
|
||||
// f3.parse({ props: { color: 'b' } });
|
||||
// const badcheck3 = () => f3.parse({ name: 'a', props: { color: 'b', numScales: 3 } } as any);
|
||||
// expect(badcheck3).toThrow();
|
||||
// });
|
||||
// test('f4', () => {
|
||||
// const badcheck4 = () => fish.pick({ props: { color: true, unknown: true } });
|
||||
// expect(badcheck4).toThrow();
|
||||
// });
|
||||
// test('f6', () => {
|
||||
// const f6 = fish.omit({ props: true });
|
||||
// const badcheck6 = () => f6.parse({ name: 'a', props: { color: 'b', numScales: 3 } } as any);
|
||||
// f6.parse({ name: 'adsf' });
|
||||
// expect(badcheck6).toThrow();
|
||||
// });
|
||||
// test('f7', () => {
|
||||
// const f7 = fish.omit({ props: { color: true } });
|
||||
// f7.parse({ name: 'a', props: { numScales: 3 } });
|
||||
// const badcheck7 = () => f7.parse({ name: 'a', props: { color: 'b', numScales: 3 } } as any);
|
||||
// expect(badcheck7).toThrow();
|
||||
// });
|
||||
// test('f8', () => {
|
||||
// const badcheck8 = () => fish.omit({ props: { color: true, unknown: true } });
|
||||
// expect(badcheck8).toThrow();
|
||||
// });
|
||||
// test('f9', () => {
|
||||
// const f9 = nonStrict.pick(true);
|
||||
// f9.parse({ name: 'a', color: 'asdf' });
|
||||
// });
|
||||
// test('f10', () => {
|
||||
// const f10 = nonStrict.pick({ name: true });
|
||||
// f10.parse({ name: 'a' });
|
||||
// const val = f10.parse({ name: 'a', color: 'b' });
|
||||
// expect(val).toEqual({ name: 'a' });
|
||||
// });
|
||||
// test('f12', () => {
|
||||
// const badfcheck12 = () => nonStrict.omit({ color: true, asdf: true });
|
||||
// expect(badfcheck12).toThrow();
|
||||
// });
|
||||
|
||||
// test('array masking', () => {
|
||||
// const fishArray = z.array(fish);
|
||||
// const modFishArray = fishArray.pick({
|
||||
// name: true,
|
||||
// props: {
|
||||
// numScales: true,
|
||||
// },
|
||||
// });
|
||||
|
||||
// modFishArray.parse([{ name: 'fish', props: { numScales: 12 } }]);
|
||||
// const bad1 = () => modFishArray.parse([{ name: 'fish', props: { numScales: 12, color: 'asdf' } }] as any);
|
||||
// expect(bad1).toThrow();
|
||||
// });
|
||||
|
||||
// test('array masking', () => {
|
||||
// const fishArray = z.array(fish);
|
||||
// const fail = () =>
|
||||
// fishArray.pick({
|
||||
// name: true,
|
||||
// props: {
|
||||
// whatever: true,
|
||||
// },
|
||||
// } as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('array masking', () => {
|
||||
// const fishArray = z.array(fish);
|
||||
// const fail = () =>
|
||||
// fishArray.omit({
|
||||
// whateve: true,
|
||||
// } as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('array masking', () => {
|
||||
// const fishArray = z.array(fish);
|
||||
// const modFishList = fishArray.omit({
|
||||
// name: true,
|
||||
// props: {
|
||||
// color: true,
|
||||
// },
|
||||
// });
|
||||
|
||||
// modFishList.parse([{ props: { numScales: 12 } }]);
|
||||
// const fail = () => modFishList.parse([{ name: 'hello', props: { numScales: 12 } }] as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('primitive array masking', () => {
|
||||
// const fishArray = z.array(z.number());
|
||||
// const fail = () => fishArray.pick({} as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('other array masking', () => {
|
||||
// const fishArray = z.array(z.array(z.number()));
|
||||
// const fail = () => fishArray.pick({} as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #1', () => {
|
||||
// const fail = () => fish.pick(1 as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #2', () => {
|
||||
// const fail = () => fish.pick([] as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #3', () => {
|
||||
// const fail = () => fish.pick(false as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #4', () => {
|
||||
// const fail = () => fish.pick('asdf' as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #5', () => {
|
||||
// const fail = () => fish.omit(1 as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #6', () => {
|
||||
// const fail = () => fish.omit([] as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #7', () => {
|
||||
// const fail = () => fish.omit(false as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
|
||||
// test('invalid mask #8', () => {
|
||||
// const fail = () => fish.omit('asdf' as any);
|
||||
// expect(fail).toThrow();
|
||||
// });
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("basic defaults", () => {
|
||||
expect(z.string().default("default").parse(undefined)).toBe("default");
|
||||
});
|
||||
|
||||
test("default with transform", () => {
|
||||
const stringWithDefault = z
|
||||
.string()
|
||||
.transform((val) => val.toUpperCase())
|
||||
.default("default");
|
||||
expect(stringWithDefault.parse(undefined)).toBe("DEFAULT");
|
||||
expect(stringWithDefault).toBeInstanceOf(z.ZodDefault);
|
||||
expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodEffects);
|
||||
expect(stringWithDefault._def.innerType._def.schema).toBeInstanceOf(z.ZodSchema);
|
||||
|
||||
type inp = z.input<typeof stringWithDefault>;
|
||||
util.assertEqual<inp, string | undefined>(true);
|
||||
type out = z.output<typeof stringWithDefault>;
|
||||
util.assertEqual<out, string>(true);
|
||||
});
|
||||
|
||||
test("default on existing optional", () => {
|
||||
const stringWithDefault = z.string().optional().default("asdf");
|
||||
expect(stringWithDefault.parse(undefined)).toBe("asdf");
|
||||
expect(stringWithDefault).toBeInstanceOf(z.ZodDefault);
|
||||
expect(stringWithDefault._def.innerType).toBeInstanceOf(z.ZodOptional);
|
||||
expect(stringWithDefault._def.innerType._def.innerType).toBeInstanceOf(z.ZodString);
|
||||
|
||||
type inp = z.input<typeof stringWithDefault>;
|
||||
util.assertEqual<inp, string | undefined>(true);
|
||||
type out = z.output<typeof stringWithDefault>;
|
||||
util.assertEqual<out, string>(true);
|
||||
});
|
||||
|
||||
test("optional on default", () => {
|
||||
const stringWithDefault = z.string().default("asdf").optional();
|
||||
|
||||
type inp = z.input<typeof stringWithDefault>;
|
||||
util.assertEqual<inp, string | undefined>(true);
|
||||
type out = z.output<typeof stringWithDefault>;
|
||||
util.assertEqual<out, string | undefined>(true);
|
||||
});
|
||||
|
||||
test("complex chain example", () => {
|
||||
const complex = z
|
||||
.string()
|
||||
.default("asdf")
|
||||
.transform((val) => val.toUpperCase())
|
||||
.default("qwer")
|
||||
.removeDefault()
|
||||
.optional()
|
||||
.default("asdfasdf");
|
||||
|
||||
expect(complex.parse(undefined)).toBe("ASDFASDF");
|
||||
});
|
||||
|
||||
test("removeDefault", () => {
|
||||
const stringWithRemovedDefault = z.string().default("asdf").removeDefault();
|
||||
|
||||
type out = z.output<typeof stringWithRemovedDefault>;
|
||||
util.assertEqual<out, string>(true);
|
||||
});
|
||||
|
||||
test("nested", () => {
|
||||
const inner = z.string().default("asdf");
|
||||
const outer = z.object({ inner }).default({
|
||||
inner: undefined,
|
||||
});
|
||||
type input = z.input<typeof outer>;
|
||||
util.assertEqual<input, { inner?: string | undefined } | undefined>(true);
|
||||
type out = z.output<typeof outer>;
|
||||
util.assertEqual<out, { inner: string }>(true);
|
||||
expect(outer.parse(undefined)).toEqual({ inner: "asdf" });
|
||||
expect(outer.parse({})).toEqual({ inner: "asdf" });
|
||||
expect(outer.parse({ inner: undefined })).toEqual({ inner: "asdf" });
|
||||
});
|
||||
|
||||
test("chained defaults", () => {
|
||||
const stringWithDefault = z.string().default("inner").default("outer");
|
||||
const result = stringWithDefault.parse(undefined);
|
||||
expect(result).toEqual("outer");
|
||||
});
|
||||
|
||||
test("factory", () => {
|
||||
expect(z.ZodDefault.create(z.string(), { default: "asdf" }).parse(undefined)).toEqual("asdf");
|
||||
});
|
||||
|
||||
test("native enum", () => {
|
||||
enum Fruits {
|
||||
apple = "apple",
|
||||
orange = "orange",
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
fruit: z.nativeEnum(Fruits).default(Fruits.apple),
|
||||
});
|
||||
|
||||
expect(schema.parse({})).toEqual({ fruit: Fruits.apple });
|
||||
});
|
||||
|
||||
test("enum", () => {
|
||||
const schema = z.object({
|
||||
fruit: z.enum(["apple", "orange"]).default("apple"),
|
||||
});
|
||||
|
||||
expect(schema.parse({})).toEqual({ fruit: "apple" });
|
||||
});
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const description = "a description";
|
||||
|
||||
test("passing `description` to schema should add a description", () => {
|
||||
expect(z.string({ description }).description).toEqual(description);
|
||||
expect(z.number({ description }).description).toEqual(description);
|
||||
expect(z.boolean({ description }).description).toEqual(description);
|
||||
});
|
||||
|
||||
test("`.describe` should add a description", () => {
|
||||
expect(z.string().describe(description).description).toEqual(description);
|
||||
expect(z.number().describe(description).description).toEqual(description);
|
||||
expect(z.boolean().describe(description).description).toEqual(description);
|
||||
});
|
||||
|
||||
test("description should carry over to chained schemas", () => {
|
||||
const schema = z.string({ description });
|
||||
expect(schema.description).toEqual(description);
|
||||
expect(schema.optional().description).toEqual(description);
|
||||
expect(schema.optional().nullable().default("default").description).toEqual(description);
|
||||
});
|
||||
|
||||
test("description should not carry over to chained array schema", () => {
|
||||
const schema = z.string().describe(description);
|
||||
|
||||
expect(schema.description).toEqual(description);
|
||||
expect(schema.array().description).toEqual(undefined);
|
||||
expect(z.array(schema).description).toEqual(undefined);
|
||||
});
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("valid", () => {
|
||||
expect(
|
||||
z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("a"), a: z.string() }),
|
||||
z.object({ type: z.literal("b"), b: z.string() }),
|
||||
])
|
||||
.parse({ type: "a", a: "abc" })
|
||||
).toEqual({ type: "a", a: "abc" });
|
||||
});
|
||||
|
||||
test("valid - discriminator value of various primitive types", () => {
|
||||
const schema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("1"), val: z.literal(1) }),
|
||||
z.object({ type: z.literal(1), val: z.literal(2) }),
|
||||
z.object({ type: z.literal(BigInt(1)), val: z.literal(3) }),
|
||||
z.object({ type: z.literal("true"), val: z.literal(4) }),
|
||||
z.object({ type: z.literal(true), val: z.literal(5) }),
|
||||
z.object({ type: z.literal("null"), val: z.literal(6) }),
|
||||
z.object({ type: z.literal(null), val: z.literal(7) }),
|
||||
z.object({ type: z.literal("undefined"), val: z.literal(8) }),
|
||||
z.object({ type: z.literal(undefined), val: z.literal(9) }),
|
||||
z.object({ type: z.literal("transform"), val: z.literal(10) }),
|
||||
z.object({ type: z.literal("refine"), val: z.literal(11) }),
|
||||
z.object({ type: z.literal("superRefine"), val: z.literal(12) }),
|
||||
]);
|
||||
|
||||
expect(schema.parse({ type: "1", val: 1 })).toEqual({ type: "1", val: 1 });
|
||||
expect(schema.parse({ type: 1, val: 2 })).toEqual({ type: 1, val: 2 });
|
||||
expect(schema.parse({ type: BigInt(1), val: 3 })).toEqual({
|
||||
type: BigInt(1),
|
||||
val: 3,
|
||||
});
|
||||
expect(schema.parse({ type: "true", val: 4 })).toEqual({
|
||||
type: "true",
|
||||
val: 4,
|
||||
});
|
||||
expect(schema.parse({ type: true, val: 5 })).toEqual({
|
||||
type: true,
|
||||
val: 5,
|
||||
});
|
||||
expect(schema.parse({ type: "null", val: 6 })).toEqual({
|
||||
type: "null",
|
||||
val: 6,
|
||||
});
|
||||
expect(schema.parse({ type: null, val: 7 })).toEqual({
|
||||
type: null,
|
||||
val: 7,
|
||||
});
|
||||
expect(schema.parse({ type: "undefined", val: 8 })).toEqual({
|
||||
type: "undefined",
|
||||
val: 8,
|
||||
});
|
||||
expect(schema.parse({ type: undefined, val: 9 })).toEqual({
|
||||
type: undefined,
|
||||
val: 9,
|
||||
});
|
||||
});
|
||||
|
||||
test("invalid - null", () => {
|
||||
try {
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("a"), a: z.string() }),
|
||||
z.object({ type: z.literal("b"), b: z.string() }),
|
||||
]).parse(null);
|
||||
throw new Error();
|
||||
} catch (e: any) {
|
||||
expect(JSON.parse(e.message)).toEqual([
|
||||
{
|
||||
code: z.ZodIssueCode.invalid_type,
|
||||
expected: z.ZodParsedType.object,
|
||||
message: "Expected object, received null",
|
||||
received: z.ZodParsedType.null,
|
||||
path: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid discriminator value", () => {
|
||||
try {
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("a"), a: z.string() }),
|
||||
z.object({ type: z.literal("b"), b: z.string() }),
|
||||
]).parse({ type: "x", a: "abc" });
|
||||
throw new Error();
|
||||
} catch (e: any) {
|
||||
expect(JSON.parse(e.message)).toEqual([
|
||||
{
|
||||
code: z.ZodIssueCode.invalid_union_discriminator,
|
||||
options: ["a", "b"],
|
||||
message: "Invalid discriminator value. Expected 'a' | 'b'",
|
||||
path: ["type"],
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test("valid discriminator value, invalid data", () => {
|
||||
try {
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("a"), a: z.string() }),
|
||||
z.object({ type: z.literal("b"), b: z.string() }),
|
||||
]).parse({ type: "a", b: "abc" });
|
||||
throw new Error();
|
||||
} catch (e: any) {
|
||||
expect(JSON.parse(e.message)).toEqual([
|
||||
{
|
||||
code: z.ZodIssueCode.invalid_type,
|
||||
expected: z.ZodParsedType.string,
|
||||
message: "Required",
|
||||
path: ["a"],
|
||||
received: z.ZodParsedType.undefined,
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test("wrong schema - missing discriminator", () => {
|
||||
try {
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("a"), a: z.string() }),
|
||||
z.object({ b: z.string() }) as any,
|
||||
]);
|
||||
throw new Error();
|
||||
} catch (e: any) {
|
||||
expect(e.message.includes("could not be extracted")).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("wrong schema - duplicate discriminator values", () => {
|
||||
try {
|
||||
z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal("a"), a: z.string() }),
|
||||
z.object({ type: z.literal("a"), b: z.string() }),
|
||||
]);
|
||||
throw new Error();
|
||||
} catch (e: any) {
|
||||
expect(e.message.includes("has duplicate value")).toEqual(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("async - valid", async () => {
|
||||
expect(
|
||||
await z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal("a"),
|
||||
a: z
|
||||
.string()
|
||||
.refine(async () => true)
|
||||
.transform(async (val) => Number(val)),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("b"),
|
||||
b: z.string(),
|
||||
}),
|
||||
])
|
||||
.parseAsync({ type: "a", a: "1" })
|
||||
).toEqual({ type: "a", a: 1 });
|
||||
});
|
||||
|
||||
test("async - invalid", async () => {
|
||||
try {
|
||||
await z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal("a"),
|
||||
a: z
|
||||
.string()
|
||||
.refine(async () => true)
|
||||
.transform(async (val) => val),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("b"),
|
||||
b: z.string(),
|
||||
}),
|
||||
])
|
||||
.parseAsync({ type: "a", a: 1 });
|
||||
throw new Error();
|
||||
} catch (e: any) {
|
||||
expect(JSON.parse(e.message)).toEqual([
|
||||
{
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
received: "number",
|
||||
path: ["a"],
|
||||
message: "Expected string, received number",
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test("valid - literals with .default or .preprocess", () => {
|
||||
const schema = z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal("foo").default("foo"),
|
||||
a: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal("custom"),
|
||||
method: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
type: z.preprocess((val) => String(val), z.literal("bar")),
|
||||
c: z.string(),
|
||||
}),
|
||||
]);
|
||||
expect(schema.parse({ type: "foo", a: "foo" })).toEqual({
|
||||
type: "foo",
|
||||
a: "foo",
|
||||
});
|
||||
});
|
||||
|
||||
test("enum and nativeEnum", () => {
|
||||
enum MyEnum {
|
||||
d = 0,
|
||||
e = "e",
|
||||
}
|
||||
|
||||
const schema = z.discriminatedUnion("key", [
|
||||
z.object({
|
||||
key: z.literal("a"),
|
||||
// Add other properties specific to this option
|
||||
}),
|
||||
z.object({
|
||||
key: z.enum(["b", "c"]),
|
||||
// Add other properties specific to this option
|
||||
}),
|
||||
z.object({
|
||||
key: z.nativeEnum(MyEnum),
|
||||
// Add other properties specific to this option
|
||||
}),
|
||||
]);
|
||||
|
||||
// type schema = z.infer<typeof schema>;
|
||||
|
||||
schema.parse({ key: "a" });
|
||||
schema.parse({ key: "b" });
|
||||
schema.parse({ key: "c" });
|
||||
schema.parse({ key: MyEnum.d });
|
||||
schema.parse({ key: MyEnum.e });
|
||||
schema.parse({ key: "e" });
|
||||
});
|
||||
|
||||
test("branded", () => {
|
||||
const schema = z.discriminatedUnion("key", [
|
||||
z.object({
|
||||
key: z.literal("a"),
|
||||
// Add other properties specific to this option
|
||||
}),
|
||||
z.object({
|
||||
key: z.literal("b").brand("asdfaf"),
|
||||
// Add other properties specific to this option
|
||||
}),
|
||||
]);
|
||||
|
||||
// type schema = z.infer<typeof schema>;
|
||||
|
||||
schema.parse({ key: "a" });
|
||||
schema.parse({ key: "b" });
|
||||
expect(() => {
|
||||
schema.parse({ key: "c" });
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("optional and nullable", () => {
|
||||
const schema = z.discriminatedUnion("key", [
|
||||
z.object({
|
||||
key: z.literal("a").optional(),
|
||||
a: z.literal(true),
|
||||
}),
|
||||
z.object({
|
||||
key: z.literal("b").nullable(),
|
||||
b: z.literal(true),
|
||||
// Add other properties specific to this option
|
||||
}),
|
||||
]);
|
||||
|
||||
type schema = z.infer<typeof schema>;
|
||||
z.util.assertEqual<schema, { key?: "a" | undefined; a: true } | { key: "b" | null; b: true }>(true);
|
||||
|
||||
schema.parse({ key: "a", a: true });
|
||||
schema.parse({ key: undefined, a: true });
|
||||
schema.parse({ key: "b", b: true });
|
||||
schema.parse({ key: null, b: true });
|
||||
expect(() => {
|
||||
schema.parse({ key: null, a: true });
|
||||
}).toThrow();
|
||||
expect(() => {
|
||||
schema.parse({ key: "b", a: true });
|
||||
}).toThrow();
|
||||
|
||||
const value = schema.parse({ key: null, b: true });
|
||||
|
||||
if (!("key" in value)) value.a;
|
||||
if (value.key === undefined) value.a;
|
||||
if (value.key === "a") value.a;
|
||||
if (value.key === "b") value.b;
|
||||
if (value.key === null) value.b;
|
||||
});
|
||||
|
||||
test("readonly array of options", () => {
|
||||
const options = [
|
||||
z.object({ type: z.literal("x"), val: z.literal(1) }),
|
||||
z.object({ type: z.literal("y"), val: z.literal(2) }),
|
||||
] as const;
|
||||
|
||||
expect(z.discriminatedUnion("type", options).parse({ type: "x", val: 1 })).toEqual({ type: "x", val: 1 });
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("create enum", () => {
|
||||
const MyEnum = z.enum(["Red", "Green", "Blue"]);
|
||||
expect(MyEnum.Values.Red).toEqual("Red");
|
||||
expect(MyEnum.Enum.Red).toEqual("Red");
|
||||
expect(MyEnum.enum.Red).toEqual("Red");
|
||||
});
|
||||
|
||||
test("infer enum", () => {
|
||||
const MyEnum = z.enum(["Red", "Green", "Blue"]);
|
||||
type MyEnum = z.infer<typeof MyEnum>;
|
||||
util.assertEqual<MyEnum, "Red" | "Green" | "Blue">(true);
|
||||
});
|
||||
|
||||
test("get options", () => {
|
||||
expect(z.enum(["tuna", "trout"]).options).toEqual(["tuna", "trout"]);
|
||||
});
|
||||
|
||||
test("readonly enum", () => {
|
||||
const HTTP_SUCCESS = ["200", "201"] as const;
|
||||
const arg = z.enum(HTTP_SUCCESS);
|
||||
type arg = z.infer<typeof arg>;
|
||||
util.assertEqual<arg, "200" | "201">(true);
|
||||
|
||||
arg.parse("201");
|
||||
expect(() => arg.parse("202")).toThrow();
|
||||
});
|
||||
|
||||
test("error params", () => {
|
||||
const result = z.enum(["test"], { required_error: "REQUIRED" }).safeParse(undefined);
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].message).toEqual("REQUIRED");
|
||||
}
|
||||
});
|
||||
|
||||
test("extract/exclude", () => {
|
||||
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
|
||||
const FoodEnum = z.enum(foods);
|
||||
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
|
||||
const UnhealthyEnum = FoodEnum.exclude(["Salad"]);
|
||||
const EmptyFoodEnum = FoodEnum.exclude(foods);
|
||||
|
||||
util.assertEqual<z.infer<typeof ItalianEnum>, "Pasta" | "Pizza">(true);
|
||||
util.assertEqual<z.infer<typeof UnhealthyEnum>, "Pasta" | "Pizza" | "Tacos" | "Burgers">(true);
|
||||
// @ts-expect-error TS2344
|
||||
util.assertEqual<typeof EmptyFoodEnum, z.ZodEnum<[]>>(true);
|
||||
util.assertEqual<z.infer<typeof EmptyFoodEnum>, never>(true);
|
||||
});
|
||||
|
||||
test("error map in extract/exclude", () => {
|
||||
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
|
||||
const FoodEnum = z.enum(foods, {
|
||||
errorMap: () => ({ message: "This is not food!" }),
|
||||
});
|
||||
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
|
||||
const foodsError = FoodEnum.safeParse("Cucumbers");
|
||||
const italianError = ItalianEnum.safeParse("Tacos");
|
||||
if (!foodsError.success && !italianError.success) {
|
||||
expect(foodsError.error.issues[0].message).toEqual(italianError.error.issues[0].message);
|
||||
}
|
||||
|
||||
const UnhealthyEnum = FoodEnum.exclude(["Salad"], {
|
||||
errorMap: () => ({ message: "This is not healthy food!" }),
|
||||
});
|
||||
const unhealthyError = UnhealthyEnum.safeParse("Salad");
|
||||
if (!unhealthyError.success) {
|
||||
expect(unhealthyError.error.issues[0].message).toEqual("This is not healthy food!");
|
||||
}
|
||||
});
|
||||
|
||||
test("readonly in ZodEnumDef", () => {
|
||||
let _t!: z.ZodEnumDef<readonly ["a", "b"]>;
|
||||
_t;
|
||||
});
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { ZodError, ZodIssueCode } from "../ZodError.js";
|
||||
import { ZodParsedType } from "../helpers/util.js";
|
||||
|
||||
test("error creation", () => {
|
||||
const err1 = ZodError.create([]);
|
||||
err1.addIssue({
|
||||
code: ZodIssueCode.invalid_type,
|
||||
expected: ZodParsedType.object,
|
||||
received: ZodParsedType.string,
|
||||
path: [],
|
||||
message: "",
|
||||
fatal: true,
|
||||
});
|
||||
err1.isEmpty;
|
||||
|
||||
const err2 = ZodError.create(err1.issues);
|
||||
const err3 = new ZodError([]);
|
||||
err3.addIssues(err1.issues);
|
||||
err3.addIssue(err1.issues[0]);
|
||||
err1.message;
|
||||
err2.message;
|
||||
err3.message;
|
||||
});
|
||||
|
||||
const errorMap: z.ZodErrorMap = (error, ctx) => {
|
||||
if (error.code === ZodIssueCode.invalid_type) {
|
||||
if (error.expected === "string") {
|
||||
return { message: "bad type!" };
|
||||
}
|
||||
}
|
||||
if (error.code === ZodIssueCode.custom) {
|
||||
return { message: `less-than-${error.params?.minimum}` };
|
||||
}
|
||||
return { message: ctx.defaultError };
|
||||
};
|
||||
|
||||
test("type error with custom error map", () => {
|
||||
try {
|
||||
z.string().parse(234, { errorMap });
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
|
||||
expect(zerr.issues[0].code).toEqual(z.ZodIssueCode.invalid_type);
|
||||
expect(zerr.issues[0].message).toEqual(`bad type!`);
|
||||
}
|
||||
});
|
||||
|
||||
test("refinement fail with params", () => {
|
||||
try {
|
||||
z.number()
|
||||
.refine((val) => val >= 3, {
|
||||
params: { minimum: 3 },
|
||||
})
|
||||
.parse(2, { errorMap });
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues[0].code).toEqual(z.ZodIssueCode.custom);
|
||||
expect(zerr.issues[0].message).toEqual(`less-than-3`);
|
||||
}
|
||||
});
|
||||
|
||||
test("custom error with custom errormap", () => {
|
||||
try {
|
||||
z.string()
|
||||
.refine((val) => val.length > 12, {
|
||||
params: { minimum: 13 },
|
||||
message: "override",
|
||||
})
|
||||
.parse("asdf", { errorMap });
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues[0].message).toEqual("override");
|
||||
}
|
||||
});
|
||||
|
||||
test("default error message", () => {
|
||||
try {
|
||||
z.number()
|
||||
.refine((x) => x > 3)
|
||||
.parse(2);
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual("Invalid input");
|
||||
}
|
||||
});
|
||||
|
||||
test("override error in refine", () => {
|
||||
try {
|
||||
z.number()
|
||||
.refine((x) => x > 3, "override")
|
||||
.parse(2);
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual("override");
|
||||
}
|
||||
});
|
||||
|
||||
test("override error in refinement", () => {
|
||||
try {
|
||||
z.number()
|
||||
.refine((x) => x > 3, {
|
||||
message: "override",
|
||||
})
|
||||
.parse(2);
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual("override");
|
||||
}
|
||||
});
|
||||
|
||||
test("array minimum", () => {
|
||||
try {
|
||||
z.array(z.string()).min(3, "tooshort").parse(["asdf", "qwer"]);
|
||||
} catch (err) {
|
||||
const zerr: ZodError = err as any;
|
||||
expect(zerr.issues[0].code).toEqual(ZodIssueCode.too_small);
|
||||
expect(zerr.issues[0].message).toEqual("tooshort");
|
||||
}
|
||||
try {
|
||||
z.array(z.string()).min(3).parse(["asdf", "qwer"]);
|
||||
} catch (err) {
|
||||
const zerr: ZodError = err as any;
|
||||
expect(zerr.issues[0].code).toEqual(ZodIssueCode.too_small);
|
||||
expect(zerr.issues[0].message).toEqual(`Array must contain at least 3 element(s)`);
|
||||
}
|
||||
});
|
||||
|
||||
// implement test for semi-smart union logic that checks for type error on either left or right
|
||||
// test("union smart errors", () => {
|
||||
// // expect.assertions(2);
|
||||
|
||||
// const p1 = z
|
||||
// .union([z.string(), z.number().refine((x) => x > 0)])
|
||||
// .safeParse(-3.2);
|
||||
|
||||
// if (p1.success === true) throw new Error();
|
||||
// expect(p1.success).toBe(false);
|
||||
// expect(p1.error.issues[0].code).toEqual(ZodIssueCode.custom);
|
||||
|
||||
// const p2 = z.union([z.string(), z.number()]).safeParse(false);
|
||||
// // .catch(err => expect(err.issues[0].code).toEqual(ZodIssueCode.invalid_union));
|
||||
// if (p2.success === true) throw new Error();
|
||||
// expect(p2.success).toBe(false);
|
||||
// expect(p2.error.issues[0].code).toEqual(ZodIssueCode.invalid_union);
|
||||
// });
|
||||
|
||||
test("custom path in custom error map", () => {
|
||||
const schema = z.object({
|
||||
items: z.array(z.string()).refine((data) => data.length > 3, {
|
||||
path: ["items-too-few"],
|
||||
}),
|
||||
});
|
||||
|
||||
const errorMap: z.ZodErrorMap = (error) => {
|
||||
expect(error.path.length).toBe(2);
|
||||
return { message: "doesnt matter" };
|
||||
};
|
||||
const result = schema.safeParse({ items: ["first"] }, { errorMap });
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].path).toEqual(["items", "items-too-few"]);
|
||||
}
|
||||
});
|
||||
|
||||
test("error metadata from value", () => {
|
||||
const dynamicRefine = z.string().refine(
|
||||
(val) => val === val.toUpperCase(),
|
||||
(val) => ({ params: { val } })
|
||||
);
|
||||
|
||||
const result = dynamicRefine.safeParse("asdf");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
const sub = result.error.issues[0];
|
||||
expect(result.error.issues[0].code).toEqual("custom");
|
||||
if (sub.code === "custom") {
|
||||
expect(sub.params!.val).toEqual("asdf");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// test("don't call refine after validation failed", () => {
|
||||
// const asdf = z
|
||||
// .union([
|
||||
// z.number(),
|
||||
// z.string().transform(z.number(), (val) => {
|
||||
// return parseFloat(val);
|
||||
// }),
|
||||
// ])
|
||||
// .refine((v) => v >= 1);
|
||||
|
||||
// expect(() => asdf.safeParse("foo")).not.toThrow();
|
||||
// });
|
||||
|
||||
test("root level formatting", () => {
|
||||
const schema = z.string().email();
|
||||
const result = schema.safeParse("asdfsdf");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.format()._errors).toEqual(["Invalid email"]);
|
||||
}
|
||||
});
|
||||
|
||||
test("custom path", () => {
|
||||
const schema = z
|
||||
.object({
|
||||
password: z.string(),
|
||||
confirm: z.string(),
|
||||
})
|
||||
.refine((val) => val.confirm === val.password, { path: ["confirm"] });
|
||||
|
||||
const result = schema.safeParse({
|
||||
password: "peanuts",
|
||||
confirm: "qeanuts",
|
||||
});
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
// nested errors
|
||||
const error = result.error.format();
|
||||
expect(error._errors).toEqual([]);
|
||||
expect(error.password?._errors).toEqual(undefined);
|
||||
expect(error.confirm?._errors).toEqual(["Invalid input"]);
|
||||
}
|
||||
});
|
||||
|
||||
test("custom path", () => {
|
||||
const schema = z
|
||||
.object({
|
||||
password: z.string().min(6),
|
||||
confirm: z.string().min(6),
|
||||
})
|
||||
.refine((val) => val.confirm === val.password);
|
||||
|
||||
const result = schema.safeParse({
|
||||
password: "qwer",
|
||||
confirm: "asdf",
|
||||
});
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues.length).toEqual(3);
|
||||
}
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
inner: z.object({
|
||||
name: z
|
||||
.string()
|
||||
.refine((val) => val.length > 5)
|
||||
.array()
|
||||
.refine((val) => val.length <= 1),
|
||||
}),
|
||||
});
|
||||
|
||||
test("no abort early on refinements", () => {
|
||||
const invalidItem = {
|
||||
inner: { name: ["aasd", "asdfasdfasfd"] },
|
||||
};
|
||||
|
||||
const result1 = schema.safeParse(invalidItem);
|
||||
expect(result1.success).toEqual(false);
|
||||
if (!result1.success) {
|
||||
expect(result1.error.issues.length).toEqual(2);
|
||||
}
|
||||
});
|
||||
test("formatting", () => {
|
||||
const invalidItem = {
|
||||
inner: { name: ["aasd", "asdfasdfasfd"] },
|
||||
};
|
||||
const invalidArray = {
|
||||
inner: { name: ["asdfasdf", "asdfasdfasfd"] },
|
||||
};
|
||||
const result1 = schema.safeParse(invalidItem);
|
||||
const result2 = schema.safeParse(invalidArray);
|
||||
|
||||
expect(result1.success).toEqual(false);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result1.success) {
|
||||
const error = result1.error.format();
|
||||
|
||||
expect(error._errors).toEqual([]);
|
||||
expect(error.inner?._errors).toEqual([]);
|
||||
// expect(error.inner?.name?._errors).toEqual(["Invalid input"]);
|
||||
// expect(error.inner?.name?.[0]._errors).toEqual(["Invalid input"]);
|
||||
expect(error.inner?.name?.[1]).toEqual(undefined);
|
||||
}
|
||||
if (!result2.success) {
|
||||
type FormattedError = z.inferFormattedError<typeof schema>;
|
||||
const error: FormattedError = result2.error.format();
|
||||
expect(error._errors).toEqual([]);
|
||||
expect(error.inner?._errors).toEqual([]);
|
||||
expect(error.inner?.name?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.inner?.name?.[0]).toEqual(undefined);
|
||||
expect(error.inner?.name?.[1]).toEqual(undefined);
|
||||
expect(error.inner?.name?.[2]).toEqual(undefined);
|
||||
}
|
||||
|
||||
// test custom mapper
|
||||
if (!result2.success) {
|
||||
type FormattedError = z.inferFormattedError<typeof schema, number>;
|
||||
const error: FormattedError = result2.error.format(() => 5);
|
||||
expect(error._errors).toEqual([]);
|
||||
expect(error.inner?._errors).toEqual([]);
|
||||
expect(error.inner?.name?._errors).toEqual([5]);
|
||||
}
|
||||
});
|
||||
|
||||
test("formatting with nullable and optional fields", () => {
|
||||
const nameSchema = z.string().refine((val) => val.length > 5);
|
||||
const schema = z.object({
|
||||
nullableObject: z.object({ name: nameSchema }).nullable(),
|
||||
nullableArray: z.array(nameSchema).nullable(),
|
||||
nullableTuple: z.tuple([nameSchema, nameSchema, z.number()]).nullable(),
|
||||
optionalObject: z.object({ name: nameSchema }).optional(),
|
||||
optionalArray: z.array(nameSchema).optional(),
|
||||
optionalTuple: z.tuple([nameSchema, nameSchema, z.number()]).optional(),
|
||||
});
|
||||
const invalidItem = {
|
||||
nullableObject: { name: "abcd" },
|
||||
nullableArray: ["abcd"],
|
||||
nullableTuple: ["abcd", "abcd", 1],
|
||||
optionalObject: { name: "abcd" },
|
||||
optionalArray: ["abcd"],
|
||||
optionalTuple: ["abcd", "abcd", 1],
|
||||
};
|
||||
const result = schema.safeParse(invalidItem);
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
type FormattedError = z.inferFormattedError<typeof schema>;
|
||||
const error: FormattedError = result.error.format();
|
||||
expect(error._errors).toEqual([]);
|
||||
expect(error.nullableObject?._errors).toEqual([]);
|
||||
expect(error.nullableObject?.name?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.nullableArray?._errors).toEqual([]);
|
||||
expect(error.nullableArray?.[0]?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.nullableTuple?._errors).toEqual([]);
|
||||
expect(error.nullableTuple?.[0]?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.nullableTuple?.[1]?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.optionalObject?._errors).toEqual([]);
|
||||
expect(error.optionalObject?.name?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.optionalArray?._errors).toEqual([]);
|
||||
expect(error.optionalArray?.[0]?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.optionalTuple?._errors).toEqual([]);
|
||||
expect(error.optionalTuple?.[0]?._errors).toEqual(["Invalid input"]);
|
||||
expect(error.optionalTuple?.[1]?._errors).toEqual(["Invalid input"]);
|
||||
}
|
||||
});
|
||||
|
||||
const stringWithCustomError = z.string({
|
||||
errorMap: (issue, ctx) => ({
|
||||
message: issue.code === "invalid_type" ? (ctx.data ? "Invalid name" : "Name is required") : ctx.defaultError,
|
||||
}),
|
||||
});
|
||||
|
||||
test("schema-bound error map", () => {
|
||||
const result = stringWithCustomError.safeParse(1234);
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].message).toEqual("Invalid name");
|
||||
}
|
||||
|
||||
const result2 = stringWithCustomError.safeParse(undefined);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result2.success) {
|
||||
expect(result2.error.issues[0].message).toEqual("Name is required");
|
||||
}
|
||||
|
||||
// support contextual override
|
||||
const result3 = stringWithCustomError.safeParse(undefined, {
|
||||
errorMap: () => ({ message: "OVERRIDE" }),
|
||||
});
|
||||
expect(result3.success).toEqual(false);
|
||||
if (!result3.success) {
|
||||
expect(result3.error.issues[0].message).toEqual("OVERRIDE");
|
||||
}
|
||||
});
|
||||
|
||||
test("overrideErrorMap", () => {
|
||||
// support overrideErrorMap
|
||||
z.setErrorMap(() => ({ message: "OVERRIDE" }));
|
||||
const result4 = stringWithCustomError.min(10).safeParse("tooshort");
|
||||
expect(result4.success).toEqual(false);
|
||||
if (!result4.success) {
|
||||
expect(result4.error.issues[0].message).toEqual("OVERRIDE");
|
||||
}
|
||||
z.setErrorMap(z.defaultErrorMap);
|
||||
});
|
||||
|
||||
test("invalid and required", () => {
|
||||
const str = z.string({
|
||||
invalid_type_error: "Invalid name",
|
||||
required_error: "Name is required",
|
||||
});
|
||||
const result1 = str.safeParse(1234);
|
||||
expect(result1.success).toEqual(false);
|
||||
if (!result1.success) {
|
||||
expect(result1.error.issues[0].message).toEqual("Invalid name");
|
||||
}
|
||||
const result2 = str.safeParse(undefined);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result2.success) {
|
||||
expect(result2.error.issues[0].message).toEqual("Name is required");
|
||||
}
|
||||
});
|
||||
|
||||
test("Fallback to default required error", () => {
|
||||
const str = z.string({
|
||||
invalid_type_error: "Invalid name",
|
||||
// required_error: "Name is required",
|
||||
});
|
||||
|
||||
const result2 = str.safeParse(undefined);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result2.success) {
|
||||
expect(result2.error.issues[0].message).toEqual("Required");
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid and required and errorMap", () => {
|
||||
expect(() => {
|
||||
return z.string({
|
||||
invalid_type_error: "Invalid name",
|
||||
required_error: "Name is required",
|
||||
errorMap: () => ({ message: "OVERRIDE" }),
|
||||
});
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
test("strict error message", () => {
|
||||
const errorMsg = "Invalid object";
|
||||
const obj = z.object({ x: z.string() }).strict(errorMsg);
|
||||
const result = obj.safeParse({ x: "a", y: "b" });
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].message).toEqual(errorMsg);
|
||||
}
|
||||
});
|
||||
|
||||
test("enum error message, invalid enum elementstring", () => {
|
||||
try {
|
||||
z.enum(["Tuna", "Trout"]).parse("Salmon");
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual("Invalid enum value. Expected 'Tuna' | 'Trout', received 'Salmon'");
|
||||
}
|
||||
});
|
||||
|
||||
test("enum error message, invalid type", () => {
|
||||
try {
|
||||
z.enum(["Tuna", "Trout"]).parse(12);
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual("Expected 'Tuna' | 'Trout', received number");
|
||||
}
|
||||
});
|
||||
|
||||
test("nativeEnum default error message", () => {
|
||||
enum Fish {
|
||||
Tuna = "Tuna",
|
||||
Trout = "Trout",
|
||||
}
|
||||
try {
|
||||
z.nativeEnum(Fish).parse("Salmon");
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual("Invalid enum value. Expected 'Tuna' | 'Trout', received 'Salmon'");
|
||||
}
|
||||
});
|
||||
|
||||
test("literal default error message", () => {
|
||||
try {
|
||||
z.literal("Tuna").parse("Trout");
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual(`Invalid literal value, expected "Tuna"`);
|
||||
}
|
||||
});
|
||||
|
||||
test("literal bigint default error message", () => {
|
||||
try {
|
||||
z.literal(BigInt(12)).parse(BigInt(13));
|
||||
} catch (err) {
|
||||
const zerr: z.ZodError = err as any;
|
||||
expect(zerr.issues.length).toEqual(1);
|
||||
expect(zerr.issues[0].message).toEqual(`Invalid literal value, expected "12"`);
|
||||
}
|
||||
});
|
||||
|
||||
test("enum with message returns the custom error message", () => {
|
||||
const schema = z.enum(["apple", "banana"], {
|
||||
message: "the value provided is invalid",
|
||||
});
|
||||
|
||||
const result1 = schema.safeParse("berries");
|
||||
expect(result1.success).toEqual(false);
|
||||
if (!result1.success) {
|
||||
expect(result1.error.issues[0].message).toEqual("the value provided is invalid");
|
||||
}
|
||||
|
||||
const result2 = schema.safeParse(undefined);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result2.success) {
|
||||
expect(result2.error.issues[0].message).toEqual("the value provided is invalid");
|
||||
}
|
||||
|
||||
const result3 = schema.safeParse("banana");
|
||||
expect(result3.success).toEqual(true);
|
||||
|
||||
const result4 = schema.safeParse(null);
|
||||
expect(result4.success).toEqual(false);
|
||||
if (!result4.success) {
|
||||
expect(result4.error.issues[0].message).toEqual("the value provided is invalid");
|
||||
}
|
||||
});
|
||||
|
||||
test("when the message is falsy, it is used as is provided", () => {
|
||||
const schema = z.string().max(1, { message: "" });
|
||||
const result = schema.safeParse("asdf");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].message).toEqual("");
|
||||
}
|
||||
});
|
||||
|
||||
// test("dont short circuit on continuable errors", () => {
|
||||
// const user = z
|
||||
// .object({
|
||||
// password: z.string().min(6),
|
||||
// confirm: z.string(),
|
||||
// })
|
||||
// .refine((data) => data.password === data.confirm, {
|
||||
// message: "Passwords don't match",
|
||||
// path: ["confirm"],
|
||||
// });
|
||||
// const result = user.safeParse({ password: "asdf", confirm: "qwer" });
|
||||
// if (!result.success) {
|
||||
// expect(result.error.issues.length).toEqual(2);
|
||||
// }
|
||||
// });
|
||||
|
||||
test("format() handles Object.prototype names in the issue path", () => {
|
||||
const result = z
|
||||
.record(z.string(), z.object({ pwn: z.number() }))
|
||||
.safeParse(JSON.parse('{"__proto__": {"pwn": "x"}}'));
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
// the node must be a real own data property, not the inherited accessor
|
||||
const node = Object.getOwnPropertyDescriptor(result.error.format(), "__proto__")!.value;
|
||||
expect(node.pwn._errors).toHaveLength(1);
|
||||
}
|
||||
expect(({} as any).pwn).toBeUndefined();
|
||||
|
||||
const inherited = z.record(z.string(), z.string()).safeParse(JSON.parse('{"toString": 1}'));
|
||||
expect(inherited.success).toEqual(false);
|
||||
if (!inherited.success) {
|
||||
expect((inherited.error.format() as any).toString._errors).toHaveLength(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("format() handles an input-derived _errors path", () => {
|
||||
const result = z.object({ parent: z.record(z.string()) }).safeParse({ parent: { _errors: 1 } });
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.format().parent?._errors).toEqual(["Expected string, received number"]);
|
||||
}
|
||||
|
||||
const nested = z
|
||||
.object({ parent: z.record(z.object({ child: z.string() })) })
|
||||
.safeParse({ parent: { _errors: { child: 1 } } });
|
||||
expect(nested.success).toEqual(false);
|
||||
if (!nested.success) {
|
||||
expect((nested.error.format() as any).parent.child._errors).toEqual(["Expected string, received number"]);
|
||||
}
|
||||
});
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("first party switch", () => {
|
||||
const myType = z.string() as z.ZodFirstPartySchemaTypes;
|
||||
const def = myType._def;
|
||||
|
||||
switch (def.typeName) {
|
||||
case z.ZodFirstPartyTypeKind.ZodString:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodNumber:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodNaN:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodBigInt:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodBoolean:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodDate:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodUndefined:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodNull:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodAny:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodUnknown:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodNever:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodVoid:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodArray:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodObject:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodUnion:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodDiscriminatedUnion:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodIntersection:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodTuple:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodRecord:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodMap:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodSet:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodFunction:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodLazy:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodLiteral:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodEnum:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodEffects:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodNativeEnum:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodOptional:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodNullable:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodDefault:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodCatch:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodPromise:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodBranded:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodPipeline:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodSymbol:
|
||||
break;
|
||||
case z.ZodFirstPartyTypeKind.ZodReadonly:
|
||||
break;
|
||||
default:
|
||||
util.assertNever(def);
|
||||
}
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
import type { ZodFirstPartySchemaTypes, ZodFirstPartyTypeKind } from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("Identify missing [ZodFirstPartySchemaTypes]", () => {
|
||||
type ZodFirstPartySchemaForType<T extends ZodFirstPartyTypeKind> = ZodFirstPartySchemaTypes extends infer Schema
|
||||
? Schema extends { _def: { typeName: T } }
|
||||
? Schema
|
||||
: never
|
||||
: never;
|
||||
type ZodMappedTypes = {
|
||||
[key in ZodFirstPartyTypeKind]: ZodFirstPartySchemaForType<key>;
|
||||
};
|
||||
type ZodFirstPartySchemaTypesMissingFromUnion = keyof {
|
||||
[key in keyof ZodMappedTypes as ZodMappedTypes[key] extends { _def: never } ? key : never]: unknown;
|
||||
};
|
||||
|
||||
util.assertEqual<ZodFirstPartySchemaTypesMissingFromUnion, never>(true);
|
||||
});
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const args1 = z.tuple([z.string()]);
|
||||
const returns1 = z.number();
|
||||
const func1 = z.function(args1, returns1);
|
||||
|
||||
test("function parsing", () => {
|
||||
const parsed = func1.parse((arg: any) => arg.length);
|
||||
const result = parsed("asdf");
|
||||
expect(result).toBe(4);
|
||||
});
|
||||
|
||||
test("parsed function fail 1", () => {
|
||||
const parsed = func1.parse((x: string) => x);
|
||||
expect(() => parsed("asdf")).toThrow();
|
||||
});
|
||||
|
||||
test("parsed function fail 2", () => {
|
||||
const parsed = func1.parse((x: string) => x);
|
||||
expect(() => parsed(13 as any)).toThrow();
|
||||
});
|
||||
|
||||
test("function inference 1", () => {
|
||||
type func1 = z.TypeOf<typeof func1>;
|
||||
util.assertEqual<func1, (k: string) => number>(true);
|
||||
});
|
||||
|
||||
test("method parsing", () => {
|
||||
const methodObject = z.object({
|
||||
property: z.number(),
|
||||
method: z.function().args(z.string()).returns(z.number()),
|
||||
});
|
||||
const methodInstance = {
|
||||
property: 3,
|
||||
method: function (s: string) {
|
||||
return s.length + this.property;
|
||||
},
|
||||
};
|
||||
const parsed = methodObject.parse(methodInstance);
|
||||
expect(parsed.method("length=8")).toBe(11); // 8 length + 3 property
|
||||
});
|
||||
|
||||
test("async method parsing", async () => {
|
||||
const methodObject = z.object({
|
||||
property: z.number(),
|
||||
method: z.function().args(z.string()).returns(z.promise(z.number())),
|
||||
});
|
||||
const methodInstance = {
|
||||
property: 3,
|
||||
method: async function (s: string) {
|
||||
return s.length + this.property;
|
||||
},
|
||||
};
|
||||
const parsed = methodObject.parse(methodInstance);
|
||||
expect(await parsed.method("length=8")).toBe(11); // 8 length + 3 property
|
||||
});
|
||||
|
||||
test("args method", () => {
|
||||
const t1 = z.function();
|
||||
type t1 = z.infer<typeof t1>;
|
||||
util.assertEqual<t1, (...args_1: unknown[]) => unknown>(true);
|
||||
|
||||
const t2 = t1.args(z.string());
|
||||
type t2 = z.infer<typeof t2>;
|
||||
util.assertEqual<t2, (arg: string, ...args_1: unknown[]) => unknown>(true);
|
||||
|
||||
const t3 = t2.returns(z.boolean());
|
||||
type t3 = z.infer<typeof t3>;
|
||||
util.assertEqual<t3, (arg: string, ...args_1: unknown[]) => boolean>(true);
|
||||
});
|
||||
|
||||
const args2 = z.tuple([
|
||||
z.object({
|
||||
f1: z.number(),
|
||||
f2: z.string().nullable(),
|
||||
f3: z.array(z.boolean().optional()).optional(),
|
||||
}),
|
||||
]);
|
||||
const returns2 = z.union([z.string(), z.number()]);
|
||||
|
||||
const func2 = z.function(args2, returns2);
|
||||
|
||||
test("function inference 2", () => {
|
||||
type func2 = z.TypeOf<typeof func2>;
|
||||
util.assertEqual<
|
||||
func2,
|
||||
(arg: {
|
||||
f1: number;
|
||||
f2: string | null;
|
||||
f3?: (boolean | undefined)[] | undefined;
|
||||
}) => string | number
|
||||
>(true);
|
||||
});
|
||||
|
||||
test("valid function run", () => {
|
||||
const validFunc2Instance = func2.validate((_x) => {
|
||||
return "adf" as any;
|
||||
});
|
||||
|
||||
const checker = () => {
|
||||
validFunc2Instance({
|
||||
f1: 21,
|
||||
f2: "asdf",
|
||||
f3: [true, false],
|
||||
});
|
||||
};
|
||||
|
||||
checker();
|
||||
});
|
||||
|
||||
test("input validation error", () => {
|
||||
const invalidFuncInstance = func2.validate((_x) => {
|
||||
return "adf" as any;
|
||||
});
|
||||
|
||||
const checker = () => {
|
||||
invalidFuncInstance("Invalid_input" as any);
|
||||
};
|
||||
|
||||
expect(checker).toThrow();
|
||||
});
|
||||
|
||||
test("output validation error", () => {
|
||||
const invalidFuncInstance = func2.validate((_x) => {
|
||||
return ["this", "is", "not", "valid", "output"] as any;
|
||||
});
|
||||
|
||||
const checker = () => {
|
||||
invalidFuncInstance({
|
||||
f1: 21,
|
||||
f2: "asdf",
|
||||
f3: [true, false],
|
||||
});
|
||||
};
|
||||
|
||||
expect(checker).toThrow();
|
||||
});
|
||||
|
||||
z.function(z.tuple([z.string()])).args()._def.args;
|
||||
|
||||
test("special function error codes", () => {
|
||||
const checker = z.function(z.tuple([z.string()]), z.boolean()).implement((arg) => {
|
||||
return arg.length as any;
|
||||
});
|
||||
try {
|
||||
checker("12" as any);
|
||||
} catch (err) {
|
||||
const zerr = err as z.ZodError;
|
||||
const first = zerr.issues[0];
|
||||
if (first.code !== z.ZodIssueCode.invalid_return_type) throw new Error();
|
||||
|
||||
expect(first.returnTypeError).toBeInstanceOf(z.ZodError);
|
||||
}
|
||||
|
||||
try {
|
||||
checker(12 as any);
|
||||
} catch (err) {
|
||||
const zerr = err as z.ZodError;
|
||||
const first = zerr.issues[0];
|
||||
if (first.code !== z.ZodIssueCode.invalid_arguments) throw new Error();
|
||||
expect(first.argumentsError).toBeInstanceOf(z.ZodError);
|
||||
}
|
||||
});
|
||||
|
||||
test("function with async refinements", async () => {
|
||||
const func = z
|
||||
.function()
|
||||
.args(z.string().refine(async (val) => val.length > 10))
|
||||
.returns(z.promise(z.number().refine(async (val) => val > 10)))
|
||||
.implement(async (val) => {
|
||||
return val.length;
|
||||
});
|
||||
const results = [];
|
||||
try {
|
||||
await func("asdfasdf");
|
||||
results.push("success");
|
||||
} catch (_err) {
|
||||
results.push("fail");
|
||||
}
|
||||
try {
|
||||
await func("asdflkjasdflkjsf");
|
||||
results.push("success");
|
||||
} catch (_err) {
|
||||
results.push("fail");
|
||||
}
|
||||
|
||||
expect(results).toEqual(["fail", "success"]);
|
||||
});
|
||||
|
||||
test("non async function with async refinements should fail", async () => {
|
||||
const func = z
|
||||
.function()
|
||||
.args(z.string().refine(async (val) => val.length > 10))
|
||||
.returns(z.number().refine(async (val) => val > 10))
|
||||
.implement((val) => {
|
||||
return val.length;
|
||||
});
|
||||
|
||||
const results = [];
|
||||
try {
|
||||
await func("asdasdfasdffasdf");
|
||||
results.push("success");
|
||||
} catch (_err) {
|
||||
results.push("fail");
|
||||
}
|
||||
|
||||
expect(results).toEqual(["fail"]);
|
||||
});
|
||||
|
||||
test("allow extra parameters", () => {
|
||||
const maxLength5 = z
|
||||
.function()
|
||||
.args(z.string())
|
||||
.returns(z.boolean())
|
||||
.implement((str, _arg, _qewr) => {
|
||||
return str.length <= 5;
|
||||
});
|
||||
|
||||
const filteredList = ["apple", "orange", "pear", "banana", "strawberry"].filter(maxLength5);
|
||||
expect(filteredList.length).toEqual(2);
|
||||
});
|
||||
|
||||
test("params and returnType getters", () => {
|
||||
const func = z.function().args(z.string()).returns(z.string());
|
||||
|
||||
const paramResult = func.parameters().items[0].parse("asdf");
|
||||
expect(paramResult).toBe("asdf");
|
||||
|
||||
const returnResult = func.returnType().parse("asdf");
|
||||
expect(returnResult).toBe("asdf");
|
||||
});
|
||||
|
||||
test("inference with transforms", () => {
|
||||
const funcSchema = z
|
||||
.function()
|
||||
.args(z.string().transform((val) => val.length))
|
||||
.returns(z.object({ val: z.number() }));
|
||||
const myFunc = funcSchema.implement((val) => {
|
||||
return { val, extra: "stuff" };
|
||||
});
|
||||
myFunc("asdf");
|
||||
|
||||
util.assertEqual<typeof myFunc, (arg: string, ...args_1: unknown[]) => { val: number; extra: string }>(true);
|
||||
});
|
||||
|
||||
test("fallback to OuterTypeOfFunction", () => {
|
||||
const funcSchema = z
|
||||
.function()
|
||||
.args(z.string().transform((val) => val.length))
|
||||
.returns(z.object({ arg: z.number() }).transform((val) => val.arg));
|
||||
|
||||
const myFunc = funcSchema.implement((val) => {
|
||||
return { arg: val, arg2: false };
|
||||
});
|
||||
|
||||
util.assertEqual<typeof myFunc, (arg: string, ...args_1: unknown[]) => number>(true);
|
||||
});
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("generics", () => {
|
||||
async function stripOuter<TData extends z.ZodTypeAny>(schema: TData, data: unknown) {
|
||||
return z
|
||||
.object({
|
||||
nested: schema, // as z.ZodTypeAny,
|
||||
})
|
||||
.transform((data) => {
|
||||
return data.nested!;
|
||||
})
|
||||
.parse({ nested: data });
|
||||
}
|
||||
|
||||
const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
|
||||
util.assertEqual<typeof result, Promise<{ a: string }>>(true);
|
||||
});
|
||||
|
||||
// test("assignability", () => {
|
||||
// const createSchemaAndParse = <K extends string, VS extends z.ZodString>(
|
||||
// key: K,
|
||||
// valueSchema: VS,
|
||||
// data: unknown
|
||||
// ) => {
|
||||
// const schema = z.object({
|
||||
// [key]: valueSchema,
|
||||
// } as { [k in K]: VS });
|
||||
// return { [key]: valueSchema };
|
||||
// const parsed = schema.parse(data);
|
||||
// return parsed;
|
||||
// // const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed;
|
||||
// // return inferred;
|
||||
// };
|
||||
// const parsed = createSchemaAndParse("foo", z.string(), { foo: "" });
|
||||
// util.assertEqual<typeof parsed, { foo: string }>(true);
|
||||
// });
|
||||
|
||||
test("nested no undefined", () => {
|
||||
const inner = z.string().or(z.array(z.string()));
|
||||
const outer = z.object({ inner });
|
||||
type outerSchema = z.infer<typeof outer>;
|
||||
z.util.assertEqual<outerSchema, { inner: string | string[] }>(true);
|
||||
expect(outer.safeParse({ inner: undefined }).success).toEqual(false);
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("instanceof", async () => {
|
||||
class Test {}
|
||||
class Subtest extends Test {}
|
||||
abstract class AbstractBar {
|
||||
constructor(public val: string) {}
|
||||
}
|
||||
class Bar extends AbstractBar {}
|
||||
|
||||
const TestSchema = z.instanceof(Test);
|
||||
const SubtestSchema = z.instanceof(Subtest);
|
||||
const AbstractSchema = z.instanceof(AbstractBar);
|
||||
const BarSchema = z.instanceof(Bar);
|
||||
|
||||
TestSchema.parse(new Test());
|
||||
TestSchema.parse(new Subtest());
|
||||
SubtestSchema.parse(new Subtest());
|
||||
AbstractSchema.parse(new Bar("asdf"));
|
||||
const bar = BarSchema.parse(new Bar("asdf"));
|
||||
expect(bar.val).toEqual("asdf");
|
||||
|
||||
await expect(() => SubtestSchema.parse(new Test())).toThrow(/Input not instance of Subtest/);
|
||||
await expect(() => TestSchema.parse(12)).toThrow(/Input not instance of Test/);
|
||||
|
||||
util.assertEqual<Test, z.infer<typeof TestSchema>>(true);
|
||||
});
|
||||
|
||||
test("instanceof fatal", () => {
|
||||
const schema = z.instanceof(Date).refine((d) => d.toString());
|
||||
const res = schema.safeParse(null);
|
||||
expect(res.success).toBe(false);
|
||||
});
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("object intersection", () => {
|
||||
const BaseTeacher = z.object({
|
||||
subjects: z.array(z.string()),
|
||||
});
|
||||
const HasID = z.object({ id: z.string() });
|
||||
|
||||
const Teacher = z.intersection(BaseTeacher.passthrough(), HasID); // BaseTeacher.merge(HasID);
|
||||
const data = {
|
||||
subjects: ["math"],
|
||||
id: "asdfasdf",
|
||||
};
|
||||
expect(Teacher.parse(data)).toEqual(data);
|
||||
expect(() => Teacher.parse({ subject: data.subjects })).toThrow();
|
||||
expect(Teacher.parse({ ...data, extra: 12 })).toEqual({ ...data, extra: 12 });
|
||||
|
||||
expect(() => z.intersection(BaseTeacher.strict(), HasID).parse({ ...data, extra: 12 })).toThrow();
|
||||
});
|
||||
|
||||
test("object intersection strips __proto__ from pass-through operands", () => {
|
||||
const cases = [
|
||||
[
|
||||
z.intersection(z.object({ name: z.string() }), z.unknown()),
|
||||
JSON.parse('{"__proto__":{"isAdmin":true},"name":"alice"}'),
|
||||
{ name: "alice" },
|
||||
],
|
||||
[z.intersection(z.record(z.string(), z.unknown()), z.any()), JSON.parse('{"__proto__":{},"a":1}'), { a: 1 }],
|
||||
] as const;
|
||||
|
||||
for (const [schema, input, expected] of cases) {
|
||||
const parsed = schema.parse(input);
|
||||
expect(parsed).toEqual(expected);
|
||||
expect(Object.getPrototypeOf(parsed)).toBe(Object.prototype);
|
||||
expect(Object.prototype.hasOwnProperty.call(parsed, "__proto__")).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
test("deep intersection", () => {
|
||||
const Animal = z.object({
|
||||
properties: z.object({
|
||||
is_animal: z.boolean(),
|
||||
}),
|
||||
});
|
||||
const Cat = z
|
||||
.object({
|
||||
properties: z.object({
|
||||
jumped: z.boolean(),
|
||||
}),
|
||||
})
|
||||
.and(Animal);
|
||||
|
||||
type _Cat = z.infer<typeof Cat>;
|
||||
// const cat:Cat = 'asdf' as any;
|
||||
const cat = Cat.parse({ properties: { is_animal: true, jumped: true } });
|
||||
expect(cat.properties).toEqual({ is_animal: true, jumped: true });
|
||||
});
|
||||
|
||||
test("deep intersection of arrays", async () => {
|
||||
const Author = z.object({
|
||||
posts: z.array(
|
||||
z.object({
|
||||
post_id: z.number(),
|
||||
})
|
||||
),
|
||||
});
|
||||
const Registry = z
|
||||
.object({
|
||||
posts: z.array(
|
||||
z.object({
|
||||
title: z.string(),
|
||||
})
|
||||
),
|
||||
})
|
||||
.and(Author);
|
||||
|
||||
const posts = [
|
||||
{ post_id: 1, title: "Novels" },
|
||||
{ post_id: 2, title: "Fairy tales" },
|
||||
];
|
||||
const cat = Registry.parse({ posts });
|
||||
expect(cat.posts).toEqual(posts);
|
||||
const asyncCat = await Registry.parseAsync({ posts });
|
||||
expect(asyncCat.posts).toEqual(posts);
|
||||
});
|
||||
|
||||
test("invalid intersection types", async () => {
|
||||
const numberIntersection = z.intersection(
|
||||
z.number(),
|
||||
z.number().transform((x) => x + 1)
|
||||
);
|
||||
|
||||
const syncResult = numberIntersection.safeParse(1234);
|
||||
expect(syncResult.success).toEqual(false);
|
||||
if (!syncResult.success) {
|
||||
expect(syncResult.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_intersection_types);
|
||||
}
|
||||
|
||||
const asyncResult = await numberIntersection.spa(1234);
|
||||
expect(asyncResult.success).toEqual(false);
|
||||
if (!asyncResult.success) {
|
||||
expect(asyncResult.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_intersection_types);
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid array merge", async () => {
|
||||
const stringArrInt = z.intersection(
|
||||
z.string().array(),
|
||||
z
|
||||
.string()
|
||||
.array()
|
||||
.transform((val) => [...val, "asdf"])
|
||||
);
|
||||
const syncResult = stringArrInt.safeParse(["asdf", "qwer"]);
|
||||
expect(syncResult.success).toEqual(false);
|
||||
if (!syncResult.success) {
|
||||
expect(syncResult.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_intersection_types);
|
||||
}
|
||||
|
||||
const asyncResult = await stringArrInt.spa(["asdf", "qwer"]);
|
||||
expect(asyncResult.success).toEqual(false);
|
||||
if (!asyncResult.success) {
|
||||
expect(asyncResult.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_intersection_types);
|
||||
}
|
||||
});
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import * as z from "zod/v3";
|
||||
|
||||
export const filePath = __filename;
|
||||
|
||||
// z.object()
|
||||
|
||||
export const Test = z.object({
|
||||
f1: z.number(),
|
||||
});
|
||||
|
||||
export type Test = z.infer<typeof Test>;
|
||||
|
||||
export const instanceOfTest: Test = {
|
||||
f1: 1,
|
||||
};
|
||||
|
||||
// z.object().merge()
|
||||
|
||||
export const TestMerge = z
|
||||
.object({
|
||||
f2: z.string().optional(),
|
||||
})
|
||||
.merge(Test);
|
||||
|
||||
export type TestMerge = z.infer<typeof TestMerge>;
|
||||
|
||||
export const instanceOfTestMerge: TestMerge = {
|
||||
f1: 1,
|
||||
f2: "string",
|
||||
};
|
||||
|
||||
// z.union()
|
||||
|
||||
export const TestUnion = z.union([
|
||||
z.object({
|
||||
f2: z.string().optional(),
|
||||
}),
|
||||
Test,
|
||||
]);
|
||||
|
||||
export type TestUnion = z.infer<typeof TestUnion>;
|
||||
|
||||
export const instanceOfTestUnion: TestUnion = {
|
||||
f1: 1,
|
||||
f2: "string",
|
||||
};
|
||||
|
||||
// z.object().partial()
|
||||
|
||||
export const TestPartial = Test.partial();
|
||||
|
||||
export type TestPartial = z.infer<typeof TestPartial>;
|
||||
|
||||
export const instanceOfTestPartial: TestPartial = {
|
||||
f1: 1,
|
||||
};
|
||||
|
||||
// z.object().pick()
|
||||
|
||||
export const TestPick = TestMerge.pick({ f1: true });
|
||||
|
||||
export type TestPick = z.infer<typeof TestPick>;
|
||||
|
||||
export const instanceOfTestPick: TestPick = {
|
||||
f1: 1,
|
||||
};
|
||||
|
||||
// z.object().omit()
|
||||
|
||||
export const TestOmit = TestMerge.omit({ f2: true });
|
||||
|
||||
export type TestOmit = z.infer<typeof TestOmit>;
|
||||
|
||||
export const instanceOfTestOmit: TestOmit = {
|
||||
f1: 1,
|
||||
};
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
import { test } from "vitest";
|
||||
// import path from "path";
|
||||
// import { Node, Project, SyntaxKind } from "ts-morph";
|
||||
|
||||
// import { filePath } from "./language-server.source";
|
||||
|
||||
// The following tool is helpful for understanding the TypeScript AST associated with these tests: https://ts-ast-viewer.com/ (just copy the contents of language-server.source into the viewer)
|
||||
|
||||
test("", () => {});
|
||||
// describe("Executing Go To Definition (and therefore Find Usages and Rename Refactoring) using an IDE works on inferred object properties", () => {
|
||||
// // Compile file developmentEnvironment.source
|
||||
// const project = new Project({
|
||||
// tsConfigFilePath: path.join(__dirname, "..", "..", "tsconfig.json"),
|
||||
// skipAddingFilesFromTsConfig: true,
|
||||
// });
|
||||
// const sourceFile = project.addSourceFileAtPath(filePath);
|
||||
|
||||
// test("works for object properties inferred from z.object()", () => {
|
||||
// // Find usage of Test.f1 property
|
||||
// const instanceVariable =
|
||||
// sourceFile.getVariableDeclarationOrThrow("instanceOfTest");
|
||||
// const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// instanceVariable,
|
||||
// "f1"
|
||||
// );
|
||||
|
||||
// // Find definition of Test.f1 property
|
||||
// const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// SyntaxKind.VariableDeclaration
|
||||
// );
|
||||
|
||||
// // Assert that find definition returned the Zod definition of Test
|
||||
// expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
|
||||
// expect(parentOfProperty?.getName()).toEqual("Test");
|
||||
// });
|
||||
|
||||
// // test("works for first object properties inferred from z.object().merge()", () => {
|
||||
// // // Find usage of TestMerge.f1 property
|
||||
// // const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
|
||||
// // "instanceOfTestMerge"
|
||||
// // );
|
||||
// // const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// // instanceVariable,
|
||||
// // "f1"
|
||||
// // );
|
||||
|
||||
// // // Find definition of TestMerge.f1 property
|
||||
// // const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// // const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// // SyntaxKind.VariableDeclaration
|
||||
// // );
|
||||
|
||||
// // // Assert that find definition returned the Zod definition of Test
|
||||
// // expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
|
||||
// // expect(parentOfProperty?.getName()).toEqual("Test");
|
||||
// // });
|
||||
|
||||
// // test("works for second object properties inferred from z.object().merge()", () => {
|
||||
// // // Find usage of TestMerge.f2 property
|
||||
// // const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
|
||||
// // "instanceOfTestMerge"
|
||||
// // );
|
||||
// // const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// // instanceVariable,
|
||||
// // "f2"
|
||||
// // );
|
||||
|
||||
// // // Find definition of TestMerge.f2 property
|
||||
// // const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// // const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// // SyntaxKind.VariableDeclaration
|
||||
// // );
|
||||
|
||||
// // // Assert that find definition returned the Zod definition of TestMerge
|
||||
// // expect(definitionOfProperty?.getText()).toEqual(
|
||||
// // "f2: z.string().optional()"
|
||||
// // );
|
||||
// // expect(parentOfProperty?.getName()).toEqual("TestMerge");
|
||||
// // });
|
||||
|
||||
// test("works for first object properties inferred from z.union()", () => {
|
||||
// // Find usage of TestUnion.f1 property
|
||||
// const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
|
||||
// "instanceOfTestUnion"
|
||||
// );
|
||||
// const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// instanceVariable,
|
||||
// "f1"
|
||||
// );
|
||||
|
||||
// // Find definition of TestUnion.f1 property
|
||||
// const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// SyntaxKind.VariableDeclaration
|
||||
// );
|
||||
|
||||
// // Assert that find definition returned the Zod definition of Test
|
||||
// expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
|
||||
// expect(parentOfProperty?.getName()).toEqual("Test");
|
||||
// });
|
||||
|
||||
// test("works for second object properties inferred from z.union()", () => {
|
||||
// // Find usage of TestUnion.f2 property
|
||||
// const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
|
||||
// "instanceOfTestUnion"
|
||||
// );
|
||||
// const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// instanceVariable,
|
||||
// "f2"
|
||||
// );
|
||||
|
||||
// // Find definition of TestUnion.f2 property
|
||||
// const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// SyntaxKind.VariableDeclaration
|
||||
// );
|
||||
|
||||
// // Assert that find definition returned the Zod definition of TestUnion
|
||||
// expect(definitionOfProperty?.getText()).toEqual(
|
||||
// "f2: z.string().optional()"
|
||||
// );
|
||||
// expect(parentOfProperty?.getName()).toEqual("TestUnion");
|
||||
// });
|
||||
|
||||
// test("works for object properties inferred from z.object().partial()", () => {
|
||||
// // Find usage of TestPartial.f1 property
|
||||
// const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
|
||||
// "instanceOfTestPartial"
|
||||
// );
|
||||
// const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// instanceVariable,
|
||||
// "f1"
|
||||
// );
|
||||
|
||||
// // Find definition of TestPartial.f1 property
|
||||
// const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// SyntaxKind.VariableDeclaration
|
||||
// );
|
||||
|
||||
// // Assert that find definition returned the Zod definition of Test
|
||||
// expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
|
||||
// expect(parentOfProperty?.getName()).toEqual("Test");
|
||||
// });
|
||||
|
||||
// test("works for object properties inferred from z.object().pick()", () => {
|
||||
// // Find usage of TestPick.f1 property
|
||||
// const instanceVariable =
|
||||
// sourceFile.getVariableDeclarationOrThrow("instanceOfTestPick");
|
||||
// const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// instanceVariable,
|
||||
// "f1"
|
||||
// );
|
||||
|
||||
// // Find definition of TestPick.f1 property
|
||||
// const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// SyntaxKind.VariableDeclaration
|
||||
// );
|
||||
|
||||
// // Assert that find definition returned the Zod definition of Test
|
||||
// expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
|
||||
// expect(parentOfProperty?.getName()).toEqual("Test");
|
||||
// });
|
||||
|
||||
// test("works for object properties inferred from z.object().omit()", () => {
|
||||
// // Find usage of TestOmit.f1 property
|
||||
// const instanceVariable =
|
||||
// sourceFile.getVariableDeclarationOrThrow("instanceOfTestOmit");
|
||||
// const propertyBeingAssigned = getPropertyBeingAssigned(
|
||||
// instanceVariable,
|
||||
// "f1"
|
||||
// );
|
||||
|
||||
// // Find definition of TestOmit.f1 property
|
||||
// const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
|
||||
// const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
|
||||
// SyntaxKind.VariableDeclaration
|
||||
// );
|
||||
|
||||
// // Assert that find definition returned the Zod definition of Test
|
||||
// expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
|
||||
// expect(parentOfProperty?.getName()).toEqual("Test");
|
||||
// });
|
||||
// });
|
||||
|
||||
// const getPropertyBeingAssigned = (node: Node, name: string) => {
|
||||
// const propertyAssignment = node.forEachDescendant((descendent) =>
|
||||
// Node.isPropertyAssignment(descendent) && descendent.getName() == name
|
||||
// ? descendent
|
||||
// : undefined
|
||||
// );
|
||||
|
||||
// if (propertyAssignment == null)
|
||||
// fail(`Could not find property assignment with name ${name}`);
|
||||
|
||||
// const propertyLiteral = propertyAssignment.getFirstDescendantByKind(
|
||||
// SyntaxKind.Identifier
|
||||
// );
|
||||
|
||||
// if (propertyLiteral == null)
|
||||
// fail(`Could not find property literal with name ${name}`);
|
||||
|
||||
// return propertyLiteral;
|
||||
// };
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const literalTuna = z.literal("tuna");
|
||||
const literalFortyTwo = z.literal(42);
|
||||
const literalTrue = z.literal(true);
|
||||
|
||||
const terrificSymbol = Symbol("terrific");
|
||||
const literalTerrificSymbol = z.literal(terrificSymbol);
|
||||
|
||||
test("passing validations", () => {
|
||||
literalTuna.parse("tuna");
|
||||
literalFortyTwo.parse(42);
|
||||
literalTrue.parse(true);
|
||||
literalTerrificSymbol.parse(terrificSymbol);
|
||||
});
|
||||
|
||||
test("failing validations", () => {
|
||||
expect(() => literalTuna.parse("shark")).toThrow();
|
||||
expect(() => literalFortyTwo.parse(43)).toThrow();
|
||||
expect(() => literalTrue.parse(false)).toThrow();
|
||||
expect(() => literalTerrificSymbol.parse(Symbol("terrific"))).toThrow();
|
||||
});
|
||||
|
||||
test("invalid_literal should have `received` field with data", () => {
|
||||
const data = "shark";
|
||||
const result = literalTuna.safeParse(data);
|
||||
if (!result.success) {
|
||||
const issue = result.error.issues[0];
|
||||
if (issue.code === "invalid_literal") {
|
||||
expect(issue.received).toBe(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { ZodIssueCode } from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const stringMap = z.map(z.string(), z.string());
|
||||
type stringMap = z.infer<typeof stringMap>;
|
||||
|
||||
test("type inference", () => {
|
||||
util.assertEqual<stringMap, Map<string, string>>(true);
|
||||
});
|
||||
|
||||
test("valid parse", () => {
|
||||
const result = stringMap.safeParse(
|
||||
new Map([
|
||||
["first", "foo"],
|
||||
["second", "bar"],
|
||||
])
|
||||
);
|
||||
expect(result.success).toEqual(true);
|
||||
if (result.success) {
|
||||
expect(result.data.has("first")).toEqual(true);
|
||||
expect(result.data.has("second")).toEqual(true);
|
||||
expect(result.data.get("first")).toEqual("foo");
|
||||
expect(result.data.get("second")).toEqual("bar");
|
||||
}
|
||||
});
|
||||
|
||||
test("valid parse async", async () => {
|
||||
const result = await stringMap.spa(
|
||||
new Map([
|
||||
["first", "foo"],
|
||||
["second", "bar"],
|
||||
])
|
||||
);
|
||||
expect(result.success).toEqual(true);
|
||||
if (result.success) {
|
||||
expect(result.data.has("first")).toEqual(true);
|
||||
expect(result.data.has("second")).toEqual(true);
|
||||
expect(result.data.get("first")).toEqual("foo");
|
||||
expect(result.data.get("second")).toEqual("bar");
|
||||
}
|
||||
});
|
||||
|
||||
test("throws when a Set is given", () => {
|
||||
const result = stringMap.safeParse(new Set([]));
|
||||
expect(result.success).toEqual(false);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(1);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type);
|
||||
}
|
||||
});
|
||||
|
||||
test("throws when the given map has invalid key and invalid input", () => {
|
||||
const result = stringMap.safeParse(new Map([[42, Symbol()]]));
|
||||
expect(result.success).toEqual(false);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(2);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[0].path).toEqual([0, "key"]);
|
||||
expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[1].path).toEqual([0, "value"]);
|
||||
}
|
||||
});
|
||||
|
||||
test("throws when the given map has multiple invalid entries", () => {
|
||||
// const result = stringMap.safeParse(new Map([[42, Symbol()]]));
|
||||
|
||||
const result = stringMap.safeParse(
|
||||
new Map([
|
||||
[1, "foo"],
|
||||
["bar", 2],
|
||||
] as [any, any][]) as Map<any, any>
|
||||
);
|
||||
|
||||
// const result = stringMap.safeParse(new Map([[42, Symbol()]]));
|
||||
expect(result.success).toEqual(false);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(2);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[0].path).toEqual([0, "key"]);
|
||||
expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[1].path).toEqual([1, "value"]);
|
||||
}
|
||||
});
|
||||
|
||||
test("dirty", async () => {
|
||||
const map = z.map(
|
||||
z.string().refine((val) => val === val.toUpperCase(), {
|
||||
message: "Keys must be uppercase",
|
||||
}),
|
||||
z.string()
|
||||
);
|
||||
const result = await map.spa(
|
||||
new Map([
|
||||
["first", "foo"],
|
||||
["second", "bar"],
|
||||
])
|
||||
);
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues.length).toEqual(2);
|
||||
expect(result.error.issues[0].code).toEqual(z.ZodIssueCode.custom);
|
||||
expect(result.error.issues[0].message).toEqual("Keys must be uppercase");
|
||||
expect(result.error.issues[1].code).toEqual(z.ZodIssueCode.custom);
|
||||
expect(result.error.issues[1].message).toEqual("Keys must be uppercase");
|
||||
}
|
||||
});
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
test("masking test", () => {});
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
import { Mocker } from "./Mocker.js";
|
||||
|
||||
test("mocker", () => {
|
||||
const mocker = new Mocker();
|
||||
mocker.string;
|
||||
mocker.number;
|
||||
mocker.boolean;
|
||||
mocker.null;
|
||||
mocker.undefined;
|
||||
mocker.stringOptional;
|
||||
mocker.stringNullable;
|
||||
mocker.numberOptional;
|
||||
mocker.numberNullable;
|
||||
mocker.booleanOptional;
|
||||
mocker.booleanNullable;
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const schema = z.nan();
|
||||
|
||||
test("passing validations", () => {
|
||||
const result1 = schema.parse(Number.NaN);
|
||||
expect(Number.isNaN(result1)).toBe(true);
|
||||
|
||||
const result2 = schema.parse(Number("Not a number"));
|
||||
expect(Number.isNaN(result2)).toBe(true);
|
||||
});
|
||||
|
||||
test("failing validations", () => {
|
||||
expect(() => schema.parse(5)).toThrow();
|
||||
expect(() => schema.parse("John")).toThrow();
|
||||
expect(() => schema.parse(true)).toThrow();
|
||||
expect(() => schema.parse(null)).toThrow();
|
||||
expect(() => schema.parse(undefined)).toThrow();
|
||||
expect(() => schema.parse({})).toThrow();
|
||||
expect(() => schema.parse([])).toThrow();
|
||||
});
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("nativeEnum test with consts", () => {
|
||||
const Fruits: { Apple: "apple"; Banana: "banana" } = {
|
||||
Apple: "apple",
|
||||
Banana: "banana",
|
||||
};
|
||||
const fruitEnum = z.nativeEnum(Fruits);
|
||||
type fruitEnum = z.infer<typeof fruitEnum>;
|
||||
fruitEnum.parse("apple");
|
||||
fruitEnum.parse("banana");
|
||||
fruitEnum.parse(Fruits.Apple);
|
||||
fruitEnum.parse(Fruits.Banana);
|
||||
util.assertEqual<fruitEnum, "apple" | "banana">(true);
|
||||
});
|
||||
|
||||
test("nativeEnum test with real enum", () => {
|
||||
enum Fruits {
|
||||
Apple = "apple",
|
||||
Banana = "banana",
|
||||
}
|
||||
// @ts-ignore
|
||||
const fruitEnum = z.nativeEnum(Fruits);
|
||||
type fruitEnum = z.infer<typeof fruitEnum>;
|
||||
fruitEnum.parse("apple");
|
||||
fruitEnum.parse("banana");
|
||||
fruitEnum.parse(Fruits.Apple);
|
||||
fruitEnum.parse(Fruits.Banana);
|
||||
util.assertIs<fruitEnum extends Fruits ? true : false>(true);
|
||||
});
|
||||
|
||||
test("nativeEnum test with const with numeric keys", () => {
|
||||
const FruitValues = {
|
||||
Apple: 10,
|
||||
Banana: 20,
|
||||
// @ts-ignore
|
||||
} as const;
|
||||
const fruitEnum = z.nativeEnum(FruitValues);
|
||||
type fruitEnum = z.infer<typeof fruitEnum>;
|
||||
fruitEnum.parse(10);
|
||||
fruitEnum.parse(20);
|
||||
fruitEnum.parse(FruitValues.Apple);
|
||||
fruitEnum.parse(FruitValues.Banana);
|
||||
util.assertEqual<fruitEnum, 10 | 20>(true);
|
||||
});
|
||||
|
||||
test("from enum", () => {
|
||||
enum Fruits {
|
||||
Cantaloupe = 0,
|
||||
Apple = "apple",
|
||||
Banana = "banana",
|
||||
}
|
||||
|
||||
const FruitEnum = z.nativeEnum(Fruits as any);
|
||||
type _FruitEnum = z.infer<typeof FruitEnum>;
|
||||
FruitEnum.parse(Fruits.Cantaloupe);
|
||||
FruitEnum.parse(Fruits.Apple);
|
||||
FruitEnum.parse("apple");
|
||||
FruitEnum.parse(0);
|
||||
expect(() => FruitEnum.parse(1)).toThrow();
|
||||
expect(() => FruitEnum.parse("Apple")).toThrow();
|
||||
expect(() => FruitEnum.parse("Cantaloupe")).toThrow();
|
||||
});
|
||||
|
||||
test("from const", () => {
|
||||
const Greek = {
|
||||
Alpha: "a",
|
||||
Beta: "b",
|
||||
Gamma: 3,
|
||||
// @ts-ignore
|
||||
} as const;
|
||||
|
||||
const GreekEnum = z.nativeEnum(Greek);
|
||||
type _GreekEnum = z.infer<typeof GreekEnum>;
|
||||
GreekEnum.parse("a");
|
||||
GreekEnum.parse("b");
|
||||
GreekEnum.parse(3);
|
||||
expect(() => GreekEnum.parse("v")).toThrow();
|
||||
expect(() => GreekEnum.parse("Alpha")).toThrow();
|
||||
expect(() => GreekEnum.parse(2)).toThrow();
|
||||
|
||||
expect(GreekEnum.enum.Alpha).toEqual("a");
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
function checkErrors(a: z.ZodTypeAny, bad: any) {
|
||||
let expected: any;
|
||||
try {
|
||||
a.parse(bad);
|
||||
} catch (error) {
|
||||
expected = (error as z.ZodError).formErrors;
|
||||
}
|
||||
try {
|
||||
a.nullable().parse(bad);
|
||||
} catch (error) {
|
||||
expect((error as z.ZodError).formErrors).toEqual(expected);
|
||||
}
|
||||
}
|
||||
|
||||
test("Should have error messages appropriate for the underlying type", () => {
|
||||
checkErrors(z.string().min(2), 1);
|
||||
z.string().min(2).nullable().parse(null);
|
||||
checkErrors(z.number().gte(2), 1);
|
||||
z.number().gte(2).nullable().parse(null);
|
||||
checkErrors(z.boolean(), "");
|
||||
z.boolean().nullable().parse(null);
|
||||
checkErrors(z.null(), null);
|
||||
z.null().nullable().parse(null);
|
||||
checkErrors(z.null(), {});
|
||||
z.null().nullable().parse(null);
|
||||
checkErrors(z.object({}), 1);
|
||||
z.object({}).nullable().parse(null);
|
||||
checkErrors(z.tuple([]), 1);
|
||||
z.tuple([]).nullable().parse(null);
|
||||
checkErrors(z.unknown(), 1);
|
||||
z.unknown().nullable().parse(null);
|
||||
});
|
||||
|
||||
test("unwrap", () => {
|
||||
const unwrapped = z.string().nullable().unwrap();
|
||||
expect(unwrapped).toBeInstanceOf(z.ZodString);
|
||||
});
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const gtFive = z.number().gt(5);
|
||||
const gteFive = z.number().gte(-5).gte(5);
|
||||
const minFive = z.number().min(0).min(5);
|
||||
const ltFive = z.number().lte(10).lt(5);
|
||||
const lteFive = z.number().lte(5);
|
||||
const maxFive = z.number().max(10).max(5);
|
||||
const intNum = z.number().int();
|
||||
const positive = z.number().positive();
|
||||
const negative = z.number().negative();
|
||||
const nonpositive = z.number().nonpositive();
|
||||
const nonnegative = z.number().nonnegative();
|
||||
const multipleOfFive = z.number().multipleOf(5);
|
||||
const multipleOfNegativeFive = z.number().multipleOf(-5);
|
||||
const finite = z.number().finite();
|
||||
const safe = z.number().safe();
|
||||
const stepPointOne = z.number().step(0.1);
|
||||
const stepPointZeroZeroZeroOne = z.number().step(0.0001);
|
||||
const stepSixPointFour = z.number().step(6.4);
|
||||
|
||||
test("passing validations", () => {
|
||||
z.number().parse(1);
|
||||
z.number().parse(1.5);
|
||||
z.number().parse(0);
|
||||
z.number().parse(-1.5);
|
||||
z.number().parse(-1);
|
||||
z.number().parse(Number.POSITIVE_INFINITY);
|
||||
z.number().parse(Number.NEGATIVE_INFINITY);
|
||||
gtFive.parse(6);
|
||||
gtFive.parse(Number.POSITIVE_INFINITY);
|
||||
gteFive.parse(5);
|
||||
gteFive.parse(Number.POSITIVE_INFINITY);
|
||||
minFive.parse(5);
|
||||
minFive.parse(Number.POSITIVE_INFINITY);
|
||||
ltFive.parse(4);
|
||||
ltFive.parse(Number.NEGATIVE_INFINITY);
|
||||
lteFive.parse(5);
|
||||
lteFive.parse(Number.NEGATIVE_INFINITY);
|
||||
maxFive.parse(5);
|
||||
maxFive.parse(Number.NEGATIVE_INFINITY);
|
||||
intNum.parse(4);
|
||||
positive.parse(1);
|
||||
positive.parse(Number.POSITIVE_INFINITY);
|
||||
negative.parse(-1);
|
||||
negative.parse(Number.NEGATIVE_INFINITY);
|
||||
nonpositive.parse(0);
|
||||
nonpositive.parse(-1);
|
||||
nonpositive.parse(Number.NEGATIVE_INFINITY);
|
||||
nonnegative.parse(0);
|
||||
nonnegative.parse(1);
|
||||
nonnegative.parse(Number.POSITIVE_INFINITY);
|
||||
multipleOfFive.parse(15);
|
||||
multipleOfFive.parse(-15);
|
||||
multipleOfNegativeFive.parse(-15);
|
||||
multipleOfNegativeFive.parse(15);
|
||||
finite.parse(123);
|
||||
safe.parse(Number.MIN_SAFE_INTEGER);
|
||||
safe.parse(Number.MAX_SAFE_INTEGER);
|
||||
stepPointOne.parse(6);
|
||||
stepPointOne.parse(6.1);
|
||||
stepPointOne.parse(6.1);
|
||||
stepSixPointFour.parse(12.8);
|
||||
stepPointZeroZeroZeroOne.parse(3.01);
|
||||
});
|
||||
|
||||
test("failing validations", () => {
|
||||
expect(() => ltFive.parse(5)).toThrow();
|
||||
expect(() => lteFive.parse(6)).toThrow();
|
||||
expect(() => maxFive.parse(6)).toThrow();
|
||||
expect(() => gtFive.parse(5)).toThrow();
|
||||
expect(() => gteFive.parse(4)).toThrow();
|
||||
expect(() => minFive.parse(4)).toThrow();
|
||||
expect(() => intNum.parse(3.14)).toThrow();
|
||||
expect(() => positive.parse(0)).toThrow();
|
||||
expect(() => positive.parse(-1)).toThrow();
|
||||
expect(() => negative.parse(0)).toThrow();
|
||||
expect(() => negative.parse(1)).toThrow();
|
||||
expect(() => nonpositive.parse(1)).toThrow();
|
||||
expect(() => nonnegative.parse(-1)).toThrow();
|
||||
expect(() => multipleOfFive.parse(7.5)).toThrow();
|
||||
expect(() => multipleOfFive.parse(-7.5)).toThrow();
|
||||
expect(() => multipleOfNegativeFive.parse(-7.5)).toThrow();
|
||||
expect(() => multipleOfNegativeFive.parse(7.5)).toThrow();
|
||||
expect(() => finite.parse(Number.POSITIVE_INFINITY)).toThrow();
|
||||
expect(() => finite.parse(Number.NEGATIVE_INFINITY)).toThrow();
|
||||
expect(() => safe.parse(Number.MIN_SAFE_INTEGER - 1)).toThrow();
|
||||
expect(() => safe.parse(Number.MAX_SAFE_INTEGER + 1)).toThrow();
|
||||
|
||||
expect(() => stepPointOne.parse(6.11)).toThrow();
|
||||
expect(() => stepPointOne.parse(6.1000000001)).toThrow();
|
||||
expect(() => stepSixPointFour.parse(6.41)).toThrow();
|
||||
});
|
||||
|
||||
test("parse NaN", () => {
|
||||
expect(() => z.number().parse(Number.NaN)).toThrow();
|
||||
});
|
||||
|
||||
test("min max getters", () => {
|
||||
expect(z.number().minValue).toBeNull;
|
||||
expect(ltFive.minValue).toBeNull;
|
||||
expect(lteFive.minValue).toBeNull;
|
||||
expect(maxFive.minValue).toBeNull;
|
||||
expect(negative.minValue).toBeNull;
|
||||
expect(nonpositive.minValue).toBeNull;
|
||||
expect(intNum.minValue).toBeNull;
|
||||
expect(multipleOfFive.minValue).toBeNull;
|
||||
expect(finite.minValue).toBeNull;
|
||||
expect(gtFive.minValue).toEqual(5);
|
||||
expect(gteFive.minValue).toEqual(5);
|
||||
expect(minFive.minValue).toEqual(5);
|
||||
expect(minFive.min(10).minValue).toEqual(10);
|
||||
expect(positive.minValue).toEqual(0);
|
||||
expect(nonnegative.minValue).toEqual(0);
|
||||
expect(safe.minValue).toEqual(Number.MIN_SAFE_INTEGER);
|
||||
|
||||
expect(z.number().maxValue).toBeNull;
|
||||
expect(gtFive.maxValue).toBeNull;
|
||||
expect(gteFive.maxValue).toBeNull;
|
||||
expect(minFive.maxValue).toBeNull;
|
||||
expect(positive.maxValue).toBeNull;
|
||||
expect(nonnegative.maxValue).toBeNull;
|
||||
expect(intNum.minValue).toBeNull;
|
||||
expect(multipleOfFive.minValue).toBeNull;
|
||||
expect(finite.minValue).toBeNull;
|
||||
expect(ltFive.maxValue).toEqual(5);
|
||||
expect(lteFive.maxValue).toEqual(5);
|
||||
expect(maxFive.maxValue).toEqual(5);
|
||||
expect(maxFive.max(1).maxValue).toEqual(1);
|
||||
expect(negative.maxValue).toEqual(0);
|
||||
expect(nonpositive.maxValue).toEqual(0);
|
||||
expect(safe.maxValue).toEqual(Number.MAX_SAFE_INTEGER);
|
||||
});
|
||||
|
||||
test("int getter", () => {
|
||||
expect(z.number().isInt).toEqual(false);
|
||||
expect(z.number().multipleOf(1.5).isInt).toEqual(false);
|
||||
expect(gtFive.isInt).toEqual(false);
|
||||
expect(gteFive.isInt).toEqual(false);
|
||||
expect(minFive.isInt).toEqual(false);
|
||||
expect(positive.isInt).toEqual(false);
|
||||
expect(nonnegative.isInt).toEqual(false);
|
||||
expect(finite.isInt).toEqual(false);
|
||||
expect(ltFive.isInt).toEqual(false);
|
||||
expect(lteFive.isInt).toEqual(false);
|
||||
expect(maxFive.isInt).toEqual(false);
|
||||
expect(negative.isInt).toEqual(false);
|
||||
expect(nonpositive.isInt).toEqual(false);
|
||||
expect(safe.isInt).toEqual(false);
|
||||
|
||||
expect(intNum.isInt).toEqual(true);
|
||||
expect(multipleOfFive.isInt).toEqual(true);
|
||||
});
|
||||
|
||||
test("finite getter", () => {
|
||||
expect(z.number().isFinite).toEqual(false);
|
||||
expect(gtFive.isFinite).toEqual(false);
|
||||
expect(gteFive.isFinite).toEqual(false);
|
||||
expect(minFive.isFinite).toEqual(false);
|
||||
expect(positive.isFinite).toEqual(false);
|
||||
expect(nonnegative.isFinite).toEqual(false);
|
||||
expect(ltFive.isFinite).toEqual(false);
|
||||
expect(lteFive.isFinite).toEqual(false);
|
||||
expect(maxFive.isFinite).toEqual(false);
|
||||
expect(negative.isFinite).toEqual(false);
|
||||
expect(nonpositive.isFinite).toEqual(false);
|
||||
|
||||
expect(finite.isFinite).toEqual(true);
|
||||
expect(intNum.isFinite).toEqual(true);
|
||||
expect(multipleOfFive.isFinite).toEqual(true);
|
||||
expect(z.number().min(5).max(10).isFinite).toEqual(true);
|
||||
expect(safe.isFinite).toEqual(true);
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("object augmentation", () => {
|
||||
const Animal = z
|
||||
.object({
|
||||
species: z.string(),
|
||||
})
|
||||
.augment({
|
||||
population: z.number(),
|
||||
});
|
||||
// overwrites `species`
|
||||
const ModifiedAnimal = Animal.augment({
|
||||
species: z.array(z.string()),
|
||||
});
|
||||
ModifiedAnimal.parse({
|
||||
species: ["asd"],
|
||||
population: 1324,
|
||||
});
|
||||
|
||||
const bad = () =>
|
||||
ModifiedAnimal.parse({
|
||||
species: "asdf",
|
||||
population: 1324,
|
||||
} as any);
|
||||
expect(bad).toThrow();
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
const RealSet = Set;
|
||||
const RealMap = Map;
|
||||
const RealDate = Date;
|
||||
|
||||
test("doesn’t throw when Date is undefined", () => {
|
||||
delete (globalThis as any).Date;
|
||||
const result = z.object({}).safeParse({});
|
||||
expect(result.success).toEqual(true);
|
||||
globalThis.Date = RealDate;
|
||||
});
|
||||
|
||||
test("doesn’t throw when Set is undefined", () => {
|
||||
delete (globalThis as any).Set;
|
||||
const result = z.object({}).safeParse({});
|
||||
expect(result.success).toEqual(true);
|
||||
globalThis.Set = RealSet;
|
||||
});
|
||||
|
||||
test("doesn’t throw when Map is undefined", () => {
|
||||
delete (globalThis as any).Map;
|
||||
const result = z.object({}).safeParse({});
|
||||
expect(result.success).toEqual(true);
|
||||
globalThis.Map = RealMap;
|
||||
});
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const Test = z.object({
|
||||
f1: z.number(),
|
||||
f2: z.string().optional(),
|
||||
f3: z.string().nullable(),
|
||||
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
|
||||
});
|
||||
|
||||
test("object type inference", () => {
|
||||
type TestType = {
|
||||
f1: number;
|
||||
f2?: string | undefined;
|
||||
f3: string | null;
|
||||
f4: { t: string | boolean }[];
|
||||
};
|
||||
|
||||
util.assertEqual<z.TypeOf<typeof Test>, TestType>(true);
|
||||
});
|
||||
|
||||
test("unknown throw", () => {
|
||||
const asdf: unknown = 35;
|
||||
expect(() => Test.parse(asdf)).toThrow();
|
||||
});
|
||||
|
||||
test("shape() should return schema of particular key", () => {
|
||||
const f1Schema = Test.shape.f1;
|
||||
const f2Schema = Test.shape.f2;
|
||||
const f3Schema = Test.shape.f3;
|
||||
const f4Schema = Test.shape.f4;
|
||||
|
||||
expect(f1Schema).toBeInstanceOf(z.ZodNumber);
|
||||
expect(f2Schema).toBeInstanceOf(z.ZodOptional);
|
||||
expect(f3Schema).toBeInstanceOf(z.ZodNullable);
|
||||
expect(f4Schema).toBeInstanceOf(z.ZodArray);
|
||||
});
|
||||
|
||||
test("correct parsing", () => {
|
||||
Test.parse({
|
||||
f1: 12,
|
||||
f2: "string",
|
||||
f3: "string",
|
||||
f4: [
|
||||
{
|
||||
t: "string",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
Test.parse({
|
||||
f1: 12,
|
||||
f3: null,
|
||||
f4: [
|
||||
{
|
||||
t: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("incorrect #1", () => {
|
||||
expect(() => Test.parse({} as any)).toThrow();
|
||||
});
|
||||
|
||||
test("nonstrict by default", () => {
|
||||
z.object({ points: z.number() }).parse({
|
||||
points: 2314,
|
||||
unknown: "asdf",
|
||||
});
|
||||
});
|
||||
|
||||
const data = {
|
||||
points: 2314,
|
||||
unknown: "asdf",
|
||||
};
|
||||
|
||||
test("strip by default", () => {
|
||||
const val = z.object({ points: z.number() }).parse(data);
|
||||
expect(val).toEqual({ points: 2314 });
|
||||
});
|
||||
|
||||
test("unknownkeys override", () => {
|
||||
const val = z.object({ points: z.number() }).strict().passthrough().strip().nonstrict().parse(data);
|
||||
|
||||
expect(val).toEqual(data);
|
||||
});
|
||||
|
||||
test("passthrough unknown", () => {
|
||||
const val = z.object({ points: z.number() }).passthrough().parse(data);
|
||||
|
||||
expect(val).toEqual(data);
|
||||
});
|
||||
|
||||
test("strip unknown", () => {
|
||||
const val = z.object({ points: z.number() }).strip().parse(data);
|
||||
|
||||
expect(val).toEqual({ points: 2314 });
|
||||
});
|
||||
|
||||
test("strict", () => {
|
||||
const val = z.object({ points: z.number() }).strict().safeParse(data);
|
||||
|
||||
expect(val.success).toEqual(false);
|
||||
});
|
||||
|
||||
test("catchall inference", () => {
|
||||
const o1 = z
|
||||
.object({
|
||||
first: z.string(),
|
||||
})
|
||||
.catchall(z.number());
|
||||
|
||||
const d1 = o1.parse({ first: "asdf", num: 1243 });
|
||||
util.assertEqual<number, (typeof d1)["asdf"]>(true);
|
||||
util.assertEqual<string, (typeof d1)["first"]>(true);
|
||||
});
|
||||
|
||||
test("catchall overrides strict", () => {
|
||||
const o1 = z.object({ first: z.string().optional() }).strict().catchall(z.number());
|
||||
|
||||
// should run fine setting a catchall overrides the unknownKeys behavior
|
||||
o1.parse({
|
||||
asdf: 1234,
|
||||
});
|
||||
|
||||
// should only run catchall validation against unknown keys
|
||||
o1.parse({
|
||||
first: "asdf",
|
||||
asdf: 1234,
|
||||
});
|
||||
});
|
||||
|
||||
test("catchall overrides strict", () => {
|
||||
const o1 = z
|
||||
.object({
|
||||
first: z.string(),
|
||||
})
|
||||
.strict()
|
||||
.catchall(z.number());
|
||||
|
||||
// should run fine setting a catchall overrides the unknownKeys behavior
|
||||
o1.parse({
|
||||
first: "asdf",
|
||||
asdf: 1234,
|
||||
});
|
||||
});
|
||||
|
||||
test("test that optional keys are unset", () => {
|
||||
const SNamedEntity = z.object({
|
||||
id: z.string(),
|
||||
set: z.string().optional(),
|
||||
unset: z.string().optional(),
|
||||
});
|
||||
const result = SNamedEntity.parse({
|
||||
id: "asdf",
|
||||
set: undefined,
|
||||
});
|
||||
// eslint-disable-next-line ban/ban
|
||||
expect(Object.keys(result)).toEqual(["id", "set"]);
|
||||
});
|
||||
|
||||
test("test catchall parsing", async () => {
|
||||
const result = z.object({ name: z.string() }).catchall(z.number()).parse({ name: "Foo", validExtraKey: 61 });
|
||||
|
||||
expect(result).toEqual({ name: "Foo", validExtraKey: 61 });
|
||||
|
||||
const result2 = z
|
||||
.object({ name: z.string() })
|
||||
.catchall(z.number())
|
||||
.safeParse({ name: "Foo", validExtraKey: 61, invalid: "asdf" });
|
||||
|
||||
expect(result2.success).toEqual(false);
|
||||
});
|
||||
|
||||
test("test nonexistent keys", async () => {
|
||||
const Schema = z.union([z.object({ a: z.string() }), z.object({ b: z.number() })]);
|
||||
const obj = { a: "A" };
|
||||
const result = await Schema.spa(obj); // Works with 1.11.10, breaks with 2.0.0-beta.21
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
test("test async union", async () => {
|
||||
const Schema2 = z.union([
|
||||
z.object({
|
||||
ty: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
ty: z.number(),
|
||||
}),
|
||||
]);
|
||||
|
||||
const obj = { ty: "A" };
|
||||
const result = await Schema2.spa(obj); // Works with 1.11.10, breaks with 2.0.0-beta.21
|
||||
expect(result.success).toEqual(true);
|
||||
});
|
||||
|
||||
test("test inferred merged type", async () => {
|
||||
const asdf = z.object({ a: z.string() }).merge(z.object({ a: z.number() }));
|
||||
type asdf = z.infer<typeof asdf>;
|
||||
util.assertEqual<asdf, { a: number }>(true);
|
||||
});
|
||||
|
||||
test("inferred merged object type with optional properties", async () => {
|
||||
const Merged = z
|
||||
.object({ a: z.string(), b: z.string().optional() })
|
||||
.merge(z.object({ a: z.string().optional(), b: z.string() }));
|
||||
type Merged = z.infer<typeof Merged>;
|
||||
util.assertEqual<Merged, { a?: string | undefined; b: string }>(true);
|
||||
// todo
|
||||
// util.assertEqual<Merged, { a?: string | undefined; b: string }>(true);
|
||||
});
|
||||
|
||||
test("inferred unioned object type with optional properties", async () => {
|
||||
const Unioned = z.union([
|
||||
z.object({ a: z.string(), b: z.string().optional() }),
|
||||
z.object({ a: z.string().optional(), b: z.string() }),
|
||||
]);
|
||||
type Unioned = z.infer<typeof Unioned>;
|
||||
util.assertEqual<Unioned, { a: string; b?: string | undefined } | { a?: string | undefined; b: string }>(true);
|
||||
});
|
||||
|
||||
test("inferred enum type", async () => {
|
||||
const Enum = z.object({ a: z.string(), b: z.string().optional() }).keyof();
|
||||
|
||||
expect(Enum.Values).toEqual({
|
||||
a: "a",
|
||||
b: "b",
|
||||
});
|
||||
expect(Enum.enum).toEqual({
|
||||
a: "a",
|
||||
b: "b",
|
||||
});
|
||||
expect(Enum._def.values).toEqual(["a", "b"]);
|
||||
type Enum = z.infer<typeof Enum>;
|
||||
util.assertEqual<Enum, "a" | "b">(true);
|
||||
});
|
||||
|
||||
test("inferred partial object type with optional properties", async () => {
|
||||
const Partial = z.object({ a: z.string(), b: z.string().optional() }).partial();
|
||||
type Partial = z.infer<typeof Partial>;
|
||||
util.assertEqual<Partial, { a?: string | undefined; b?: string | undefined }>(true);
|
||||
});
|
||||
|
||||
test("inferred picked object type with optional properties", async () => {
|
||||
const Picked = z.object({ a: z.string(), b: z.string().optional() }).pick({ b: true });
|
||||
type Picked = z.infer<typeof Picked>;
|
||||
util.assertEqual<Picked, { b?: string | undefined }>(true);
|
||||
});
|
||||
|
||||
test("inferred type for unknown/any keys", () => {
|
||||
const myType = z.object({
|
||||
anyOptional: z.any().optional(),
|
||||
anyRequired: z.any(),
|
||||
unknownOptional: z.unknown().optional(),
|
||||
unknownRequired: z.unknown(),
|
||||
});
|
||||
type myType = z.infer<typeof myType>;
|
||||
util.assertEqual<
|
||||
myType,
|
||||
{
|
||||
anyOptional?: any;
|
||||
anyRequired?: any;
|
||||
unknownOptional?: unknown;
|
||||
unknownRequired?: unknown;
|
||||
}
|
||||
>(true);
|
||||
});
|
||||
|
||||
test("setKey", () => {
|
||||
const base = z.object({ name: z.string() });
|
||||
const withNewKey = base.setKey("age", z.number());
|
||||
|
||||
type withNewKey = z.infer<typeof withNewKey>;
|
||||
util.assertEqual<withNewKey, { name: string; age: number }>(true);
|
||||
withNewKey.parse({ name: "asdf", age: 1234 });
|
||||
});
|
||||
|
||||
test("strictcreate", async () => {
|
||||
const strictObj = z.strictObject({
|
||||
name: z.string(),
|
||||
});
|
||||
|
||||
const syncResult = strictObj.safeParse({ name: "asdf", unexpected: 13 });
|
||||
expect(syncResult.success).toEqual(false);
|
||||
|
||||
const asyncResult = await strictObj.spa({ name: "asdf", unexpected: 13 });
|
||||
expect(asyncResult.success).toEqual(false);
|
||||
});
|
||||
|
||||
test("object with refine", async () => {
|
||||
const schema = z
|
||||
.object({
|
||||
a: z.string().default("foo"),
|
||||
b: z.number(),
|
||||
})
|
||||
.refine(() => true);
|
||||
expect(schema.parse({ b: 5 })).toEqual({ b: 5, a: "foo" });
|
||||
const result = await schema.parseAsync({ b: 5 });
|
||||
expect(result).toEqual({ b: 5, a: "foo" });
|
||||
});
|
||||
|
||||
test("intersection of object with date", async () => {
|
||||
const schema = z.object({
|
||||
a: z.date(),
|
||||
});
|
||||
expect(schema.and(schema).parse({ a: new Date(1637353595983) })).toEqual({
|
||||
a: new Date(1637353595983),
|
||||
});
|
||||
const result = await schema.parseAsync({ a: new Date(1637353595983) });
|
||||
expect(result).toEqual({ a: new Date(1637353595983) });
|
||||
});
|
||||
|
||||
test("intersection of object with refine with date", async () => {
|
||||
const schema = z
|
||||
.object({
|
||||
a: z.date(),
|
||||
})
|
||||
.refine(() => true);
|
||||
expect(schema.and(schema).parse({ a: new Date(1637353595983) })).toEqual({
|
||||
a: new Date(1637353595983),
|
||||
});
|
||||
const result = await schema.parseAsync({ a: new Date(1637353595983) });
|
||||
expect(result).toEqual({ a: new Date(1637353595983) });
|
||||
});
|
||||
|
||||
test("constructor key", () => {
|
||||
const person = z
|
||||
.object({
|
||||
name: z.string(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
expect(() =>
|
||||
person.parse({
|
||||
name: "bob dylan",
|
||||
constructor: 61,
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("constructor key", () => {
|
||||
const Example = z.object({
|
||||
prop: z.string(),
|
||||
opt: z.number().optional(),
|
||||
arr: z.string().array(),
|
||||
});
|
||||
|
||||
type Example = z.infer<typeof Example>;
|
||||
util.assertEqual<keyof Example, "prop" | "opt" | "arr">(true);
|
||||
});
|
||||
|
||||
test("unknownkeys merging", () => {
|
||||
// This one is "strict"
|
||||
const schemaA = z
|
||||
.object({
|
||||
a: z.string(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
// This one is "strip"
|
||||
const schemaB = z
|
||||
.object({
|
||||
b: z.string(),
|
||||
})
|
||||
.catchall(z.string());
|
||||
|
||||
const mergedSchema = schemaA.merge(schemaB);
|
||||
type mergedSchema = typeof mergedSchema;
|
||||
util.assertEqual<mergedSchema["_def"]["unknownKeys"], "strip">(true);
|
||||
expect(mergedSchema._def.unknownKeys).toEqual("strip");
|
||||
|
||||
util.assertEqual<mergedSchema["_def"]["catchall"], z.ZodString>(true);
|
||||
expect(mergedSchema._def.catchall instanceof z.ZodString).toEqual(true);
|
||||
});
|
||||
|
||||
const personToExtend = z.object({
|
||||
firstName: z.string(),
|
||||
lastName: z.string(),
|
||||
});
|
||||
|
||||
test("extend() should return schema with new key", () => {
|
||||
const PersonWithNickname = personToExtend.extend({ nickName: z.string() });
|
||||
type PersonWithNickname = z.infer<typeof PersonWithNickname>;
|
||||
|
||||
const expected = { firstName: "f", nickName: "n", lastName: "l" };
|
||||
const actual = PersonWithNickname.parse(expected);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
util.assertEqual<keyof PersonWithNickname, "firstName" | "lastName" | "nickName">(true);
|
||||
util.assertEqual<PersonWithNickname, { firstName: string; lastName: string; nickName: string }>(true);
|
||||
});
|
||||
|
||||
test("extend() should have power to override existing key", () => {
|
||||
const PersonWithNumberAsLastName = personToExtend.extend({
|
||||
lastName: z.number(),
|
||||
});
|
||||
type PersonWithNumberAsLastName = z.infer<typeof PersonWithNumberAsLastName>;
|
||||
|
||||
const expected = { firstName: "f", lastName: 42 };
|
||||
const actual = PersonWithNumberAsLastName.parse(expected);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
util.assertEqual<PersonWithNumberAsLastName, { firstName: string; lastName: number }>(true);
|
||||
});
|
||||
|
||||
test("passthrough index signature", () => {
|
||||
const a = z.object({ a: z.string() });
|
||||
type a = z.infer<typeof a>;
|
||||
util.assertEqual<{ a: string }, a>(true);
|
||||
const b = a.passthrough();
|
||||
type b = z.infer<typeof b>;
|
||||
util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
|
||||
});
|
||||
|
||||
test("xor", () => {
|
||||
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
|
||||
type XOR<T, U> = T extends object ? (U extends object ? (Without<T, U> & U) | (Without<U, T> & T) : U) : T;
|
||||
|
||||
type A = { name: string; a: number };
|
||||
type B = { name: string; b: number };
|
||||
type C = XOR<A, B>;
|
||||
type Outer = { data: C };
|
||||
|
||||
const _Outer: z.ZodType<Outer> = z.object({
|
||||
data: z.union([z.object({ name: z.string(), a: z.number() }), z.object({ name: z.string(), b: z.number() })]),
|
||||
});
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
function checkErrors(a: z.ZodTypeAny, bad: any) {
|
||||
let expected: any;
|
||||
try {
|
||||
a.parse(bad);
|
||||
} catch (error) {
|
||||
expected = (error as z.ZodError).formErrors;
|
||||
}
|
||||
try {
|
||||
a.optional().parse(bad);
|
||||
} catch (error) {
|
||||
expect((error as z.ZodError).formErrors).toEqual(expected);
|
||||
}
|
||||
}
|
||||
|
||||
test("Should have error messages appropriate for the underlying type", () => {
|
||||
checkErrors(z.string().min(2), 1);
|
||||
z.string().min(2).optional().parse(undefined);
|
||||
checkErrors(z.number().gte(2), 1);
|
||||
z.number().gte(2).optional().parse(undefined);
|
||||
checkErrors(z.boolean(), "");
|
||||
z.boolean().optional().parse(undefined);
|
||||
checkErrors(z.undefined(), null);
|
||||
z.undefined().optional().parse(undefined);
|
||||
checkErrors(z.null(), {});
|
||||
z.null().optional().parse(undefined);
|
||||
checkErrors(z.object({}), 1);
|
||||
z.object({}).optional().parse(undefined);
|
||||
checkErrors(z.tuple([]), 1);
|
||||
z.tuple([]).optional().parse(undefined);
|
||||
checkErrors(z.unknown(), 1);
|
||||
z.unknown().optional().parse(undefined);
|
||||
});
|
||||
|
||||
test("unwrap", () => {
|
||||
const unwrapped = z.string().optional().unwrap();
|
||||
expect(unwrapped).toBeInstanceOf(z.ZodString);
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import { type SyncParseReturnType, isAborted, isDirty, isValid } from "../helpers/parseUtil.js";
|
||||
|
||||
test("parseUtil isInvalid should use structural typing", () => {
|
||||
// Test for issue #556: https://github.com/colinhacks/zod/issues/556
|
||||
const aborted: SyncParseReturnType = { status: "aborted" };
|
||||
const dirty: SyncParseReturnType = { status: "dirty", value: "whatever" };
|
||||
const valid: SyncParseReturnType = { status: "valid", value: "whatever" };
|
||||
|
||||
expect(isAborted(aborted)).toBe(true);
|
||||
expect(isAborted(dirty)).toBe(false);
|
||||
expect(isAborted(valid)).toBe(false);
|
||||
|
||||
expect(isDirty(aborted)).toBe(false);
|
||||
expect(isDirty(dirty)).toBe(true);
|
||||
expect(isDirty(valid)).toBe(false);
|
||||
|
||||
expect(isValid(aborted)).toBe(false);
|
||||
expect(isValid(dirty)).toBe(false);
|
||||
expect(isValid(valid)).toBe(true);
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("parse strict object with unknown keys", () => {
|
||||
expect(() =>
|
||||
z
|
||||
.object({ name: z.string() })
|
||||
.strict()
|
||||
.parse({ name: "bill", unknownKey: 12 } as any)
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("parse nonstrict object with unknown keys", () => {
|
||||
z.object({ name: z.string() }).nonstrict().parse({ name: "bill", unknownKey: 12 });
|
||||
});
|
||||
|
||||
test("invalid left side of intersection", () => {
|
||||
expect(() => z.intersection(z.string(), z.number()).parse(12 as any)).toThrow();
|
||||
});
|
||||
|
||||
test("invalid right side of intersection", () => {
|
||||
expect(() => z.intersection(z.string(), z.number()).parse("12" as any)).toThrow();
|
||||
});
|
||||
|
||||
test("parsing non-array in tuple schema", () => {
|
||||
expect(() => z.tuple([]).parse("12" as any)).toThrow();
|
||||
});
|
||||
|
||||
test("incorrect num elements in tuple", () => {
|
||||
expect(() => z.tuple([]).parse(["asdf"] as any)).toThrow();
|
||||
});
|
||||
|
||||
test("invalid enum value", () => {
|
||||
expect(() => z.enum(["Blue"]).parse("Red" as any)).toThrow();
|
||||
});
|
||||
|
||||
test("parsing unknown", () => {
|
||||
z.string().parse("Red" as unknown);
|
||||
});
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { ZodNullable, ZodOptional } from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const nested = z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
outer: z.object({
|
||||
inner: z.string(),
|
||||
}),
|
||||
array: z.array(z.object({ asdf: z.string() })),
|
||||
});
|
||||
|
||||
test("shallow inference", () => {
|
||||
const shallow = nested.partial();
|
||||
type shallow = z.infer<typeof shallow>;
|
||||
type correct = {
|
||||
name?: string | undefined;
|
||||
age?: number | undefined;
|
||||
outer?: { inner: string } | undefined;
|
||||
array?: { asdf: string }[] | undefined;
|
||||
};
|
||||
util.assertEqual<shallow, correct>(true);
|
||||
});
|
||||
|
||||
test("shallow partial parse", () => {
|
||||
const shallow = nested.partial();
|
||||
shallow.parse({});
|
||||
shallow.parse({
|
||||
name: "asdf",
|
||||
age: 23143,
|
||||
});
|
||||
});
|
||||
|
||||
test("deep partial inference", () => {
|
||||
const deep = nested.deepPartial();
|
||||
const asdf = deep.shape.array.unwrap().element.shape.asdf.unwrap();
|
||||
asdf.parse("asdf");
|
||||
type deep = z.infer<typeof deep>;
|
||||
type correct = {
|
||||
array?: { asdf?: string | undefined }[] | undefined;
|
||||
name?: string | undefined;
|
||||
age?: number | undefined;
|
||||
outer?: { inner?: string | undefined } | undefined;
|
||||
};
|
||||
|
||||
util.assertEqual<deep, correct>(true);
|
||||
});
|
||||
|
||||
test("deep partial parse", () => {
|
||||
const deep = nested.deepPartial();
|
||||
|
||||
expect(deep.shape.name instanceof z.ZodOptional).toBe(true);
|
||||
expect(deep.shape.outer instanceof z.ZodOptional).toBe(true);
|
||||
expect(deep.shape.outer._def.innerType instanceof z.ZodObject).toBe(true);
|
||||
expect(deep.shape.outer._def.innerType.shape.inner instanceof z.ZodOptional).toBe(true);
|
||||
expect(deep.shape.outer._def.innerType.shape.inner._def.innerType instanceof z.ZodString).toBe(true);
|
||||
});
|
||||
|
||||
test("deep partial runtime tests", () => {
|
||||
const deep = nested.deepPartial();
|
||||
deep.parse({});
|
||||
deep.parse({
|
||||
outer: {},
|
||||
});
|
||||
deep.parse({
|
||||
name: "asdf",
|
||||
age: 23143,
|
||||
outer: {
|
||||
inner: "adsf",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("deep partial optional/nullable", () => {
|
||||
const schema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
age: z.number().nullable(),
|
||||
})
|
||||
.deepPartial();
|
||||
|
||||
expect(schema.shape.name.unwrap()).toBeInstanceOf(ZodOptional);
|
||||
expect(schema.shape.age.unwrap()).toBeInstanceOf(ZodNullable);
|
||||
});
|
||||
|
||||
test("deep partial tuple", () => {
|
||||
const schema = z
|
||||
.object({
|
||||
tuple: z.tuple([
|
||||
z.object({
|
||||
name: z.string().optional(),
|
||||
age: z.number().nullable(),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
.deepPartial();
|
||||
|
||||
expect(schema.shape.tuple.unwrap().items[0].shape.name).toBeInstanceOf(ZodOptional);
|
||||
});
|
||||
|
||||
test("deep partial inference", () => {
|
||||
const mySchema = z.object({
|
||||
name: z.string(),
|
||||
array: z.array(z.object({ asdf: z.string() })),
|
||||
tuple: z.tuple([z.object({ value: z.string() })]),
|
||||
});
|
||||
|
||||
const partialed = mySchema.deepPartial();
|
||||
type partialed = z.infer<typeof partialed>;
|
||||
type expected = {
|
||||
name?: string | undefined;
|
||||
array?:
|
||||
| {
|
||||
asdf?: string | undefined;
|
||||
}[]
|
||||
| undefined;
|
||||
tuple?: [{ value?: string | undefined }] | undefined;
|
||||
};
|
||||
util.assertEqual<expected, partialed>(true);
|
||||
});
|
||||
|
||||
test("required", () => {
|
||||
const object = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().optional(),
|
||||
field: z.string().optional().default("asdf"),
|
||||
nullableField: z.number().nullable(),
|
||||
nullishField: z.string().nullish(),
|
||||
});
|
||||
|
||||
const requiredObject = object.required();
|
||||
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
|
||||
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNumber);
|
||||
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault);
|
||||
expect(requiredObject.shape.nullableField).toBeInstanceOf(z.ZodNullable);
|
||||
expect(requiredObject.shape.nullishField).toBeInstanceOf(z.ZodNullable);
|
||||
});
|
||||
|
||||
test("required inference", () => {
|
||||
const object = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().optional(),
|
||||
field: z.string().optional().default("asdf"),
|
||||
nullableField: z.number().nullable(),
|
||||
nullishField: z.string().nullish(),
|
||||
});
|
||||
|
||||
const requiredObject = object.required();
|
||||
|
||||
type required = z.infer<typeof requiredObject>;
|
||||
type expected = {
|
||||
name: string;
|
||||
age: number;
|
||||
field: string;
|
||||
nullableField: number | null;
|
||||
nullishField: string | null;
|
||||
};
|
||||
util.assertEqual<expected, required>(true);
|
||||
});
|
||||
|
||||
test("required with mask", () => {
|
||||
const object = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().optional(),
|
||||
field: z.string().optional().default("asdf"),
|
||||
country: z.string().optional(),
|
||||
});
|
||||
|
||||
const requiredObject = object.required({ age: true });
|
||||
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
|
||||
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNumber);
|
||||
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault);
|
||||
expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional);
|
||||
});
|
||||
|
||||
test("required with mask -- ignore falsy values", () => {
|
||||
const object = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().optional(),
|
||||
field: z.string().optional().default("asdf"),
|
||||
country: z.string().optional(),
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
const requiredObject = object.required({ age: true, country: false });
|
||||
expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString);
|
||||
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNumber);
|
||||
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault);
|
||||
expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional);
|
||||
});
|
||||
|
||||
test("partial with mask", async () => {
|
||||
const object = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().optional(),
|
||||
field: z.string().optional().default("asdf"),
|
||||
country: z.string(),
|
||||
});
|
||||
|
||||
const masked = object.partial({ age: true, field: true, name: true }).strict();
|
||||
|
||||
expect(masked.shape.name).toBeInstanceOf(z.ZodOptional);
|
||||
expect(masked.shape.age).toBeInstanceOf(z.ZodOptional);
|
||||
expect(masked.shape.field).toBeInstanceOf(z.ZodOptional);
|
||||
expect(masked.shape.country).toBeInstanceOf(z.ZodString);
|
||||
|
||||
masked.parse({ country: "US" });
|
||||
await masked.parseAsync({ country: "US" });
|
||||
});
|
||||
|
||||
test("partial with mask -- ignore falsy values", async () => {
|
||||
const object = z.object({
|
||||
name: z.string(),
|
||||
age: z.number().optional(),
|
||||
field: z.string().optional().default("asdf"),
|
||||
country: z.string(),
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
const masked = object.partial({ name: true, country: false }).strict();
|
||||
|
||||
expect(masked.shape.name).toBeInstanceOf(z.ZodOptional);
|
||||
expect(masked.shape.age).toBeInstanceOf(z.ZodOptional);
|
||||
expect(masked.shape.field).toBeInstanceOf(z.ZodDefault);
|
||||
expect(masked.shape.country).toBeInstanceOf(z.ZodString);
|
||||
|
||||
masked.parse({ country: "US" });
|
||||
await masked.parseAsync({ country: "US" });
|
||||
});
|
||||
|
||||
test("deeppartial array", () => {
|
||||
const schema = z.object({ array: z.string().array().min(42) }).deepPartial();
|
||||
|
||||
// works as expected
|
||||
schema.parse({});
|
||||
|
||||
// should be false, but is true
|
||||
expect(schema.safeParse({ array: [] }).success).toBe(false);
|
||||
});
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const fish = z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
nested: z.object({}),
|
||||
});
|
||||
|
||||
test("pick type inference", () => {
|
||||
const nameonlyFish = fish.pick({ name: true });
|
||||
type nameonlyFish = z.infer<typeof nameonlyFish>;
|
||||
util.assertEqual<nameonlyFish, { name: string }>(true);
|
||||
});
|
||||
|
||||
test("pick parse - success", () => {
|
||||
const nameonlyFish = fish.pick({ name: true });
|
||||
nameonlyFish.parse({ name: "bob" });
|
||||
|
||||
// @ts-expect-error checking runtime picks `name` only.
|
||||
const anotherNameonlyFish = fish.pick({ name: true, age: false });
|
||||
anotherNameonlyFish.parse({ name: "bob" });
|
||||
});
|
||||
|
||||
test("pick parse - fail", () => {
|
||||
fish.pick({ name: true }).parse({ name: "12" } as any);
|
||||
fish.pick({ name: true }).parse({ name: "bob", age: 12 } as any);
|
||||
fish.pick({ age: true }).parse({ age: 12 } as any);
|
||||
|
||||
const nameonlyFish = fish.pick({ name: true }).strict();
|
||||
const bad1 = () => nameonlyFish.parse({ name: 12 } as any);
|
||||
const bad2 = () => nameonlyFish.parse({ name: "bob", age: 12 } as any);
|
||||
const bad3 = () => nameonlyFish.parse({ age: 12 } as any);
|
||||
|
||||
// @ts-expect-error checking runtime picks `name` only.
|
||||
const anotherNameonlyFish = fish.pick({ name: true, age: false }).strict();
|
||||
const bad4 = () => anotherNameonlyFish.parse({ name: "bob", age: 12 } as any);
|
||||
|
||||
expect(bad1).toThrow();
|
||||
expect(bad2).toThrow();
|
||||
expect(bad3).toThrow();
|
||||
expect(bad4).toThrow();
|
||||
});
|
||||
|
||||
test("omit type inference", () => {
|
||||
const nonameFish = fish.omit({ name: true });
|
||||
type nonameFish = z.infer<typeof nonameFish>;
|
||||
util.assertEqual<nonameFish, { age: number; nested: {} }>(true);
|
||||
});
|
||||
|
||||
test("omit parse - success", () => {
|
||||
const nonameFish = fish.omit({ name: true });
|
||||
nonameFish.parse({ age: 12, nested: {} });
|
||||
|
||||
// @ts-expect-error checking runtime omits `name` only.
|
||||
const anotherNonameFish = fish.omit({ name: true, age: false });
|
||||
anotherNonameFish.parse({ age: 12, nested: {} });
|
||||
});
|
||||
|
||||
test("omit parse - fail", () => {
|
||||
const nonameFish = fish.omit({ name: true });
|
||||
const bad1 = () => nonameFish.parse({ name: 12 } as any);
|
||||
const bad2 = () => nonameFish.parse({ age: 12 } as any);
|
||||
const bad3 = () => nonameFish.parse({} as any);
|
||||
|
||||
// @ts-expect-error checking runtime omits `name` only.
|
||||
const anotherNonameFish = fish.omit({ name: true, age: false });
|
||||
const bad4 = () => anotherNonameFish.parse({ nested: {} } as any);
|
||||
|
||||
expect(bad1).toThrow();
|
||||
expect(bad2).toThrow();
|
||||
expect(bad3).toThrow();
|
||||
expect(bad4).toThrow();
|
||||
});
|
||||
|
||||
test("nonstrict inference", () => {
|
||||
const laxfish = fish.pick({ name: true }).catchall(z.any());
|
||||
type laxfish = z.infer<typeof laxfish>;
|
||||
util.assertEqual<laxfish, { name: string } & { [k: string]: any }>(true);
|
||||
});
|
||||
|
||||
test("nonstrict parsing - pass", () => {
|
||||
const laxfish = fish.passthrough().pick({ name: true });
|
||||
laxfish.parse({ name: "asdf", whatever: "asdf" });
|
||||
laxfish.parse({ name: "asdf", age: 12, nested: {} });
|
||||
});
|
||||
|
||||
test("nonstrict parsing - fail", () => {
|
||||
const laxfish = fish.passthrough().pick({ name: true });
|
||||
const bad = () => laxfish.parse({ whatever: "asdf" } as any);
|
||||
expect(bad).toThrow();
|
||||
});
|
||||
|
||||
test("pick/omit/required/partial - do not allow unknown keys", () => {
|
||||
const schema = z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
});
|
||||
|
||||
// @ts-expect-error
|
||||
schema.pick({ $unknown: true });
|
||||
// @ts-expect-error
|
||||
schema.omit({ $unknown: true });
|
||||
// @ts-expect-error
|
||||
schema.required({ $unknown: true });
|
||||
// @ts-expect-error
|
||||
schema.partial({ $unknown: true });
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("string to number pipeline", () => {
|
||||
const schema = z.string().transform(Number).pipe(z.number());
|
||||
expect(schema.parse("1234")).toEqual(1234);
|
||||
});
|
||||
|
||||
test("string to number pipeline async", async () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.transform(async (val) => Number(val))
|
||||
.pipe(z.number());
|
||||
expect(await schema.parseAsync("1234")).toEqual(1234);
|
||||
});
|
||||
|
||||
test("break if dirty", () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.refine((c) => c === "1234")
|
||||
.transform(async (val) => Number(val))
|
||||
.pipe(z.number().refine((v) => v < 100));
|
||||
const r1: any = schema.safeParse("12345");
|
||||
expect(r1.error.issues.length).toBe(1);
|
||||
const r2: any = schema.safeParse("3");
|
||||
expect(r2.error.issues.length).toBe(1);
|
||||
});
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("preprocess", () => {
|
||||
const schema = z.preprocess((data) => [data], z.string().array());
|
||||
|
||||
const value = schema.parse("asdf");
|
||||
expect(value).toEqual(["asdf"]);
|
||||
util.assertEqual<(typeof schema)["_input"], unknown>(true);
|
||||
});
|
||||
|
||||
test("async preprocess", async () => {
|
||||
const schema = z.preprocess(async (data) => [data], z.string().array());
|
||||
|
||||
const value = await schema.parseAsync("asdf");
|
||||
expect(value).toEqual(["asdf"]);
|
||||
});
|
||||
|
||||
test("preprocess ctx.addIssue with parse", () => {
|
||||
expect(() => {
|
||||
z.preprocess((data, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `${data} is not one of our allowed strings`,
|
||||
});
|
||||
return data;
|
||||
}, z.string()).parse("asdf");
|
||||
}).toThrow(
|
||||
JSON.stringify(
|
||||
[
|
||||
{
|
||||
code: "custom",
|
||||
message: "asdf is not one of our allowed strings",
|
||||
path: [],
|
||||
},
|
||||
],
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("preprocess ctx.addIssue non-fatal by default", () => {
|
||||
try {
|
||||
z.preprocess((data, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
});
|
||||
return data;
|
||||
}, z.string()).parse(1234);
|
||||
} catch (err) {
|
||||
z.ZodError.assert(err);
|
||||
expect(err.issues.length).toEqual(2);
|
||||
}
|
||||
});
|
||||
|
||||
test("preprocess ctx.addIssue fatal true", () => {
|
||||
try {
|
||||
z.preprocess((data, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
fatal: true,
|
||||
});
|
||||
return data;
|
||||
}, z.string()).parse(1234);
|
||||
} catch (err) {
|
||||
z.ZodError.assert(err);
|
||||
expect(err.issues.length).toEqual(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("async preprocess ctx.addIssue with parse", async () => {
|
||||
const schema = z.preprocess(async (data, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `custom error`,
|
||||
});
|
||||
return data;
|
||||
}, z.string());
|
||||
|
||||
expect(await schema.safeParseAsync("asdf")).toMatchInlineSnapshot(`
|
||||
{
|
||||
"error": [ZodError: [
|
||||
{
|
||||
"code": "custom",
|
||||
"message": "custom error",
|
||||
"path": []
|
||||
}
|
||||
]],
|
||||
"success": false,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("preprocess ctx.addIssue with parseAsync", async () => {
|
||||
const result = await z
|
||||
.preprocess(async (data, ctx) => {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `${data} is not one of our allowed strings`,
|
||||
});
|
||||
return data;
|
||||
}, z.string())
|
||||
.safeParseAsync("asdf");
|
||||
|
||||
expect(JSON.parse(JSON.stringify(result))).toEqual({
|
||||
success: false,
|
||||
error: {
|
||||
issues: [
|
||||
{
|
||||
code: "custom",
|
||||
message: "asdf is not one of our allowed strings",
|
||||
path: [],
|
||||
},
|
||||
],
|
||||
name: "ZodError",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("z.NEVER in preprocess", () => {
|
||||
const foo = z.preprocess((val, ctx) => {
|
||||
if (!val) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "bad" });
|
||||
return z.NEVER;
|
||||
}
|
||||
return val;
|
||||
}, z.number());
|
||||
|
||||
type foo = z.infer<typeof foo>;
|
||||
util.assertEqual<foo, number>(true);
|
||||
const arg = foo.safeParse(undefined);
|
||||
expect(arg.error!.issues).toHaveLength(2);
|
||||
expect(arg.error!.issues[0].message).toEqual("bad");
|
||||
});
|
||||
test("preprocess as the second property of object", () => {
|
||||
const schema = z.object({
|
||||
nonEmptyStr: z.string().min(1),
|
||||
positiveNum: z.preprocess((v) => Number(v), z.number().positive()),
|
||||
});
|
||||
const result = schema.safeParse({
|
||||
nonEmptyStr: "",
|
||||
positiveNum: "",
|
||||
});
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues.length).toEqual(2);
|
||||
expect(result.error.issues[0].code).toEqual(z.ZodIssueCode.too_small);
|
||||
expect(result.error.issues[1].code).toEqual(z.ZodIssueCode.too_small);
|
||||
}
|
||||
});
|
||||
|
||||
test("preprocess validates with sibling errors", () => {
|
||||
expect(() => {
|
||||
z.object({
|
||||
// Must be first
|
||||
missing: z.string().refine(() => false),
|
||||
preprocess: z.preprocess((data: any) => data?.trim(), z.string().regex(/ asdf/)),
|
||||
}).parse({ preprocess: " asdf" });
|
||||
}).toThrow(
|
||||
JSON.stringify(
|
||||
[
|
||||
{
|
||||
code: "invalid_type",
|
||||
expected: "string",
|
||||
received: "undefined",
|
||||
path: ["missing"],
|
||||
message: "Required",
|
||||
},
|
||||
{
|
||||
validation: "regex",
|
||||
code: "invalid_string",
|
||||
message: "Invalid",
|
||||
path: ["preprocess"],
|
||||
},
|
||||
],
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
});
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
import { Mocker } from "./Mocker.js";
|
||||
|
||||
const literalStringSchema = z.literal("asdf");
|
||||
const literalNumberSchema = z.literal(12);
|
||||
const literalBooleanSchema = z.literal(true);
|
||||
const literalBigIntSchema = z.literal(BigInt(42));
|
||||
const MySymbol = Symbol("stuff");
|
||||
const literalSymbolSchema = z.literal(MySymbol);
|
||||
const stringSchema = z.string();
|
||||
const numberSchema = z.number();
|
||||
const bigintSchema = z.bigint();
|
||||
const booleanSchema = z.boolean();
|
||||
const dateSchema = z.date();
|
||||
const symbolSchema = z.symbol();
|
||||
|
||||
const nullSchema = z.null();
|
||||
const undefinedSchema = z.undefined();
|
||||
const stringSchemaOptional = z.string().optional();
|
||||
const stringSchemaNullable = z.string().nullable();
|
||||
const numberSchemaOptional = z.number().optional();
|
||||
const numberSchemaNullable = z.number().nullable();
|
||||
const bigintSchemaOptional = z.bigint().optional();
|
||||
const bigintSchemaNullable = z.bigint().nullable();
|
||||
const booleanSchemaOptional = z.boolean().optional();
|
||||
const booleanSchemaNullable = z.boolean().nullable();
|
||||
const dateSchemaOptional = z.date().optional();
|
||||
const dateSchemaNullable = z.date().nullable();
|
||||
const symbolSchemaOptional = z.symbol().optional();
|
||||
const symbolSchemaNullable = z.symbol().nullable();
|
||||
|
||||
const val = new Mocker();
|
||||
|
||||
test("literal string correct", () => {
|
||||
expect(literalStringSchema.parse("asdf")).toBe("asdf");
|
||||
});
|
||||
|
||||
test("literal string incorrect", () => {
|
||||
const f = () => literalStringSchema.parse("not_asdf");
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal string number", () => {
|
||||
const f = () => literalStringSchema.parse(123);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal string boolean", () => {
|
||||
const f = () => literalStringSchema.parse(true);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal string boolean", () => {
|
||||
const f = () => literalStringSchema.parse(true);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal string object", () => {
|
||||
const f = () => literalStringSchema.parse({});
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal number correct", () => {
|
||||
expect(literalNumberSchema.parse(12)).toBe(12);
|
||||
});
|
||||
|
||||
test("literal number incorrect", () => {
|
||||
const f = () => literalNumberSchema.parse(13);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal number number", () => {
|
||||
const f = () => literalNumberSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal number boolean", () => {
|
||||
const f = () => literalNumberSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal number object", () => {
|
||||
const f = () => literalStringSchema.parse({});
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal boolean correct", () => {
|
||||
expect(literalBooleanSchema.parse(true)).toBe(true);
|
||||
});
|
||||
|
||||
test("literal boolean incorrect", () => {
|
||||
const f = () => literalBooleanSchema.parse(false);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal boolean number", () => {
|
||||
const f = () => literalBooleanSchema.parse("asdf");
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal boolean boolean", () => {
|
||||
const f = () => literalBooleanSchema.parse(123);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal boolean object", () => {
|
||||
const f = () => literalBooleanSchema.parse({});
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal bigint correct", () => {
|
||||
expect(literalBigIntSchema.parse(BigInt(42))).toBe(BigInt(42));
|
||||
});
|
||||
|
||||
test("literal bigint incorrect", () => {
|
||||
const f = () => literalBigIntSchema.parse(BigInt(43));
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal bigint number", () => {
|
||||
const f = () => literalBigIntSchema.parse("asdf");
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal bigint boolean", () => {
|
||||
const f = () => literalBigIntSchema.parse(123);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal bigint object", () => {
|
||||
const f = () => literalBigIntSchema.parse({});
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("literal symbol", () => {
|
||||
util.assertEqual<z.infer<typeof literalSymbolSchema>, typeof MySymbol>(true);
|
||||
literalSymbolSchema.parse(MySymbol);
|
||||
expect(() => literalSymbolSchema.parse(Symbol("asdf"))).toThrow();
|
||||
});
|
||||
|
||||
test("parse stringSchema string", () => {
|
||||
stringSchema.parse(val.string);
|
||||
});
|
||||
|
||||
test("parse stringSchema number", () => {
|
||||
const f = () => stringSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse stringSchema boolean", () => {
|
||||
const f = () => stringSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse stringSchema undefined", () => {
|
||||
const f = () => stringSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse stringSchema null", () => {
|
||||
const f = () => stringSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse numberSchema string", () => {
|
||||
const f = () => numberSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse numberSchema number", () => {
|
||||
numberSchema.parse(val.number);
|
||||
});
|
||||
|
||||
test("parse numberSchema bigint", () => {
|
||||
const f = () => numberSchema.parse(val.bigint);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse numberSchema boolean", () => {
|
||||
const f = () => numberSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse numberSchema undefined", () => {
|
||||
const f = () => numberSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse numberSchema null", () => {
|
||||
const f = () => numberSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse bigintSchema string", () => {
|
||||
const f = () => bigintSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse bigintSchema number", () => {
|
||||
const f = () => bigintSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse bigintSchema bigint", () => {
|
||||
bigintSchema.parse(val.bigint);
|
||||
});
|
||||
|
||||
test("parse bigintSchema boolean", () => {
|
||||
const f = () => bigintSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse bigintSchema undefined", () => {
|
||||
const f = () => bigintSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse bigintSchema null", () => {
|
||||
const f = () => bigintSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse booleanSchema string", () => {
|
||||
const f = () => booleanSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse booleanSchema number", () => {
|
||||
const f = () => booleanSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse booleanSchema boolean", () => {
|
||||
booleanSchema.parse(val.boolean);
|
||||
});
|
||||
|
||||
test("parse booleanSchema undefined", () => {
|
||||
const f = () => booleanSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse booleanSchema null", () => {
|
||||
const f = () => booleanSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
// ==============
|
||||
|
||||
test("parse dateSchema string", () => {
|
||||
const f = () => dateSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse dateSchema number", () => {
|
||||
const f = () => dateSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse dateSchema boolean", () => {
|
||||
const f = () => dateSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse dateSchema date", () => {
|
||||
dateSchema.parse(val.date);
|
||||
});
|
||||
|
||||
test("parse dateSchema undefined", () => {
|
||||
const f = () => dateSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse dateSchema null", () => {
|
||||
const f = () => dateSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse dateSchema invalid date", async () => {
|
||||
try {
|
||||
await dateSchema.parseAsync(new Date("invalid"));
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].code).toEqual(z.ZodIssueCode.invalid_date);
|
||||
}
|
||||
});
|
||||
// ==============
|
||||
|
||||
test("parse symbolSchema string", () => {
|
||||
const f = () => symbolSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse symbolSchema number", () => {
|
||||
const f = () => symbolSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse symbolSchema boolean", () => {
|
||||
const f = () => symbolSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse symbolSchema date", () => {
|
||||
const f = () => symbolSchema.parse(val.date);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse symbolSchema symbol", () => {
|
||||
symbolSchema.parse(val.symbol);
|
||||
});
|
||||
|
||||
test("parse symbolSchema undefined", () => {
|
||||
const f = () => symbolSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse symbolSchema null", () => {
|
||||
const f = () => symbolSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
// ==============
|
||||
|
||||
test("parse undefinedSchema string", () => {
|
||||
const f = () => undefinedSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse undefinedSchema number", () => {
|
||||
const f = () => undefinedSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse undefinedSchema boolean", () => {
|
||||
const f = () => undefinedSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse undefinedSchema undefined", () => {
|
||||
undefinedSchema.parse(val.undefined);
|
||||
});
|
||||
|
||||
test("parse undefinedSchema null", () => {
|
||||
const f = () => undefinedSchema.parse(val.null);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse nullSchema string", () => {
|
||||
const f = () => nullSchema.parse(val.string);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse nullSchema number", () => {
|
||||
const f = () => nullSchema.parse(val.number);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse nullSchema boolean", () => {
|
||||
const f = () => nullSchema.parse(val.boolean);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse nullSchema undefined", () => {
|
||||
const f = () => nullSchema.parse(val.undefined);
|
||||
expect(f).toThrow();
|
||||
});
|
||||
|
||||
test("parse nullSchema null", () => {
|
||||
nullSchema.parse(val.null);
|
||||
});
|
||||
|
||||
test("primitive inference", () => {
|
||||
util.assertEqual<z.TypeOf<typeof literalStringSchema>, "asdf">(true);
|
||||
util.assertEqual<z.TypeOf<typeof literalNumberSchema>, 12>(true);
|
||||
util.assertEqual<z.TypeOf<typeof literalBooleanSchema>, true>(true);
|
||||
util.assertEqual<z.TypeOf<typeof literalBigIntSchema>, bigint>(true);
|
||||
util.assertEqual<z.TypeOf<typeof stringSchema>, string>(true);
|
||||
util.assertEqual<z.TypeOf<typeof numberSchema>, number>(true);
|
||||
util.assertEqual<z.TypeOf<typeof bigintSchema>, bigint>(true);
|
||||
util.assertEqual<z.TypeOf<typeof booleanSchema>, boolean>(true);
|
||||
util.assertEqual<z.TypeOf<typeof dateSchema>, Date>(true);
|
||||
util.assertEqual<z.TypeOf<typeof symbolSchema>, symbol>(true);
|
||||
|
||||
util.assertEqual<z.TypeOf<typeof nullSchema>, null>(true);
|
||||
util.assertEqual<z.TypeOf<typeof undefinedSchema>, undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof stringSchemaOptional>, string | undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof stringSchemaNullable>, string | null>(true);
|
||||
util.assertEqual<z.TypeOf<typeof numberSchemaOptional>, number | undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof numberSchemaNullable>, number | null>(true);
|
||||
util.assertEqual<z.TypeOf<typeof bigintSchemaOptional>, bigint | undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof bigintSchemaNullable>, bigint | null>(true);
|
||||
util.assertEqual<z.TypeOf<typeof booleanSchemaOptional>, boolean | undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof booleanSchemaNullable>, boolean | null>(true);
|
||||
util.assertEqual<z.TypeOf<typeof dateSchemaOptional>, Date | undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof dateSchemaNullable>, Date | null>(true);
|
||||
util.assertEqual<z.TypeOf<typeof symbolSchemaOptional>, symbol | undefined>(true);
|
||||
util.assertEqual<z.TypeOf<typeof symbolSchemaNullable>, symbol | null>(true);
|
||||
|
||||
// [
|
||||
// literalStringSchemaTest,
|
||||
// literalNumberSchemaTest,
|
||||
// literalBooleanSchemaTest,
|
||||
// literalBigIntSchemaTest,
|
||||
// stringSchemaTest,
|
||||
// numberSchemaTest,
|
||||
// bigintSchemaTest,
|
||||
// booleanSchemaTest,
|
||||
// dateSchemaTest,
|
||||
// symbolSchemaTest,
|
||||
|
||||
// nullSchemaTest,
|
||||
// undefinedSchemaTest,
|
||||
// stringSchemaOptionalTest,
|
||||
// stringSchemaNullableTest,
|
||||
// numberSchemaOptionalTest,
|
||||
// numberSchemaNullableTest,
|
||||
// bigintSchemaOptionalTest,
|
||||
// bigintSchemaNullableTest,
|
||||
// booleanSchemaOptionalTest,
|
||||
// booleanSchemaNullableTest,
|
||||
// dateSchemaOptionalTest,
|
||||
// dateSchemaNullableTest,
|
||||
// symbolSchemaOptionalTest,
|
||||
// symbolSchemaNullableTest,
|
||||
|
||||
// ];
|
||||
});
|
||||
|
||||
test("get literal value", () => {
|
||||
expect(literalStringSchema.value).toEqual("asdf");
|
||||
});
|
||||
|
||||
test("optional convenience method", () => {
|
||||
z.ostring().parse(undefined);
|
||||
z.onumber().parse(undefined);
|
||||
z.oboolean().parse(undefined);
|
||||
});
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const promSchema = z.promise(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
);
|
||||
|
||||
test("promise inference", () => {
|
||||
type promSchemaType = z.infer<typeof promSchema>;
|
||||
util.assertEqual<promSchemaType, Promise<{ name: string; age: number }>>(true);
|
||||
});
|
||||
|
||||
test("promise parsing success", async () => {
|
||||
const pr = promSchema.parse(Promise.resolve({ name: "Bobby", age: 10 }));
|
||||
expect(pr).toBeInstanceOf(Promise);
|
||||
const result = await pr;
|
||||
expect(typeof result).toBe("object");
|
||||
expect(typeof result.age).toBe("number");
|
||||
expect(typeof result.name).toBe("string");
|
||||
});
|
||||
|
||||
test("promise parsing success 2", () => {
|
||||
const fakePromise = {
|
||||
then() {
|
||||
return this;
|
||||
},
|
||||
catch() {
|
||||
return this;
|
||||
},
|
||||
};
|
||||
promSchema.parse(fakePromise);
|
||||
});
|
||||
|
||||
test("promise parsing fail", async () => {
|
||||
const bad = promSchema.parse(Promise.resolve({ name: "Bobby", age: "10" }));
|
||||
// return await expect(bad).resolves.toBe({ name: 'Bobby', age: '10' });
|
||||
return await expect(bad).rejects.toBeInstanceOf(z.ZodError);
|
||||
// done();
|
||||
});
|
||||
|
||||
test("promise parsing fail 2", async () => {
|
||||
const failPromise = promSchema.parse(Promise.resolve({ name: "Bobby", age: "10" }));
|
||||
await expect(failPromise).rejects.toBeInstanceOf(z.ZodError);
|
||||
// done();/z
|
||||
});
|
||||
|
||||
test("promise parsing fail", () => {
|
||||
const bad = () => promSchema.parse({ then: () => {}, catch: {} });
|
||||
expect(bad).toThrow();
|
||||
});
|
||||
|
||||
// test('sync promise parsing', () => {
|
||||
// expect(() => z.promise(z.string()).parse(Promise.resolve('asfd'))).toThrow();
|
||||
// });
|
||||
|
||||
const asyncFunction = z.function(z.tuple([]), promSchema);
|
||||
|
||||
test("async function pass", async () => {
|
||||
const validatedFunction = asyncFunction.implement(async () => {
|
||||
return { name: "jimmy", age: 14 };
|
||||
});
|
||||
await expect(validatedFunction()).resolves.toEqual({
|
||||
name: "jimmy",
|
||||
age: 14,
|
||||
});
|
||||
});
|
||||
|
||||
test("async function fail", async () => {
|
||||
const validatedFunction = asyncFunction.implement(() => {
|
||||
return Promise.resolve("asdf" as any);
|
||||
});
|
||||
await expect(validatedFunction()).rejects.toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
test("async promise parsing", () => {
|
||||
const res = z.promise(z.number()).parseAsync(Promise.resolve(12));
|
||||
expect(res).toBeInstanceOf(Promise);
|
||||
});
|
||||
|
||||
test("resolves", () => {
|
||||
const foo = z.literal("foo");
|
||||
const res = z.promise(foo);
|
||||
expect(res.unwrap()).toEqual(foo);
|
||||
});
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
enum testEnum {
|
||||
A = 0,
|
||||
B = 1,
|
||||
}
|
||||
|
||||
const schemas = [
|
||||
z.string().readonly(),
|
||||
z.number().readonly(),
|
||||
z.nan().readonly(),
|
||||
z.bigint().readonly(),
|
||||
z.boolean().readonly(),
|
||||
z.date().readonly(),
|
||||
z.undefined().readonly(),
|
||||
z.null().readonly(),
|
||||
z.any().readonly(),
|
||||
z.unknown().readonly(),
|
||||
z.void().readonly(),
|
||||
z.function().args(z.string(), z.number()).readonly(),
|
||||
|
||||
z.array(z.string()).readonly(),
|
||||
z.tuple([z.string(), z.number()]).readonly(),
|
||||
z.map(z.string(), z.date()).readonly(),
|
||||
z.set(z.promise(z.string())).readonly(),
|
||||
z.record(z.string()).readonly(),
|
||||
z.record(z.string(), z.number()).readonly(),
|
||||
z.object({ a: z.string(), 1: z.number() }).readonly(),
|
||||
z.nativeEnum(testEnum).readonly(),
|
||||
z.promise(z.string()).readonly(),
|
||||
] as const;
|
||||
|
||||
test("flat inference", () => {
|
||||
util.assertEqual<z.infer<(typeof schemas)[0]>, string>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[1]>, number>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[2]>, number>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[3]>, bigint>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[4]>, boolean>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[5]>, Date>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[6]>, undefined>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[7]>, null>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[8]>, any>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[9]>, Readonly<unknown>>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[10]>, void>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[11]>, (args_0: string, args_1: number, ...args_2: unknown[]) => unknown>(
|
||||
true
|
||||
);
|
||||
util.assertEqual<z.infer<(typeof schemas)[12]>, readonly string[]>(true);
|
||||
|
||||
util.assertEqual<z.infer<(typeof schemas)[13]>, readonly [string, number]>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[14]>, ReadonlyMap<string, Date>>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[15]>, ReadonlySet<Promise<string>>>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[16]>, Readonly<Record<string, string>>>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[17]>, Readonly<Record<string, number>>>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[18]>, { readonly a: string; readonly 1: number }>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[19]>, Readonly<testEnum>>(true);
|
||||
util.assertEqual<z.infer<(typeof schemas)[20]>, Promise<string>>(true);
|
||||
});
|
||||
|
||||
test("object freezing", () => {
|
||||
expect(Object.isFrozen(z.array(z.string()).readonly().parse(["a"]))).toBe(true);
|
||||
expect(Object.isFrozen(z.tuple([z.string(), z.number()]).readonly().parse(["a", 1]))).toBe(true);
|
||||
expect(
|
||||
Object.isFrozen(
|
||||
z
|
||||
.map(z.string(), z.date())
|
||||
.readonly()
|
||||
.parse(new Map([["a", new Date()]]))
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
Object.isFrozen(
|
||||
z
|
||||
.set(z.promise(z.string()))
|
||||
.readonly()
|
||||
.parse(new Set([Promise.resolve("a")]))
|
||||
)
|
||||
).toBe(true);
|
||||
expect(Object.isFrozen(z.record(z.string()).readonly().parse({ a: "b" }))).toBe(true);
|
||||
expect(Object.isFrozen(z.record(z.string(), z.number()).readonly().parse({ a: 1 }))).toBe(true);
|
||||
expect(Object.isFrozen(z.object({ a: z.string(), 1: z.number() }).readonly().parse({ a: "b", 1: 2 }))).toBe(true);
|
||||
expect(Object.isFrozen(z.promise(z.string()).readonly().parse(Promise.resolve("a")))).toBe(true);
|
||||
});
|
||||
|
||||
test("async object freezing", async () => {
|
||||
expect(Object.isFrozen(await z.array(z.string()).readonly().parseAsync(["a"]))).toBe(true);
|
||||
expect(Object.isFrozen(await z.tuple([z.string(), z.number()]).readonly().parseAsync(["a", 1]))).toBe(true);
|
||||
expect(
|
||||
Object.isFrozen(
|
||||
await z
|
||||
.map(z.string(), z.date())
|
||||
.readonly()
|
||||
.parseAsync(new Map([["a", new Date()]]))
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
Object.isFrozen(
|
||||
await z
|
||||
.set(z.promise(z.string()))
|
||||
.readonly()
|
||||
.parseAsync(new Set([Promise.resolve("a")]))
|
||||
)
|
||||
).toBe(true);
|
||||
expect(Object.isFrozen(await z.record(z.string()).readonly().parseAsync({ a: "b" }))).toBe(true);
|
||||
expect(Object.isFrozen(await z.record(z.string(), z.number()).readonly().parseAsync({ a: 1 }))).toBe(true);
|
||||
expect(
|
||||
Object.isFrozen(await z.object({ a: z.string(), 1: z.number() }).readonly().parseAsync({ a: "b", 1: 2 }))
|
||||
).toBe(true);
|
||||
expect(Object.isFrozen(await z.promise(z.string()).readonly().parseAsync(Promise.resolve("a")))).toBe(true);
|
||||
});
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const booleanRecord = z.record(z.boolean());
|
||||
type booleanRecord = z.infer<typeof booleanRecord>;
|
||||
|
||||
const recordWithEnumKeys = z.record(z.enum(["Tuna", "Salmon"]), z.string());
|
||||
type recordWithEnumKeys = z.infer<typeof recordWithEnumKeys>;
|
||||
|
||||
const recordWithLiteralKeys = z.record(z.union([z.literal("Tuna"), z.literal("Salmon")]), z.string());
|
||||
type recordWithLiteralKeys = z.infer<typeof recordWithLiteralKeys>;
|
||||
|
||||
test("type inference", () => {
|
||||
util.assertEqual<booleanRecord, Record<string, boolean>>(true);
|
||||
|
||||
util.assertEqual<recordWithEnumKeys, Partial<Record<"Tuna" | "Salmon", string>>>(true);
|
||||
|
||||
util.assertEqual<recordWithLiteralKeys, Partial<Record<"Tuna" | "Salmon", string>>>(true);
|
||||
});
|
||||
|
||||
test("methods", () => {
|
||||
booleanRecord.optional();
|
||||
booleanRecord.nullable();
|
||||
});
|
||||
|
||||
test("string record parse - pass", () => {
|
||||
booleanRecord.parse({
|
||||
k1: true,
|
||||
k2: false,
|
||||
1234: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("string record parse - fail", () => {
|
||||
const badCheck = () =>
|
||||
booleanRecord.parse({
|
||||
asdf: 1234,
|
||||
} as any);
|
||||
expect(badCheck).toThrow();
|
||||
|
||||
expect(() => booleanRecord.parse("asdf")).toThrow();
|
||||
});
|
||||
|
||||
test("string record parse - fail", () => {
|
||||
const badCheck = () =>
|
||||
booleanRecord.parse({
|
||||
asdf: {},
|
||||
} as any);
|
||||
expect(badCheck).toThrow();
|
||||
});
|
||||
|
||||
test("string record parse - fail", () => {
|
||||
const badCheck = () =>
|
||||
booleanRecord.parse({
|
||||
asdf: [],
|
||||
} as any);
|
||||
expect(badCheck).toThrow();
|
||||
});
|
||||
|
||||
test("key schema", () => {
|
||||
const result1 = recordWithEnumKeys.parse({
|
||||
Tuna: "asdf",
|
||||
Salmon: "asdf",
|
||||
});
|
||||
expect(result1).toEqual({
|
||||
Tuna: "asdf",
|
||||
Salmon: "asdf",
|
||||
});
|
||||
|
||||
const result2 = recordWithLiteralKeys.parse({
|
||||
Tuna: "asdf",
|
||||
Salmon: "asdf",
|
||||
});
|
||||
expect(result2).toEqual({
|
||||
Tuna: "asdf",
|
||||
Salmon: "asdf",
|
||||
});
|
||||
|
||||
// shouldn't require us to specify all props in record
|
||||
const result3 = recordWithEnumKeys.parse({
|
||||
Tuna: "abcd",
|
||||
});
|
||||
expect(result3).toEqual({
|
||||
Tuna: "abcd",
|
||||
});
|
||||
|
||||
// shouldn't require us to specify all props in record
|
||||
const result4 = recordWithLiteralKeys.parse({
|
||||
Salmon: "abcd",
|
||||
});
|
||||
expect(result4).toEqual({
|
||||
Salmon: "abcd",
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
recordWithEnumKeys.parse({
|
||||
Tuna: "asdf",
|
||||
Salmon: "asdf",
|
||||
Trout: "asdf",
|
||||
})
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
recordWithLiteralKeys.parse({
|
||||
Tuna: "asdf",
|
||||
Salmon: "asdf",
|
||||
|
||||
Trout: "asdf",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
// test("record element", () => {
|
||||
// expect(booleanRecord.element).toBeInstanceOf(z.ZodBoolean);
|
||||
// });
|
||||
|
||||
test("key and value getters", () => {
|
||||
const rec = z.record(z.string(), z.number());
|
||||
|
||||
rec.keySchema.parse("asdf");
|
||||
rec.valueSchema.parse(1234);
|
||||
rec.element.parse(1234);
|
||||
});
|
||||
|
||||
test("is not vulnerable to prototype pollution", async () => {
|
||||
const rec = z.record(
|
||||
z.object({
|
||||
a: z.string(),
|
||||
})
|
||||
);
|
||||
|
||||
const data = JSON.parse(`
|
||||
{
|
||||
"__proto__": {
|
||||
"a": "evil"
|
||||
},
|
||||
"b": {
|
||||
"a": "good"
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const obj1 = rec.parse(data);
|
||||
expect(obj1.a).toBeUndefined();
|
||||
|
||||
const obj2 = rec.safeParse(data);
|
||||
expect(obj2.success).toBe(true);
|
||||
if (obj2.success) {
|
||||
expect(obj2.data.a).toBeUndefined();
|
||||
}
|
||||
|
||||
const obj3 = await rec.parseAsync(data);
|
||||
expect(obj3.a).toBeUndefined();
|
||||
|
||||
const obj4 = await rec.safeParseAsync(data);
|
||||
expect(obj4.success).toBe(true);
|
||||
if (obj4.success) {
|
||||
expect(obj4.data.a).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("dont parse undefined values", () => {
|
||||
const result1 = z.record(z.any()).parse({ foo: undefined });
|
||||
|
||||
expect(result1).toEqual({
|
||||
foo: undefined,
|
||||
});
|
||||
});
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
// @ts-ignore TS6133
|
||||
import { test } from "vitest";
|
||||
|
||||
import { z } from "zod/v3";
|
||||
|
||||
interface Category {
|
||||
name: string;
|
||||
subcategories: Category[];
|
||||
}
|
||||
|
||||
const testCategory: Category = {
|
||||
name: "I",
|
||||
subcategories: [
|
||||
{
|
||||
name: "A",
|
||||
subcategories: [
|
||||
{
|
||||
name: "1",
|
||||
subcategories: [
|
||||
{
|
||||
name: "a",
|
||||
subcategories: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
test("recursion with z.late.object", () => {
|
||||
const Category: z.ZodType<Category> = z.late.object(() => ({
|
||||
name: z.string(),
|
||||
subcategories: z.array(Category),
|
||||
}));
|
||||
Category.parse(testCategory);
|
||||
});
|
||||
|
||||
test("recursion with z.lazy", () => {
|
||||
const Category: z.ZodType<Category> = z.lazy(() =>
|
||||
z.object({
|
||||
name: z.string(),
|
||||
subcategories: z.array(Category),
|
||||
})
|
||||
);
|
||||
Category.parse(testCategory);
|
||||
});
|
||||
|
||||
test("schema getter", () => {
|
||||
z.lazy(() => z.string()).schema.parse("asdf");
|
||||
});
|
||||
|
||||
type LinkedList = null | { value: number; next: LinkedList };
|
||||
|
||||
const linkedListExample = {
|
||||
value: 1,
|
||||
next: {
|
||||
value: 2,
|
||||
next: {
|
||||
value: 3,
|
||||
next: {
|
||||
value: 4,
|
||||
next: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test("recursion involving union type", () => {
|
||||
const LinkedListSchema: z.ZodType<LinkedList> = z.lazy(() =>
|
||||
z.union([
|
||||
z.null(),
|
||||
z.object({
|
||||
value: z.number(),
|
||||
next: LinkedListSchema,
|
||||
}),
|
||||
])
|
||||
);
|
||||
LinkedListSchema.parse(linkedListExample);
|
||||
});
|
||||
|
||||
// interface A {
|
||||
// val: number;
|
||||
// b: B;
|
||||
// }
|
||||
|
||||
// interface B {
|
||||
// val: number;
|
||||
// a: A;
|
||||
// }
|
||||
|
||||
// const A: z.ZodType<A> = z.late.object(() => ({
|
||||
// val: z.number(),
|
||||
// b: B,
|
||||
// }));
|
||||
|
||||
// const B: z.ZodType<B> = z.late.object(() => ({
|
||||
// val: z.number(),
|
||||
// a: A,
|
||||
// }));
|
||||
|
||||
// const Alazy: z.ZodType<A> = z.lazy(() => z.object({
|
||||
// val: z.number(),
|
||||
// b: B,
|
||||
// }));
|
||||
|
||||
// const Blazy: z.ZodType<B> = z.lazy(() => z.object({
|
||||
// val: z.number(),
|
||||
// a: A,
|
||||
// }));
|
||||
|
||||
// const a: any = { val: 1 };
|
||||
// const b: any = { val: 2 };
|
||||
// a.b = b;
|
||||
// b.a = a;
|
||||
|
||||
// test('valid check', () => {
|
||||
// A.parse(a);
|
||||
// B.parse(b);
|
||||
// });
|
||||
|
||||
// test("valid check lazy", () => {
|
||||
// A.parse({val:1, b:});
|
||||
// B.parse(b);
|
||||
// });
|
||||
|
||||
// test('masking check', () => {
|
||||
// const FragmentOnA = z
|
||||
// .object({
|
||||
// val: z.number(),
|
||||
// b: z
|
||||
// .object({
|
||||
// val: z.number(),
|
||||
// a: z
|
||||
// .object({
|
||||
// val: z.number(),
|
||||
// })
|
||||
// .nonstrict(),
|
||||
// })
|
||||
// .nonstrict(),
|
||||
// })
|
||||
// .nonstrict();
|
||||
|
||||
// const fragment = FragmentOnA.parse(a);
|
||||
// fragment;
|
||||
// });
|
||||
|
||||
// test('invalid check', () => {
|
||||
// expect(() => A.parse({} as any)).toThrow();
|
||||
// });
|
||||
|
||||
// test('schema getter', () => {
|
||||
// (A as z.ZodLazy<any>).schema;
|
||||
// });
|
||||
|
||||
// test("self recursion with cyclical data", () => {
|
||||
// interface Category {
|
||||
// name: string;
|
||||
// subcategories: Category[];
|
||||
// }
|
||||
|
||||
// const Category: z.ZodType<Category> = z.late.object(() => ({
|
||||
// name: z.string(),
|
||||
// subcategories: z.array(Category),
|
||||
// }));
|
||||
|
||||
// const untypedCategory: any = {
|
||||
// name: "Category A",
|
||||
// };
|
||||
// // creating a cycle
|
||||
// untypedCategory.subcategories = [untypedCategory];
|
||||
// Category.parse(untypedCategory);
|
||||
// });
|
||||
|
||||
// test("self recursion with base type", () => {
|
||||
// const BaseCategory = z.object({
|
||||
// name: z.string(),
|
||||
// });
|
||||
// type BaseCategory = z.infer<typeof BaseCategory>;
|
||||
|
||||
// type Category = BaseCategory & { subcategories: Category[] };
|
||||
|
||||
// const Category: z.ZodType<Category> = z.late
|
||||
// .object(() => ({
|
||||
// subcategories: z.array(Category),
|
||||
// }))
|
||||
// .extend({
|
||||
// name: z.string(),
|
||||
// });
|
||||
|
||||
// const untypedCategory: any = {
|
||||
// name: "Category A",
|
||||
// };
|
||||
// // creating a cycle
|
||||
// untypedCategory.subcategories = [untypedCategory];
|
||||
// Category.parse(untypedCategory); // parses successfully
|
||||
// });
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { ZodIssueCode } from "../ZodError.js";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
test("refinement", () => {
|
||||
const obj1 = z.object({
|
||||
first: z.string(),
|
||||
second: z.string(),
|
||||
});
|
||||
const obj2 = obj1.partial().strict();
|
||||
|
||||
const obj3 = obj2.refine((data) => data.first || data.second, "Either first or second should be filled in.");
|
||||
|
||||
expect(obj1 === (obj2 as any)).toEqual(false);
|
||||
expect(obj2 === (obj3 as any)).toEqual(false);
|
||||
|
||||
expect(() => obj1.parse({})).toThrow();
|
||||
expect(() => obj2.parse({ third: "adsf" })).toThrow();
|
||||
expect(() => obj3.parse({})).toThrow();
|
||||
obj3.parse({ first: "a" });
|
||||
obj3.parse({ second: "a" });
|
||||
obj3.parse({ first: "a", second: "a" });
|
||||
});
|
||||
|
||||
test("refinement 2", () => {
|
||||
const validationSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
confirmPassword: z.string(),
|
||||
})
|
||||
.refine((data) => data.password === data.confirmPassword, "Both password and confirmation must match");
|
||||
|
||||
expect(() =>
|
||||
validationSchema.parse({
|
||||
email: "aaaa@gmail.com",
|
||||
password: "aaaaaaaa",
|
||||
confirmPassword: "bbbbbbbb",
|
||||
})
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("refinement type guard", () => {
|
||||
const validationSchema = z.object({
|
||||
a: z.string().refine((s): s is "a" => s === "a"),
|
||||
});
|
||||
type Input = z.input<typeof validationSchema>;
|
||||
type Schema = z.infer<typeof validationSchema>;
|
||||
|
||||
util.assertEqual<"a", Input["a"]>(false);
|
||||
util.assertEqual<string, Input["a"]>(true);
|
||||
|
||||
util.assertEqual<"a", Schema["a"]>(true);
|
||||
util.assertEqual<string, Schema["a"]>(false);
|
||||
});
|
||||
|
||||
test("refinement Promise", async () => {
|
||||
const validationSchema = z
|
||||
.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
confirmPassword: z.string(),
|
||||
})
|
||||
.refine(
|
||||
(data) => Promise.resolve().then(() => data.password === data.confirmPassword),
|
||||
"Both password and confirmation must match"
|
||||
);
|
||||
|
||||
await validationSchema.parseAsync({
|
||||
email: "aaaa@gmail.com",
|
||||
password: "password",
|
||||
confirmPassword: "password",
|
||||
});
|
||||
});
|
||||
|
||||
test("custom path", async () => {
|
||||
const result = await z
|
||||
.object({
|
||||
password: z.string(),
|
||||
confirm: z.string(),
|
||||
})
|
||||
.refine((data) => data.confirm === data.password, { path: ["confirm"] })
|
||||
.spa({ password: "asdf", confirm: "qewr" });
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].path).toEqual(["confirm"]);
|
||||
}
|
||||
});
|
||||
|
||||
test("use path in refinement context", async () => {
|
||||
const noNested = z.string()._refinement((_val, ctx) => {
|
||||
if (ctx.path.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `schema cannot be nested. path: ${ctx.path.join(".")}`,
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
const data = z.object({
|
||||
foo: noNested,
|
||||
});
|
||||
|
||||
const t1 = await noNested.spa("asdf");
|
||||
const t2 = await data.spa({ foo: "asdf" });
|
||||
|
||||
expect(t1.success).toBe(true);
|
||||
expect(t2.success).toBe(false);
|
||||
if (t2.success === false) {
|
||||
expect(t2.error.issues[0].message).toEqual("schema cannot be nested. path: foo");
|
||||
}
|
||||
});
|
||||
|
||||
test("superRefine", () => {
|
||||
const Strings = z.array(z.string()).superRefine((val, ctx) => {
|
||||
if (val.length > 3) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.too_big,
|
||||
maximum: 3,
|
||||
type: "array",
|
||||
inclusive: true,
|
||||
exact: true,
|
||||
message: "Too many items 😡",
|
||||
});
|
||||
}
|
||||
|
||||
if (val.length !== new Set(val).size) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `No duplicates allowed.`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const result = Strings.safeParse(["asfd", "asfd", "asfd", "asfd"]);
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) expect(result.error.issues.length).toEqual(2);
|
||||
|
||||
Strings.parse(["asfd", "qwer"]);
|
||||
});
|
||||
|
||||
test("superRefine async", async () => {
|
||||
const Strings = z.array(z.string()).superRefine(async (val, ctx) => {
|
||||
if (val.length > 3) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.too_big,
|
||||
maximum: 3,
|
||||
type: "array",
|
||||
inclusive: true,
|
||||
exact: true,
|
||||
message: "Too many items 😡",
|
||||
});
|
||||
}
|
||||
|
||||
if (val.length !== new Set(val).size) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `No duplicates allowed.`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const result = await Strings.safeParseAsync(["asfd", "asfd", "asfd", "asfd"]);
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) expect(result.error.issues.length).toEqual(2);
|
||||
|
||||
Strings.parseAsync(["asfd", "qwer"]);
|
||||
});
|
||||
|
||||
test("superRefine - type narrowing", () => {
|
||||
type NarrowType = { type: string; age: number };
|
||||
const schema = z
|
||||
.object({
|
||||
type: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
.nullable()
|
||||
.superRefine((arg, ctx): arg is NarrowType => {
|
||||
if (!arg) {
|
||||
// still need to make a call to ctx.addIssue
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "cannot be null",
|
||||
fatal: true,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
util.assertEqual<z.infer<typeof schema>, NarrowType>(true);
|
||||
|
||||
expect(schema.safeParse({ type: "test", age: 0 }).success).toEqual(true);
|
||||
expect(schema.safeParse(null).success).toEqual(false);
|
||||
});
|
||||
|
||||
test("chained mixed refining types", () => {
|
||||
type firstRefinement = { first: string; second: number; third: true };
|
||||
type secondRefinement = { first: "bob"; second: number; third: true };
|
||||
type thirdRefinement = { first: "bob"; second: 33; third: true };
|
||||
const schema = z
|
||||
.object({
|
||||
first: z.string(),
|
||||
second: z.number(),
|
||||
third: z.boolean(),
|
||||
})
|
||||
.nullable()
|
||||
.refine((arg): arg is firstRefinement => !!arg?.third)
|
||||
.superRefine((arg, ctx): arg is secondRefinement => {
|
||||
util.assertEqual<typeof arg, firstRefinement>(true);
|
||||
if (arg.first !== "bob") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "`first` property must be `bob`",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.refine((arg): arg is thirdRefinement => {
|
||||
util.assertEqual<typeof arg, secondRefinement>(true);
|
||||
return arg.second === 33;
|
||||
});
|
||||
|
||||
util.assertEqual<z.infer<typeof schema>, thirdRefinement>(true);
|
||||
});
|
||||
|
||||
test("get inner type", () => {
|
||||
z.string()
|
||||
.refine(() => true)
|
||||
.innerType()
|
||||
.parse("asdf");
|
||||
});
|
||||
|
||||
test("chained refinements", () => {
|
||||
const objectSchema = z
|
||||
.object({
|
||||
length: z.number(),
|
||||
size: z.number(),
|
||||
})
|
||||
.refine(({ length }) => length > 5, {
|
||||
path: ["length"],
|
||||
message: "length greater than 5",
|
||||
})
|
||||
.refine(({ size }) => size > 7, {
|
||||
path: ["size"],
|
||||
message: "size greater than 7",
|
||||
});
|
||||
const r1 = objectSchema.safeParse({
|
||||
length: 4,
|
||||
size: 9,
|
||||
});
|
||||
expect(r1.success).toEqual(false);
|
||||
if (!r1.success) expect(r1.error.issues.length).toEqual(1);
|
||||
|
||||
const r2 = objectSchema.safeParse({
|
||||
length: 4,
|
||||
size: 3,
|
||||
});
|
||||
expect(r2.success).toEqual(false);
|
||||
if (!r2.success) expect(r2.error.issues.length).toEqual(2);
|
||||
});
|
||||
|
||||
test("fatal superRefine", () => {
|
||||
const Strings = z
|
||||
.string()
|
||||
.superRefine((val, ctx) => {
|
||||
if (val === "") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "foo",
|
||||
fatal: true,
|
||||
});
|
||||
}
|
||||
})
|
||||
.superRefine((val, ctx) => {
|
||||
if (val !== " ") {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "bar",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const result = Strings.safeParse("");
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) expect(result.error.issues.length).toEqual(1);
|
||||
});
|
||||
|
||||
test("superRefine after skipped transform", () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.regex(/^\d+$/)
|
||||
.transform((val) => Number(val))
|
||||
.superRefine((val) => {
|
||||
if (typeof val !== "number") {
|
||||
throw new Error("Called without transform");
|
||||
}
|
||||
});
|
||||
|
||||
const result = schema.safeParse("");
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
});
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
const stringSchema = z.string();
|
||||
|
||||
test("safeparse fail", () => {
|
||||
const safe = stringSchema.safeParse(12);
|
||||
expect(safe.success).toEqual(false);
|
||||
expect(safe.error).toBeInstanceOf(z.ZodError);
|
||||
});
|
||||
|
||||
test("safeparse pass", () => {
|
||||
const safe = stringSchema.safeParse("12");
|
||||
expect(safe.success).toEqual(true);
|
||||
expect(safe.data).toEqual("12");
|
||||
});
|
||||
|
||||
test("safeparse unexpected error", () => {
|
||||
expect(() =>
|
||||
stringSchema
|
||||
.refine((data) => {
|
||||
throw new Error(data);
|
||||
})
|
||||
.safeParse("12")
|
||||
).toThrow();
|
||||
});
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { ZodIssueCode } from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const stringSet = z.set(z.string());
|
||||
type stringSet = z.infer<typeof stringSet>;
|
||||
|
||||
const minTwo = z.set(z.string()).min(2);
|
||||
const maxTwo = z.set(z.string()).max(2);
|
||||
const justTwo = z.set(z.string()).size(2);
|
||||
const nonEmpty = z.set(z.string()).nonempty();
|
||||
const nonEmptyMax = z.set(z.string()).nonempty().max(2);
|
||||
|
||||
test("type inference", () => {
|
||||
util.assertEqual<stringSet, Set<string>>(true);
|
||||
});
|
||||
|
||||
test("valid parse", () => {
|
||||
const result = stringSet.safeParse(new Set(["first", "second"]));
|
||||
expect(result.success).toEqual(true);
|
||||
if (result.success) {
|
||||
expect(result.data.has("first")).toEqual(true);
|
||||
expect(result.data.has("second")).toEqual(true);
|
||||
expect(result.data.has("third")).toEqual(false);
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
minTwo.parse(new Set(["a", "b"]));
|
||||
minTwo.parse(new Set(["a", "b", "c"]));
|
||||
maxTwo.parse(new Set(["a", "b"]));
|
||||
maxTwo.parse(new Set(["a"]));
|
||||
justTwo.parse(new Set(["a", "b"]));
|
||||
nonEmpty.parse(new Set(["a"]));
|
||||
nonEmptyMax.parse(new Set(["a"]));
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
test("valid parse async", async () => {
|
||||
const result = await stringSet.spa(new Set(["first", "second"]));
|
||||
expect(result.success).toEqual(true);
|
||||
if (result.success) {
|
||||
expect(result.data.has("first")).toEqual(true);
|
||||
expect(result.data.has("second")).toEqual(true);
|
||||
expect(result.data.has("third")).toEqual(false);
|
||||
}
|
||||
|
||||
const asyncResult = await stringSet.safeParse(new Set(["first", "second"]));
|
||||
expect(asyncResult.success).toEqual(true);
|
||||
if (asyncResult.success) {
|
||||
expect(asyncResult.data.has("first")).toEqual(true);
|
||||
expect(asyncResult.data.has("second")).toEqual(true);
|
||||
expect(asyncResult.data.has("third")).toEqual(false);
|
||||
}
|
||||
});
|
||||
|
||||
test("valid parse: size-related methods", () => {
|
||||
expect(() => {
|
||||
minTwo.parse(new Set(["a", "b"]));
|
||||
minTwo.parse(new Set(["a", "b", "c"]));
|
||||
maxTwo.parse(new Set(["a", "b"]));
|
||||
maxTwo.parse(new Set(["a"]));
|
||||
justTwo.parse(new Set(["a", "b"]));
|
||||
nonEmpty.parse(new Set(["a"]));
|
||||
nonEmptyMax.parse(new Set(["a"]));
|
||||
}).not.toThrow();
|
||||
|
||||
const sizeZeroResult = stringSet.parse(new Set());
|
||||
expect(sizeZeroResult.size).toBe(0);
|
||||
|
||||
const sizeTwoResult = minTwo.parse(new Set(["a", "b"]));
|
||||
expect(sizeTwoResult.size).toBe(2);
|
||||
});
|
||||
|
||||
test("failing when parsing empty set in nonempty ", () => {
|
||||
const result = nonEmpty.safeParse(new Set());
|
||||
expect(result.success).toEqual(false);
|
||||
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(1);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.too_small);
|
||||
}
|
||||
});
|
||||
|
||||
test("failing when set is smaller than min() ", () => {
|
||||
const result = minTwo.safeParse(new Set(["just_one"]));
|
||||
expect(result.success).toEqual(false);
|
||||
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(1);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.too_small);
|
||||
}
|
||||
});
|
||||
|
||||
test("failing when set is bigger than max() ", () => {
|
||||
const result = maxTwo.safeParse(new Set(["one", "two", "three"]));
|
||||
expect(result.success).toEqual(false);
|
||||
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(1);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.too_big);
|
||||
}
|
||||
});
|
||||
|
||||
test("doesn’t throw when an empty set is given", () => {
|
||||
const result = stringSet.safeParse(new Set([]));
|
||||
expect(result.success).toEqual(true);
|
||||
});
|
||||
|
||||
test("throws when a Map is given", () => {
|
||||
const result = stringSet.safeParse(new Map([]));
|
||||
expect(result.success).toEqual(false);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(1);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type);
|
||||
}
|
||||
});
|
||||
|
||||
test("throws when the given set has invalid input", () => {
|
||||
const result = stringSet.safeParse(new Set([Symbol()]));
|
||||
expect(result.success).toEqual(false);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(1);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[0].path).toEqual([0]);
|
||||
}
|
||||
});
|
||||
|
||||
test("throws when the given set has multiple invalid entries", () => {
|
||||
const result = stringSet.safeParse(new Set([1, 2] as any[]) as Set<any>);
|
||||
|
||||
expect(result.success).toEqual(false);
|
||||
if (result.success === false) {
|
||||
expect(result.error.issues.length).toEqual(2);
|
||||
expect(result.error.issues[0].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[0].path).toEqual([0]);
|
||||
expect(result.error.issues[1].code).toEqual(ZodIssueCode.invalid_type);
|
||||
expect(result.error.issues[1].path).toEqual([1]);
|
||||
}
|
||||
});
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// import type { StandardSchemaV1 } from "@standard-schema/spec";
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
import type { StandardSchemaV1 } from "../standard-schema.js";
|
||||
|
||||
test("assignability", () => {
|
||||
const _s1: StandardSchemaV1 = z.string();
|
||||
const _s2: StandardSchemaV1<string> = z.string();
|
||||
const _s3: StandardSchemaV1<string, string> = z.string();
|
||||
const _s4: StandardSchemaV1<unknown, string> = z.string();
|
||||
[_s1, _s2, _s3, _s4];
|
||||
});
|
||||
|
||||
test("type inference", () => {
|
||||
const stringToNumber = z.string().transform((x) => x.length);
|
||||
type input = StandardSchemaV1.InferInput<typeof stringToNumber>;
|
||||
util.assertEqual<input, string>(true);
|
||||
type output = StandardSchemaV1.InferOutput<typeof stringToNumber>;
|
||||
util.assertEqual<output, number>(true);
|
||||
});
|
||||
|
||||
test("valid parse", () => {
|
||||
const schema = z.string();
|
||||
const result = schema["~standard"].validate("hello");
|
||||
if (result instanceof Promise) {
|
||||
throw new Error("Expected sync result");
|
||||
}
|
||||
expect(result.issues).toEqual(undefined);
|
||||
if (result.issues) {
|
||||
throw new Error("Expected no issues");
|
||||
} else {
|
||||
expect(result.value).toEqual("hello");
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid parse", () => {
|
||||
const schema = z.string();
|
||||
const result = schema["~standard"].validate(1234);
|
||||
if (result instanceof Promise) {
|
||||
throw new Error("Expected sync result");
|
||||
}
|
||||
expect(result.issues).toBeDefined();
|
||||
if (!result.issues) {
|
||||
throw new Error("Expected issues");
|
||||
}
|
||||
expect(result.issues.length).toEqual(1);
|
||||
expect(result.issues[0].path).toEqual([]);
|
||||
});
|
||||
|
||||
test("valid parse async", async () => {
|
||||
const schema = z.string().refine(async () => true);
|
||||
const _result = schema["~standard"].validate("hello");
|
||||
if (_result instanceof Promise) {
|
||||
const result = await _result;
|
||||
expect(result.issues).toEqual(undefined);
|
||||
if (result.issues) {
|
||||
throw new Error("Expected no issues");
|
||||
} else {
|
||||
expect(result.value).toEqual("hello");
|
||||
}
|
||||
} else {
|
||||
throw new Error("Expected async result");
|
||||
}
|
||||
});
|
||||
|
||||
test("invalid parse async", async () => {
|
||||
const schema = z.string().refine(async () => false);
|
||||
const _result = schema["~standard"].validate("hello");
|
||||
if (_result instanceof Promise) {
|
||||
const result = await _result;
|
||||
expect(result.issues).toBeDefined();
|
||||
if (!result.issues) {
|
||||
throw new Error("Expected issues");
|
||||
}
|
||||
expect(result.issues.length).toEqual(1);
|
||||
expect(result.issues[0].path).toEqual([]);
|
||||
} else {
|
||||
throw new Error("Expected async result");
|
||||
}
|
||||
});
|
||||
+922
File diff suppressed because one or more lines are too long
+233
@@ -0,0 +1,233 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const stringToNumber = z.string().transform((arg) => Number.parseFloat(arg));
|
||||
// const numberToString = z
|
||||
// .transformer(z.number())
|
||||
// .transform((n) => String(n));
|
||||
const asyncNumberToString = z.number().transform(async (n) => String(n));
|
||||
|
||||
test("transform ctx.addIssue with parse", () => {
|
||||
const strs = ["foo", "bar"];
|
||||
|
||||
expect(() => {
|
||||
z.string()
|
||||
.transform((data, ctx) => {
|
||||
const i = strs.indexOf(data);
|
||||
if (i === -1) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `${data} is not one of our allowed strings`,
|
||||
});
|
||||
}
|
||||
return data.length;
|
||||
})
|
||||
.parse("asdf");
|
||||
}).toThrow(
|
||||
JSON.stringify(
|
||||
[
|
||||
{
|
||||
code: "custom",
|
||||
message: "asdf is not one of our allowed strings",
|
||||
path: [],
|
||||
},
|
||||
],
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("transform ctx.addIssue with parseAsync", async () => {
|
||||
const strs = ["foo", "bar"];
|
||||
|
||||
const result = await z
|
||||
.string()
|
||||
.transform(async (data, ctx) => {
|
||||
const i = strs.indexOf(data);
|
||||
if (i === -1) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `${data} is not one of our allowed strings`,
|
||||
});
|
||||
}
|
||||
return data.length;
|
||||
})
|
||||
.safeParseAsync("asdf");
|
||||
|
||||
expect(JSON.parse(JSON.stringify(result))).toEqual({
|
||||
success: false,
|
||||
error: {
|
||||
issues: [
|
||||
{
|
||||
code: "custom",
|
||||
message: "asdf is not one of our allowed strings",
|
||||
path: [],
|
||||
},
|
||||
],
|
||||
name: "ZodError",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("z.NEVER in transform", () => {
|
||||
const foo = z
|
||||
.number()
|
||||
.optional()
|
||||
.transform((val, ctx) => {
|
||||
if (!val) {
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "bad" });
|
||||
return z.NEVER;
|
||||
}
|
||||
return val;
|
||||
});
|
||||
type foo = z.infer<typeof foo>;
|
||||
util.assertEqual<foo, number>(true);
|
||||
const arg = foo.safeParse(undefined);
|
||||
if (!arg.success) {
|
||||
expect(arg.error.issues[0].message).toEqual("bad");
|
||||
}
|
||||
});
|
||||
|
||||
test("basic transformations", () => {
|
||||
const r1 = z
|
||||
.string()
|
||||
.transform((data) => data.length)
|
||||
.parse("asdf");
|
||||
expect(r1).toEqual(4);
|
||||
});
|
||||
|
||||
test("coercion", () => {
|
||||
const numToString = z.number().transform((n) => String(n));
|
||||
const data = z
|
||||
.object({
|
||||
id: numToString,
|
||||
})
|
||||
.parse({ id: 5 });
|
||||
|
||||
expect(data).toEqual({ id: "5" });
|
||||
});
|
||||
|
||||
test("async coercion", async () => {
|
||||
const numToString = z.number().transform(async (n) => String(n));
|
||||
const data = await z
|
||||
.object({
|
||||
id: numToString,
|
||||
})
|
||||
.parseAsync({ id: 5 });
|
||||
|
||||
expect(data).toEqual({ id: "5" });
|
||||
});
|
||||
|
||||
test("sync coercion async error", async () => {
|
||||
expect(() =>
|
||||
z
|
||||
.object({
|
||||
id: asyncNumberToString,
|
||||
})
|
||||
.parse({ id: 5 })
|
||||
).toThrow();
|
||||
// expect(data).toEqual({ id: '5' });
|
||||
});
|
||||
|
||||
test("default", () => {
|
||||
const data = z.string().default("asdf").parse(undefined); // => "asdf"
|
||||
expect(data).toEqual("asdf");
|
||||
});
|
||||
|
||||
test("dynamic default", () => {
|
||||
const data = z
|
||||
.string()
|
||||
.default(() => "string")
|
||||
.parse(undefined); // => "asdf"
|
||||
expect(data).toEqual("string");
|
||||
});
|
||||
|
||||
test("default when property is null or undefined", () => {
|
||||
const data = z
|
||||
.object({
|
||||
foo: z.boolean().nullable().default(true),
|
||||
bar: z.boolean().default(true),
|
||||
})
|
||||
.parse({ foo: null });
|
||||
|
||||
expect(data).toEqual({ foo: null, bar: true });
|
||||
});
|
||||
|
||||
test("default with falsy values", () => {
|
||||
const schema = z.object({
|
||||
emptyStr: z.string().default("def"),
|
||||
zero: z.number().default(5),
|
||||
falseBoolean: z.boolean().default(true),
|
||||
});
|
||||
const input = { emptyStr: "", zero: 0, falseBoolean: true };
|
||||
const output = schema.parse(input);
|
||||
// defaults are not supposed to be used
|
||||
expect(output).toEqual(input);
|
||||
});
|
||||
|
||||
test("object typing", () => {
|
||||
const t1 = z.object({
|
||||
stringToNumber,
|
||||
});
|
||||
|
||||
type t1 = z.input<typeof t1>;
|
||||
type t2 = z.output<typeof t1>;
|
||||
|
||||
util.assertEqual<t1, { stringToNumber: string }>(true);
|
||||
util.assertEqual<t2, { stringToNumber: number }>(true);
|
||||
});
|
||||
|
||||
test("transform method overloads", () => {
|
||||
const t1 = z.string().transform((val) => val.toUpperCase());
|
||||
expect(t1.parse("asdf")).toEqual("ASDF");
|
||||
|
||||
const t2 = z.string().transform((val) => val.length);
|
||||
expect(t2.parse("asdf")).toEqual(4);
|
||||
});
|
||||
|
||||
test("multiple transformers", () => {
|
||||
const doubler = stringToNumber.transform((val) => {
|
||||
return val * 2;
|
||||
});
|
||||
expect(doubler.parse("5")).toEqual(10);
|
||||
});
|
||||
|
||||
test("short circuit on dirty", () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.refine(() => false)
|
||||
.transform((val) => val.toUpperCase());
|
||||
const result = schema.safeParse("asdf");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].code).toEqual(z.ZodIssueCode.custom);
|
||||
}
|
||||
|
||||
const result2 = schema.safeParse(1234);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result2.success) {
|
||||
expect(result2.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_type);
|
||||
}
|
||||
});
|
||||
|
||||
test("async short circuit on dirty", async () => {
|
||||
const schema = z
|
||||
.string()
|
||||
.refine(() => false)
|
||||
.transform((val) => val.toUpperCase());
|
||||
const result = await schema.spa("asdf");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues[0].code).toEqual(z.ZodIssueCode.custom);
|
||||
}
|
||||
|
||||
const result2 = await schema.spa(1234);
|
||||
expect(result2.success).toEqual(false);
|
||||
if (!result2.success) {
|
||||
expect(result2.error.issues[0].code).toEqual(z.ZodIssueCode.invalid_type);
|
||||
}
|
||||
});
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { ZodError } from "../ZodError.js";
|
||||
import { util } from "../helpers/util.js";
|
||||
|
||||
const testTuple = z.tuple([z.string(), z.object({ name: z.literal("Rudy") }), z.array(z.literal("blue"))]);
|
||||
const testData = ["asdf", { name: "Rudy" }, ["blue"]];
|
||||
const badData = [123, { name: "Rudy2" }, ["blue", "red"]];
|
||||
|
||||
test("tuple inference", () => {
|
||||
const args1 = z.tuple([z.string()]);
|
||||
const returns1 = z.number();
|
||||
const func1 = z.function(args1, returns1);
|
||||
type func1 = z.TypeOf<typeof func1>;
|
||||
util.assertEqual<func1, (k: string) => number>(true);
|
||||
});
|
||||
|
||||
test("successful validation", () => {
|
||||
const val = testTuple.parse(testData);
|
||||
expect(val).toEqual(["asdf", { name: "Rudy" }, ["blue"]]);
|
||||
});
|
||||
|
||||
test("successful async validation", async () => {
|
||||
const val = await testTuple.parseAsync(testData);
|
||||
return expect(val).toEqual(testData);
|
||||
});
|
||||
|
||||
test("failed validation", () => {
|
||||
const checker = () => {
|
||||
testTuple.parse([123, { name: "Rudy2" }, ["blue", "red"]] as any);
|
||||
};
|
||||
try {
|
||||
checker();
|
||||
} catch (err) {
|
||||
if (err instanceof ZodError) {
|
||||
expect(err.issues.length).toEqual(3);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("failed async validation", async () => {
|
||||
const res = await testTuple.safeParse(badData);
|
||||
expect(res.success).toEqual(false);
|
||||
if (!res.success) {
|
||||
expect(res.error.issues.length).toEqual(3);
|
||||
}
|
||||
// try {
|
||||
// checker();
|
||||
// } catch (err) {
|
||||
// if (err instanceof ZodError) {
|
||||
// expect(err.issues.length).toEqual(3);
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
test("tuple with transformers", () => {
|
||||
const stringToNumber = z.string().transform((val) => val.length);
|
||||
const val = z.tuple([stringToNumber]);
|
||||
|
||||
type t1 = z.input<typeof val>;
|
||||
util.assertEqual<t1, [string]>(true);
|
||||
type t2 = z.output<typeof val>;
|
||||
util.assertEqual<t2, [number]>(true);
|
||||
expect(val.parse(["1234"])).toEqual([4]);
|
||||
});
|
||||
|
||||
test("tuple with rest schema", () => {
|
||||
const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean());
|
||||
expect(myTuple.parse(["asdf", 1234, true, false, true])).toEqual(["asdf", 1234, true, false, true]);
|
||||
|
||||
expect(myTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]);
|
||||
|
||||
expect(() => myTuple.parse(["asdf", 1234, "asdf"])).toThrow();
|
||||
type t1 = z.output<typeof myTuple>;
|
||||
|
||||
util.assertEqual<t1, [string, number, ...boolean[]]>(true);
|
||||
});
|
||||
|
||||
test("parse should fail given sparse array as tuple", () => {
|
||||
expect(() => testTuple.parse(new Array(3))).toThrow();
|
||||
});
|
||||
|
||||
// test('tuple with optional elements', () => {
|
||||
// const result = z
|
||||
// .tuple([z.string(), z.number().optional()])
|
||||
// .safeParse(['asdf']);
|
||||
// expect(result).toEqual(['asdf']);
|
||||
// });
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("function parsing", () => {
|
||||
const schema = z.union([z.string().refine(() => false), z.number().refine(() => false)]);
|
||||
const result = schema.safeParse("asdf");
|
||||
expect(result.success).toEqual(false);
|
||||
});
|
||||
|
||||
test("union 2", () => {
|
||||
const result = z.union([z.number(), z.string().refine(() => false)]).safeParse("a");
|
||||
expect(result.success).toEqual(false);
|
||||
});
|
||||
|
||||
test("return valid over invalid", () => {
|
||||
const schema = z.union([
|
||||
z.object({
|
||||
email: z.string().email(),
|
||||
}),
|
||||
z.string(),
|
||||
]);
|
||||
expect(schema.parse("asdf")).toEqual("asdf");
|
||||
expect(schema.parse({ email: "asdlkjf@lkajsdf.com" })).toEqual({
|
||||
email: "asdlkjf@lkajsdf.com",
|
||||
});
|
||||
});
|
||||
|
||||
test("return dirty result over aborted", () => {
|
||||
const result = z.union([z.number(), z.string().refine(() => false)]).safeParse("a");
|
||||
expect(result.success).toEqual(false);
|
||||
if (!result.success) {
|
||||
expect(result.error.issues).toEqual([
|
||||
{
|
||||
code: "custom",
|
||||
message: "Invalid input",
|
||||
path: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
test("options getter", async () => {
|
||||
const union = z.union([z.string(), z.number()]);
|
||||
union.options[0].parse("asdf");
|
||||
union.options[1].parse(1234);
|
||||
await union.options[0].parseAsync("asdf");
|
||||
await union.options[1].parseAsync(1234);
|
||||
});
|
||||
|
||||
test("readonly union", async () => {
|
||||
const options = [z.string(), z.number()] as const;
|
||||
const union = z.union(options);
|
||||
union.parse("asdf");
|
||||
union.parse(12);
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
|
||||
test("array min", async () => {
|
||||
try {
|
||||
await z.array(z.string()).min(4).parseAsync([]);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Array must contain at least 4 element(s)");
|
||||
}
|
||||
});
|
||||
|
||||
test("array max", async () => {
|
||||
try {
|
||||
await z.array(z.string()).max(2).parseAsync(["asdf", "asdf", "asdf"]);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Array must contain at most 2 element(s)");
|
||||
}
|
||||
});
|
||||
|
||||
test("array length", async () => {
|
||||
try {
|
||||
await z.array(z.string()).length(2).parseAsync(["asdf", "asdf", "asdf"]);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Array must contain exactly 2 element(s)");
|
||||
}
|
||||
|
||||
try {
|
||||
await z.array(z.string()).length(2).parseAsync(["asdf"]);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Array must contain exactly 2 element(s)");
|
||||
}
|
||||
});
|
||||
|
||||
test("string length", async () => {
|
||||
try {
|
||||
await z.string().length(4).parseAsync("asd");
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("String must contain exactly 4 character(s)");
|
||||
}
|
||||
|
||||
try {
|
||||
await z.string().length(4).parseAsync("asdaa");
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("String must contain exactly 4 character(s)");
|
||||
}
|
||||
});
|
||||
|
||||
test("string min", async () => {
|
||||
try {
|
||||
await z.string().min(4).parseAsync("asd");
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("String must contain at least 4 character(s)");
|
||||
}
|
||||
});
|
||||
|
||||
test("string max", async () => {
|
||||
try {
|
||||
await z.string().max(4).parseAsync("aasdfsdfsd");
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("String must contain at most 4 character(s)");
|
||||
}
|
||||
});
|
||||
|
||||
test("number min", async () => {
|
||||
try {
|
||||
await z.number().gte(3).parseAsync(2);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Number must be greater than or equal to 3");
|
||||
}
|
||||
});
|
||||
|
||||
test("number max", async () => {
|
||||
try {
|
||||
await z.number().lte(3).parseAsync(4);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Number must be less than or equal to 3");
|
||||
}
|
||||
});
|
||||
|
||||
test("number nonnegative", async () => {
|
||||
try {
|
||||
await z.number().nonnegative().parseAsync(-1);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Number must be greater than or equal to 0");
|
||||
}
|
||||
});
|
||||
|
||||
test("number nonpositive", async () => {
|
||||
try {
|
||||
await z.number().nonpositive().parseAsync(1);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Number must be less than or equal to 0");
|
||||
}
|
||||
});
|
||||
|
||||
test("number negative", async () => {
|
||||
try {
|
||||
await z.number().negative().parseAsync(1);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Number must be less than 0");
|
||||
}
|
||||
});
|
||||
|
||||
test("number positive", async () => {
|
||||
try {
|
||||
await z.number().positive().parseAsync(-1);
|
||||
} catch (err) {
|
||||
expect((err as z.ZodError).issues[0].message).toEqual("Number must be greater than 0");
|
||||
}
|
||||
});
|
||||
|
||||
test("instantiation", () => {
|
||||
z.string().min(5);
|
||||
z.string().max(5);
|
||||
z.string().length(5);
|
||||
z.string().email();
|
||||
z.string().url();
|
||||
z.string().uuid();
|
||||
z.string().min(5, { message: "Must be 5 or more characters long" });
|
||||
z.string().max(5, { message: "Must be 5 or fewer characters long" });
|
||||
z.string().length(5, { message: "Must be exactly 5 characters long" });
|
||||
z.string().email({ message: "Invalid email address." });
|
||||
z.string().url({ message: "Invalid url" });
|
||||
z.string().uuid({ message: "Invalid UUID" });
|
||||
});
|
||||
|
||||
test("int", async () => {
|
||||
const int = z.number().int();
|
||||
int.parse(4);
|
||||
expect(() => int.parse(3.5)).toThrow();
|
||||
});
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @ts-ignore TS6133
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import * as z from "zod/v3";
|
||||
import { util } from "../helpers/util.js";
|
||||
test("void", () => {
|
||||
const v = z.void();
|
||||
v.parse(undefined);
|
||||
|
||||
expect(() => v.parse(null)).toThrow();
|
||||
expect(() => v.parse("")).toThrow();
|
||||
|
||||
type v = z.infer<typeof v>;
|
||||
util.assertEqual<v, void>(true);
|
||||
});
|
||||
+5138
File diff suppressed because it is too large
Load Diff
+3
@@ -0,0 +1,3 @@
|
||||
import * as z from "../v4/mini/external.js";
|
||||
export * from "../v4/mini/external.js";
|
||||
export { z };
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
export {
|
||||
_lt as lt,
|
||||
_lte as lte,
|
||||
_gt as gt,
|
||||
_gte as gte,
|
||||
_positive as positive,
|
||||
_negative as negative,
|
||||
_nonpositive as nonpositive,
|
||||
_nonnegative as nonnegative,
|
||||
_multipleOf as multipleOf,
|
||||
_maxSize as maxSize,
|
||||
_minSize as minSize,
|
||||
_size as size,
|
||||
_maxLength as maxLength,
|
||||
_minLength as minLength,
|
||||
_length as length,
|
||||
_regex as regex,
|
||||
_lowercase as lowercase,
|
||||
_uppercase as uppercase,
|
||||
_includes as includes,
|
||||
_startsWith as startsWith,
|
||||
_endsWith as endsWith,
|
||||
_property as property,
|
||||
_properties as properties,
|
||||
_mime as mime,
|
||||
_overwrite as overwrite,
|
||||
_normalize as normalize,
|
||||
_trim as trim,
|
||||
_toLowerCase as toLowerCase,
|
||||
_toUpperCase as toUpperCase,
|
||||
_slugify as slugify,
|
||||
type $RefinementCtx as RefinementCtx,
|
||||
} from "../core/index.js";
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import * as core from "../core/index.js";
|
||||
import * as schemas from "./schemas.js";
|
||||
|
||||
export interface ZodCoercedString<T = unknown> extends schemas._ZodString<core.$ZodStringInternals<T>> {}
|
||||
export function string<T = unknown>(params?: string | core.$ZodStringParams): ZodCoercedString<T> {
|
||||
return core._coercedString(schemas.ZodString, params) as any;
|
||||
}
|
||||
|
||||
export interface ZodCoercedNumber<T = unknown> extends schemas._ZodNumber<core.$ZodNumberInternals<T>> {}
|
||||
export function number<T = unknown>(params?: string | core.$ZodNumberParams): ZodCoercedNumber<T> {
|
||||
return core._coercedNumber(schemas.ZodNumber, params) as ZodCoercedNumber<T>;
|
||||
}
|
||||
|
||||
export interface ZodCoercedBoolean<T = unknown> extends schemas._ZodBoolean<core.$ZodBooleanInternals<T>> {}
|
||||
export function boolean<T = unknown>(params?: string | core.$ZodBooleanParams): ZodCoercedBoolean<T> {
|
||||
return core._coercedBoolean(schemas.ZodBoolean, params) as ZodCoercedBoolean<T>;
|
||||
}
|
||||
|
||||
export interface ZodCoercedBigInt<T = unknown> extends schemas._ZodBigInt<core.$ZodBigIntInternals<T>> {}
|
||||
export function bigint<T = unknown>(params?: string | core.$ZodBigIntParams): ZodCoercedBigInt<T> {
|
||||
return core._coercedBigint(schemas.ZodBigInt, params) as ZodCoercedBigInt<T>;
|
||||
}
|
||||
|
||||
export interface ZodCoercedDate<T = unknown> extends schemas._ZodDate<core.$ZodDateInternals<T>> {}
|
||||
export function date<T = unknown>(params?: string | core.$ZodDateParams): ZodCoercedDate<T> {
|
||||
return core._coercedDate(schemas.ZodDate, params) as ZodCoercedDate<T>;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Zod 3 compat layer
|
||||
|
||||
import * as core from "../core/index.js";
|
||||
import type { ZodType } from "./schemas.js";
|
||||
|
||||
/** @deprecated Use `z.output<T>` instead. */
|
||||
export type TypeOf<T> = core.output<T>;
|
||||
/** @deprecated Use `z.output<T>` instead. */
|
||||
export type Infer<T> = core.output<T>;
|
||||
/** @deprecated Use `z.core.$ZodTypes` instead */
|
||||
export type ZodFirstPartySchemaTypes = core.$ZodTypes;
|
||||
|
||||
/** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
|
||||
export const ZodIssueCode = {
|
||||
invalid_type: "invalid_type",
|
||||
too_big: "too_big",
|
||||
too_small: "too_small",
|
||||
invalid_format: "invalid_format",
|
||||
not_multiple_of: "not_multiple_of",
|
||||
unrecognized_keys: "unrecognized_keys",
|
||||
invalid_union: "invalid_union",
|
||||
invalid_key: "invalid_key",
|
||||
invalid_element: "invalid_element",
|
||||
invalid_value: "invalid_value",
|
||||
custom: "custom",
|
||||
} as const;
|
||||
|
||||
/** @deprecated Use `z.core.$ZodFlattenedError<z.output<T>>` instead; it takes an inferred type, not a schema. */
|
||||
export type inferFlattenedErrors<T extends core.$ZodType, U = string> = core.$ZodFlattenedError<core.output<T>, U>;
|
||||
|
||||
/** @deprecated Use `z.core.$ZodFormattedError<z.output<T>>` instead; it takes an inferred type, not a schema. */
|
||||
export type inferFormattedError<T extends core.$ZodType<any, any>, U = string> = core.$ZodFormattedError<
|
||||
core.output<T>,
|
||||
U
|
||||
>;
|
||||
|
||||
/** Use `z.$brand` instead */
|
||||
export type BRAND<T extends string | number | symbol = string | number | symbol> = {
|
||||
[core.$brand]: { [k in T]: true };
|
||||
};
|
||||
export { $brand, config } from "../core/index.js";
|
||||
|
||||
/** @deprecated Use `z.config(params)` instead. */
|
||||
export function setErrorMap(map: core.$ZodErrorMap): void {
|
||||
core.config({
|
||||
customError: map,
|
||||
});
|
||||
}
|
||||
|
||||
/** @deprecated Use `z.config()` instead. */
|
||||
export function getErrorMap(): core.$ZodErrorMap<core.$ZodIssue> | undefined {
|
||||
return core.config().customError;
|
||||
}
|
||||
|
||||
/** @deprecated Use z.ZodType (without generics) instead. */
|
||||
export type ZodTypeAny<
|
||||
Output = unknown,
|
||||
Input = unknown,
|
||||
Internals extends core.$ZodTypeInternals<Output, Input> = core.$ZodTypeInternals<Output, Input>,
|
||||
> = ZodType<Output, Input, Internals>;
|
||||
/** @deprecated Use `z.ZodType` */
|
||||
export type ZodSchema<
|
||||
Output = unknown,
|
||||
Input = unknown,
|
||||
Internals extends core.$ZodTypeInternals<Output, Input> = core.$ZodTypeInternals<Output, Input>,
|
||||
> = ZodType<Output, Input, Internals>;
|
||||
/** @deprecated Use `z.ZodType` */
|
||||
export type Schema<
|
||||
Output = unknown,
|
||||
Input = unknown,
|
||||
Internals extends core.$ZodTypeInternals<Output, Input> = core.$ZodTypeInternals<Output, Input>,
|
||||
> = ZodType<Output, Input, Internals>;
|
||||
|
||||
/** Included for Zod 3 compatibility */
|
||||
export type ZodRawShape = core.$ZodShape;
|
||||
|
||||
/** @deprecated Do not use. Stub definition, only included for zod-to-json-schema compatibility. */
|
||||
export enum ZodFirstPartyTypeKind {}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
import type * as core from "../core/index.js";
|
||||
import { visit } from "../core/visit.js";
|
||||
import * as schemas from "./schemas.js";
|
||||
|
||||
/** Distributes `.partial()` through every object in a schema type, preserving the wrappers around them. */
|
||||
// biome-ignore format: preserve vertical layout for readability.
|
||||
export type DeepPartial<T extends core.SomeType> =
|
||||
T extends schemas.ZodObject<infer Shape, infer Config>
|
||||
? schemas.ZodObject<
|
||||
{ -readonly [K in keyof Shape]: schemas.ZodOptional<DeepPartial<Shape[K]>> },
|
||||
Config
|
||||
>
|
||||
// Must precede ZodUnion; degrades to it, matching the runtime.
|
||||
: T extends schemas.ZodDiscriminatedUnion<infer Options, any>
|
||||
? schemas.ZodUnion<
|
||||
{ -readonly [I in keyof Options]: Options[I] extends core.SomeType ? DeepPartial<Options[I]> : Options[I] }
|
||||
>
|
||||
: T extends schemas.ZodUnion<infer Options>
|
||||
? schemas.ZodUnion<
|
||||
{ -readonly [I in keyof Options]: Options[I] extends core.SomeType ? DeepPartial<Options[I]> : Options[I] }
|
||||
>
|
||||
: T extends schemas.ZodArray<infer El>
|
||||
? schemas.ZodArray<DeepPartial<El>>
|
||||
: T extends schemas.ZodTuple<infer Items, infer Rest>
|
||||
? schemas.ZodTuple<
|
||||
{ -readonly [K in keyof Items]: Items[K] extends core.SomeType ? DeepPartial<Items[K]> : Items[K] },
|
||||
Rest extends core.SomeType ? DeepPartial<Rest> : Rest
|
||||
>
|
||||
: T extends schemas.ZodIntersection<infer A, infer B>
|
||||
? schemas.ZodIntersection<DeepPartial<A>, DeepPartial<B>>
|
||||
: T extends schemas.ZodRecord<infer K, infer V>
|
||||
? schemas.ZodRecord<K, DeepPartial<V>>
|
||||
: T extends schemas.ZodMap<infer K, infer V>
|
||||
? schemas.ZodMap<K, DeepPartial<V>>
|
||||
: T extends schemas.ZodSet<infer El>
|
||||
? schemas.ZodSet<DeepPartial<El>>
|
||||
: T extends schemas.ZodOptional<infer Inner>
|
||||
? schemas.ZodOptional<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodNullable<infer Inner>
|
||||
? schemas.ZodNullable<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodDefault<infer Inner>
|
||||
? schemas.ZodDefault<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodPrefault<infer Inner>
|
||||
? schemas.ZodPrefault<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodNonOptional<infer Inner>
|
||||
? schemas.ZodNonOptional<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodCatch<infer Inner>
|
||||
? schemas.ZodCatch<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodReadonly<infer Inner>
|
||||
? schemas.ZodReadonly<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodSuccess<infer Inner>
|
||||
? schemas.ZodSuccess<DeepPartial<Inner>>
|
||||
: T extends schemas.ZodPromise<infer Inner>
|
||||
? schemas.ZodPromise<DeepPartial<Inner>>
|
||||
// Must precede ZodPipe, which both extend structurally, or the subtype is lost.
|
||||
: T extends schemas.ZodCodec<infer A, infer B>
|
||||
? schemas.ZodCodec<DeepPartial<A>, DeepPartial<B>>
|
||||
: T extends schemas.ZodPreprocess<infer B>
|
||||
? schemas.ZodPreprocess<DeepPartial<B>>
|
||||
: T extends schemas.ZodPipe<infer A, infer B>
|
||||
? schemas.ZodPipe<DeepPartial<A>, DeepPartial<B>>
|
||||
: T extends schemas.ZodLazy<infer Inner>
|
||||
? schemas.ZodLazy<DeepPartial<Inner>>
|
||||
: T;
|
||||
|
||||
/** Returns a copy of the schema with every nested object's properties made optional. */
|
||||
export function deepPartial<T extends core.SomeType>(schema: T): DeepPartial<T> {
|
||||
return visit(schema, {
|
||||
object: (s) => (s as schemas.ZodObject).partial(),
|
||||
// Every partialed option now admits `undefined`, which the constructor rejects as a duplicate.
|
||||
union: (s) => {
|
||||
const def = s._zod.def as core.$ZodDiscriminatedUnionDef;
|
||||
return def.discriminator === undefined ? s : schemas.union(def.options as schemas.ZodType[]);
|
||||
},
|
||||
}) as unknown as DeepPartial<T>;
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
import * as core from "../core/index.js";
|
||||
import { $ZodError } from "../core/index.js";
|
||||
import * as util from "../core/util.js";
|
||||
|
||||
/** @deprecated Use `z.core.$ZodIssue` from `@zod/core` instead, especially if you are building a library on top of Zod. */
|
||||
export type ZodIssue = core.$ZodIssue;
|
||||
|
||||
/** An Error-like class used to store Zod validation issues. */
|
||||
export interface ZodError<T = unknown> extends $ZodError<T> {
|
||||
/** @deprecated Use the `z.treeifyError(err)` function instead. */
|
||||
format(): core.$ZodFormattedError<T>;
|
||||
format<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFormattedError<T, U>;
|
||||
/** @deprecated Use the `z.treeifyError(err)` function instead. */
|
||||
flatten(): core.$ZodFlattenedError<T>;
|
||||
flatten<U>(mapper: (issue: core.$ZodIssue) => U): core.$ZodFlattenedError<T, U>;
|
||||
/** @deprecated Push directly to `.issues` instead. */
|
||||
addIssue(issue: core.$ZodIssue): void;
|
||||
/** @deprecated Push directly to `.issues` instead. */
|
||||
addIssues(issues: core.$ZodIssue[]): void;
|
||||
|
||||
/** @deprecated Check `err.issues.length === 0` instead. */
|
||||
isEmpty: boolean;
|
||||
}
|
||||
|
||||
/* Prototypes that already carry the lazy helper methods. Seeded with the
|
||||
* intrinsics so that `init` on a foreign object — it accepts any object —
|
||||
* can never install an accessor onto a prototype we do not own. */
|
||||
const _installedErrorProtos = /* @__PURE__ */ new WeakSet<object>([Object.prototype, Error.prototype]);
|
||||
|
||||
/* Helper methods live as non-enumerable lazy getters on the shared
|
||||
* prototype instead of own properties on every instance. On first
|
||||
* access the getter allocates the per-instance closure and caches it
|
||||
* as a non-enumerable own property, so detached usage still works and
|
||||
* the allocation only happens for methods actually touched. */
|
||||
function _lazyMethod(proto: object, key: string, make: (self: ZodError) => unknown): void {
|
||||
Object.defineProperty(proto, key, {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get(this: ZodError) {
|
||||
const value = make(this);
|
||||
Object.defineProperty(this, key, { value, configurable: true, writable: true });
|
||||
return value;
|
||||
},
|
||||
set(this: ZodError, value: unknown) {
|
||||
Object.defineProperty(this, key, { value, configurable: true, writable: true });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const initializer = (inst: ZodError, issues: core.$ZodIssue[]) => {
|
||||
$ZodError.init(inst, issues);
|
||||
inst.name = "ZodError";
|
||||
|
||||
const proto = Object.getPrototypeOf(inst);
|
||||
if (_installedErrorProtos.has(proto)) return;
|
||||
_installedErrorProtos.add(proto);
|
||||
|
||||
_lazyMethod(proto, "format", (self) => (mapper: any) => core.formatError(self, mapper));
|
||||
_lazyMethod(proto, "flatten", (self) => (mapper: any) => core.flattenError(self, mapper));
|
||||
_lazyMethod(proto, "addIssue", (self) => (issue: any) => {
|
||||
self.issues.push(issue);
|
||||
self.message = JSON.stringify(self.issues, util.jsonStringifyReplacer, 2);
|
||||
});
|
||||
_lazyMethod(proto, "addIssues", (self) => (issues: any) => {
|
||||
self.issues.push(...issues);
|
||||
self.message = JSON.stringify(self.issues, util.jsonStringifyReplacer, 2);
|
||||
});
|
||||
Object.defineProperty(proto, "isEmpty", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get(this: ZodError) {
|
||||
return this.issues.length === 0;
|
||||
},
|
||||
});
|
||||
};
|
||||
export const ZodError: core.$constructor<ZodError> = /*@__PURE__*/ core.$constructor("ZodError", initializer);
|
||||
export const ZodRealError: core.$constructor<ZodError> = /*@__PURE__*/ core.$constructor(
|
||||
"ZodError",
|
||||
initializer,
|
||||
undefined,
|
||||
{
|
||||
Parent: Error,
|
||||
}
|
||||
);
|
||||
|
||||
/** @deprecated Use `z.core.$ZodFlattenedError` instead. */
|
||||
export type ZodFlattenedError<T, U = string> = core.$ZodFlattenedError<T, U>;
|
||||
/** @deprecated Use `z.core.$ZodFormattedError` instead. */
|
||||
export type ZodFormattedError<T, U = string> = core.$ZodFormattedError<T, U>;
|
||||
/** @deprecated Use `z.core.$ZodErrorMap` instead. */
|
||||
export type ZodErrorMap<T extends core.$ZodIssueBase = core.$ZodIssue> = core.$ZodErrorMap<T>;
|
||||
|
||||
/** @deprecated Use `z.core.$ZodRawIssue` instead. */
|
||||
export type IssueData = core.$ZodRawIssue;
|
||||
|
||||
// /** @deprecated Use `z.core.$ZodErrorMapCtx` instead. */
|
||||
// export type ErrorMapCtx = core.$ZodErrorMapCtx;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
export * as core from "../core/index.js";
|
||||
export * from "./schemas.js";
|
||||
export * from "./checks.js";
|
||||
export * from "./errors.js";
|
||||
export * from "./parse.js";
|
||||
export * from "./compat.js";
|
||||
|
||||
export type { infer } from "../core/index.js";
|
||||
export type { JSONType } from "../core/util.js";
|
||||
export type { CompileOptions } from "../core/index.js";
|
||||
export {
|
||||
globalRegistry,
|
||||
type GlobalMeta,
|
||||
registry,
|
||||
config,
|
||||
memoizer,
|
||||
$output,
|
||||
$input,
|
||||
$brand,
|
||||
clone,
|
||||
regexes,
|
||||
treeifyError,
|
||||
prettifyError,
|
||||
formatError,
|
||||
flattenError,
|
||||
TimePrecision,
|
||||
util,
|
||||
NEVER,
|
||||
INVALID,
|
||||
toZod,
|
||||
compile,
|
||||
withParser,
|
||||
ZodCompileAsyncError,
|
||||
ZodCompileUnsupportedError,
|
||||
getDiscriminatedOption,
|
||||
} from "../core/index.js";
|
||||
export { toJSONSchema } from "../core/json-schema-processors.js";
|
||||
export { fromJSONSchema } from "./from-json-schema.js";
|
||||
export { deepPartial } from "./deep-partial.js";
|
||||
// Types and values both, from one module — re-exporting the types from core would collide.
|
||||
export { input, output } from "./in-out.js";
|
||||
|
||||
export * as locales from "../locales/index.js";
|
||||
|
||||
// iso must be exported from top-level https://github.com/colinhacks/zod/issues/4491
|
||||
export { ZodISODateTime, ZodISODate, ZodISOTime, ZodISODuration } from "./iso.js";
|
||||
export * as iso from "./iso.js";
|
||||
|
||||
// coerce
|
||||
export type {
|
||||
ZodCoercedString,
|
||||
ZodCoercedNumber,
|
||||
ZodCoercedBigInt,
|
||||
ZodCoercedBoolean,
|
||||
ZodCoercedDate,
|
||||
} from "./coerce.js";
|
||||
export * as coerce from "./coerce.js";
|
||||
+944
@@ -0,0 +1,944 @@
|
||||
import type * as JSONSchema from "../core/json-schema.js";
|
||||
import { type $ZodRegistry, globalRegistry } from "../core/registries.js";
|
||||
import { assignProp, isPlainObject } from "../core/util.js";
|
||||
import * as _checks from "./checks.js";
|
||||
import * as _iso from "./iso.js";
|
||||
import * as _schemas from "./schemas.js";
|
||||
import type { ZodNumber, ZodString, ZodType } from "./schemas.js";
|
||||
|
||||
// Local z object to avoid circular dependency with ../index.js
|
||||
const z = {
|
||||
..._schemas,
|
||||
..._checks,
|
||||
iso: _iso,
|
||||
};
|
||||
|
||||
type JSONSchemaVersion = "draft-2020-12" | "draft-7" | "draft-4" | "openapi-3.0";
|
||||
|
||||
interface FromJSONSchemaParams {
|
||||
defaultTarget?: JSONSchemaVersion;
|
||||
registry?: $ZodRegistry<any>;
|
||||
}
|
||||
|
||||
interface ConversionContext {
|
||||
version: JSONSchemaVersion;
|
||||
defs: Record<string, JSONSchema.JSONSchema>;
|
||||
refs: Map<string, ZodType>;
|
||||
processing: Set<string>;
|
||||
rootSchema: JSONSchema.JSONSchema;
|
||||
registry: $ZodRegistry<any>;
|
||||
}
|
||||
|
||||
// Keys that are recognized and handled by the conversion logic
|
||||
const RECOGNIZED_KEYS = /*@__PURE__*/ new Set([
|
||||
// Schema identification
|
||||
"$schema",
|
||||
"$ref",
|
||||
"$defs",
|
||||
"definitions",
|
||||
// Core schema keywords
|
||||
"$id",
|
||||
"id",
|
||||
"$comment",
|
||||
"$anchor",
|
||||
"$vocabulary",
|
||||
"$dynamicRef",
|
||||
"$dynamicAnchor",
|
||||
// Type
|
||||
"type",
|
||||
"enum",
|
||||
"const",
|
||||
// Composition
|
||||
"anyOf",
|
||||
"oneOf",
|
||||
"allOf",
|
||||
"not",
|
||||
// Object
|
||||
"properties",
|
||||
"required",
|
||||
"additionalProperties",
|
||||
"patternProperties",
|
||||
"propertyNames",
|
||||
"minProperties",
|
||||
"maxProperties",
|
||||
// Array
|
||||
"items",
|
||||
"prefixItems",
|
||||
"additionalItems",
|
||||
"minItems",
|
||||
"maxItems",
|
||||
"uniqueItems",
|
||||
"contains",
|
||||
"minContains",
|
||||
"maxContains",
|
||||
// String
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"pattern",
|
||||
"format",
|
||||
// Number
|
||||
"minimum",
|
||||
"maximum",
|
||||
"exclusiveMinimum",
|
||||
"exclusiveMaximum",
|
||||
"multipleOf",
|
||||
// Already handled metadata
|
||||
"description",
|
||||
"default",
|
||||
// Content
|
||||
"contentEncoding",
|
||||
"contentMediaType",
|
||||
"contentSchema",
|
||||
// Unsupported (error-throwing)
|
||||
"unevaluatedItems",
|
||||
"unevaluatedProperties",
|
||||
"if",
|
||||
"then",
|
||||
"else",
|
||||
"dependentSchemas",
|
||||
"dependentRequired",
|
||||
// OpenAPI
|
||||
"nullable",
|
||||
"readOnly",
|
||||
]);
|
||||
|
||||
function detectVersion(schema: JSONSchema.JSONSchema, defaultTarget?: JSONSchemaVersion): JSONSchemaVersion {
|
||||
const $schema = schema.$schema;
|
||||
|
||||
if ($schema === "https://json-schema.org/draft/2020-12/schema") {
|
||||
return "draft-2020-12";
|
||||
}
|
||||
if ($schema === "http://json-schema.org/draft-07/schema#") {
|
||||
return "draft-7";
|
||||
}
|
||||
if ($schema === "http://json-schema.org/draft-04/schema#") {
|
||||
return "draft-4";
|
||||
}
|
||||
|
||||
// Use defaultTarget if provided, otherwise default to draft-2020-12
|
||||
return defaultTarget ?? "draft-2020-12";
|
||||
}
|
||||
|
||||
// Positional schemas constrain the elements that are present; only minItems makes them required.
|
||||
function applyMinItems(items: ZodType[], minItems: number): ZodType[] {
|
||||
return items.map((item, index) => (index < minItems ? item : item.optional()));
|
||||
}
|
||||
|
||||
// Inverse of the encoding applied to $ref pointer segments in to-json-schema.ts. Per RFC 6901 the `~1` replacement must run before `~0`.
|
||||
function decodeJSONPointerSegment(segment: string): string {
|
||||
return segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
||||
}
|
||||
|
||||
function resolveRef(ref: string, ctx: ConversionContext): JSONSchema.JSONSchema {
|
||||
if (!ref.startsWith("#")) {
|
||||
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
||||
}
|
||||
|
||||
const path = ref.slice(1).split("/").filter(Boolean);
|
||||
|
||||
// Handle root reference "#"
|
||||
if (path.length === 0) {
|
||||
return ctx.rootSchema;
|
||||
}
|
||||
|
||||
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
||||
|
||||
if (path[0] === defsKey) {
|
||||
const key = path[1] === undefined ? undefined : decodeJSONPointerSegment(path[1]);
|
||||
if (!key || !ctx.defs[key]) {
|
||||
throw new Error(`Reference not found: ${ref}`);
|
||||
}
|
||||
return ctx.defs[key]!;
|
||||
}
|
||||
|
||||
throw new Error(`Reference not found: ${ref}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces the keywords that constrain an object's own keys — `propertyNames`,
|
||||
* `minProperties`, `maxProperties` — before `objectSchema` runs. The guard has
|
||||
* to see the raw input: an object parse drops `__proto__` and can add keys from
|
||||
* a property `default`, so its output is not the set of names the instance
|
||||
* actually carried.
|
||||
*/
|
||||
function checkObjectGuards(
|
||||
objectSchema: ZodType,
|
||||
guards: { keySchema?: ZodType | undefined; minProperties?: number | undefined; maxProperties?: number | undefined }
|
||||
): ZodType {
|
||||
// An identity transform, not z.any(), so `toJSONSchema` reports the object on both the input and the output side of the pipe.
|
||||
const guard = z
|
||||
.transform((value: unknown) => value)
|
||||
.check((payload) => {
|
||||
const value = payload.value;
|
||||
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
||||
const keys = Object.getOwnPropertyNames(value);
|
||||
if (guards.minProperties !== undefined && keys.length < guards.minProperties) {
|
||||
payload.issues.push({
|
||||
origin: "object",
|
||||
code: "too_small",
|
||||
minimum: guards.minProperties,
|
||||
inclusive: true,
|
||||
message: `Too small: expected object to have >=${guards.minProperties} properties`,
|
||||
input: value,
|
||||
inst: objectSchema,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
if (guards.maxProperties !== undefined && keys.length > guards.maxProperties) {
|
||||
payload.issues.push({
|
||||
origin: "object",
|
||||
code: "too_big",
|
||||
maximum: guards.maxProperties,
|
||||
inclusive: true,
|
||||
message: `Too big: expected object to have <=${guards.maxProperties} properties`,
|
||||
input: value,
|
||||
inst: objectSchema,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
if (guards.keySchema) {
|
||||
for (const key of keys) {
|
||||
const result = guards.keySchema.safeParse(key);
|
||||
if (result.success) continue;
|
||||
payload.issues.push({
|
||||
code: "invalid_key",
|
||||
origin: "record",
|
||||
issues: result.error.issues,
|
||||
input: key,
|
||||
path: [key],
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return guard.pipe(objectSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* An injective string for JSON data: two values share a key exactly when `uniqueItems` calls them
|
||||
* equal, so duplicate detection is a Map lookup rather than a pairwise walk. Strings and keys are
|
||||
* length-prefixed so no value can spell another one. `null` means "never equal to anything" — a
|
||||
* cycle or a NaN, neither of which JSON can express.
|
||||
*/
|
||||
function canonicalKey(value: unknown, seen: Set<object>): string | null {
|
||||
if (value === null) return "z";
|
||||
const type = typeof value;
|
||||
if (type !== "object") {
|
||||
if (type === "number" && Number.isNaN(value)) return null;
|
||||
const raw = String(value);
|
||||
return `${type[0]}${raw.length}:${raw}`;
|
||||
}
|
||||
if (seen.has(value as object)) return null;
|
||||
seen.add(value as object);
|
||||
try {
|
||||
if (Array.isArray(value)) {
|
||||
const parts: string[] = [];
|
||||
for (const item of value) {
|
||||
const key = canonicalKey(item, seen);
|
||||
if (key === null) return null;
|
||||
parts.push(key);
|
||||
}
|
||||
return `a${parts.length}:[${parts.join(",")}]`;
|
||||
}
|
||||
const keys = Object.keys(value as object).sort();
|
||||
const parts: string[] = [];
|
||||
for (const k of keys) {
|
||||
const key = canonicalKey((value as any)[k], seen);
|
||||
if (key === null) return null;
|
||||
parts.push(`${k.length}:${k}=${key}`);
|
||||
}
|
||||
return `o${parts.length}:{${parts.join(",")}}`;
|
||||
} finally {
|
||||
seen.delete(value as object);
|
||||
}
|
||||
}
|
||||
|
||||
// keywords whose value is a schema or an array of them
|
||||
const SCHEMA_KEYWORDS = /*@__PURE__*/ new Set([
|
||||
"items",
|
||||
"prefixItems",
|
||||
"additionalItems",
|
||||
"additionalProperties",
|
||||
"contains",
|
||||
"propertyNames",
|
||||
"not",
|
||||
"if",
|
||||
"then",
|
||||
"else",
|
||||
"allOf",
|
||||
"anyOf",
|
||||
"oneOf",
|
||||
"unevaluatedItems",
|
||||
"unevaluatedProperties",
|
||||
"contentSchema",
|
||||
]);
|
||||
|
||||
// keywords whose value maps names to schemas. draft-7 `dependencies` also allows an array of property names, which holds no schema to walk
|
||||
const SCHEMA_MAP_KEYWORDS = /*@__PURE__*/ new Set([
|
||||
"properties",
|
||||
"patternProperties",
|
||||
"dependentSchemas",
|
||||
"dependencies",
|
||||
"$defs",
|
||||
"definitions",
|
||||
]);
|
||||
|
||||
/**
|
||||
* True when a subschema holds a `$ref` anywhere a schema can appear. Only those positions are
|
||||
* walked: `$ref` under `default` or an `x-` annotation is instance data with an ordinary key, and
|
||||
* reading it as a reference would drop a constraint that resolves perfectly well.
|
||||
*/
|
||||
function containsRef(value: unknown): boolean {
|
||||
if (typeof value !== "object" || value === null) return false;
|
||||
if (Array.isArray(value)) return value.some(containsRef);
|
||||
if (typeof (value as any).$ref === "string") return true;
|
||||
return Object.entries(value).some(([key, sub]) => {
|
||||
if (SCHEMA_KEYWORDS.has(key)) return containsRef(sub);
|
||||
if (!SCHEMA_MAP_KEYWORDS.has(key) || typeof sub !== "object" || sub === null) return false;
|
||||
return Object.values(sub).some(containsRef);
|
||||
});
|
||||
}
|
||||
|
||||
function plural(n: number): string {
|
||||
return n === 1 ? "element" : "elements";
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces `uniqueItems` and the `contains` family before `arraySchema` runs, for the same reason
|
||||
* `checkObjectGuards` runs pre-pipe: an item parse can apply a nested `default`, so the parsed
|
||||
* array is not the instance the keywords are defined over. A guard issue aborts the pipe, so
|
||||
* `minItems`/`maxItems` are not also reported on the same parse — instance correctness is worth
|
||||
* more than co-reporting two issues.
|
||||
*/
|
||||
function checkArrayGuards(
|
||||
arraySchema: ZodType,
|
||||
guards: {
|
||||
uniqueItems?: boolean | undefined;
|
||||
containsSchema?: ZodType | undefined;
|
||||
minContains?: number | undefined;
|
||||
maxContains?: number | undefined;
|
||||
}
|
||||
): ZodType {
|
||||
// An identity transform, not z.any(), so `toJSONSchema` reports the array on both the input and the output side of the pipe.
|
||||
const guard = z
|
||||
.transform((value: unknown) => value)
|
||||
.check((payload) => {
|
||||
const items = payload.value;
|
||||
if (!Array.isArray(items)) return;
|
||||
if (guards.uniqueItems === true) {
|
||||
const firstSeen = new Map<string, number>();
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const key = canonicalKey(items[i], new Set());
|
||||
if (key === null) continue;
|
||||
const first = firstSeen.get(key);
|
||||
if (first === undefined) {
|
||||
firstSeen.set(key, i);
|
||||
continue;
|
||||
}
|
||||
payload.issues.push({
|
||||
code: "custom",
|
||||
message: `Array items must be unique: element at index ${i} duplicates the one at index ${first}`,
|
||||
input: items,
|
||||
path: [i],
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (guards.containsSchema) {
|
||||
const minContains = guards.minContains ?? 1;
|
||||
// one past the ceiling already proves the failure, and nothing above it changes the verdict
|
||||
const ceiling = guards.maxContains !== undefined ? guards.maxContains + 1 : Number.POSITIVE_INFINITY;
|
||||
let matches = 0;
|
||||
for (const item of items) {
|
||||
if (guards.containsSchema.safeParse(item).success && ++matches >= ceiling) break;
|
||||
}
|
||||
if (matches < minContains) {
|
||||
// the scan ran to the end, so this count is exact
|
||||
payload.issues.push({
|
||||
code: "custom",
|
||||
message: `Array must contain at least ${minContains} matching ${plural(minContains)}; found ${matches}`,
|
||||
input: items,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
if (guards.maxContains !== undefined && matches > guards.maxContains) {
|
||||
payload.issues.push({
|
||||
code: "custom",
|
||||
message: `Array must contain at most ${guards.maxContains} matching ${plural(guards.maxContains)}`,
|
||||
input: items,
|
||||
continue: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return guard.pipe(arraySchema);
|
||||
}
|
||||
|
||||
function getTupleRest(restSchema: JSONSchema._JSONSchema | undefined, ctx: ConversionContext): ZodType | undefined {
|
||||
if (restSchema === false) {
|
||||
return undefined;
|
||||
}
|
||||
if (restSchema === undefined || restSchema === true) {
|
||||
return z.any();
|
||||
}
|
||||
return convertSchema(restSchema, ctx);
|
||||
}
|
||||
|
||||
// the RFC 3339 full-time keyword, narrower than `z.iso.time()`; local because sharing it via `timeSource` pins that builder into every bundle (+139 B gzipped on a mini `z.boolean()`)
|
||||
const fullTime = /^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
|
||||
|
||||
function convertBaseSchema(schema: JSONSchema.JSONSchema, ctx: ConversionContext): ZodType {
|
||||
// Handle unsupported features
|
||||
if (schema.not !== undefined) {
|
||||
// Special case: { not: {} } represents never
|
||||
if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
|
||||
return z.never();
|
||||
}
|
||||
throw new Error("not is not supported in Zod (except { not: {} } for never)");
|
||||
}
|
||||
if (schema.unevaluatedItems !== undefined) {
|
||||
throw new Error("unevaluatedItems is not supported");
|
||||
}
|
||||
if (schema.unevaluatedProperties !== undefined) {
|
||||
throw new Error("unevaluatedProperties is not supported");
|
||||
}
|
||||
if (schema.if !== undefined || schema.then !== undefined || schema.else !== undefined) {
|
||||
throw new Error("Conditional schemas (if/then/else) are not supported");
|
||||
}
|
||||
if (schema.dependentSchemas !== undefined || schema.dependentRequired !== undefined) {
|
||||
throw new Error("dependentSchemas and dependentRequired are not supported");
|
||||
}
|
||||
|
||||
// Handle $ref
|
||||
if (schema.$ref) {
|
||||
const refPath = schema.$ref;
|
||||
if (ctx.refs.has(refPath)) {
|
||||
return ctx.refs.get(refPath)!;
|
||||
}
|
||||
|
||||
if (ctx.processing.has(refPath)) {
|
||||
// Circular reference - use lazy
|
||||
return z.lazy(() => {
|
||||
if (!ctx.refs.has(refPath)) {
|
||||
throw new Error(`Circular reference not resolved: ${refPath}`);
|
||||
}
|
||||
return ctx.refs.get(refPath)!;
|
||||
});
|
||||
}
|
||||
|
||||
ctx.processing.add(refPath);
|
||||
const resolved = resolveRef(refPath, ctx);
|
||||
const zodSchema = convertSchema(resolved, ctx);
|
||||
ctx.refs.set(refPath, zodSchema);
|
||||
ctx.processing.delete(refPath);
|
||||
return zodSchema;
|
||||
}
|
||||
|
||||
// Handle enum
|
||||
if (schema.enum !== undefined) {
|
||||
const enumValues = schema.enum;
|
||||
|
||||
// Special case: OpenAPI 3.0 null representation { type: "string", nullable: true, enum: [null] }
|
||||
if (
|
||||
ctx.version === "openapi-3.0" &&
|
||||
schema.nullable === true &&
|
||||
enumValues.length === 1 &&
|
||||
enumValues[0] === null
|
||||
) {
|
||||
return z.null();
|
||||
}
|
||||
|
||||
if (enumValues.length === 0) {
|
||||
return z.never();
|
||||
}
|
||||
if (enumValues.length === 1) {
|
||||
return z.literal(enumValues[0]!);
|
||||
}
|
||||
// Check if all values are strings
|
||||
if (enumValues.every((v) => typeof v === "string")) {
|
||||
return z.enum(enumValues as [string, ...string[]]);
|
||||
}
|
||||
// Mixed types - use union of literals
|
||||
const literalSchemas = enumValues.map((v) => z.literal(v));
|
||||
if (literalSchemas.length < 2) {
|
||||
return literalSchemas[0]!;
|
||||
}
|
||||
return z.union([literalSchemas[0]!, literalSchemas[1]!, ...literalSchemas.slice(2)] as [
|
||||
ZodType,
|
||||
ZodType,
|
||||
...ZodType[],
|
||||
]);
|
||||
}
|
||||
|
||||
// Handle const
|
||||
if (schema.const !== undefined) {
|
||||
return z.literal(schema.const);
|
||||
}
|
||||
|
||||
// Handle type
|
||||
const type = schema.type;
|
||||
|
||||
if (Array.isArray(type)) {
|
||||
// Expand type array into anyOf union
|
||||
const typeSchemas = type.map((t) => {
|
||||
const typeSchema: JSONSchema.JSONSchema = { ...schema, type: t };
|
||||
return convertBaseSchema(typeSchema, ctx);
|
||||
});
|
||||
if (typeSchemas.length === 0) {
|
||||
return z.never();
|
||||
}
|
||||
if (typeSchemas.length === 1) {
|
||||
return typeSchemas[0]!;
|
||||
}
|
||||
return z.union(typeSchemas as [ZodType, ZodType, ...ZodType[]]);
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
// No type specified - empty schema (any)
|
||||
return z.any();
|
||||
}
|
||||
|
||||
let zodSchema: ZodType;
|
||||
|
||||
switch (type) {
|
||||
case "string": {
|
||||
let stringSchema: ZodString = z.string();
|
||||
|
||||
// Apply format using .check() with Zod format functions
|
||||
if (schema.format) {
|
||||
const format = schema.format;
|
||||
// Map common formats to Zod check functions
|
||||
if (format === "email") {
|
||||
stringSchema = stringSchema.check(z.email());
|
||||
} else if (format === "uri" || format === "uri-reference") {
|
||||
stringSchema = stringSchema.check(z.url());
|
||||
} else if (format === "uuid" || format === "guid") {
|
||||
stringSchema = stringSchema.check(z.uuid());
|
||||
} else if (format === "date-time") {
|
||||
stringSchema = stringSchema.check(z.iso.datetime({ offset: true }));
|
||||
} else if (format === "date") {
|
||||
stringSchema = stringSchema.check(z.iso.date());
|
||||
} else if (format === "time") {
|
||||
stringSchema = stringSchema.check(z.regex(fullTime));
|
||||
} else if (format === "duration") {
|
||||
stringSchema = stringSchema.check(z.iso.duration());
|
||||
} else if (format === "hostname") {
|
||||
stringSchema = stringSchema.check(z.hostname());
|
||||
} else if (format === "ipv4") {
|
||||
stringSchema = stringSchema.check(z.ipv4());
|
||||
} else if (format === "ipv6") {
|
||||
stringSchema = stringSchema.check(z.ipv6());
|
||||
} else if (format === "mac") {
|
||||
stringSchema = stringSchema.check(z.mac());
|
||||
} else if (format === "cidr") {
|
||||
stringSchema = stringSchema.check(z.cidrv4());
|
||||
} else if (format === "cidr-v6") {
|
||||
stringSchema = stringSchema.check(z.cidrv6());
|
||||
} else if (format === "base64") {
|
||||
stringSchema = stringSchema.check(z.base64());
|
||||
} else if (format === "base64url") {
|
||||
stringSchema = stringSchema.check(z.base64url());
|
||||
} else if (format === "e164") {
|
||||
stringSchema = stringSchema.check(z.e164());
|
||||
} else if (format === "credit_card") {
|
||||
stringSchema = stringSchema.check(z.creditCard());
|
||||
} else if (format === "iban") {
|
||||
stringSchema = stringSchema.check(z.iban());
|
||||
} else if (format === "jwt") {
|
||||
stringSchema = stringSchema.check(z.jwt());
|
||||
} else if (format === "emoji") {
|
||||
stringSchema = stringSchema.check(z.emoji());
|
||||
} else if (format === "nanoid") {
|
||||
stringSchema = stringSchema.check(z.nanoid());
|
||||
} else if (format === "cuid") {
|
||||
stringSchema = stringSchema.check(z.cuid());
|
||||
} else if (format === "cuid2") {
|
||||
stringSchema = stringSchema.check(z.cuid2());
|
||||
} else if (format === "ulid") {
|
||||
stringSchema = stringSchema.check(z.ulid());
|
||||
} else if (format === "xid") {
|
||||
stringSchema = stringSchema.check(z.xid());
|
||||
} else if (format === "ksuid") {
|
||||
stringSchema = stringSchema.check(z.ksuid());
|
||||
}
|
||||
// Note: json-string format is not currently supported by Zod
|
||||
// Custom formats are ignored - keep as plain string
|
||||
}
|
||||
|
||||
// Apply constraints
|
||||
if (typeof schema.minLength === "number") {
|
||||
stringSchema = stringSchema.min(schema.minLength);
|
||||
}
|
||||
if (typeof schema.maxLength === "number") {
|
||||
stringSchema = stringSchema.max(schema.maxLength);
|
||||
}
|
||||
if (schema.pattern) {
|
||||
// JSON Schema patterns are not implicitly anchored (match anywhere in string)
|
||||
stringSchema = stringSchema.regex(new RegExp(schema.pattern));
|
||||
}
|
||||
|
||||
zodSchema = stringSchema;
|
||||
break;
|
||||
}
|
||||
|
||||
case "number":
|
||||
case "integer": {
|
||||
let numberSchema: ZodNumber = type === "integer" ? z.number().int() : z.number();
|
||||
|
||||
// Apply constraints
|
||||
|
||||
// In draft-04, `exclusiveMinimum: true` makes the sibling `minimum` exclusive rather than an independent bound, so the inclusive `.min()` is skipped; emitting it too would be dominated by the exclusive check and report a second, weaker issue.
|
||||
if (typeof schema.minimum === "number" && schema.exclusiveMinimum !== true) {
|
||||
numberSchema = numberSchema.min(schema.minimum);
|
||||
}
|
||||
if (typeof schema.maximum === "number" && schema.exclusiveMaximum !== true) {
|
||||
numberSchema = numberSchema.max(schema.maximum);
|
||||
}
|
||||
if (typeof schema.exclusiveMinimum === "number") {
|
||||
numberSchema = numberSchema.gt(schema.exclusiveMinimum);
|
||||
} else if (schema.exclusiveMinimum === true && typeof schema.minimum === "number") {
|
||||
numberSchema = numberSchema.gt(schema.minimum);
|
||||
}
|
||||
if (typeof schema.exclusiveMaximum === "number") {
|
||||
numberSchema = numberSchema.lt(schema.exclusiveMaximum);
|
||||
} else if (schema.exclusiveMaximum === true && typeof schema.maximum === "number") {
|
||||
numberSchema = numberSchema.lt(schema.maximum);
|
||||
}
|
||||
if (typeof schema.multipleOf === "number") {
|
||||
numberSchema = numberSchema.multipleOf(schema.multipleOf);
|
||||
}
|
||||
|
||||
zodSchema = numberSchema;
|
||||
break;
|
||||
}
|
||||
|
||||
case "boolean": {
|
||||
zodSchema = z.boolean();
|
||||
break;
|
||||
}
|
||||
|
||||
case "null": {
|
||||
zodSchema = z.null();
|
||||
break;
|
||||
}
|
||||
|
||||
case "object": {
|
||||
const shape: Record<string, ZodType> = {};
|
||||
const properties = schema.properties || {};
|
||||
const requiredSet = new Set(schema.required || []);
|
||||
|
||||
const additionalSchema =
|
||||
typeof schema.additionalProperties === "object"
|
||||
? convertSchema(schema.additionalProperties as JSONSchema.JSONSchema, ctx)
|
||||
: undefined;
|
||||
|
||||
// Convert properties - mark optional ones
|
||||
for (const [key, propSchema] of Object.entries(properties)) {
|
||||
const propZodSchema = convertSchema(propSchema as JSONSchema.JSONSchema, ctx);
|
||||
// If not in required array, make it optional. assignProp so a __proto__ key becomes an own property instead of hitting the inherited setter
|
||||
assignProp(shape, key, requiredSet.has(key) ? propZodSchema : propZodSchema.optional());
|
||||
}
|
||||
|
||||
// Handle patternProperties
|
||||
if (schema.patternProperties) {
|
||||
// patternProperties: keys matching pattern must satisfy corresponding schema. Use loose records so non-matching keys pass through
|
||||
const patternProps = schema.patternProperties;
|
||||
const patternKeys = Object.keys(patternProps);
|
||||
const looseRecords: ZodType[] = [];
|
||||
|
||||
for (const pattern of patternKeys) {
|
||||
const patternValue = convertSchema(patternProps[pattern] as JSONSchema.JSONSchema, ctx);
|
||||
const keySchema = z.string().regex(new RegExp(pattern));
|
||||
looseRecords.push(z.looseRecord(keySchema, patternValue));
|
||||
}
|
||||
|
||||
// Build intersection: object schema + all pattern property records
|
||||
const schemasToIntersect: ZodType[] = [];
|
||||
if (Object.keys(shape).length > 0) {
|
||||
// Use passthrough so patternProperties can validate additional keys
|
||||
schemasToIntersect.push(z.object(shape).passthrough());
|
||||
}
|
||||
schemasToIntersect.push(...looseRecords);
|
||||
|
||||
if (schemasToIntersect.length === 0) {
|
||||
zodSchema = z.object({}).passthrough();
|
||||
} else if (schemasToIntersect.length === 1) {
|
||||
zodSchema = schemasToIntersect[0]!;
|
||||
} else {
|
||||
// Chain intersections: (A & B) & C & D ...
|
||||
let result = z.intersection(schemasToIntersect[0]!, schemasToIntersect[1]!);
|
||||
for (let i = 2; i < schemasToIntersect.length; i++) {
|
||||
result = z.intersection(result, schemasToIntersect[i]!);
|
||||
}
|
||||
zodSchema = result;
|
||||
}
|
||||
|
||||
// When additionalProperties is false, reject keys that are neither defined in properties nor matched by any patternProperty.
|
||||
if (schema.additionalProperties === false) {
|
||||
const propertyKeys = Object.keys(shape);
|
||||
const patterns = patternKeys.map((p) => new RegExp(p));
|
||||
const basePatternSchema = zodSchema;
|
||||
zodSchema = zodSchema.check((payload) => {
|
||||
if (!isPlainObject(payload.value)) return;
|
||||
const unrecognized: string[] = [];
|
||||
for (const key of Object.keys(payload.value)) {
|
||||
if (propertyKeys.includes(key)) continue;
|
||||
if (patterns.some((regex) => regex.test(key))) continue;
|
||||
unrecognized.push(key);
|
||||
}
|
||||
if (unrecognized.length) {
|
||||
payload.issues.push({
|
||||
code: "unrecognized_keys",
|
||||
keys: unrecognized,
|
||||
input: payload.value,
|
||||
inst: basePatternSchema,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Handle additionalProperties. In JSON Schema, additionalProperties defaults to true (allow any extra properties). In Zod, objects strip unknown keys by default, so we need to handle this explicitly
|
||||
const objectSchema = z.object(shape);
|
||||
if (schema.additionalProperties === false) {
|
||||
// Strict mode - no extra properties allowed
|
||||
zodSchema = objectSchema.strict();
|
||||
} else if (additionalSchema) {
|
||||
// Extra properties must match the specified schema
|
||||
zodSchema = objectSchema.catchall(additionalSchema);
|
||||
} else {
|
||||
// additionalProperties is true or undefined - allow any extra properties (passthrough)
|
||||
zodSchema = objectSchema.passthrough();
|
||||
}
|
||||
}
|
||||
|
||||
// propertyNames constrains key *names* only, and says nothing about which keys are required or how their values validate. Layering it on top of the result keeps properties/patternProperties/additionalProperties composing underneath. `true` allows every name, so it needs no guard.
|
||||
const hasKeyGuard = schema.propertyNames !== undefined && schema.propertyNames !== true;
|
||||
const minProperties = typeof schema.minProperties === "number" ? schema.minProperties : undefined;
|
||||
const maxProperties = typeof schema.maxProperties === "number" ? schema.maxProperties : undefined;
|
||||
if (hasKeyGuard || minProperties !== undefined || maxProperties !== undefined) {
|
||||
let keySchema: ZodType | undefined;
|
||||
if (hasKeyGuard) {
|
||||
// Keys are always strings, so a propertyNames subschema that omits `type` still constrains them — without this it would convert to z.any().
|
||||
const keyJSONSchema =
|
||||
typeof schema.propertyNames === "object" && schema.propertyNames.type === undefined
|
||||
? { type: "string", ...schema.propertyNames }
|
||||
: schema.propertyNames;
|
||||
keySchema = convertSchema(keyJSONSchema as JSONSchema.JSONSchema, ctx);
|
||||
}
|
||||
zodSchema = checkObjectGuards(zodSchema, { keySchema, minProperties, maxProperties });
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "array": {
|
||||
// Check if this is a tuple (prefixItems or items as array)
|
||||
const prefixItems = schema.prefixItems;
|
||||
const items = schema.items;
|
||||
|
||||
if (prefixItems && Array.isArray(prefixItems)) {
|
||||
// Tuple with prefixItems (draft-2020-12)
|
||||
const minItems = typeof schema.minItems === "number" ? schema.minItems : 0;
|
||||
const tupleItems = prefixItems.map((item) => convertSchema(item as JSONSchema.JSONSchema, ctx));
|
||||
const positionalItems = applyMinItems(tupleItems, minItems);
|
||||
const rest = !Array.isArray(items) ? getTupleRest(items, ctx) : undefined;
|
||||
const tupleSchema = z.tuple(positionalItems as [ZodType, ...ZodType[]]);
|
||||
zodSchema = rest ? tupleSchema.rest(rest) : tupleSchema;
|
||||
// Apply minItems/maxItems constraints to tuples
|
||||
if (typeof schema.minItems === "number") {
|
||||
zodSchema = zodSchema.check(z.minLength(schema.minItems));
|
||||
}
|
||||
if (typeof schema.maxItems === "number") {
|
||||
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
||||
}
|
||||
} else if (Array.isArray(items)) {
|
||||
// Tuple with items array (draft-7)
|
||||
const minItems = typeof schema.minItems === "number" ? schema.minItems : 0;
|
||||
const tupleItems = items.map((item) => convertSchema(item as JSONSchema.JSONSchema, ctx));
|
||||
const positionalItems = applyMinItems(tupleItems, minItems);
|
||||
const rest = getTupleRest(schema.additionalItems, ctx);
|
||||
const tupleSchema = z.tuple(positionalItems as [ZodType, ...ZodType[]]);
|
||||
zodSchema = rest ? tupleSchema.rest(rest) : tupleSchema;
|
||||
// Apply minItems/maxItems constraints to tuples
|
||||
if (typeof schema.minItems === "number") {
|
||||
zodSchema = zodSchema.check(z.minLength(schema.minItems));
|
||||
}
|
||||
if (typeof schema.maxItems === "number") {
|
||||
zodSchema = zodSchema.check(z.maxLength(schema.maxItems));
|
||||
}
|
||||
} else if (items !== undefined) {
|
||||
// Regular array
|
||||
const element = convertSchema(items as JSONSchema.JSONSchema, ctx);
|
||||
let arraySchema = z.array(element);
|
||||
|
||||
// Apply constraints
|
||||
if (typeof schema.minItems === "number") {
|
||||
arraySchema = arraySchema.min(schema.minItems);
|
||||
}
|
||||
if (typeof schema.maxItems === "number") {
|
||||
arraySchema = arraySchema.max(schema.maxItems);
|
||||
}
|
||||
|
||||
zodSchema = arraySchema;
|
||||
} else {
|
||||
// No items specified - array of any
|
||||
zodSchema = z.array(z.any());
|
||||
}
|
||||
|
||||
// minContains/maxContains only constrain anything when `contains` itself is present
|
||||
if (schema.uniqueItems === true || schema.contains !== undefined) {
|
||||
zodSchema = checkArrayGuards(zodSchema, {
|
||||
uniqueItems: schema.uniqueItems === true,
|
||||
containsSchema:
|
||||
schema.contains !== undefined ? convertSchema(schema.contains as JSONSchema.JSONSchema, ctx) : undefined,
|
||||
minContains: typeof schema.minContains === "number" ? schema.minContains : undefined,
|
||||
maxContains: typeof schema.maxContains === "number" ? schema.maxContains : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported type: ${type}`);
|
||||
}
|
||||
|
||||
return zodSchema;
|
||||
}
|
||||
|
||||
function convertSchema(schema: JSONSchema.JSONSchema | boolean, ctx: ConversionContext): ZodType {
|
||||
if (typeof schema === "boolean") {
|
||||
return schema ? z.any() : z.never();
|
||||
}
|
||||
|
||||
// Convert base schema first (ignoring composition keywords)
|
||||
let baseSchema = convertBaseSchema(schema, ctx);
|
||||
const hasExplicitType = schema.type || schema.enum !== undefined || schema.const !== undefined;
|
||||
|
||||
// Process composition keywords LAST (they can appear together)
|
||||
|
||||
// Handle anyOf - wrap base schema with union
|
||||
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
||||
const options = schema.anyOf.map((s) => convertSchema(s, ctx));
|
||||
const anyOfUnion = z.union(options as [ZodType, ZodType, ...ZodType[]]);
|
||||
baseSchema = hasExplicitType ? z.intersection(baseSchema, anyOfUnion) : anyOfUnion;
|
||||
}
|
||||
|
||||
// Handle oneOf - exclusive union (exactly one must match)
|
||||
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
||||
const options = schema.oneOf.map((s) => convertSchema(s, ctx));
|
||||
const oneOfUnion = z.xor(options as [ZodType, ZodType, ...ZodType[]]);
|
||||
baseSchema = hasExplicitType ? z.intersection(baseSchema, oneOfUnion) : oneOfUnion;
|
||||
}
|
||||
|
||||
// Handle allOf - wrap base schema with intersection
|
||||
if (schema.allOf && Array.isArray(schema.allOf)) {
|
||||
if (schema.allOf.length === 0) {
|
||||
baseSchema = hasExplicitType ? baseSchema : z.any();
|
||||
} else {
|
||||
let result = hasExplicitType ? baseSchema : convertSchema(schema.allOf[0]!, ctx);
|
||||
const startIdx = hasExplicitType ? 0 : 1;
|
||||
for (let i = startIdx; i < schema.allOf.length; i++) {
|
||||
result = z.intersection(result, convertSchema(schema.allOf[i]!, ctx));
|
||||
}
|
||||
baseSchema = result;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle nullable (OpenAPI 3.0)
|
||||
if (schema.nullable === true && ctx.version === "openapi-3.0") {
|
||||
baseSchema = z.nullable(baseSchema);
|
||||
}
|
||||
|
||||
// Handle readOnly
|
||||
if (schema.readOnly === true) {
|
||||
baseSchema = z.readonly(baseSchema);
|
||||
}
|
||||
|
||||
// Apply `default` so it wraps the fully-composed schema. This ensures `parse(undefined) -> default` works regardless of which branch of `convertBaseSchema` produced the inner schema (enum/const/not/typed/etc.).
|
||||
if (schema.default !== undefined) {
|
||||
baseSchema = baseSchema.default(schema.default);
|
||||
}
|
||||
|
||||
// Collect non-description annotation metadata into the user-supplied registry. Description is handled separately below via `.describe()` to preserve the contract that `schema.description` reads from globalRegistry.
|
||||
const extraMeta: Record<string, unknown> = {};
|
||||
|
||||
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
|
||||
for (const key of coreMetadataKeys) {
|
||||
if (key in schema) {
|
||||
extraMeta[key] = schema[key];
|
||||
}
|
||||
}
|
||||
|
||||
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
|
||||
for (const key of contentMetadataKeys) {
|
||||
if (key in schema) {
|
||||
extraMeta[key] = schema[key];
|
||||
}
|
||||
}
|
||||
|
||||
// `propertyNames` and `contains` are enforced by a guard, which `toJSONSchema` cannot infer, so the original keyword is carried as metadata to keep the round-trip lossless. Only where it was actually applied: on any other type it is inert, and on a `$ref` the metadata would land on the target every reference shares. A carried subschema is copied verbatim while the root `$defs` stay behind, so one holding a `$ref` is dropped rather than emitted as a dangling reference.
|
||||
if (schema.type === "object" && schema.$ref === undefined) {
|
||||
if (schema.propertyNames !== undefined && !containsRef(schema.propertyNames)) {
|
||||
extraMeta.propertyNames = schema.propertyNames;
|
||||
}
|
||||
for (const key of ["minProperties", "maxProperties"] as const) {
|
||||
if (schema[key] !== undefined) extraMeta[key] = schema[key];
|
||||
}
|
||||
}
|
||||
if (schema.type === "array" && schema.$ref === undefined) {
|
||||
if (schema.contains !== undefined && !containsRef(schema.contains)) {
|
||||
extraMeta.contains = schema.contains;
|
||||
}
|
||||
for (const key of ["uniqueItems", "minContains", "maxContains"] as const) {
|
||||
if (schema[key] !== undefined) extraMeta[key] = schema[key];
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(schema)) {
|
||||
if (!RECOGNIZED_KEYS.has(key)) {
|
||||
assignProp(extraMeta, key, schema[key]);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(extraMeta).length > 0) {
|
||||
ctx.registry.add(baseSchema, extraMeta);
|
||||
}
|
||||
|
||||
// Apply description last. `.describe()` clones the schema and sets `_zod.parent` on the clone, so registry lookups on the returned reference still resolve `extraMeta` via parent inheritance.
|
||||
if (schema.description) {
|
||||
baseSchema = baseSchema.describe(schema.description);
|
||||
}
|
||||
|
||||
return baseSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a JSON Schema to a Zod schema. This function should be considered semi-experimental. It's behavior is liable to change. */
|
||||
export function fromJSONSchema(schema: JSONSchema.JSONSchema | boolean, params?: FromJSONSchemaParams): ZodType {
|
||||
// Handle boolean schemas
|
||||
if (typeof schema === "boolean") {
|
||||
return schema ? z.any() : z.never();
|
||||
}
|
||||
|
||||
// Normalize input via a JSON round-trip. This guarantees the converter walks a plain, finite, JSON-valid object graph: cyclic inputs fail here, getter/Proxy-based properties are materialized into static values, and class instances collapse to plain objects.
|
||||
let normalized: JSONSchema.JSONSchema;
|
||||
try {
|
||||
normalized = JSON.parse(JSON.stringify(schema));
|
||||
} catch {
|
||||
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
|
||||
}
|
||||
|
||||
const version = detectVersion(normalized, params?.defaultTarget);
|
||||
const defs = (normalized.$defs || normalized.definitions || {}) as Record<string, JSONSchema.JSONSchema>;
|
||||
|
||||
const ctx: ConversionContext = {
|
||||
version,
|
||||
defs,
|
||||
refs: new Map(),
|
||||
processing: new Set(),
|
||||
rootSchema: normalized,
|
||||
registry: params?.registry ?? globalRegistry,
|
||||
};
|
||||
|
||||
return convertSchema(normalized, ctx);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import type * as core from "../core/index.js";
|
||||
import { clone, mergeDefs } from "../core/util.js";
|
||||
import { visit } from "../core/visit.js";
|
||||
import * as schemas from "./schemas.js";
|
||||
|
||||
// Co-located with the functions: a type and a value can only share a name from one module.
|
||||
export type input<T> = core.input<T>;
|
||||
export type output<T> = core.output<T>;
|
||||
|
||||
/** Appends a pipe's own checks to the side that replaces it, mirroring how `.check()` clones. */
|
||||
function withChecks(side: core.$ZodType, checks: core.$ZodTypeDef["checks"]): core.$ZodType {
|
||||
if (!checks?.length) return side;
|
||||
const def = side._zod.def;
|
||||
return clone(side, mergeDefs(def, { checks: [...(def.checks ?? []), ...checks] }), { parent: true });
|
||||
}
|
||||
|
||||
/** The out side, carrying the pipe's own checks: those run against the decoded value, which is what `out` produces. */
|
||||
function outSide(def: core.$ZodPipeDef): core.$ZodType {
|
||||
return withChecks(def.out, def.checks);
|
||||
}
|
||||
|
||||
/** The in side. `z.preprocess` pipes a transform into a schema, and a bare transform validates nothing, so the schema it feeds is the real input side — the resolution `toJSONSchema` already makes for this case. */
|
||||
function inSide(def: core.$ZodPipeDef): core.$ZodType {
|
||||
return def.in._zod.traits.has("$ZodTransform") ? outSide(def) : def.in;
|
||||
}
|
||||
|
||||
/** Returns a copy of the schema with every pipe replaced by its input side. A codec's checks are dropped: they constrain the decoded value the input side never produces. */
|
||||
export function input<T extends core.$ZodType>(schema: T): schemas.ZodType<core.input<T>, core.input<T>> {
|
||||
return visit(schema, {
|
||||
pipe: (s) => inSide(s._zod.def),
|
||||
// A default value belongs to the output side, so a rewritten inner type leaves it stranded. `.default()` widens the declared input type with `undefined`, and `optional` is what carries that across.
|
||||
default: (s, rewritten) => (rewritten ? schemas.optional(s._zod.def.innerType) : s),
|
||||
// A catch value is output-side too, but `.catch()` leaves the declared input type alone, so the inner schema stands on its own.
|
||||
catch: (s, rewritten) => (rewritten ? s._zod.def.innerType : s),
|
||||
}) as schemas.ZodType<core.input<T>, core.input<T>>;
|
||||
}
|
||||
|
||||
/** Returns a copy of the schema with every pipe replaced by its output side, carrying over the pipe's own checks. */
|
||||
export function output<T extends core.$ZodType>(schema: T): schemas.ZodType<core.output<T>, core.output<T>> {
|
||||
return visit(schema, {
|
||||
pipe: (s) => outSide(s._zod.def),
|
||||
// A prefault value is fed through the schema, which makes it input-side, so a rewritten inner type leaves it stranded.
|
||||
prefault: (s, rewritten) => (rewritten ? s._zod.def.innerType : s),
|
||||
}) as schemas.ZodType<core.output<T>, core.output<T>>;
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import * as z from "./external.js";
|
||||
|
||||
export * from "./external.js";
|
||||
// See the note in `src/index.ts` — aliasing keeps the default export shakable.
|
||||
export { z, z as default };
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import * as core from "../core/index.js";
|
||||
import { ZodISODate, ZodISODateTime, ZodISODuration, ZodISOTime } from "./schemas.js";
|
||||
|
||||
export { ZodISODate, ZodISODateTime, ZodISODuration, ZodISOTime } from "./schemas.js";
|
||||
|
||||
export function datetime(params?: string | core.$ZodISODateTimeParams): ZodISODateTime {
|
||||
return core._isoDateTime(ZodISODateTime, params);
|
||||
}
|
||||
|
||||
export function date(params?: string | core.$ZodISODateParams): ZodISODate {
|
||||
return core._isoDate(ZodISODate, params);
|
||||
}
|
||||
|
||||
export function time(params?: string | core.$ZodISOTimeParams): ZodISOTime {
|
||||
return core._isoTime(ZodISOTime, params);
|
||||
}
|
||||
|
||||
export function duration(params?: string | core.$ZodISODurationParams): ZodISODuration {
|
||||
return core._isoDuration(ZodISODuration, params);
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import * as core from "../core/index.js";
|
||||
import { type ZodError, ZodRealError } from "./errors.js";
|
||||
|
||||
export type ZodSafeParseResult<T> = ZodSafeParseSuccess<T> | ZodSafeParseError<T>;
|
||||
export type ZodSafeParseSuccess<T> = { success: true; data: T; error?: never };
|
||||
export type ZodSafeParseError<T> = { success: false; data?: never; error: ZodError<T> };
|
||||
|
||||
export const parse: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: unknown,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>,
|
||||
_params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => core.output<T> = /* @__PURE__ */ core._parse(ZodRealError);
|
||||
|
||||
export const parseAsync: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: unknown,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>,
|
||||
_params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => Promise<core.output<T>> = /* @__PURE__ */ core._parseAsync(ZodRealError);
|
||||
|
||||
export const safeParse: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: unknown,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>
|
||||
// _params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => ZodSafeParseResult<core.output<T>> = /* @__PURE__ */ core._safeParse(ZodRealError) as any;
|
||||
|
||||
export const safeParseAsync: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: unknown,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>
|
||||
) => Promise<ZodSafeParseResult<core.output<T>>> = /* @__PURE__ */ core._safeParseAsync(ZodRealError) as any;
|
||||
|
||||
export { validate, validateAsync } from "../core/index.js";
|
||||
|
||||
// Codec functions
|
||||
export const encode: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.output<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>,
|
||||
_params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => core.input<T> = /* @__PURE__ */ core._encode(ZodRealError);
|
||||
|
||||
export const decode: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.input<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>,
|
||||
_params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => core.output<T> = /* @__PURE__ */ core._decode(ZodRealError);
|
||||
|
||||
export const encodeAsync: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.output<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>,
|
||||
_params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => Promise<core.input<T>> = /* @__PURE__ */ core._encodeAsync(ZodRealError);
|
||||
|
||||
export const decodeAsync: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.input<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>,
|
||||
_params?: { callee?: core.util.AnyFunc; Err?: core.$ZodErrorClass }
|
||||
) => Promise<core.output<T>> = /* @__PURE__ */ core._decodeAsync(ZodRealError);
|
||||
|
||||
export const safeEncode: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.output<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>
|
||||
) => ZodSafeParseResult<core.input<T>> = /* @__PURE__ */ core._safeEncode(ZodRealError) as any;
|
||||
|
||||
export const safeDecode: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.input<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>
|
||||
) => ZodSafeParseResult<core.output<T>> = /* @__PURE__ */ core._safeDecode(ZodRealError) as any;
|
||||
|
||||
export const safeEncodeAsync: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.output<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>
|
||||
) => Promise<ZodSafeParseResult<core.input<T>>> = /* @__PURE__ */ core._safeEncodeAsync(ZodRealError) as any;
|
||||
|
||||
export const safeDecodeAsync: <T extends core.$ZodType>(
|
||||
schema: T,
|
||||
value: core.input<T>,
|
||||
_ctx?: core.ParseContext<core.$ZodIssue>
|
||||
) => Promise<ZodSafeParseResult<core.output<T>>> = /* @__PURE__ */ core._safeDecodeAsync(ZodRealError) as any;
|
||||
+2937
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user