Update code

This commit is contained in:
User
2026-03-12 12:47:56 +08:00
parent 92e7fc5bda
commit 9dab61345c
9383 changed files with 1463454 additions and 1 deletions

View File

@@ -0,0 +1,32 @@
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,
_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";

View File

@@ -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>;
}

View File

@@ -0,0 +1,70 @@
// Zod 3 compat layer
import * as core from "../core/index.js";
import type { ZodType } from "./schemas.js";
export type {
/** @deprecated Use `z.output<T>` instead. */
output as TypeOf,
/** @deprecated Use `z.output<T>` instead. */
output as Infer,
/** @deprecated Use `z.core.$$ZodFirstPartyTypes` instead */
$ZodTypes as ZodFirstPartySchemaTypes,
} from "../core/index.js";
/** @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.$ZodFlattenedError` */
export type inferFlattenedErrors<T extends core.$ZodType, U = string> = core.$ZodFlattenedError<core.output<T>, U>;
/** @deprecated Use `z.$ZodFormattedError` */
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;
}
export type {
/** @deprecated Use z.ZodType (without generics) instead. */
ZodType as ZodTypeAny,
/** @deprecated Use `z.ZodType` */
ZodType as ZodSchema,
/** @deprecated Use `z.ZodType` */
ZodType as Schema,
};
/** 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 {}

View File

@@ -0,0 +1,82 @@
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;
}
const initializer = (inst: ZodError, issues: core.$ZodIssue[]) => {
$ZodError.init(inst, issues);
inst.name = "ZodError";
Object.defineProperties(inst, {
format: {
value: (mapper: any) => core.formatError(inst, mapper),
// enumerable: false,
},
flatten: {
value: (mapper: any) => core.flattenError(inst, mapper),
// enumerable: false,
},
addIssue: {
value: (issue: any) => {
inst.issues.push(issue);
inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
},
// enumerable: false,
},
addIssues: {
value: (issues: any) => {
inst.issues.push(...issues);
inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
},
// enumerable: false,
},
isEmpty: {
get() {
return inst.issues.length === 0;
},
// enumerable: false,
},
});
// Object.defineProperty(inst, "isEmpty", {
// get() {
// return inst.issues.length === 0;
// },
// });
};
export const ZodError: core.$constructor<ZodError> = core.$constructor("ZodError", initializer);
export const ZodRealError: core.$constructor<ZodError> = core.$constructor("ZodError", initializer, {
Parent: Error,
});
export type {
/** @deprecated Use `z.core.$ZodFlattenedError` instead. */
$ZodFlattenedError as ZodFlattenedError,
/** @deprecated Use `z.core.$ZodFormattedError` instead. */
$ZodFormattedError as ZodFormattedError,
/** @deprecated Use `z.core.$ZodErrorMap` instead. */
$ZodErrorMap as ZodErrorMap,
} from "../core/index.js";
/** @deprecated Use `z.core.$ZodRawIssue` instead. */
export type IssueData = core.$ZodRawIssue;
// /** @deprecated Use `z.core.$ZodErrorMapCtx` instead. */
// export type ErrorMapCtx = core.$ZodErrorMapCtx;

View File

@@ -0,0 +1,51 @@
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";
// zod-specified
import { config } from "../core/index.js";
import en from "../locales/en.js";
config(en());
export type { infer, output, input } from "../core/index.js";
export {
globalRegistry,
type GlobalMeta,
registry,
config,
$output,
$input,
$brand,
clone,
regexes,
treeifyError,
prettifyError,
formatError,
flattenError,
TimePrecision,
util,
NEVER,
} from "../core/index.js";
export { toJSONSchema } from "../core/json-schema-processors.js";
export { fromJSONSchema } from "./from-json-schema.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";

View File

@@ -0,0 +1,643 @@
import type * as JSONSchema from "../core/json-schema.js";
import { type $ZodRegistry, globalRegistry } from "../core/registries.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 = 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";
}
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];
if (!key || !ctx.defs[key]) {
throw new Error(`Reference not found: ${ref}`);
}
return ctx.defs[key]!;
}
throw new Error(`Reference not found: ${ref}`);
}
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());
} else if (format === "date") {
stringSchema = stringSchema.check(z.iso.date());
} else if (format === "time") {
stringSchema = stringSchema.check(z.iso.time());
} else if (format === "duration") {
stringSchema = stringSchema.check(z.iso.duration());
} 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 === "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
if (typeof schema.minimum === "number") {
numberSchema = numberSchema.min(schema.minimum);
}
if (typeof schema.maximum === "number") {
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 || []);
// 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
shape[key] = requiredSet.has(key) ? propZodSchema : propZodSchema.optional();
}
// Handle propertyNames
if (schema.propertyNames) {
const keySchema = convertSchema(schema.propertyNames, ctx) as ZodString;
const valueSchema =
schema.additionalProperties && typeof schema.additionalProperties === "object"
? convertSchema(schema.additionalProperties as JSONSchema.JSONSchema, ctx)
: z.any();
// Case A: No properties (pure record)
if (Object.keys(shape).length === 0) {
zodSchema = z.record(keySchema, valueSchema);
break;
}
// Case B: With properties (intersection of object and looseRecord)
const objectSchema = z.object(shape).passthrough();
const recordSchema = z.looseRecord(keySchema, valueSchema);
zodSchema = z.intersection(objectSchema, recordSchema);
break;
}
// 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;
}
break;
}
// 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 (typeof schema.additionalProperties === "object") {
// Extra properties must match the specified schema
zodSchema = objectSchema.catchall(convertSchema(schema.additionalProperties as JSONSchema.JSONSchema, ctx));
} else {
// additionalProperties is true or undefined - allow any extra properties (passthrough)
zodSchema = objectSchema.passthrough();
}
break;
}
case "array": {
// TODO: uniqueItems is not supported
// TODO: contains/minContains/maxContains are not supported
// 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 tupleItems = prefixItems.map((item) => convertSchema(item as JSONSchema.JSONSchema, ctx));
const rest =
items && typeof items === "object" && !Array.isArray(items)
? convertSchema(items as JSONSchema.JSONSchema, ctx)
: undefined;
if (rest) {
zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]).rest(rest);
} else {
zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]);
}
// Apply minItems/maxItems constraints to tuples
if (typeof schema.minItems === "number") {
zodSchema = (zodSchema as any).check(z.minLength(schema.minItems));
}
if (typeof schema.maxItems === "number") {
zodSchema = (zodSchema as any).check(z.maxLength(schema.maxItems));
}
} else if (Array.isArray(items)) {
// Tuple with items array (draft-7)
const tupleItems = items.map((item) => convertSchema(item as JSONSchema.JSONSchema, ctx));
const rest =
schema.additionalItems && typeof schema.additionalItems === "object"
? convertSchema(schema.additionalItems as JSONSchema.JSONSchema, ctx)
: undefined; // additionalItems: false means no rest, handled by default tuple behavior
if (rest) {
zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]).rest(rest);
} else {
zodSchema = z.tuple(tupleItems as [ZodType, ...ZodType[]]);
}
// Apply minItems/maxItems constraints to tuples
if (typeof schema.minItems === "number") {
zodSchema = (zodSchema as any).check(z.minLength(schema.minItems));
}
if (typeof schema.maxItems === "number") {
zodSchema = (zodSchema as any).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 as any).min(schema.minItems);
}
if (typeof schema.maxItems === "number") {
arraySchema = (arraySchema as any).max(schema.maxItems);
}
zodSchema = arraySchema;
} else {
// No items specified - array of any
zodSchema = z.array(z.any());
}
break;
}
default:
throw new Error(`Unsupported type: ${type}`);
}
// Apply metadata
if (schema.description) {
zodSchema = zodSchema.describe(schema.description);
}
if (schema.default !== undefined) {
zodSchema = (zodSchema as any).default(schema.default);
}
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);
}
// Collect metadata: core schema keywords and unrecognized keys
const extraMeta: Record<string, unknown> = {};
// Core schema keywords that should be captured as metadata
const coreMetadataKeys = ["$id", "id", "$comment", "$anchor", "$vocabulary", "$dynamicRef", "$dynamicAnchor"];
for (const key of coreMetadataKeys) {
if (key in schema) {
extraMeta[key] = schema[key];
}
}
// Content keywords - store as metadata
const contentMetadataKeys = ["contentEncoding", "contentMediaType", "contentSchema"];
for (const key of contentMetadataKeys) {
if (key in schema) {
extraMeta[key] = schema[key];
}
}
// Unrecognized keys (custom metadata)
for (const key of Object.keys(schema)) {
if (!RECOGNIZED_KEYS.has(key)) {
extraMeta[key] = schema[key];
}
}
if (Object.keys(extraMeta).length > 0) {
ctx.registry.add(baseSchema, extraMeta);
}
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();
}
const version = detectVersion(schema, params?.defaultTarget);
const defs = (schema.$defs || schema.definitions || {}) as Record<string, JSONSchema.JSONSchema>;
const ctx: ConversionContext = {
version,
defs,
refs: new Map(),
processing: new Set(),
rootSchema: schema,
registry: params?.registry ?? globalRegistry,
};
return convertSchema(schema, ctx);
}

View File

@@ -0,0 +1,5 @@
import * as z from "./external.js";
export { z };
export * from "./external.js";
export default z;

90
mcp-server-ssh/node_modules/zod/src/v4/classic/iso.ts generated vendored Normal file
View File

@@ -0,0 +1,90 @@
import * as core from "../core/index.js";
import * as schemas from "./schemas.js";
//////////////////////////////////////////////
//////////////////////////////////////////////
////////// //////////
////////// ZodISODateTime //////////
////////// //////////
//////////////////////////////////////////////
//////////////////////////////////////////////
export interface ZodISODateTime extends schemas.ZodStringFormat {
_zod: core.$ZodISODateTimeInternals;
}
export const ZodISODateTime: core.$constructor<ZodISODateTime> = /*@__PURE__*/ core.$constructor(
"ZodISODateTime",
(inst, def) => {
core.$ZodISODateTime.init(inst, def);
schemas.ZodStringFormat.init(inst, def);
}
);
export function datetime(params?: string | core.$ZodISODateTimeParams): ZodISODateTime {
return core._isoDateTime(ZodISODateTime, params);
}
//////////////////////////////////////////
//////////////////////////////////////////
////////// //////////
////////// ZodISODate //////////
////////// //////////
//////////////////////////////////////////
//////////////////////////////////////////
export interface ZodISODate extends schemas.ZodStringFormat {
_zod: core.$ZodISODateInternals;
}
export const ZodISODate: core.$constructor<ZodISODate> = /*@__PURE__*/ core.$constructor("ZodISODate", (inst, def) => {
core.$ZodISODate.init(inst, def);
schemas.ZodStringFormat.init(inst, def);
});
export function date(params?: string | core.$ZodISODateParams): ZodISODate {
return core._isoDate(ZodISODate, params);
}
// ZodISOTime
//////////////////////////////////////////
//////////////////////////////////////////
////////// //////////
////////// ZodISOTime //////////
////////// //////////
//////////////////////////////////////////
//////////////////////////////////////////
export interface ZodISOTime extends schemas.ZodStringFormat {
_zod: core.$ZodISOTimeInternals;
}
export const ZodISOTime: core.$constructor<ZodISOTime> = /*@__PURE__*/ core.$constructor("ZodISOTime", (inst, def) => {
core.$ZodISOTime.init(inst, def);
schemas.ZodStringFormat.init(inst, def);
});
export function time(params?: string | core.$ZodISOTimeParams): ZodISOTime {
return core._isoTime(ZodISOTime, params);
}
//////////////////////////////////////////////
//////////////////////////////////////////////
////////// //////////
////////// ZodISODuration //////////
////////// //////////
//////////////////////////////////////////////
//////////////////////////////////////////////
export interface ZodISODuration extends schemas.ZodStringFormat {
_zod: core.$ZodISODurationInternals;
}
export const ZodISODuration: core.$constructor<ZodISODuration> = /*@__PURE__*/ core.$constructor(
"ZodISODuration",
(inst, def) => {
core.$ZodISODuration.init(inst, def);
schemas.ZodStringFormat.init(inst, def);
}
);
export function duration(params?: string | core.$ZodISODurationParams): ZodISODuration {
return core._isoDuration(ZodISODuration, params);
}

View File

@@ -0,0 +1,82 @@
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) as any;
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) as any;
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;
// Codec functions
export const encode: <T extends core.$ZodType>(
schema: T,
value: core.output<T>,
_ctx?: core.ParseContext<core.$ZodIssue>
) => core.input<T> = /* @__PURE__ */ core._encode(ZodRealError) as any;
export const decode: <T extends core.$ZodType>(
schema: T,
value: core.input<T>,
_ctx?: core.ParseContext<core.$ZodIssue>
) => core.output<T> = /* @__PURE__ */ core._decode(ZodRealError) as any;
export const encodeAsync: <T extends core.$ZodType>(
schema: T,
value: core.output<T>,
_ctx?: core.ParseContext<core.$ZodIssue>
) => Promise<core.input<T>> = /* @__PURE__ */ core._encodeAsync(ZodRealError) as any;
export const decodeAsync: <T extends core.$ZodType>(
schema: T,
value: core.input<T>,
_ctx?: core.ParseContext<core.$ZodIssue>
) => Promise<core.output<T>> = /* @__PURE__ */ core._decodeAsync(ZodRealError) as any;
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;

2409
mcp-server-ssh/node_modules/zod/src/v4/classic/schemas.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("check any inference", () => {
const t1 = z.any();
t1.optional();
t1.nullable();
type t1 = z.infer<typeof t1>;
expectTypeOf<t1>().toEqualTypeOf<any>();
});
test("check unknown inference", () => {
const t1 = z.unknown();
t1.optional();
t1.nullable();
type t1 = z.infer<typeof t1>;
expectTypeOf<t1>().toEqualTypeOf<unknown>();
});
test("check never inference", () => {
const t1 = z.never();
expect(() => t1.parse(undefined)).toThrow();
expect(() => t1.parse("asdf")).toThrow();
expect(() => t1.parse(null)).toThrow();
});

View File

@@ -0,0 +1,59 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("basic apply (object)", () => {
const schema = z
.object({
a: z.number(),
b: z.string(),
})
.apply((s) => s.omit({ b: true }))
.apply((s) => s.extend({ c: z.boolean() }));
expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"a": {
"type": "number",
},
"c": {
"type": "boolean",
},
},
"required": [
"a",
"c",
],
"type": "object",
}
`);
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<{
a: number;
c: boolean;
}>();
});
test("basic apply (number)", () => {
const setCommonNumberChecks = <T extends z.ZodNumber>(schema: T) => {
return schema.min(0).max(100);
};
const schema = z.number().apply(setCommonNumberChecks).nullable();
expect(() => schema.parse(-1)).toThrowError();
expect(() => schema.parse(101)).toThrowError();
expect(schema.parse(0)).toBe(0);
expect(schema.parse(null)).toBe(null);
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<number | null>();
});
test("The callback's return value becomes the apply's return value.", () => {
const symbol = Symbol();
const result = z.number().apply(() => symbol);
expect(result).toBe(symbol);
expectTypeOf<typeof result>().toEqualTypeOf<symbol>();
});

View File

@@ -0,0 +1,264 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("type inference", () => {
const schema = z.string().array();
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<string[]>();
});
test("array min/max", () => {
const schema = z.array(z.string()).min(2).max(2);
const r1 = schema.safeParse(["asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected array to have >=2 items",
"minimum": 2,
"origin": "array",
"path": [],
},
]
`);
const r2 = schema.safeParse(["asdf", "asdf", "asdf"]);
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 2,
"message": "Too big: expected array to have <=2 items",
"origin": "array",
"path": [],
},
]
`);
});
test("array length", () => {
const schema = z.array(z.string()).length(2);
schema.parse(["asdf", "asdf"]);
const r1 = schema.safeParse(["asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"exact": true,
"inclusive": true,
"message": "Too small: expected array to have >=2 items",
"minimum": 2,
"origin": "array",
"path": [],
},
]
`);
const r2 = schema.safeParse(["asdf", "asdf", "asdf"]);
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"exact": true,
"inclusive": true,
"maximum": 2,
"message": "Too big: expected array to have <=2 items",
"origin": "array",
"path": [],
},
]
`);
});
test("array.nonempty()", () => {
const schema = z.string().array().nonempty();
schema.parse(["a"]);
expect(() => schema.parse([])).toThrow();
});
test("array.nonempty().max()", () => {
const schema = z.string().array().nonempty().max(2);
schema.parse(["a"]);
expect(() => schema.parse([])).toThrow();
expect(() => schema.parse(["a", "a", "a"])).toThrow();
});
test("parse empty array in nonempty", () => {
expect(() =>
z
.array(z.string())
.nonempty()
.parse([] as any)
).toThrow();
});
test("get element", () => {
const schema = z.string().array();
schema.element.parse("asdf");
expect(() => schema.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).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"people",
0
],
"message": "Invalid input: expected string, received number"
},
{
"origin": "array",
"code": "too_small",
"minimum": 2,
"inclusive": true,
"path": [
"people"
],
"message": "Too small: expected array to have >=2 items"
}
]],
"success": false,
}
`);
});
test("parse should fail given sparse array", () => {
const schema = z.array(z.string()).nonempty().min(1).max(3);
const result = schema.safeParse(new Array(3));
expect(result.success).toEqual(false);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
0
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
1
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
2
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
// const unique = z.string().array().unique();
// const uniqueArrayOfObjects = z.array(z.object({ name: z.string() })).unique({ identifier: (item) => item.name });
// test("passing unique validation", () => {
// unique.parse(["a", "b", "c"]);
// uniqueArrayOfObjects.parse([{ name: "Leo" }, { name: "Joe" }]);
// });
// test("failing unique validation", () => {
// expect(() => unique.parse(["a", "a", "b"])).toThrow();
// expect(() => uniqueArrayOfObjects.parse([{ name: "Leo" }, { name: "Leo" }])).toThrow();
// });
// test("continue parsing despite array of primitives uniqueness error", () => {
// const schema = z.number().array().unique();
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Values must be unique");
// }
// });
// test("continue parsing despite array of objects not_unique error", () => {
// const schema = z.array(z.object({ name: z.string() })).unique({
// identifier: (item) => item.name,
// showDuplicates: true,
// });
// const result = schema.safeParse([
// { name: "Leo" },
// { name: "Joe" },
// { name: "Leo" },
// ]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Element(s): 'Leo' not unique");
// }
// });
// test("returns custom error message without duplicate elements", () => {
// const schema = z.number().array().unique({ message: "Custom message" });
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Custom message");
// }
// });
// test("returns error message with duplicate elements", () => {
// const schema = z.number().array().unique({ showDuplicates: true });
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Element(s): '1,2' not unique");
// }
// });
// test("returns custom error message with duplicate elements", () => {
// const schema = z
// .number()
// .array()
// .unique({
// message: (item) => `Custom message: '${item}' are not unique`,
// showDuplicates: true,
// });
// const result = schema.safeParse([1, 1, 2, 2, 3]);
// expect(result.success).toEqual(false);
// if (!result.success) {
// const issue = result.error.issues.find(({ code }) => code === "not_unique");
// expect(issue?.message).toEqual("Custom message: '1,2' are not unique");
// }
// });

View File

@@ -0,0 +1,210 @@
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("assignability", () => {
// $ZodString
z.string() satisfies z.core.$ZodString;
z.string() satisfies z.ZodString;
// $ZodNumber
z.number() satisfies z.core.$ZodNumber;
z.number() satisfies z.ZodNumber;
// $ZodBigInt
z.bigint() satisfies z.core.$ZodBigInt;
z.bigint() satisfies z.ZodBigInt;
// $ZodBoolean
z.boolean() satisfies z.core.$ZodBoolean;
z.boolean() satisfies z.ZodBoolean;
// $ZodDate
z.date() satisfies z.core.$ZodDate;
z.date() satisfies z.ZodDate;
// $ZodSymbol
z.symbol() satisfies z.core.$ZodSymbol;
z.symbol() satisfies z.ZodSymbol;
// $ZodUndefined
z.undefined() satisfies z.core.$ZodUndefined;
z.undefined() satisfies z.ZodUndefined;
// $ZodNullable
z.string().nullable() satisfies z.core.$ZodNullable;
z.string().nullable() satisfies z.ZodNullable;
// $ZodNull
z.null() satisfies z.core.$ZodNull;
z.null() satisfies z.ZodNull;
// $ZodAny
z.any() satisfies z.core.$ZodAny;
z.any() satisfies z.ZodAny;
// $ZodUnknown
z.unknown() satisfies z.core.$ZodUnknown;
z.unknown() satisfies z.ZodUnknown;
// $ZodNever
z.never() satisfies z.core.$ZodNever;
z.never() satisfies z.ZodNever;
// $ZodVoid
z.void() satisfies z.core.$ZodVoid;
z.void() satisfies z.ZodVoid;
// $ZodArray
z.array(z.string()) satisfies z.core.$ZodArray;
z.array(z.string()) satisfies z.ZodArray;
z.array(z.string()) satisfies z.ZodType<Array<unknown>>;
// $ZodObject
z.object({ key: z.string() }) satisfies z.core.$ZodObject;
z.object({ key: z.string() }) satisfies z.ZodObject<{ key: z.ZodType }>;
z.object({ key: z.string() }) satisfies z.ZodType<{ key: string }>;
// $ZodUnion
z.union([z.string(), z.number()]) satisfies z.core.$ZodUnion;
z.union([z.string(), z.number()]) satisfies z.ZodUnion;
z.union([z.string(), z.number()]) satisfies z.ZodType<string | number>;
// $ZodIntersection
z.intersection(z.string(), z.number()) satisfies z.core.$ZodIntersection;
z.intersection(z.string(), z.number()) satisfies z.ZodIntersection;
// $ZodTuple
z.tuple([z.string(), z.number()]) satisfies z.core.$ZodTuple;
z.tuple([z.string(), z.number()]) satisfies z.ZodTuple;
// $ZodRecord
z.record(z.string(), z.number()) satisfies z.core.$ZodRecord;
z.record(z.string(), z.number()) satisfies z.ZodRecord;
// $ZodMap
z.map(z.string(), z.number()) satisfies z.core.$ZodMap;
z.map(z.string(), z.number()) satisfies z.ZodMap;
// $ZodSet
z.set(z.string()) satisfies z.core.$ZodSet;
z.set(z.string()) satisfies z.ZodSet;
// $ZodLiteral
z.literal("example") satisfies z.core.$ZodLiteral;
z.literal("example") satisfies z.ZodLiteral;
// $ZodEnum
z.enum(["a", "b", "c"]) satisfies z.core.$ZodEnum;
z.enum(["a", "b", "c"]) satisfies z.ZodEnum;
// $ZodPromise
z.promise(z.string()) satisfies z.core.$ZodPromise;
z.promise(z.string()) satisfies z.ZodPromise;
// $ZodLazy
const lazySchema = z.lazy(() => z.string());
lazySchema satisfies z.core.$ZodLazy;
lazySchema satisfies z.ZodLazy;
// $ZodOptional
z.string().optional() satisfies z.core.$ZodOptional;
z.string().optional() satisfies z.ZodOptional;
// $ZodDefault
z.string().default("default") satisfies z.core.$ZodDefault;
z.string().default("default") satisfies z.ZodDefault;
// $ZodTemplateLiteral
z.templateLiteral([z.literal("a"), z.literal("b")]) satisfies z.core.$ZodTemplateLiteral;
z.templateLiteral([z.literal("a"), z.literal("b")]) satisfies z.ZodTemplateLiteral;
// $ZodCustom
z.custom<string>((val) => typeof val === "string") satisfies z.core.$ZodCustom;
z.custom<string>((val) => typeof val === "string") satisfies z.ZodCustom;
// $ZodTransform
z.transform((val) => val as string) satisfies z.core.$ZodTransform;
z.transform((val) => val as string) satisfies z.ZodTransform;
// $ZodNonOptional
z.string().optional().nonoptional() satisfies z.core.$ZodNonOptional;
z.string().optional().nonoptional() satisfies z.ZodNonOptional;
// $ZodReadonly
z.object({ key: z.string() }).readonly() satisfies z.core.$ZodReadonly;
z.object({ key: z.string() }).readonly() satisfies z.ZodReadonly;
// $ZodNaN
z.nan() satisfies z.core.$ZodNaN;
z.nan() satisfies z.ZodNaN;
// $ZodPipe
z.unknown().pipe(z.number()) satisfies z.core.$ZodPipe;
z.unknown().pipe(z.number()) satisfies z.ZodPipe;
// $ZodSuccess
z.success(z.string()) satisfies z.core.$ZodSuccess;
z.success(z.string()) satisfies z.ZodSuccess;
// $ZodCatch
z.string().catch("fallback") satisfies z.core.$ZodCatch;
z.string().catch("fallback") satisfies z.ZodCatch;
// $ZodFile
z.file() satisfies z.core.$ZodFile;
z.file() satisfies z.ZodFile;
});
test("checks", () => {
const _a: z.core.$ZodCheck = {} as any as z.core.$ZodChecks;
const _b: z.core.$ZodCheck = {} as any as z.core.$ZodStringFormatChecks;
const _c: z.core.$ZodType = {} as any as z.core.$ZodTypes;
const _d: z.core.$ZodType = {} as any as z.core.$ZodStringFormatTypes;
});
test("assignability to $ZodType", () => {
z.string() satisfies z.ZodType;
z.number() satisfies z.ZodType;
z.boolean() satisfies z.ZodType;
z.object({ key: z.string() }) satisfies z.ZodType;
z.object({ key: z.string() }) satisfies z.ZodType<{ key: string }>;
z.array(z.string()) satisfies z.ZodType;
z.union([z.string(), z.number()]) satisfies z.ZodType;
z.intersection(z.string(), z.number()) satisfies z.ZodType;
z.tuple([z.string(), z.number()]) satisfies z.ZodType;
z.record(z.string(), z.number()) satisfies z.ZodType;
z.map(z.string(), z.number()) satisfies z.ZodType;
z.set(z.string()) satisfies z.ZodType;
z.literal("example") satisfies z.ZodType;
expectTypeOf<z.ZodType extends z.core.$ZodType ? true : false>().toEqualTypeOf<true>();
});
test("assignability with narrowing", () => {
type _RefinedSchema<T extends z.ZodType<object> | z.ZodUnion> = T extends z.ZodUnion
? RefinedUnionSchema<T> // <-- Type instantiation is excessively deep and possibly infinite.
: T extends z.ZodType<object>
? RefinedTypeSchema<z.output<T>> // <-- Type instantiation is excessively deep and possibly infinite.
: never;
type RefinedTypeSchema<T extends object> = T;
type RefinedUnionSchema<T extends z.ZodUnion> = T;
});
test("generic assignability in objects", () => {
interface SortItem<T extends string> {
key: T;
order: string;
}
const createSortItemSchema = <T extends z.ZodType<string>>(sortKeySchema: T) =>
z.object({
key: sortKeySchema,
order: z.string(),
});
<T extends z.ZodType<string>>(sortKeySchema: T, defaultSortBy: SortItem<z.output<T>>[] = []) =>
createSortItemSchema(sortKeySchema).array().default(defaultSortBy);
});

View File

@@ -0,0 +1,381 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
/// 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.string(), 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);
expect(typeof goodResult.data).toEqual("number");
expect(goodResult.data).toEqual(123);
});
test("promise async parse bad", async () => {
const badData = Promise.resolve("XXX");
const badResult = await promiseSchema.safeParseAsync(badData);
expect(badResult.success).toBe(false);
expect(badResult.error).toBeInstanceOf(z.ZodError);
});
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) => {
expect(r1.error!.issues.length).toBe(r2.error!.issues.length);
});
});
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);
await result2.then((result2) => {
expect(result2.error!.issues.length).toBe(result1.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 () =>
new Promise((resolve) => {
setTimeout(() => resolve(false), 500);
})
: () => false
),
}),
});
const testval = { hello: 3, foo: { bar: 4 } };
const result1 = base().safeParse(testval);
const result2 = base(true).safeParseAsync(testval);
await result2.then((result2) => {
expect(result1.error!.issues.length).toBe(result2.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);
// });
});

View File

@@ -0,0 +1,68 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("async refine .parse()", async () => {
// throws ZodAsyncError
const s1 = z.string().refine(async (_val) => true);
expect(() => s1.safeParse("asdf")).toThrow();
});
test("async refine", async () => {
const s1 = z.string().refine(async (_val) => true);
const r1 = await s1.parseAsync("asdf");
expect(r1).toEqual("asdf");
const s2 = z.string().refine(async (_val) => false);
const r2 = await s2.safeParseAsync("asdf");
expect(r2.success).toBe(false);
expect(r2).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
test("async refine with Promises", 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("async refine that uses value", async () => {
const schema1 = z.string().refine(async (val) => {
return val.length > 5;
});
const r1 = await schema1.safeParseAsync("asdf");
expect(r1.success).toBe(false);
expect(r1.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
const r2 = await schema1.safeParseAsync("asdf123");
expect(r2.success).toBe(true);
expect(r2.data).toEqual("asdf123");
});

View File

@@ -0,0 +1,7 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("test this binding", () => {
const parse = z.string().parse;
expect(parse("asdf")).toBe("asdf");
});

View File

@@ -0,0 +1,54 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
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));
});

View File

@@ -0,0 +1,106 @@
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("branded types", () => {
const mySchema = z
.object({
name: z.string(),
})
.brand<"superschema">();
// simple branding
type MySchema = z.infer<typeof mySchema>;
expectTypeOf<MySchema>().toEqualTypeOf<{ name: string } & z.$brand<"superschema">>();
const doStuff = (arg: MySchema) => arg;
doStuff(mySchema.parse({ name: "hello there" }));
// inheritance
const extendedSchema = mySchema.brand<"subschema">();
type ExtendedSchema = z.infer<typeof extendedSchema>;
expectTypeOf<ExtendedSchema>().toEqualTypeOf<{ name: string } & z.BRAND<"superschema"> & z.BRAND<"subschema">>();
doStuff(extendedSchema.parse({ name: "hello again" }));
// number branding
const numberSchema = z.number().brand<42>();
type NumberSchema = z.infer<typeof numberSchema>;
expectTypeOf<NumberSchema>().toEqualTypeOf<number & { [z.$brand]: { 42: 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 } }
expectTypeOf<SymbolBrand>().toEqualTypeOf<number & z.BRAND<"sup"> & z.BRAND<MyBrand>>();
// keeping brands out of input types
const age = z.number().brand<"age">();
type Age = z.infer<typeof age>;
type AgeInput = z.input<typeof age>;
expectTypeOf<AgeInput>().not.toEqualTypeOf<Age>();
expectTypeOf<number>().toEqualTypeOf<AgeInput>();
expectTypeOf<number & z.BRAND<"age">>().toEqualTypeOf<Age>();
// @ts-expect-error
doStuff({ name: "hello there!" });
});
test("$branded", () => {
const a = z.string().brand<"a">();
expectTypeOf<typeof a>().toEqualTypeOf<z.core.$ZodBranded<z.ZodString, "a">>();
});
test("branded record", () => {
const recordWithBrandedNumberKeys = z.record(z.string().brand("SomeBrand"), z.number());
type recordWithBrandedNumberKeys = z.infer<typeof recordWithBrandedNumberKeys>;
expectTypeOf<recordWithBrandedNumberKeys>().toEqualTypeOf<Record<string & z.core.$brand<"SomeBrand">, number>>();
});
test("brand direction: out (default)", () => {
const schema = z.string().brand<"A">();
type Input = z.input<typeof schema>;
type Output = z.output<typeof schema>;
// output is branded
expectTypeOf<Output>().toEqualTypeOf<string & z.$brand<"A">>();
// input is NOT branded (default behavior)
expectTypeOf<Input>().toEqualTypeOf<string>();
});
test("brand direction: out (explicit)", () => {
const schema = z.string().brand<"A", "out">();
type Input = z.input<typeof schema>;
type Output = z.output<typeof schema>;
// output is branded
expectTypeOf<Output>().toEqualTypeOf<string & z.$brand<"A">>();
// input is NOT branded
expectTypeOf<Input>().toEqualTypeOf<string>();
});
test("brand direction: in", () => {
const schema = z.string().brand<"A", "in">();
type Input = z.input<typeof schema>;
type Output = z.output<typeof schema>;
// input is branded
expectTypeOf<Input>().toEqualTypeOf<string & z.$brand<"A">>();
// output is NOT branded
expectTypeOf<Output>().toEqualTypeOf<string>();
});
test("brand direction: inout", () => {
const schema = z.string().brand<"A", "inout">();
type Input = z.input<typeof schema>;
type Output = z.output<typeof schema>;
// both are branded
expectTypeOf<Input>().toEqualTypeOf<string & z.$brand<"A">>();
expectTypeOf<Output>().toEqualTypeOf<string & z.$brand<"A">>();
});

View File

@@ -0,0 +1,276 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
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.unwrap()).toBeInstanceOf(z.ZodPipe);
expect(stringWithDefault.unwrap().in).toBeInstanceOf(z.ZodString);
expect(stringWithDefault.unwrap().out).toBeInstanceOf(z.ZodTransform);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
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.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(stringWithDefault.unwrap().unwrap()).toBeInstanceOf(z.ZodString);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string | undefined>();
});
test("optional on catch", () => {
const stringWithDefault = z.string().catch("asdf").optional();
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string | undefined>();
});
test("complex chain example", () => {
const complex = z
.string()
.catch("asdf")
.transform((val) => `${val}!`)
.transform((val) => val.toUpperCase())
.catch("qwer")
.unwrap()
.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").unwrap();
type out = z.output<typeof stringWithRemovedDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("nested", () => {
const inner = z.string().catch("asdf");
const outer = z.object({ inner }).catch({
inner: "asdf",
});
type input = z.input<typeof outer>;
expectTypeOf<input>().toEqualTypeOf<{ inner: string }>();
type out = z.output<typeof outer>;
expectTypeOf<out>().toEqualTypeOf<{ inner: string }>();
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("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).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received object",
"path": [
"string",
],
},
{
"code": "invalid_value",
"message": "Invalid input: expected "a"",
"path": [
"obj",
"sub",
"lit",
],
"values": [
"a",
],
},
{
"code": "invalid_type",
"expected": "boolean",
"message": "Invalid input: expected boolean, received string",
"path": [
"bool",
],
},
]
`);
// expect(issues[0].message).toMatch("string");
// expect(issues[1].message).toMatch("literal");
// expect(issues[2].message).toMatch("boolean");
}
});
test("catch error", () => {
const schema = z.object({
age: z.number(),
name: z.string().catch((ctx) => {
ctx.issues;
// issues = ctx.issues;
return "John Doe";
}),
});
const result = schema.safeParse({
age: null,
name: null,
});
expect(result.success).toEqual(false);
expect(result.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
"age"
],
"message": "Invalid input: expected number, received null"
}
]]
`);
});
test("ctx.input", () => {
const schema = z.string().catch((ctx) => {
return String(ctx.input);
});
expect(schema.parse(123)).toEqual("123");
});
test("direction-aware catch", () => {
const schema = z.string().catch("fallback");
// Forward direction (regular parse): catch should be applied
expect(schema.parse(123)).toBe("fallback");
// Reverse direction (encode): catch should NOT be applied, invalid value should fail validation
expect(z.safeEncode(schema, 123 as any)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]],
"success": false,
}
`);
// But valid values should still work in reverse
expect(z.encode(schema, "world")).toBe("world");
});

View File

@@ -0,0 +1,20 @@
import { expect, test } from "vitest";
test("coalesce", () => {
expect(true).toBe(true);
});
// test("nonoptional with default", () => {
// const schema = z.string().optional().coalesce("hi");
// expectTypeOf<typeof schema._input>().toEqualTypeOf<string | undefined>();
// expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
// expect(schema.parse(undefined)).toBe("hi");
// });
// test("nonoptional in object", () => {
// const schema = z.object({ hi: z.string().optional().nonoptional("hi") });
// expectTypeOf<typeof schema._input>().toEqualTypeOf<{ hi: string | undefined }>();
// expectTypeOf<typeof schema._output>().toEqualTypeOf<{ hi: string }>();
// expect(schema.parse(undefined)).toBe("hi");
// });

View File

@@ -0,0 +1,573 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
// ============================================================================
// stringToNumber
// ============================================================================
const stringToNumber = () =>
z.codec(z.string(), z.number(), {
decode: (str) => Number.parseFloat(str),
encode: (num) => num.toString(),
});
test("stringToNumber codec", () => {
const codec = stringToNumber();
// Test decode
expect(z.decode(codec, "42.5")).toBe(42.5);
expect(z.decode(codec, "0")).toBe(0);
expect(z.decode(codec, "-123.456")).toBe(-123.456);
// Test encode
expect(z.encode(codec, 42.5)).toBe("42.5");
expect(z.encode(codec, 0)).toBe("0");
expect(z.encode(codec, -123.456)).toBe("-123.456");
// Test round trip
const original = "3.14159";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe("3.14159");
});
// ============================================================================
// stringToInt
// ============================================================================
const stringToInt = () =>
z.codec(z.string(), z.int(), {
decode: (str) => Number.parseInt(str, 10),
encode: (num) => num.toString(),
});
test("stringToInt codec", () => {
const codec = stringToInt();
// Test decode
expect(z.decode(codec, "42")).toBe(42);
expect(z.decode(codec, "0")).toBe(0);
expect(z.decode(codec, "-123")).toBe(-123);
// Test encode
expect(z.encode(codec, 42)).toBe("42");
expect(z.encode(codec, 0)).toBe("0");
expect(z.encode(codec, -123)).toBe("-123");
// Test round trip
const original = "999";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe("999");
});
// ============================================================================
// stringToBigInt
// ============================================================================
const stringToBigInt = () =>
z.codec(z.string(), z.bigint(), {
decode: (str) => BigInt(str),
encode: (bigint) => bigint.toString(),
});
test("stringToBigInt codec", () => {
const codec = stringToBigInt();
// Test decode
expect(z.decode(codec, "123456789012345678901234567890")).toBe(123456789012345678901234567890n);
expect(z.decode(codec, "0")).toBe(0n);
expect(z.decode(codec, "-999")).toBe(-999n);
// Test encode
expect(z.encode(codec, 123456789012345678901234567890n)).toBe("123456789012345678901234567890");
expect(z.encode(codec, 0n)).toBe("0");
expect(z.encode(codec, -999n)).toBe("-999");
// Test round trip
const original = "987654321098765432109876543210";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe("987654321098765432109876543210");
});
// ============================================================================
// numberToBigInt
// ============================================================================
const numberToBigInt = () =>
z.codec(z.int(), z.bigint(), {
decode: (num) => BigInt(num),
encode: (bigint) => Number(bigint),
});
test("numberToBigInt codec", () => {
const codec = numberToBigInt();
// Test decode
expect(z.decode(codec, 42)).toBe(42n);
expect(z.decode(codec, 0)).toBe(0n);
expect(z.decode(codec, -123)).toBe(-123n);
// Test encode
expect(z.encode(codec, 42n)).toBe(42);
expect(z.encode(codec, 0n)).toBe(0);
expect(z.encode(codec, -123n)).toBe(-123);
// Test round trip
const original = 999;
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(999);
});
// ============================================================================
// isoDatetimeToDate
// ============================================================================
const isoDatetimeToDate = () =>
z.codec(z.iso.datetime(), z.date(), {
decode: (isoString) => new Date(isoString),
encode: (date) => date.toISOString(),
});
test("isoDatetimeToDate codec", () => {
const codec = isoDatetimeToDate();
// Test decode
const decoded = z.decode(codec, "2024-01-15T10:30:00.000Z");
expect(decoded).toBeInstanceOf(Date);
expect(decoded.getTime()).toBe(1705314600000);
// Test encode
const date = new Date("2024-01-15T10:30:00.000Z");
expect(z.encode(codec, date)).toBe("2024-01-15T10:30:00.000Z");
// Test round trip
const original = "2024-12-25T15:45:30.123Z";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe("2024-12-25T15:45:30.123Z");
});
// ============================================================================
// epochSecondsToDate
// ============================================================================
const epochSecondsToDate = () =>
z.codec(z.int().min(0), z.date(), {
decode: (seconds) => new Date(seconds * 1000),
encode: (date) => Math.floor(date.getTime() / 1000),
});
test("epochSecondsToDate codec", () => {
const codec = epochSecondsToDate();
// Test decode
const decoded = z.decode(codec, 1705314600);
expect(decoded).toBeInstanceOf(Date);
expect(decoded.getTime()).toBe(1705314600000);
// Test encode
const date = new Date(1705314600000);
expect(z.encode(codec, date)).toBe(1705314600);
// Test round trip
const original = 1640995200; // 2022-01-01 00:00:00 UTC
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(1640995200);
});
// ============================================================================
// epochMillisToDate
// ============================================================================
const epochMillisToDate = () =>
z.codec(z.int().min(0), z.date(), {
decode: (millis) => new Date(millis),
encode: (date) => date.getTime(),
});
test("epochMillisToDate codec", () => {
const codec = epochMillisToDate();
// Test decode
const decoded = z.decode(codec, 1705314600000);
expect(decoded).toBeInstanceOf(Date);
expect(decoded.getTime()).toBe(1705314600000);
// Test encode
const date = new Date(1705314600000);
expect(z.encode(codec, date)).toBe(1705314600000);
// Test round trip
const original = 1640995200123; // 2022-01-01 00:00:00.123 UTC
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(1640995200123);
});
// ============================================================================
// json
// ============================================================================
const jsonCodec = <T extends z.core.$ZodType>(schema: T) =>
z.codec(z.string(), schema, {
decode: (jsonString, ctx) => {
try {
return JSON.parse(jsonString);
} catch (err: any) {
ctx.issues.push({
code: "invalid_format",
format: "json",
input: jsonString,
message: err.message,
});
return z.NEVER;
}
},
encode: (value) => JSON.stringify(value),
});
test("json codec", () => {
const codec = jsonCodec(z.object({ name: z.string(), age: z.number() }));
// Test decode
const decoded = z.decode(codec, '{"name":"Alice","age":30}');
expect(decoded).toEqual({ name: "Alice", age: 30 });
// Test encode
const encoded = z.encode(codec, { name: "Bob", age: 25 });
expect(encoded).toBe('{"name":"Bob","age":25}');
// Test round trip
const original = '{"name":"Charlie","age":35}';
const parsed = z.decode(codec, original);
const roundTrip = z.encode(codec, parsed);
expect(JSON.parse(roundTrip)).toEqual(JSON.parse(original));
});
// ============================================================================
// utf8ToBytes
// ============================================================================
const utf8ToBytes = () =>
z.codec(z.string(), z.instanceof(Uint8Array), {
decode: (str) => new TextEncoder().encode(str),
encode: (bytes) => new TextDecoder().decode(bytes),
});
test("utf8ToBytes codec", () => {
const codec = utf8ToBytes();
// Test decode
const decoded = z.decode(codec, "Hello, 世界!");
expect(decoded).toBeInstanceOf(Uint8Array);
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]);
// Test encode
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
expect(z.encode(codec, bytes)).toBe("Hello");
// Test round trip
const original = "Hello, 世界! 🚀";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(original);
});
// ============================================================================
// bytesToUtf8
// ============================================================================
const bytesToUtf8 = () =>
z.codec(z.instanceof(Uint8Array), z.string(), {
decode: (bytes) => new TextDecoder().decode(bytes),
encode: (str) => new TextEncoder().encode(str),
});
test("bytesToUtf8 codec", () => {
const codec = bytesToUtf8();
// Test decode
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const decoded = z.decode(codec, bytes);
expect(decoded).toBe("Hello");
// Test encode
const encoded = z.encode(codec, "Hello, 世界!");
expect(encoded).toBeInstanceOf(Uint8Array);
expect(Array.from(encoded)).toEqual([72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]);
// Test round trip
const original = new Uint8Array([72, 101, 108, 108, 111, 44, 32, 228, 184, 150, 231, 149, 140, 33]);
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toEqual(original);
});
// ============================================================================
// base64
// ============================================================================
const base64 = () =>
z.codec(z.base64(), z.instanceof(Uint8Array), {
decode: (base64String) => z.util.base64ToUint8Array(base64String),
encode: (bytes) => z.util.uint8ArrayToBase64(bytes),
});
test("base64 codec", () => {
const codec = base64();
// Test decode
const decoded = z.decode(codec, "SGVsbG8=");
expect(decoded).toBeInstanceOf(Uint8Array);
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111]);
// Test encode
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
expect(z.encode(codec, bytes)).toBe("SGVsbG8=");
// Test round trip
const original = "SGVsbG8gV29ybGQh";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(original);
});
// ============================================================================
// base64urlToBytes
// ============================================================================
const base64urlToBytes = () =>
z.codec(z.base64url(), z.instanceof(Uint8Array), {
decode: (base64urlString) => z.util.base64urlToUint8Array(base64urlString),
encode: (bytes) => z.util.uint8ArrayToBase64url(bytes),
});
test("base64urlToBytes codec", () => {
const codec = base64urlToBytes();
// Test decode
const decoded = z.decode(codec, "SGVsbG8");
expect(decoded).toBeInstanceOf(Uint8Array);
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111]);
// Test encode
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
expect(z.encode(codec, bytes)).toBe("SGVsbG8");
// Test round trip with padding case
const original = "SGVsbG9Xb3JsZA";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(original);
});
// ============================================================================
// hexToBytes
// ============================================================================
const hexToBytes = () =>
z.codec(z.hex(), z.instanceof(Uint8Array), {
decode: (hexString) => z.util.hexToUint8Array(hexString),
encode: (bytes) => z.util.uint8ArrayToHex(bytes),
});
test("hexToBytes codec", () => {
const codec = hexToBytes();
// Test decode
const decoded = z.decode(codec, "48656c6c6f");
expect(decoded).toBeInstanceOf(Uint8Array);
expect(Array.from(decoded)).toEqual([72, 101, 108, 108, 111]);
// Note: z.hex() doesn't accept 0x prefix, but our utility function can handle it
// const decodedWithPrefix = z.decode(codec, "0x48656c6c6f");
// expect(Array.from(decodedWithPrefix)).toEqual([72, 101, 108, 108, 111]);
// Test encode
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
expect(z.encode(codec, bytes)).toBe("48656c6c6f");
// Test round trip
const original = "deadbeef";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe("deadbeef");
});
// ============================================================================
// stringToURL
// ============================================================================
const stringToURL = () =>
z.codec(z.url(), z.instanceof(URL), {
decode: (urlString) => new URL(urlString),
encode: (url) => url.href,
});
test("stringToURL codec", () => {
const codec = stringToURL();
// Test decode
const decoded = z.decode(codec, "https://example.com/path?query=value");
expect(decoded).toBeInstanceOf(URL);
expect(decoded.hostname).toBe("example.com");
expect(decoded.pathname).toBe("/path");
expect(decoded.search).toBe("?query=value");
// Test encode
const url = new URL("https://example.com/path?query=value");
expect(z.encode(codec, url)).toBe("https://example.com/path?query=value");
// Test round trip
const original = "https://test.com/api/v1?foo=bar&baz=qux";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(original);
});
// ============================================================================
// stringToHttpURL
// ============================================================================
const stringToHttpURL = () =>
z.codec(z.httpUrl(), z.instanceof(URL), {
decode: (urlString) => new URL(urlString),
encode: (url) => url.href,
});
test("stringToHttpURL codec", () => {
const codec = stringToHttpURL();
// Test decode HTTPS
const decodedHttps = z.decode(codec, "https://example.com/path");
expect(decodedHttps).toBeInstanceOf(URL);
expect(decodedHttps.protocol).toBe("https:");
// Test decode HTTP
const decodedHttp = z.decode(codec, "http://example.com/path");
expect(decodedHttp).toBeInstanceOf(URL);
expect(decodedHttp.protocol).toBe("http:");
// Test encode
const url = new URL("https://example.com/path");
expect(z.encode(codec, url)).toBe("https://example.com/path");
// Test round trip
const original = "http://api.example.com/v1/users";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe(original);
});
// ============================================================================
// uriComponent
// ============================================================================
const uriComponent = () =>
z.codec(z.string(), z.string(), {
decode: (encodedString) => decodeURIComponent(encodedString),
encode: (decodedString) => encodeURIComponent(decodedString),
});
test("uriComponent codec", () => {
const codec = uriComponent();
// Test decode
const decoded = z.decode(codec, "Hello%20World%21");
expect(decoded).toBe("Hello World!");
// Test encode
const encoded = z.encode(codec, "Hello World!");
expect(encoded).toBe("Hello%20World!");
// Test round trip
const original = "Hello%20World%21%20%26%20More";
const roundTrip = z.encode(codec, z.decode(codec, original));
expect(roundTrip).toBe("Hello%20World!%20%26%20More");
// Test complex characters
const complex = "café & résumé";
const encodedComplex = z.encode(codec, complex);
const decodedComplex = z.decode(codec, encodedComplex);
expect(decodedComplex).toBe(complex);
});
// ============================================================================
// stringToBoolean
// ============================================================================
const stringToBoolean = (options?: { truthy?: string[]; falsy?: string[] }) => z.stringbool(options);
test("stringToBoolean codec", () => {
const codec = stringToBoolean();
// Test decode - default truthy values
expect(z.decode(codec, "true")).toBe(true);
expect(z.decode(codec, "yes")).toBe(true);
expect(z.decode(codec, "1")).toBe(true);
// Test decode - default falsy values
expect(z.decode(codec, "false")).toBe(false);
expect(z.decode(codec, "no")).toBe(false);
expect(z.decode(codec, "0")).toBe(false);
// Test encode - default behavior
expect(z.encode(codec, true)).toBe("true");
expect(z.encode(codec, false)).toBe("false");
// Test custom options
const customCodec = stringToBoolean({ truthy: ["yes", "y"], falsy: ["no", "n"] });
expect(z.decode(customCodec, "yes")).toBe(true);
expect(z.decode(customCodec, "y")).toBe(true);
expect(z.decode(customCodec, "no")).toBe(false);
expect(z.decode(customCodec, "n")).toBe(false);
expect(z.encode(customCodec, true)).toBe("yes");
expect(z.encode(customCodec, false)).toBe("no");
});
// ============================================================================
// Error Handling Tests
// ============================================================================
// Test error cases - these test input validation, not transform errors
test("codec input validation", () => {
// Test invalid base64 format
const base64Codec = base64();
const invalidBase64Result = z.safeDecode(base64Codec, "invalid!@#");
expect(invalidBase64Result.success).toBe(false);
// Test invalid hex format
const hexCodec = hexToBytes();
const invalidHexResult = z.safeDecode(hexCodec, "gg");
expect(invalidHexResult.success).toBe(false);
// Test invalid URL format
const urlCodec = stringToURL();
const invalidUrlResult = z.safeDecode(urlCodec, "not a url");
expect(invalidUrlResult.success).toBe(false);
// Test invalid HTTP URL format
const httpUrlCodec = stringToHttpURL();
const invalidHttpResult = z.safeDecode(httpUrlCodec, "ftp://example.com");
expect(invalidHttpResult.success).toBe(false);
});
// Test transform errors - these test errors added by transform functions
test("codec transform error handling", () => {
// JSON codec that can fail during transform
const anyJSON = jsonCodec(z.json());
// Test successful JSON parsing
const validResult = z.safeDecode(anyJSON, '{"valid": "json"}');
expect(validResult.success).toBe(true);
if (validResult.success) {
expect(validResult.data).toEqual({ valid: "json" });
}
// Test invalid JSON that should create a single "invalid_format" issue
// Verifies that the transform error aborts before reaching the output schema
const invalidResult = z.safeDecode(anyJSON, '{"invalid":,}');
expect(invalidResult.success).toBe(false);
if (!invalidResult.success) {
expect(invalidResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "json",
"message": "Unexpected token ',', "{"invalid":,}" is not valid JSON",
"path": [],
},
]
`);
}
});

View File

@@ -0,0 +1,562 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const isoDateCodec = z.codec(
z.iso.datetime(), // Input: ISO string (validates to string)
z.date(), // Output: Date object
{
decode: (isoString) => new Date(isoString), // Forward: ISO string → Date
encode: (date) => date.toISOString(), // Backward: Date → ISO string
}
);
test("instanceof", () => {
expect(isoDateCodec instanceof z.ZodCodec).toBe(true);
expect(isoDateCodec instanceof z.ZodPipe).toBe(true);
expect(isoDateCodec instanceof z.ZodType).toBe(true);
expect(isoDateCodec instanceof z.core.$ZodCodec).toBe(true);
expect(isoDateCodec instanceof z.core.$ZodPipe).toBe(true);
expect(isoDateCodec instanceof z.core.$ZodType).toBe(true);
expectTypeOf(isoDateCodec.def).toEqualTypeOf<z.core.$ZodCodecDef<z.ZodISODateTime, z.ZodDate>>();
});
test("codec basic functionality", () => {
// ISO string -> Date codec using z.iso.datetime() for input validation
const testIsoString = "2024-01-15T10:30:00.000Z";
const testDate = new Date("2024-01-15T10:30:00.000Z");
// Forward decoding (ISO string -> Date)
const decodedResult = z.decode(isoDateCodec, testIsoString);
expect(decodedResult).toBeInstanceOf(Date);
expect(decodedResult.toISOString()).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
// Backward encoding (Date -> ISO string)
const encodedResult = z.encode(isoDateCodec, testDate);
expect(typeof encodedResult).toBe("string");
expect(encodedResult).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
});
test("codec round trip", () => {
const isoDateCodec = z.codec(z.iso.datetime(), z.date(), {
decode: (isoString) => new Date(isoString),
encode: (date) => date.toISOString(),
});
const original = "2024-12-25T15:45:30.123Z";
const toDate = z.decode(isoDateCodec, original);
const backToString = z.encode(isoDateCodec, toDate);
expect(backToString).toMatchInlineSnapshot(`"2024-12-25T15:45:30.123Z"`);
expect(toDate).toBeInstanceOf(Date);
expect(toDate.getTime()).toMatchInlineSnapshot(`1735141530123`);
});
test("codec with refinement", () => {
const isoDateCodec = z
.codec(z.iso.datetime(), z.date(), {
decode: (isoString) => new Date(isoString),
encode: (date) => date.toISOString(),
})
.refine((val) => val.getFullYear() === 2024, { error: "Year must be 2024" });
// Valid 2024 date
const validDate = z.decode(isoDateCodec, "2024-01-15T10:30:00.000Z");
expect(validDate.getFullYear()).toMatchInlineSnapshot(`2024`);
expect(validDate.getTime()).toMatchInlineSnapshot(`1705314600000`);
// Invalid year should fail safely
const invalidYearResult = z.safeDecode(isoDateCodec, "2023-01-15T10:30:00.000Z");
expect(invalidYearResult.success).toBe(false);
if (!invalidYearResult.success) {
expect(invalidYearResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "custom",
"message": "Year must be 2024",
"path": [],
},
]
`);
}
});
test("safe codec operations", () => {
const isoDateCodec = z.codec(z.iso.datetime(), z.date(), {
decode: (isoString) => new Date(isoString),
encode: (date) => date.toISOString(),
});
// Safe decode with invalid input
const safeDecodeResult = z.safeDecode(isoDateCodec, "invalid-date");
expect(safeDecodeResult.success).toBe(false);
if (!safeDecodeResult.success) {
expect(safeDecodeResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "datetime",
"message": "Invalid ISO datetime",
"origin": "string",
"path": [],
"pattern": "/^(?:(?:\\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])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/",
},
]
`);
}
// Safe decode with valid input
const safeDecodeValid = z.safeDecode(isoDateCodec, "2024-01-15T10:30:00.000Z");
expect(safeDecodeValid.success).toBe(true);
if (safeDecodeValid.success) {
expect(safeDecodeValid.data).toBeInstanceOf(Date);
expect(safeDecodeValid.data.getTime()).toMatchInlineSnapshot(`1705314600000`);
}
// Safe encode with valid input
const safeEncodeResult = z.safeEncode(isoDateCodec, new Date("2024-01-01"));
expect(safeEncodeResult.success).toBe(true);
if (safeEncodeResult.success) {
expect(safeEncodeResult.data).toMatchInlineSnapshot(`"2024-01-01T00:00:00.000Z"`);
}
});
test("codec with different types", () => {
// String -> Number codec
const stringNumberCodec = z.codec(z.string(), z.number(), {
decode: (str) => Number.parseFloat(str),
encode: (num) => num.toString(),
});
const decodedNumber = z.decode(stringNumberCodec, "42.5");
expect(decodedNumber).toMatchInlineSnapshot(`42.5`);
expect(typeof decodedNumber).toBe("number");
const encodedString = z.encode(stringNumberCodec, 42.5);
expect(encodedString).toMatchInlineSnapshot(`"42.5"`);
expect(typeof encodedString).toBe("string");
});
test("async codec operations", async () => {
const isoDateCodec = z.codec(z.iso.datetime(), z.date(), {
decode: (isoString) => new Date(isoString),
encode: (date) => date.toISOString(),
});
// Async decode
const decodedResult = await z.decodeAsync(isoDateCodec, "2024-01-15T10:30:00.000Z");
expect(decodedResult).toBeInstanceOf(Date);
expect(decodedResult.getTime()).toMatchInlineSnapshot(`1705314600000`);
// Async encode
const encodedResult = await z.encodeAsync(isoDateCodec, new Date("2024-01-15T10:30:00.000Z"));
expect(typeof encodedResult).toBe("string");
expect(encodedResult).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
// Safe async operations
const safeDecodeResult = await z.safeDecodeAsync(isoDateCodec, "2024-01-15T10:30:00.000Z");
expect(safeDecodeResult.success).toBe(true);
if (safeDecodeResult.success) {
expect(safeDecodeResult.data.getTime()).toMatchInlineSnapshot(`1705314600000`);
}
const safeEncodeResult = await z.safeEncodeAsync(isoDateCodec, new Date("2024-01-15T10:30:00.000Z"));
expect(safeEncodeResult.success).toBe(true);
if (safeEncodeResult.success) {
expect(safeEncodeResult.data).toMatchInlineSnapshot(`"2024-01-15T10:30:00.000Z"`);
}
});
test("codec type inference", () => {
const codec = z.codec(z.string(), z.number(), {
decode: (str) => Number.parseInt(str),
encode: (num) => num.toString(),
});
// These should compile without type errors
const decoded: number = z.decode(codec, "123");
const encoded: string = z.encode(codec, 123);
expect(decoded).toMatchInlineSnapshot(`123`);
expect(encoded).toMatchInlineSnapshot(`"123"`);
});
test("nested codec with object containing codec property", () => {
// Nested schema: object containing a codec as one of its properties, with refinements at all levels
const waypointSchema = z
.object({
name: z.string().min(1, "Waypoint name required"),
difficulty: z.enum(["easy", "medium", "hard"]),
coordinate: z
.codec(
z
.string()
.regex(/^-?\d+,-?\d+$/, "Must be 'x,y' format"), // Input: coordinate string
z
.object({ x: z.number(), y: z.number() })
.refine((coord) => coord.x >= 0 && coord.y >= 0, { error: "Coordinates must be non-negative" }), // Output: coordinate object
{
decode: (coordString: string) => {
const [x, y] = coordString.split(",").map(Number);
return { x, y };
},
encode: (coord: { x: number; y: number }) => `${coord.x},${coord.y}`,
}
)
.refine((coord) => coord.x <= 1000 && coord.y <= 1000, { error: "Coordinates must be within bounds" }),
})
.refine((waypoint) => waypoint.difficulty !== "hard" || waypoint.coordinate.x >= 100, {
error: "Hard waypoints must be at least 100 units from origin",
});
// Test data
const inputWaypoint = {
name: "Summit Point",
difficulty: "medium" as const,
coordinate: "150,200",
};
// Forward decoding (object with string coordinate -> object with coordinate object)
const decodedWaypoint = z.decode(waypointSchema, inputWaypoint);
expect(decodedWaypoint).toMatchInlineSnapshot(`
{
"coordinate": {
"x": 150,
"y": 200,
},
"difficulty": "medium",
"name": "Summit Point",
}
`);
// Backward encoding (object with coordinate object -> object with string coordinate)
const encodedWaypoint = z.encode(waypointSchema, decodedWaypoint);
expect(encodedWaypoint).toMatchInlineSnapshot(`
{
"coordinate": "150,200",
"difficulty": "medium",
"name": "Summit Point",
}
`);
// Test refinements at all levels
// String validation (empty waypoint name)
const emptyNameResult = z.safeDecode(waypointSchema, {
name: "",
difficulty: "easy",
coordinate: "10,20",
});
expect(emptyNameResult.success).toBe(false);
if (!emptyNameResult.success) {
expect(emptyNameResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Waypoint name required",
"minimum": 1,
"origin": "string",
"path": [
"name",
],
},
]
`);
}
// Enum validation (invalid difficulty)
const invalidDifficultyResult = z.safeDecode(waypointSchema, {
name: "Test Point",
difficulty: "impossible" as any,
coordinate: "10,20",
});
expect(invalidDifficultyResult.success).toBe(false);
if (!invalidDifficultyResult.success) {
expect(invalidDifficultyResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_value",
"message": "Invalid option: expected one of "easy"|"medium"|"hard"",
"path": [
"difficulty",
],
"values": [
"easy",
"medium",
"hard",
],
},
]
`);
}
// Codec string format validation (invalid coordinate format)
const invalidFormatResult = z.safeDecode(waypointSchema, {
name: "Test Point",
difficulty: "easy",
coordinate: "invalid",
});
expect(invalidFormatResult.success).toBe(false);
if (!invalidFormatResult.success) {
expect(invalidFormatResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "regex",
"message": "Must be 'x,y' format",
"origin": "string",
"path": [
"coordinate",
],
"pattern": "/^-?\\d+,-?\\d+$/",
},
]
`);
}
// Codec object refinement (negative coordinates)
const negativeCoordResult = z.safeDecode(waypointSchema, {
name: "Test Point",
difficulty: "easy",
coordinate: "-5,10",
});
expect(negativeCoordResult.success).toBe(false);
if (!negativeCoordResult.success) {
expect(negativeCoordResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "custom",
"message": "Coordinates must be non-negative",
"path": [
"coordinate",
],
},
]
`);
}
// Codec-level refinement (coordinates out of bounds)
const outOfBoundsResult = z.safeDecode(waypointSchema, {
name: "Test Point",
difficulty: "easy",
coordinate: "1500,2000",
});
expect(outOfBoundsResult.success).toBe(false);
if (!outOfBoundsResult.success) {
expect(outOfBoundsResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "custom",
"message": "Coordinates must be within bounds",
"path": [
"coordinate",
],
},
]
`);
}
// Object-level refinement (hard waypoint too close to origin)
const hardWaypointResult = z.safeDecode(waypointSchema, {
name: "Expert Point",
difficulty: "hard",
coordinate: "50,60", // x < 100, but hard waypoints need x >= 100
});
expect(hardWaypointResult.success).toBe(false);
if (!hardWaypointResult.success) {
expect(hardWaypointResult.error.issues).toMatchInlineSnapshot(`
[
{
"code": "custom",
"message": "Hard waypoints must be at least 100 units from origin",
"path": [],
},
]
`);
}
// Round trip test
const roundTripResult = z.encode(waypointSchema, z.decode(waypointSchema, inputWaypoint));
expect(roundTripResult).toMatchInlineSnapshot(`
{
"coordinate": "150,200",
"difficulty": "medium",
"name": "Summit Point",
}
`);
});
test("mutating refinements", () => {
const A = z.codec(z.string(), z.string().trim(), {
decode: (val) => val,
encode: (val) => val,
});
expect(z.decode(A, " asdf ")).toMatchInlineSnapshot(`"asdf"`);
expect(z.encode(A, " asdf ")).toMatchInlineSnapshot(`"asdf"`);
const B = z
.codec(z.string(), z.string(), {
decode: (val) => val,
encode: (val) => val,
})
.check(z.trim(), z.maxLength(4));
expect(z.decode(B, " asdf ")).toMatchInlineSnapshot(`"asdf"`);
expect(z.encode(B, " asdf ")).toMatchInlineSnapshot(`"asdf"`);
});
test("codec type enforcement - correct encode/decode signatures", () => {
// Test that codec functions have correct type signatures
const stringToNumberCodec = z.codec(z.string(), z.number(), {
decode: (value: string) => Number(value), // core.output<A> -> core.input<B>
encode: (value: number) => String(value), // core.input<B> -> core.output<A>
});
// These should compile without errors - correct types (async support)
expectTypeOf<(value: string, payload: z.core.ParsePayload<string>) => z.core.util.MaybeAsync<number>>(
stringToNumberCodec.def.transform
).toBeFunction();
expectTypeOf<(value: number, payload: z.core.ParsePayload<number>) => z.core.util.MaybeAsync<string>>(
stringToNumberCodec.def.reverseTransform
).toBeFunction();
// Test that decode parameter type is core.output<A> (string)
const validDecode = (value: string) => Number(value);
expectTypeOf(validDecode).toMatchTypeOf<(value: string) => number>();
// Test that encode parameter type is core.input<B> (number)
const validEncode = (value: number) => String(value);
expectTypeOf(validEncode).toMatchTypeOf<(value: number) => string>();
z.codec(z.string(), z.number(), {
// @ts-expect-error - decode should NOT accept core.input<A> as parameter
decode: (value: never, _payload) => Number(value), // Wrong: should be string, not unknown
encode: (value: number, _payload) => String(value),
});
z.codec(z.string(), z.number(), {
decode: (value: string) => Number(value),
// @ts-expect-error - encode should NOT accept core.output<B> as parameter
encode: (value: never) => String(value), // Wrong: should be number, not unknown
});
z.codec(z.string(), z.number(), {
// @ts-expect-error - decode return type should be core.input<B>
decode: (value: string) => String(value), // Wrong: should return number, not string
encode: (value: number) => String(value),
});
z.codec(z.string(), z.number(), {
decode: (value: string) => Number(value),
// @ts-expect-error - encode return type should be core.output<A>
encode: (value: number) => Number(value), // Wrong: should return string, not number
});
});
test("codec type enforcement - complex types", () => {
type User = { id: number; name: string };
type UserInput = { id: string; name: string };
const userCodec = z.codec(
z.object({ id: z.string(), name: z.string() }),
z.object({ id: z.number(), name: z.string() }),
{
decode: (input: UserInput) => ({ id: Number(input.id), name: input.name }),
encode: (user: User) => ({ id: String(user.id), name: user.name }),
}
);
// Verify correct types are inferred (async support)
expectTypeOf<(input: UserInput, payload: z.core.ParsePayload<UserInput>) => z.core.util.MaybeAsync<User>>(
userCodec.def.transform
).toBeFunction();
expectTypeOf<(user: User, payload: z.core.ParsePayload<User>) => z.core.util.MaybeAsync<UserInput>>(
userCodec.def.reverseTransform
).toBeFunction();
z.codec(
z.object({
id: z.string(),
name: z.string(),
}),
z.object({ id: z.number(), name: z.string() }),
{
// @ts-expect-error - decode parameter should be UserInput, not User
decode: (input: User) => ({ id: Number(input.id), name: input.name }), // Wrong type
encode: (user: User) => ({ id: String(user.id), name: user.name }),
}
);
z.codec(
z.object({
id: z.string(),
name: z.string(),
}),
z.object({ id: z.number(), name: z.string() }),
{
decode: (input: UserInput) => ({ id: Number(input.id), name: input.name }),
// @ts-expect-error - encode parameter should be User, not UserInput
encode: (user: UserInput) => ({ id: String(user.id), name: user.name }), // Wrong type
}
);
});
test("codec with overwrites", () => {
const stringPlusA = z.string().overwrite((val) => val + "a");
const A = z
.codec(stringPlusA, stringPlusA, {
decode: (val) => val,
encode: (val) => val,
})
.overwrite((val) => val + "a");
expect(z.decode(A, "")).toEqual("aaa");
expect(z.encode(A, "")).toEqual("aaa");
// @ts-expect-error
expect(z.safeEncode(A, Symbol("a"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received symbol"
}
]],
"success": false,
}
`);
});
test("async codec functionality", async () => {
// Test that async encode/decode functions work properly
const asyncCodec = z.codec(z.string(), z.number(), {
decode: async (str) => {
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
return Number.parseFloat(str);
},
encode: async (num) => {
await new Promise((resolve) => setTimeout(resolve, 1)); // Simulate async work
return num.toString();
},
});
// Test async decode/encode
const decoded = await z.decodeAsync(asyncCodec, "42.5");
expect(decoded).toBe(42.5);
const encoded = await z.encodeAsync(asyncCodec, 42.5);
expect(encoded).toBe("42.5");
// Test that both sync and async work
const mixedCodec = z.codec(z.string(), z.number(), {
decode: async (str) => Number.parseFloat(str),
encode: (num) => num.toString(), // sync encode
});
const mixedResult = await z.decodeAsync(mixedCodec, "123");
expect(mixedResult).toBe(123);
});

View File

@@ -0,0 +1,160 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
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);
});
// test("template literal coercion", () => {
// const schema = z.coerce
// .templateLiteral()
// .interpolated(z.number().finite())
// .interpolated(
// z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional()
// );
// expect(schema.parse(300)).toEqual("300");
// expect(schema.parse(BigInt(300))).toEqual("300");
// expect(schema.parse("300")).toEqual("300");
// expect(schema.parse("300px")).toEqual("300px");
// expect(schema.parse("300em")).toEqual("300em");
// expect(schema.parse("300rem")).toEqual("300rem");
// expect(schema.parse("300vh")).toEqual("300vh");
// expect(schema.parse("300vw")).toEqual("300vw");
// expect(schema.parse("300vmin")).toEqual("300vmin");
// expect(schema.parse("300vmax")).toEqual("300vmax");
// expect(schema.parse(["300px"])).toEqual("300px");
// });
test("override input type", () => {
const a = z.coerce.string<any>();
type input = z.input<typeof a>;
expectTypeOf<input>().toEqualTypeOf<any>();
type output = z.infer<typeof a>;
expectTypeOf<output>().toEqualTypeOf<string>();
});

View File

@@ -0,0 +1,374 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("continuability", () => {
/**
* | $ZodGUID
| $ZodUUID
| $ZodEmail
| $ZodURL
| $ZodEmoji
| $ZodNanoID
| $ZodCUID
| $ZodCUID2
| $ZodULID
| $ZodXID
| $ZodKSUID
| $ZodISODateTime
| $ZodISODate
| $ZodISOTime
| $ZodISODuration
| $ZodIPv4
| $ZodIPv6
| $ZodCIDRv4
| $ZodCIDRv6
| $ZodBase64
| $ZodBase64URL
| $ZodE164
| $ZodJWT;
*/
expect(
z
.email()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "email",
"message": "Invalid email address",
"origin": "string",
"path": [],
"pattern": "/^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.uuid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "uuid",
"message": "Invalid UUID",
"origin": "string",
"path": [],
"pattern": "/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.url()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "url",
"message": "Invalid URL",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.jwt()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "jwt",
"message": "Invalid JWT",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cidrv4()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cidrv4",
"message": "Invalid IPv4 range",
"origin": "string",
"path": [],
"pattern": "/^((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])\\/([0-9]|[1-2][0-9]|3[0-2])$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cidrv6()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cidrv6",
"message": "Invalid IPv6 range",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ipv4()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ipv4",
"message": "Invalid IPv4 address",
"origin": "string",
"path": [],
"pattern": "/^(?:(?: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])$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ipv6()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ipv6",
"message": "Invalid IPv6 address",
"path": [],
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.mac()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "mac",
"message": "Invalid MAC address",
"origin": "string",
"path": [],
"pattern": "/^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.emoji()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "emoji",
"message": "Invalid emoji",
"origin": "string",
"path": [],
"pattern": "/^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$/u",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.nanoid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "nanoid",
"message": "Invalid nanoid",
"origin": "string",
"path": [],
"pattern": "/^[a-zA-Z0-9_-]{21}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cuid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cuid",
"message": "Invalid cuid",
"origin": "string",
"path": [],
"pattern": "/^[cC][^\\s-]{8,}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.cuid2()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "cuid2",
"message": "Invalid cuid2",
"origin": "string",
"path": [],
"pattern": "/^[0-9a-z]+$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ulid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ulid",
"message": "Invalid ULID",
"origin": "string",
"path": [],
"pattern": "/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.xid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "xid",
"message": "Invalid XID",
"origin": "string",
"path": [],
"pattern": "/^[0-9a-vA-V]{20}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
expect(
z
.ksuid()
.refine(() => false)
.safeParse("invalid_value").error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "invalid_format",
"format": "ksuid",
"message": "Invalid KSUID",
"origin": "string",
"path": [],
"pattern": "/^[A-Za-z0-9]{27}$/",
},
{
"code": "custom",
"message": "Invalid input",
"path": [],
},
]
`);
});

View File

@@ -0,0 +1,40 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
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);
expect(JSON.stringify(result.error).includes("customerr")).toEqual(true);
});
test("instanceof", () => {
const fn = (value: string) => Uint8Array.from(Buffer.from(value, "base64"));
// Argument of type 'ZodCustom<Uint8Array<ArrayBuffer>, unknown>' is not assignable to parameter of type '$ZodType<any, Uint8Array<ArrayBuffer>>'.
z.string().transform(fn).pipe(z.instanceof(Uint8Array));
});
test("non-continuable by default", () => {
const A = z
.custom<string>((val) => typeof val === "string")
.transform((_) => {
throw new Error("Invalid input");
});
expect(A.safeParse(123).error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});

View File

@@ -0,0 +1,62 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
const beforeBenchmarkDate = new Date(Date.UTC(2022, 10, 4));
const benchmarkDate = new Date(Date.UTC(2022, 10, 5));
const afterBenchmarkDate = new Date(Date.UTC(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("date min", () => {
const result = minCheck.safeParse(beforeBenchmarkDate);
expect(result.success).toEqual(false);
expect(result.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected date to be >=1667606400000",
"minimum": 1667606400000,
"origin": "date",
"path": [],
},
]
`);
});
test("date max", () => {
const result = maxCheck.safeParse(afterBenchmarkDate);
expect(result.success).toEqual(false);
expect(result.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 1667606400000,
"message": "Too big: expected date to be <=1667606400000",
"origin": "date",
"path": [],
},
]
`);
});
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);
});

View File

@@ -0,0 +1,302 @@
import { checkSync } from "recheck";
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("basic datetime parsing", () => {
const datetime = z.string().datetime();
datetime.parse("1970-01-01T00:00:00.000Z");
datetime.parse("2022-10-13T09:52:31.816Z");
datetime.parse("2022-10-13T09:52:31.8162314Z");
datetime.parse("1970-01-01T00:00:00Z");
datetime.parse("2022-10-13T09:52:31Z");
expect(() => datetime.parse("")).toThrow();
expect(() => datetime.parse("foo")).toThrow();
expect(() => datetime.parse("2020-10-14")).toThrow();
expect(() => datetime.parse("T18:45:12.123")).toThrow();
expect(() => datetime.parse("2020-10-14T17:42:29+00:00")).toThrow();
});
test("datetime parsing with precision -1", () => {
const datetimeNoMs = z.string().datetime({ precision: -1, offset: true, local: true });
datetimeNoMs.parse("1970-01-01T00:00Z");
datetimeNoMs.parse("2022-10-13T09:52Z");
datetimeNoMs.parse("2022-10-13T09:52+02:00");
datetimeNoMs.parse("2022-10-13T09:52");
expect(() => datetimeNoMs.parse("tuna")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52+02")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
});
test("datetime parsing with precision 0", () => {
const datetimeNoMs = z.string().datetime({ precision: 0 });
datetimeNoMs.parse("1970-01-01T00:00:00Z");
datetimeNoMs.parse("2022-10-13T09:52:31Z");
expect(() => datetimeNoMs.parse("tuna")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
});
test("datetime parsing with precision 3", () => {
const datetime3Ms = z.string().datetime({ precision: 3 });
datetime3Ms.parse("1970-01-01T00:00:00.000Z");
datetime3Ms.parse("2022-10-13T09:52:31.123Z");
expect(() => datetime3Ms.parse("tuna")).toThrow();
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.1Z")).toThrow();
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.12Z")).toThrow();
expect(() => datetime3Ms.parse("2022-10-13T09:52:31Z")).toThrow();
});
test("datetime parsing with offset", () => {
const datetimeOffset = z.string().datetime({ offset: true });
datetimeOffset.parse("1970-01-01T00:00:00.000Z");
datetimeOffset.parse("2022-10-13T09:52:31.816234134Z");
datetimeOffset.parse("1970-01-01T00:00:00Z");
datetimeOffset.parse("2022-10-13T09:52:31.4Z");
datetimeOffset.parse("2020-10-14T17:42:29+00:00");
datetimeOffset.parse("2020-10-14T17:42:29+03:15");
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+0315")).toThrow();
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+03")).toThrow();
expect(() => datetimeOffset.parse("tuna")).toThrow();
expect(() => datetimeOffset.parse("2022-10-13T09:52:31.Z")).toThrow();
// Invalid offset tests
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+24:00")).toThrow(); // out of range hours
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+00:60")).toThrow(); // out of range minutes
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+1:30")).toThrow(); // single digit hours
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+00:")).toThrow(); // incomplete offset
});
test("datetime parsing with offset and precision 0", () => {
const datetimeOffsetNoMs = z.string().datetime({ offset: true, precision: 0 });
datetimeOffsetNoMs.parse("1970-01-01T00:00:00Z");
datetimeOffsetNoMs.parse("2022-10-13T09:52:31Z");
datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00:00");
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29+0000")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00")).toThrow();
expect(() => datetimeOffsetNoMs.parse("tuna")).toThrow();
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
});
test("datetime parsing with offset and precision 4", () => {
const datetimeOffset4Ms = z.string().datetime({ offset: true, precision: 4 });
datetimeOffset4Ms.parse("1970-01-01T00:00:00.1234Z");
datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00:00");
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+0000")).toThrow();
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00")).toThrow();
expect(() => datetimeOffset4Ms.parse("tuna")).toThrow();
expect(() => datetimeOffset4Ms.parse("1970-01-01T00:00:00.123Z")).toThrow();
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
});
test("datetime offset normalization", () => {
const a = z.iso.datetime({ offset: true });
expect(a.safeParse("2020-10-14T17:42:29+02")).toMatchObject({ success: false });
expect(a.safeParse("2020-10-14T17:42:29+0200")).toMatchObject({ success: false });
a.safeParse("2020-10-14T17:42:29+02:00");
});
test("datetime parsing with local option", () => {
const a = z.string().datetime({ local: true });
expect(a.safeParse("1970-01-01T00:00")).toMatchObject({ success: true });
expect(a.safeParse("1970-01-01T00:00:00")).toMatchObject({ success: true });
expect(a.safeParse("2022-10-13T09:52:31.816")).toMatchObject({ success: true });
expect(a.safeParse("1970-01-01T00:00:00.000")).toMatchObject({ success: true });
expect(a.safeParse("1970-01-01T00")).toMatchObject({ success: false });
// Should reject timezone indicators and invalid formats
expect(() => a.parse("2022-10-13T09:52:31+00:00")).toThrow();
expect(() => a.parse("2022-10-13 09:52:31")).toThrow();
expect(() => a.parse("2022-10-13T24:52:31")).toThrow();
expect(() => a.parse("2022-10-13T24:52")).toThrow();
expect(() => a.parse("2022-10-13T24:52Z")).toThrow();
});
test("datetime parsing with local and offset", () => {
const a = z.string().datetime({ local: true, offset: true });
// expect(a.parse("2022-10-13T12:52")).toEqual("2022-10-13T12:52:00");
a.parse("2022-10-13T12:52:00");
a.parse("2022-10-13T12:52:00Z");
a.parse("2022-10-13T12:52Z");
a.parse("2022-10-13T12:52");
a.parse("2022-10-13T12:52+02:00");
expect(() => a.parse("2022-10-13T12:52:00+02")).toThrow();
// expect(() => a.parse("2022-10-13T12:52Z")).toThrow();
// expect(() => a.parse("2022-10-13T12:52+02:00")).toThrow();
});
test("date parsing", () => {
const date = z.string().date();
date.parse("1970-01-01");
date.parse("2022-01-31");
date.parse("2022-03-31");
date.parse("2022-04-30");
date.parse("2022-05-31");
date.parse("2022-06-30");
date.parse("2022-07-31");
date.parse("2022-08-31");
date.parse("2022-09-30");
date.parse("2022-10-31");
date.parse("2022-11-30");
date.parse("2022-12-31");
date.parse("2000-02-29");
date.parse("2400-02-29");
expect(() => date.parse("2022-02-29")).toThrow();
expect(() => date.parse("2100-02-29")).toThrow();
expect(() => date.parse("2200-02-29")).toThrow();
expect(() => date.parse("2300-02-29")).toThrow();
expect(() => date.parse("2500-02-29")).toThrow();
expect(() => date.parse("")).toThrow();
expect(() => date.parse("foo")).toThrow();
expect(() => date.parse("200-01-01")).toThrow();
expect(() => date.parse("20000-01-01")).toThrow();
expect(() => date.parse("2000-0-01")).toThrow();
expect(() => date.parse("2000-011-01")).toThrow();
expect(() => date.parse("2000-01-0")).toThrow();
expect(() => date.parse("2000-01-011")).toThrow();
expect(() => date.parse("2000/01/01")).toThrow();
expect(() => date.parse("01-01-2022")).toThrow();
expect(() => date.parse("01/01/2022")).toThrow();
expect(() => date.parse("2000-01-01 00:00:00Z")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29+00:00")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29Z")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29")).toThrow();
expect(() => date.parse("2020-10-14T17:42:29.123Z")).toThrow();
expect(() => date.parse("2000-00-12")).toThrow();
expect(() => date.parse("2000-12-00")).toThrow();
expect(() => date.parse("2000-01-32")).toThrow();
expect(() => date.parse("2000-13-01")).toThrow();
expect(() => date.parse("2000-21-01")).toThrow();
expect(() => date.parse("2000-02-30")).toThrow();
expect(() => date.parse("2000-02-31")).toThrow();
expect(() => date.parse("2000-04-31")).toThrow();
expect(() => date.parse("2000-06-31")).toThrow();
expect(() => date.parse("2000-09-31")).toThrow();
expect(() => date.parse("2000-11-31")).toThrow();
});
test("time parsing", () => {
const time = z.string().time();
time.parse("00:00:00");
time.parse("23:00:00");
time.parse("00:59:00");
time.parse("00:00:59");
time.parse("23:59:59");
time.parse("09:52:31");
time.parse("23:59:59.9999999");
time.parse("00:00");
expect(() => time.parse("")).toThrow();
expect(() => time.parse("foo")).toThrow();
expect(() => time.parse("00:00:00Z")).toThrow();
expect(() => time.parse("0:00:00")).toThrow();
expect(() => time.parse("00:0:00")).toThrow();
expect(() => time.parse("00:00:0")).toThrow();
expect(() => time.parse("00:00:00.000+00:00")).toThrow();
expect(() => time.parse("24:00:00")).toThrow();
expect(() => time.parse("00:60:00")).toThrow();
expect(() => time.parse("00:00:60")).toThrow();
expect(() => time.parse("24:60:60")).toThrow();
const time2 = z.string().time({ precision: 2 });
time2.parse("00:00:00.00");
time2.parse("09:52:31.12");
time2.parse("23:59:59.99");
expect(() => time2.parse("")).toThrow();
expect(() => time2.parse("foo")).toThrow();
expect(() => time2.parse("00:00:00")).toThrow();
expect(() => time2.parse("00:00:00.00Z")).toThrow();
expect(() => time2.parse("00:00:00.0")).toThrow();
expect(() => time2.parse("00:00:00.000")).toThrow();
expect(() => time2.parse("00:00:00.00+00:00")).toThrow();
const time3 = z.string().time({ precision: z.TimePrecision.Minute });
time3.parse("00:00");
expect(() => time3.parse("00:00:00")).toThrow();
});
test("duration", () => {
const duration = z.string().duration();
const validDurations = [
"P3Y6M4DT12H30M5S",
"P2Y9M3DT12H31M8.001S",
// "+P3Y6M4DT12H30M5S",
// "-PT0.001S",
// "+PT0.001S",
"PT0,001S",
"PT12H30M5S",
// "-P2M1D",
// "P-2M-1D",
// "-P5DT10H",
// "P-5DT-10H",
"P1Y",
"P2MT30M",
"PT6H",
"P5W",
// "P0.5Y",
// "P0,5Y",
// "P42YT7.004M",
];
const invalidDurations = [
"foo bar",
"",
" ",
"P",
"PT",
"P1Y2MT",
"T1H",
"P0.5Y1D",
"P0,5Y6M",
"P1YT",
"P-2M-1D",
"P-5DT-10H",
"P1W2D",
"-P1D",
];
for (const val of validDurations) {
const result = duration.safeParse(val);
if (!result.success) {
throw Error(`Valid duration could not be parsed: ${val}`);
}
}
for (const val of invalidDurations) {
const result = duration.safeParse(val);
if (result.success) {
throw Error(`Invalid duration was successful parsed: ${val}`);
}
expect(result.error.issues[0].message).toEqual("Invalid ISO duration");
}
});
test("redos checker", () => {
const a = z.iso.datetime();
const b = z.string().datetime({ offset: true });
const c = z.string().datetime({ local: true });
const d = z.string().datetime({ local: true, offset: true, precision: 3 });
const e = z.string().date();
const f = z.string().time();
const g = z.string().duration();
for (const schema of [a, b, c, d, e, f, g]) {
const result = checkSync(schema._zod.pattern.source, "");
if (result.status !== "safe") throw Error("ReDoS issue");
}
});

View File

@@ -0,0 +1,365 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("basic defaults", () => {
expect(z.string().default("default").parse(undefined)).toBe("default");
});
test("default with optional", () => {
const schema = z.string().optional().default("default");
expect(schema.parse(undefined)).toBe("default");
expect(schema.unwrap().parse(undefined)).toBe(undefined);
});
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.unwrap()).toBeInstanceOf(z.ZodPipe);
expect(stringWithDefault.unwrap().in).toBeInstanceOf(z.ZodString);
expect(stringWithDefault.unwrap().out).toBeInstanceOf(z.ZodTransform);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
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.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(stringWithDefault.unwrap().unwrap()).toBeInstanceOf(z.ZodString);
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("optional on default", () => {
const stringWithDefault = z.string().default("asdf").optional();
type inp = z.input<typeof stringWithDefault>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof stringWithDefault>;
expectTypeOf<out>().toEqualTypeOf<string | undefined>();
expect(stringWithDefault.parse(undefined)).toBe("asdf");
});
// test("complex chain example", () => {
// const complex = z
// .string()
// .default("asdf")
// .transform((val) => val.toUpperCase())
// .default("qwer")
// .unwrap()
// .optional()
// .default("asdfasdf");
// expect(complex.parse(undefined)).toBe("asdfasdf");
// });
test("removeDefault", () => {
const stringWithRemovedDefault = z.string().default("asdf").removeDefault();
type out = z.output<typeof stringWithRemovedDefault>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("apply default at output", () => {
const schema = z
.string()
.transform((_) => (Math.random() > 0 ? undefined : _))
.default("asdf");
expect(schema.parse("")).toEqual("asdf");
});
test("nested", () => {
const inner = z.string().default("asdf");
const outer = z.object({ inner }).default({
inner: "qwer",
});
type input = z.input<typeof outer>;
expectTypeOf<input>().toEqualTypeOf<{ inner?: string | undefined } | undefined>();
type out = z.output<typeof outer>;
expectTypeOf<out>().toEqualTypeOf<{ inner: string }>();
expect(outer.parse(undefined)).toEqual({ inner: "qwer" });
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("object optionality", () => {
const schema = z.object({
hi: z.string().default("hi"),
});
type schemaInput = z.input<typeof schema>;
type schemaOutput = z.output<typeof schema>;
expectTypeOf<schemaInput>().toEqualTypeOf<{ hi?: string | undefined }>();
expectTypeOf<schemaOutput>().toEqualTypeOf<{ hi: string }>();
expect(schema.parse({})).toEqual({
hi: "hi",
});
});
test("nested prefault/default", () => {
const a = z
.string()
.default("a")
.refine((val) => val.startsWith("a"));
const b = z
.string()
.refine((val) => val.startsWith("b"))
.default("b");
const c = z
.string()
.prefault("c")
.refine((val) => val.startsWith("c"));
const d = z
.string()
.refine((val) => val.startsWith("d"))
.prefault("d");
const obj = z.object({
a,
b,
c,
d,
});
expect(obj.safeParse({ a: "a1", b: "b1", c: "c1", d: "d1" })).toMatchInlineSnapshot(`
{
"data": {
"a": "a1",
"b": "b1",
"c": "c1",
"d": "d1",
},
"success": true,
}
`);
expect(obj.safeParse({ a: "f", b: "f", c: "f", d: "f" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [
"a"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"b"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"c"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"d"
],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(obj.safeParse({})).toMatchInlineSnapshot(`
{
"data": {
"a": "a",
"b": "b",
"c": "c",
"d": "d",
},
"success": true,
}
`);
expect(obj.safeParse({ a: undefined, b: undefined, c: undefined, d: undefined })).toMatchInlineSnapshot(`
{
"data": {
"a": "a",
"b": "b",
"c": "c",
"d": "d",
},
"success": true,
}
`);
const obj2 = z.object({
a: a.optional(),
b: b.optional(),
c: c.optional(),
d: d.optional(),
});
expect(obj2.safeParse({ a: undefined, b: undefined, c: undefined, d: undefined })).toMatchInlineSnapshot(`
{
"data": {
"a": "a",
"b": "b",
"c": "c",
"d": "d",
},
"success": true,
}
`);
expect(a.parse(undefined)).toBe("a");
expect(b.parse(undefined)).toBe("b");
expect(c.parse(undefined)).toBe("c");
expect(d.parse(undefined)).toBe("d");
});
test("failing default", () => {
const a = z
.string()
.default("z")
.refine((val) => val.startsWith("a"));
const b = z
.string()
.refine((val) => val.startsWith("b"))
.default("z");
const c = z
.string()
.prefault("z")
.refine((val) => val.startsWith("c"));
const d = z
.string()
.refine((val) => val.startsWith("d"))
.prefault("z");
const obj = z.object({
a,
b,
c,
d,
});
expect(
obj.safeParse({
a: undefined,
b: undefined,
c: undefined,
d: undefined,
}).error!.issues
).toMatchInlineSnapshot(`
[
{
"code": "custom",
"message": "Invalid input",
"path": [
"a",
],
},
{
"code": "custom",
"message": "Invalid input",
"path": [
"c",
],
},
{
"code": "custom",
"message": "Invalid input",
"path": [
"d",
],
},
]
`);
});
test("partial should not clobber defaults", () => {
const objWithDefaults = z.object({
a: z.string().default("defaultA"),
b: z.string().default("defaultB"),
c: z.string().default("defaultC"),
});
const objPartialWithOneRequired = objWithDefaults.partial(); //.required({ a: true });
const test = objPartialWithOneRequired.parse({});
expect(test).toMatchInlineSnapshot(`
{
"a": "defaultA",
"b": "defaultB",
"c": "defaultC",
}
`);
});
test("defaulted object schema returns shallow clone", () => {
const schema = z
.object({
a: z.string(),
})
.default({ a: "x" });
const result1 = schema.parse(undefined);
const result2 = schema.parse(undefined);
expect(result1).not.toBe(result2);
expect(result1).toEqual(result2);
});
test("defaulted array schema returns shallow clone", () => {
const schema = z.array(z.string()).default(["x"]);
const result1 = schema.parse(undefined);
const result2 = schema.parse(undefined);
expect(result1).not.toBe(result2);
expect(result1).toEqual(result2);
});
test("direction-aware defaults", () => {
const schema = z.string().default("hello");
// Forward direction (regular parse): defaults should be applied
expect(schema.parse(undefined)).toBe("hello");
expect(schema.parse("hello")).toBe("hello");
// Reverse direction (encode): defaults should NOT be applied, undefined should fail validation
expect(() => z.encode(schema, undefined as any)).toThrow();
// But valid values should still work in reverse
expect(z.safeEncode(schema, "world")).toMatchInlineSnapshot(`
{
"data": "world",
"success": true,
}
`);
expect(z.safeEncode(schema, undefined as any)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});

View File

@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";
import * as z from "../index.js";
describe("z.describe() check", () => {
it("registers description in globalRegistry", () => {
const schema = z.string().check(z.describe("A string"));
expect(z.globalRegistry.get(schema)?.description).toBe("A string");
});
});
describe("z.meta() check", () => {
it("registers metadata in globalRegistry", () => {
const schema = z.number().check(z.meta({ title: "Age", description: "User's age" }));
const meta = z.globalRegistry.get(schema);
expect(meta?.title).toBe("Age");
expect(meta?.description).toBe("User's age");
});
});
describe("combined usage", () => {
it("works with multiple checks", () => {
const schema = z.string().check(z.describe("Email address"), z.meta({ title: "Email" }));
const meta = z.globalRegistry.get(schema);
expect(meta?.description).toBe("Email address");
expect(meta?.title).toBe("Email");
});
});

View File

@@ -0,0 +1,32 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
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", () => {
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("adding description with z.globalRegistry", () => {
const schema = z.string();
z.core.globalRegistry.add(schema, { description });
z.core.globalRegistry.get(schema);
expect(schema.description).toEqual(description);
});
// in Zod 4 descriptions are not inherited
// test("description should carry over to chained schemas", () => {
// const schema = z.string().describe(description);
// expect(schema.description).toEqual(description);
// expect(schema.optional().description).toEqual(description);
// expect(schema.optional().nullable().default("default").description).toEqual(description);
// });

View File

@@ -0,0 +1,661 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("_values", () => {
expect(z.string()._zod.values).toEqual(undefined);
expect(z.enum(["a", "b"])._zod.values).toEqual(new Set(["a", "b"]));
expect(z.nativeEnum({ a: "A", b: "B" })._zod.values).toEqual(new Set(["A", "B"]));
expect(z.literal("test")._zod.values).toEqual(new Set(["test"]));
expect(z.literal(123)._zod.values).toEqual(new Set([123]));
expect(z.literal(true)._zod.values).toEqual(new Set([true]));
expect(z.literal(BigInt(123))._zod.values).toEqual(new Set([BigInt(123)]));
expect(z.undefined()._zod.values).toEqual(new Set([undefined]));
expect(z.null()._zod.values).toEqual(new Set([null]));
const t = z.literal("test");
expect(t.optional()._zod.values).toEqual(new Set(["test", undefined]));
expect(t.nullable()._zod.values).toEqual(new Set(["test", null]));
expect(t.default("test")._zod.values).toEqual(new Set(["test"]));
expect(t.catch("test")._zod.values).toEqual(new Set(["test"]));
const pre = z.preprocess((val) => String(val), z.string()).pipe(z.literal("test"));
expect(pre._zod.values).toEqual(undefined);
const post = z.literal("test").transform((_) => Math.random());
expect(post._zod.values).toEqual(new Set(["test"]));
// Test that readonly literals pass through their values property
expect(z.literal("test").readonly()._zod.values).toEqual(new Set(["test"]));
});
test("valid parse - object", () => {
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 - include discriminator key (deprecated)", () => {
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 - optional discriminator (object)", () => {
const schema = z.discriminatedUnion("type", [
z.object({ type: z.literal("a").optional(), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
]);
expect(schema.parse({ type: "a", a: "abc" })).toEqual({ type: "a", a: "abc" });
expect(schema.parse({ a: "abc" })).toEqual({ a: "abc" });
});
test("valid - discriminator value of various primitive types", () => {
const schema = z.discriminatedUnion("type", [
z.object({ type: z.literal("1"), val: z.string() }),
z.object({ type: z.literal(1), val: z.string() }),
z.object({ type: z.literal(BigInt(1)), val: z.string() }),
z.object({ type: z.literal("true"), val: z.string() }),
z.object({ type: z.literal(true), val: z.string() }),
z.object({ type: z.literal("null"), val: z.string() }),
z.object({ type: z.null(), val: z.string() }),
z.object({ type: z.literal("undefined"), val: z.string() }),
z.object({ type: z.undefined(), val: z.string() }),
]);
expect(schema.parse({ type: "1", val: "val" })).toEqual({ type: "1", val: "val" });
expect(schema.parse({ type: 1, val: "val" })).toEqual({ type: 1, val: "val" });
expect(schema.parse({ type: BigInt(1), val: "val" })).toEqual({
type: BigInt(1),
val: "val",
});
expect(schema.parse({ type: "true", val: "val" })).toEqual({
type: "true",
val: "val",
});
expect(schema.parse({ type: true, val: "val" })).toEqual({
type: true,
val: "val",
});
expect(schema.parse({ type: "null", val: "val" })).toEqual({
type: "null",
val: "val",
});
expect(schema.parse({ type: null, val: "val" })).toEqual({
type: null,
val: "val",
});
expect(schema.parse({ type: "undefined", val: "val" })).toEqual({
type: "undefined",
val: "val",
});
expect(schema.parse({ type: undefined, val: "val" })).toEqual({
type: undefined,
val: "val",
});
const fail = schema.safeParse({
type: "not_a_key",
val: "val",
});
expect(fail.error).toBeInstanceOf(z.ZodError);
});
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) {
// [
// {
// code: z.ZodIssueCode.invalid_type,
// expected: z.ZodParsedType.object,
// input: null,
// message: "Expected object, received null",
// received: z.ZodParsedType.null,
// path: [],
// },
// ];
expect(e.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "object",
"message": "Invalid input: expected object, received null",
"path": [],
},
]
`);
}
});
test("invalid discriminator value", () => {
const result = z
.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
])
.safeParse({ type: "x", a: "abc" });
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_union",
"errors": [],
"note": "No matching discriminator",
"discriminator": "type",
"path": [
"type"
],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
test("invalid discriminator value - unionFallback", () => {
const result = z
.discriminatedUnion(
"type",
[z.object({ type: z.literal("a"), a: z.string() }), z.object({ type: z.literal("b"), b: z.string() })],
{ unionFallback: true }
)
.safeParse({ type: "x", a: "abc" });
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_union",
"errors": [
[
{
"code": "invalid_value",
"values": [
"a"
],
"path": [
"type"
],
"message": "Invalid input: expected \\"a\\""
}
],
[
{
"code": "invalid_value",
"values": [
"b"
],
"path": [
"type"
],
"message": "Invalid input: expected \\"b\\""
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"b"
],
"message": "Invalid input: expected string, received undefined"
}
]
],
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
test("valid discriminator value, invalid data", () => {
const result = z
.discriminatedUnion("type", [
z.object({ type: z.literal("a"), a: z.string() }),
z.object({ type: z.literal("b"), b: z.string() }),
])
.safeParse({ type: "a", b: "abc" });
// [
// {
// code: z.ZodIssueCode.invalid_type,
// expected: z.ZodParsedType.string,
// message: "Required",
// path: ["a"],
// received: z.ZodParsedType.undefined,
// },
// ];
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"a"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
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,
])._zod.propValues;
throw new Error();
} catch (e: any) {
expect(e.message.includes("Invalid discriminated union option")).toBe(true);
}
});
// removed to account for unions of unions
// 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("Duplicate discriminator value")).toEqual(true);
// }
// });
test("async - valid", async () => {
const schema = 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(),
}),
]);
const data = { type: "a", a: "1" };
const result = await schema.safeParseAsync(data);
expect(result.data).toEqual({ type: "a", a: 1 });
});
test("async - invalid", async () => {
// try {
const a = 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(),
}),
]);
const result = await a.safeParseAsync({ type: "a", a: 1 });
// expect(JSON.parse(e.message)).toEqual([
// {
// code: "invalid_type",
// expected: "string",
// input: 1,
// received: "number",
// path: ["a"],
// message: "Expected string, received number",
// },
// ]);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"a"
],
"message": "Invalid input: expected string, received number"
}
]]
`);
});
test("valid - literals with .default or .pipe", () => {
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.literal("bar").transform((val) => val),
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>;
expectTypeOf<schema>().toEqualTypeOf<{ key: "a" } | { key: "b" | "c" } | { key: MyEnum.d | MyEnum.e }>();
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<"asdfasdf">(),
// Add other properties specific to this option
}),
]);
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<{ key: "a" } | { key: "b" & z.core.$brand<"asdfasdf"> }>();
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>;
expectTypeOf<schema>().toEqualTypeOf<{ key?: "a" | undefined; a: true } | { key: "b" | null; b: 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("multiple discriminators", () => {
const FreeConfig = z.object({
type: z.literal("free"),
min_cents: z.null(),
});
// console.log(FreeConfig.shape.type);
const PricedConfig = z.object({
type: z.literal("fiat-price"),
// min_cents: z.int().nullable(),
min_cents: z.null(),
});
const Config = z.discriminatedUnion("type", [FreeConfig, PricedConfig]);
Config.parse({
min_cents: null,
type: "fiat-price",
name: "Standard",
});
expect(() => {
Config.parse({
min_cents: null,
type: "not real",
name: "Standard",
});
}).toThrow();
});
test("single element union", () => {
const schema = z.object({
a: z.literal("discKey"),
b: z.enum(["apple", "banana"]),
c: z.object({ id: z.string() }),
});
const input = {
a: "discKey",
b: "apple",
c: {}, // Invalid, as schema requires `id` property
};
// Validation must fail here, but it doesn't
const u = z.discriminatedUnion("a", [schema]);
const result = u.safeParse(input);
expect(result).toMatchObject({ success: false });
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"c",
"id"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
expect(u.options.length).toEqual(1);
});
test("nested discriminated unions", () => {
const BaseError = z.object({ status: z.literal("failed"), message: z.string() });
const MyErrors = z.discriminatedUnion("code", [
BaseError.extend({ code: z.literal(400) }),
BaseError.extend({ code: z.literal(401) }),
BaseError.extend({ code: z.literal(500) }),
]);
const MyResult = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
MyErrors,
]);
expect(MyErrors._zod.propValues).toMatchInlineSnapshot(`
{
"code": Set {
400,
401,
500,
},
"status": Set {
"failed",
},
}
`);
expect(MyResult._zod.propValues).toMatchInlineSnapshot(`
{
"code": Set {
400,
401,
500,
},
"status": Set {
"success",
"failed",
},
}
`);
const result = MyResult.parse({ status: "success", data: "hello" });
expect(result).toMatchInlineSnapshot(`
{
"data": "hello",
"status": "success",
}
`);
const result2 = MyResult.parse({ status: "failed", code: 400, message: "bad request" });
expect(result2).toMatchInlineSnapshot(`
{
"code": 400,
"message": "bad request",
"status": "failed",
}
`);
const result3 = MyResult.parse({ status: "failed", code: 401, message: "unauthorized" });
expect(result3).toMatchInlineSnapshot(`
{
"code": 401,
"message": "unauthorized",
"status": "failed",
}
`);
const result4 = MyResult.parse({ status: "failed", code: 500, message: "internal server error" });
expect(result4).toMatchInlineSnapshot(`
{
"code": 500,
"message": "internal server error",
"status": "failed",
}
`);
});
test("readonly literal discriminator", () => {
const discUnion = z.discriminatedUnion("type", [
z.object({ type: z.literal("a").readonly(), a: z.string() }),
z.object({ type: z.literal("b"), b: z.number() }),
]);
// Test that both discriminator values are correctly included in propValues
const propValues = discUnion._zod.propValues;
expect(propValues?.type?.has("a")).toBe(true);
expect(propValues?.type?.has("b")).toBe(true);
// Test that the discriminated union works correctly
const result1 = discUnion.parse({ type: "a", a: "hello" });
expect(result1).toEqual({ type: "a", a: "hello" });
const result2 = discUnion.parse({ type: "b", b: 42 });
expect(result2).toEqual({ type: "b", b: 42 });
// Test that invalid discriminator values are rejected
expect(() => {
discUnion.parse({ type: "c", a: "hello" });
}).toThrow();
});
test("pipes", () => {
const schema = z
.object({
type: z.literal("foo"),
})
.transform((s) => ({ ...s, v: 2 }));
expect(schema._zod.propValues).toMatchInlineSnapshot(`
{
"type": Set {
"foo",
},
}
`);
const schema2 = z.object({
type: z.literal("bar"),
});
const combinedSchema = z.discriminatedUnion("type", [schema, schema2], {
unionFallback: false,
});
combinedSchema.parse({
type: "foo",
v: 2,
});
});
test("def", () => {
const schema = z.discriminatedUnion(
"type",
[z.object({ type: z.literal("play") }), z.object({ type: z.literal("pause") })],
{ unionFallback: true }
);
expect(schema.def).toBeDefined();
expect(schema.def.discriminator).toEqual("type");
expect(schema.def.unionFallback).toEqual(true);
});

View File

@@ -0,0 +1,285 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("enum from string array", () => {
const MyEnum = z.enum(["Red", "Green", "Blue"]);
expect(MyEnum.enum.Red).toEqual("Red");
type MyEnum = z.infer<typeof MyEnum>;
expectTypeOf<MyEnum>().toEqualTypeOf<"Red" | "Green" | "Blue">();
});
test("enum from const object", () => {
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);
expectTypeOf<fruitEnum>().toEqualTypeOf<"apple" | "banana">();
});
test("enum from native enum", () => {
enum Fruits {
Apple = "apple",
Banana = "banana",
Orange = 3,
}
// @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);
expect(fruitEnum.safeParse("Apple").success).toEqual(false);
expect(fruitEnum.safeParse("Cantaloupe").success).toEqual(false);
expectTypeOf<fruitEnum>().toMatchTypeOf<Fruits>();
expectTypeOf<Fruits>().toMatchTypeOf<fruitEnum>();
});
test("enum from native enum 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);
expectTypeOf<fruitEnum>().toEqualTypeOf<10 | 20>();
});
test("issue metadata", () => {
const schema = z.enum(["Red", "Green", "Blue"]);
const result = schema.safeParse("Yellow");
expect(result.error!.issues[0]).toMatchInlineSnapshot(`
{
"code": "invalid_value",
"message": "Invalid option: expected one of "Red"|"Green"|"Blue"",
"path": [],
"values": [
"Red",
"Green",
"Blue",
],
}
`);
});
test("enum from non-const inputs", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"];
const FoodEnum = z.enum(foods);
expectTypeOf<z.infer<typeof FoodEnum>>().toEqualTypeOf<string>();
expect(FoodEnum.safeParse("Pasta").success).toEqual(true);
expect(FoodEnum.safeParse("Cucumbers").success).toEqual(false);
});
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>;
expectTypeOf<arg>().toEqualTypeOf<"200" | "201">();
arg.parse("201");
expect(() => arg.parse("202")).toThrow();
});
test("error map", () => {
const result = z
.enum(["test"], { error: (iss) => (iss.input === undefined ? "REQUIRED" : undefined) })
.safeParse(undefined);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].message).toEqual("REQUIRED");
}
});
test("type signatures", () => {
const a = z.enum(["a", "b", "c"]);
const b = z.enum(a.options);
expectTypeOf(a).toEqualTypeOf(b);
const c = z.enum({ a: 1, b: 2 } as const);
expectTypeOf(c.enum).toEqualTypeOf<{
readonly a: 1;
readonly b: 2;
}>();
enum Fruit {
Apple = "apple",
Banana = "banana",
Orange = "orange",
}
const d = z.enum(Fruit);
expectTypeOf(d.enum).toEqualTypeOf(Fruit);
const e = z.enum({ a: 1, b: 2 });
expectTypeOf(e.enum).toEqualTypeOf<{
readonly a: 1;
readonly b: 2;
}>();
});
test("extract", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods);
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
expect(ItalianEnum.safeParse("Pasta").success).toEqual(true);
expect(ItalianEnum.safeParse("Tacos").success).toEqual(false);
expectTypeOf<z.infer<typeof ItalianEnum>>().toEqualTypeOf<"Pasta" | "Pizza">();
});
test("exclude", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods);
const UnhealthyEnum = FoodEnum.exclude(["Salad"]);
expect(UnhealthyEnum.safeParse("Pasta").success).toEqual(true);
expect(UnhealthyEnum.safeParse("Salad").success).toEqual(false);
expectTypeOf<z.infer<typeof UnhealthyEnum>>().toEqualTypeOf<"Pasta" | "Pizza" | "Tacos" | "Burgers">();
const EmptyFoodEnum = FoodEnum.exclude(foods);
expectTypeOf<typeof EmptyFoodEnum>().toEqualTypeOf<z.ZodEnum<{}>>();
expectTypeOf<z.infer<typeof EmptyFoodEnum>>().toEqualTypeOf<never>();
});
test("error map inheritance", () => {
const foods = ["Pasta", "Pizza", "Tacos", "Burgers", "Salad"] as const;
const FoodEnum = z.enum(foods, { error: () => "This is not food!" });
const ItalianEnum = FoodEnum.extract(["Pasta", "Pizza"]);
const foodsError = FoodEnum.safeParse("Cucumbers");
const italianError = ItalianEnum.safeParse("Tacos");
expect(foodsError.error!.issues[0].message).toEqual(italianError.error!.issues[0].message);
const UnhealthyEnum = FoodEnum.exclude(["Salad"], {
error: () => ({ 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", () => {
type _a = z.ZodEnum<{ readonly a: "a"; readonly b: "b" }>;
type _b = z.ZodEnum<{ a: "a"; b: "b" }>;
});
test("enum error message, invalid enum elementstring", () => {
const result = z.enum(["Tuna", "Trout"]).safeParse("Salmon");
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna",
"Trout"
],
"path": [],
"message": "Invalid option: expected one of \\"Tuna\\"|\\"Trout\\""
}
]]
`);
});
test("enum error message, invalid type", () => {
const result = z.enum(["Tuna", "Trout"]).safeParse(12);
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
// expect(result.error!.issues[0].message).toEqual('Invalid input: expected one of "Tuna"|"Trout"');
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna",
"Trout"
],
"path": [],
"message": "Invalid option: expected one of \\"Tuna\\"|\\"Trout\\""
}
]]
`);
});
test("nativeEnum default error message", () => {
enum Fish {
Tuna = "Tuna",
Trout = "Trout",
}
const result = z.nativeEnum(Fish).safeParse("Salmon");
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
// expect(result.error!.issues[0].message).toEqual('Invalid input: expected one of "Tuna"|"Trout"');
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna",
"Trout"
],
"path": [],
"message": "Invalid option: expected one of \\"Tuna\\"|\\"Trout\\""
}
]]
`);
});
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("enum with diagonal keys", () => {
const schema_02 = z.enum({
A: 1,
B: "A",
});
expect(schema_02.safeParse("A")).toMatchObject({ success: true });
});

View File

@@ -0,0 +1,595 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
declare const iss: z.core.$ZodIssueCode;
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 = core.inferFlattenedErrors<typeof Test, { message: string; code: number }>;
// type TestFormErrors = core.inferFlattenedErrors<typeof Test>;
const parsed = Test.safeParse({});
test("regular error", () => {
expect(parsed).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
"f1"
],
"message": "Invalid input: expected number, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"f3"
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "array",
"code": "invalid_type",
"path": [
"f4"
],
"message": "Invalid input: expected array, received undefined"
}
]],
"success": false,
}
`);
});
test(".flatten()", () => {
const flattened = parsed.error!.flatten();
// flattened.
expectTypeOf(flattened).toMatchTypeOf<{
formErrors: string[];
fieldErrors: {
f2?: string[];
f1?: string[];
f3?: string[];
f4?: string[];
};
}>();
expect(flattened).toMatchInlineSnapshot(`
{
"fieldErrors": {
"f1": [
"Invalid input: expected number, received undefined",
],
"f3": [
"Invalid input: expected string, received undefined",
],
"f4": [
"Invalid input: expected array, received undefined",
],
},
"formErrors": [],
}
`);
});
test("custom .flatten()", () => {
type ErrorType = { message: string; code: number };
const flattened = parsed.error!.flatten((iss) => ({ message: iss.message, code: 1234 }));
expectTypeOf(flattened).toMatchTypeOf<{
formErrors: ErrorType[];
fieldErrors: {
f2?: ErrorType[];
f1?: ErrorType[];
f3?: ErrorType[];
f4?: ErrorType[];
};
}>();
expect(flattened).toMatchInlineSnapshot(`
{
"fieldErrors": {
"f1": [
{
"code": 1234,
"message": "Invalid input: expected number, received undefined",
},
],
"f3": [
{
"code": 1234,
"message": "Invalid input: expected string, received undefined",
},
],
"f4": [
{
"code": 1234,
"message": "Invalid input: expected array, received undefined",
},
],
},
"formErrors": [],
}
`);
});
test(".format()", () => {
const formatted = parsed.error!.format();
expectTypeOf(formatted).toMatchTypeOf<{
_errors: string[];
f2?: { _errors: string[] };
f1?: { _errors: string[] };
f3?: { _errors: string[] };
f4?: {
[x: number]: {
_errors: string[];
t?: {
_errors: string[];
};
};
_errors: string[];
};
}>();
expect(formatted).toMatchInlineSnapshot(`
{
"_errors": [],
"f1": {
"_errors": [
"Invalid input: expected number, received undefined",
],
},
"f3": {
"_errors": [
"Invalid input: expected string, received undefined",
],
},
"f4": {
"_errors": [
"Invalid input: expected array, received undefined",
],
},
}
`);
});
test("custom .format()", () => {
type ErrorType = { message: string; code: number };
const formatted = parsed.error!.format((iss) => ({ message: iss.message, code: 1234 }));
expectTypeOf(formatted).toMatchTypeOf<{
_errors: ErrorType[];
f2?: { _errors: ErrorType[] };
f1?: { _errors: ErrorType[] };
f3?: { _errors: ErrorType[] };
f4?: {
[x: number]: {
_errors: ErrorType[];
t?: {
_errors: ErrorType[];
};
};
_errors: ErrorType[];
};
}>();
expect(formatted).toMatchInlineSnapshot(`
{
"_errors": [],
"f1": {
"_errors": [
{
"code": 1234,
"message": "Invalid input: expected number, received undefined",
},
],
},
"f3": {
"_errors": [
{
"code": 1234,
"message": "Invalid input: expected string, received undefined",
},
],
},
"f4": {
"_errors": [
{
"code": 1234,
"message": "Invalid input: expected array, received undefined",
},
],
},
}
`);
});
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" }
);
const r1 = schema.safeParse({
a: "asdf",
b: "qwer",
});
expect(z.core.flattenError(r1.error!)).toEqual({
formErrors: ["Must be equal"],
fieldErrors: {},
});
const r2 = schema.safeParse({
a: null,
b: null,
});
// const error = _error as z.ZodError;
expect(z.core.flattenError(r2.error!)).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
"Invalid input: expected string, received null",
],
"b": [
"Invalid input: expected string, received null",
],
},
"formErrors": [],
}
`);
expect(z.core.flattenError(r2.error!, (iss) => iss.message.toUpperCase())).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
"INVALID INPUT: EXPECTED STRING, RECEIVED NULL",
],
"b": [
"INVALID INPUT: EXPECTED STRING, RECEIVED NULL",
],
},
"formErrors": [],
}
`);
// Test identity
expect(z.core.flattenError(r2.error!, (i: z.ZodIssue) => i)).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received null",
"path": [
"a",
],
},
],
"b": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received null",
"path": [
"b",
],
},
],
},
"formErrors": [],
}
`);
// Test mapping
const f1 = z.core.flattenError(r2.error!, (i: z.ZodIssue) => i.message.length);
expect(f1).toMatchInlineSnapshot(`
{
"fieldErrors": {
"a": [
45,
],
"b": [
45,
],
},
"formErrors": [],
}
`);
// expect(f1.fieldErrors.a![0]).toEqual("Invalid input: expected string".length);
// expect(f1).toMatchObject({
// formErrors: [],
// fieldErrors: {
// a: ["Invalid input: expected string".length],
// b: ["Invalid input: expected string".length],
// },
// });
});
const schema = z.strictObject({
username: z.string().brand<"username">(),
favoriteNumbers: z.array(z.number()),
nesting: z.object({
a: z.string(),
}),
});
const result = schema.safeParse({
username: 1234,
favoriteNumbers: [1234, "4567"],
nesting: {
a: 123,
},
extra: 1234,
});
const tree = z.treeifyError(result.error!);
expectTypeOf(tree).toEqualTypeOf<{
errors: string[];
properties?: {
username?: {
errors: string[];
};
favoriteNumbers?: {
errors: string[];
items?: {
errors: string[];
}[];
};
nesting?: {
errors: string[];
properties?: {
a?: {
errors: string[];
};
};
};
};
}>();
test("z.treeifyError", () => {
expect(tree).toMatchInlineSnapshot(`
{
"errors": [
"Unrecognized key: "extra"",
],
"properties": {
"favoriteNumbers": {
"errors": [],
"items": [
,
{
"errors": [
"Invalid input: expected number, received string",
],
},
],
},
"nesting": {
"errors": [],
"properties": {
"a": {
"errors": [
"Invalid input: expected string, received number",
],
},
},
},
"username": {
"errors": [
"Invalid input: expected string, received number",
],
},
},
}
`);
});
test("z.treeifyError 2", () => {
const schema = z.strictObject({
name: z.string(),
logLevel: z.union([z.string(), z.number()]),
env: z.literal(["production", "development"]),
});
const data = {
name: 1000,
logLevel: false,
extra: 1000,
};
const result = schema.safeParse(data);
const err = z.treeifyError(result.error!);
expect(err).toMatchInlineSnapshot(`
{
"errors": [
"Unrecognized key: "extra"",
],
"properties": {
"env": {
"errors": [
"Invalid option: expected one of "production"|"development"",
],
},
"logLevel": {
"errors": [
"Invalid input: expected string, received boolean",
"Invalid input: expected number, received boolean",
],
},
"name": {
"errors": [
"Invalid input: expected string, received number",
],
},
},
}
`);
});
test("z.prettifyError", () => {
expect(z.prettifyError(result.error!)).toMatchInlineSnapshot(`
"✖ Unrecognized key: "extra"
✖ Invalid input: expected string, received number
→ at username
✖ Invalid input: expected number, received string
→ at favoriteNumbers[1]
✖ Invalid input: expected string, received number
→ at nesting.a"
`);
});
test("z.toDotPath", () => {
expect(z.core.toDotPath(["a", "b", 0, "c"])).toMatchInlineSnapshot(`"a.b[0].c"`);
expect(z.core.toDotPath(["a", Symbol("b"), 0, "c"])).toMatchInlineSnapshot(`"a["Symbol(b)"][0].c"`);
// Test with periods in keys
expect(z.core.toDotPath(["user.name", "first.last"])).toMatchInlineSnapshot(`"["user.name"]["first.last"]"`);
// Test with special characters
expect(z.core.toDotPath(["user", "$special", Symbol("#symbol")])).toMatchInlineSnapshot(
`"user.$special["Symbol(#symbol)"]"`
);
// Test with dots and quotes
expect(z.core.toDotPath(["search", `query("foo.bar"="abc")`])).toMatchInlineSnapshot(
`"search["query(\\"foo.bar\\"=\\"abc\\")"]"`
);
// Test with newlines
expect(z.core.toDotPath(["search", `foo\nbar`])).toMatchInlineSnapshot(`"search["foo\\nbar"]"`);
// Test with empty strings
expect(z.core.toDotPath(["", "empty"])).toMatchInlineSnapshot(`".empty"`);
// Test with array indices
expect(z.core.toDotPath(["items", 0, 1, 2])).toMatchInlineSnapshot(`"items[0][1][2]"`);
// Test with mixed path elements
expect(z.core.toDotPath(["users", "user.config", 0, "settings.theme"])).toMatchInlineSnapshot(
`"users["user.config"][0]["settings.theme"]"`
);
// Test with square brackets in keys
expect(z.core.toDotPath(["data[0]", "value"])).toMatchInlineSnapshot(`"["data[0]"].value"`);
// Test with empty path
expect(z.core.toDotPath([])).toMatchInlineSnapshot(`""`);
});
test("inheritance", () => {
const e1 = new z.ZodError([]);
expect(e1).toBeInstanceOf(z.core.$ZodError);
expect(e1).toBeInstanceOf(z.ZodError);
// expect(e1).not.toBeInstanceOf(Error);
const e2 = new z.ZodRealError([]);
expect(e2).toBeInstanceOf(z.ZodError);
expect(e2).toBeInstanceOf(z.ZodRealError);
expect(e2).toBeInstanceOf(Error);
});
test("disc union treeify/format", () => {
const schema = z.discriminatedUnion(
"foo",
[
z.object({
foo: z.literal("x"),
x: z.string(),
}),
z.object({
foo: z.literal("y"),
y: z.string(),
}),
],
{
error: "Invalid discriminator",
}
);
const error = schema.safeParse({ foo: "invalid" }).error;
expect(z.treeifyError(error!)).toMatchInlineSnapshot(`
{
"errors": [],
"properties": {
"foo": {
"errors": [
"Invalid discriminator",
],
},
},
}
`);
expect(z.prettifyError(error!)).toMatchInlineSnapshot(`
"✖ Invalid discriminator
→ at foo"
`);
expect(z.formatError(error!)).toMatchInlineSnapshot(`
{
"_errors": [],
"foo": {
"_errors": [
"Invalid discriminator",
],
},
}
`);
});
test("update message after adding issues", () => {
const e = new z.ZodError([]);
e.addIssue({
code: "custom",
message: "message",
input: "asdf",
path: [],
});
expect(e.message).toMatchInlineSnapshot(`
"[
{
"code": "custom",
"message": "message",
"input": "asdf",
"path": []
}
]"
`);
e.addIssue({
code: "custom",
message: "message",
input: "asdf",
path: [],
});
expect(e.message).toMatchInlineSnapshot(`
"[
{
"code": "custom",
"message": "message",
"input": "asdf",
"path": []
},
{
"code": "custom",
"message": "message",
"input": "asdf",
"path": []
}
]"
`);
});

View File

@@ -0,0 +1,711 @@
import { inspect } from "node:util";
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("error creation", () => {
const err1 = new z.ZodError([]);
err1.issues.push({
code: "invalid_type",
expected: "object",
path: [],
message: "",
input: "adf",
});
err1.isEmpty;
const err2 = new z.ZodError(err1.issues);
const err3 = new z.ZodError([]);
err3.addIssues(err1.issues);
err3.addIssue(err1.issues[0]);
err1.message;
err2.message;
err3.message;
});
test("do not allow error and message together", () => {
expect(() =>
z.string().refine((_) => true, {
message: "override",
error: (iss) => (iss.input === undefined ? "asdf" : null),
})
).toThrow();
});
const errorMap: z.ZodErrorMap = (issue) => {
if (issue.code === "invalid_type") {
if (issue.expected === "string") {
return { message: "bad type!" };
}
}
if (issue.code === "custom") {
return { message: `less-than-${issue.params?.minimum}` };
}
return undefined;
};
test("type error with custom error map", () => {
const result = z.string().safeParse(234, { error: errorMap });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "bad type!"
}
]]
`);
});
test("refinement fail with params", () => {
const result = z
.number()
.refine((val) => val >= 3, {
params: { minimum: 3 },
})
.safeParse(2, { error: errorMap });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"params": {
"minimum": 3
},
"message": "less-than-3"
}
]]
`);
});
test("hard coded error with custom errormap", () => {
const result = z
.string()
.refine((val) => val.length > 12, {
params: { minimum: 13 },
message: "override",
})
.safeParse("asdf", { error: () => "contextual" });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"params": {
"minimum": 13
},
"message": "override"
}
]]
`);
});
test("default error message", () => {
const result = z
.number()
.refine((x) => x > 3)
.safeParse(2);
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});
test("override error in refine", () => {
const result = z
.number()
.refine((x) => x > 3, "override")
.safeParse(2);
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "override"
}
]]
`);
});
test("override error in refinement", () => {
const result = z
.number()
.refine((x) => x > 3, {
message: "override",
})
.safeParse(2);
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "override"
}
]]
`);
});
test("array minimum", () => {
let result = z.array(z.string()).min(3, "tooshort").safeParse(["asdf", "qwer"]);
expect(result.success).toBe(false);
expect(result.error!.issues[0].code).toEqual("too_small");
expect(result.error!.issues[0].message).toEqual("tooshort");
result = z.array(z.string()).min(3).safeParse(["asdf", "qwer"]);
expect(result.success).toBe(false);
expect(result.error!.issues[0].code).toEqual("too_small");
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "array",
"code": "too_small",
"minimum": 3,
"inclusive": true,
"path": [],
"message": "Too small: expected array to have >=3 items"
}
]]
`);
});
test("literal bigint default error message", () => {
const result = z.literal(BigInt(12)).safeParse(BigInt(13));
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"12"
],
"path": [],
"message": "Invalid input: expected 12n"
}
]]
`);
});
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 = (issue) => {
expect((issue.path ?? []).length).toBe(2);
return { message: "doesnt matter" };
};
const result = schema.safeParse({ items: ["first"] }, { error: errorMap });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"items",
"items-too-few"
],
"message": "doesnt matter"
}
]]
`);
});
// 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).toBe(false);
expect(result.error!.format()).toMatchInlineSnapshot(`
{
"_errors": [
"Invalid email address",
],
}
`);
});
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).toBe(false);
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).toBe(false);
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).toBe(false);
expect(result1.error!.issues.length).toEqual(2);
});
test("detect issue with input fallback", () => {
const schema = z
.string()
.transform((val) => val.length)
.refine(() => false, { message: "always fails" })
.refine(
(val) => {
if (typeof val !== "number") throw new Error();
return (val ^ 2) > 10;
} // should be number but it's a string
);
expect(() => schema.parse("hello")).toThrow(z.ZodError);
});
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).toBe(false);
expect(result2.success).toBe(false);
const error1 = result1.error!.format();
expect(error1._errors).toEqual([]);
expect(error1.inner?._errors).toEqual([]);
expect(error1.inner?.name?.[1]).toEqual(undefined);
type FormattedError = z.inferFormattedError<typeof schema>;
const error2: FormattedError = result2.error!.format();
expect(error2._errors).toEqual([]);
expect(error2.inner?._errors).toEqual([]);
expect(error2.inner?.name?._errors).toEqual(["Invalid input"]);
expect(error2.inner?.name?.[0]).toEqual(undefined);
expect(error2.inner?.name?.[1]).toEqual(undefined);
expect(error2.inner?.name?.[2]).toEqual(undefined);
// test custom mapper
type FormattedErrorWithNumber = z.inferFormattedError<typeof schema, number>;
const errorWithNumber: FormattedErrorWithNumber = result2.error!.format(() => 5);
expect(errorWithNumber._errors).toEqual([]);
expect(errorWithNumber.inner?._errors).toEqual([]);
expect(errorWithNumber.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).toBe(false);
const error: z.inferFormattedError<typeof schema> = 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"]);
expect(error).toMatchInlineSnapshot(`
{
"_errors": [],
"nullableArray": {
"0": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
"nullableObject": {
"_errors": [],
"name": {
"_errors": [
"Invalid input",
],
},
},
"nullableTuple": {
"0": {
"_errors": [
"Invalid input",
],
},
"1": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
"optionalArray": {
"0": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
"optionalObject": {
"_errors": [],
"name": {
"_errors": [
"Invalid input",
],
},
},
"optionalTuple": {
"0": {
"_errors": [
"Invalid input",
],
},
"1": {
"_errors": [
"Invalid input",
],
},
"_errors": [],
},
}
`);
});
test("inferFlattenedErrors", () => {
const schemaWithTransform = z.object({ foo: z.string() }).transform((o) => ({ bar: o.foo }));
const result = schemaWithTransform.safeParse({});
expect(result.success).toBe(false);
type ValidationErrors = z.inferFlattenedErrors<typeof schemaWithTransform>;
const error: ValidationErrors = result.error!.flatten();
expect(error).toMatchInlineSnapshot(`
{
"fieldErrors": {
"foo": [
"Invalid input: expected string, received undefined",
],
},
"formErrors": [],
}
`);
});
const stringWithCustomError = z.string({
error: () => "bound",
});
test("schema-bound error map", () => {
const result = stringWithCustomError.safeParse(1234);
expect(result.success).toBe(false);
expect(result.error!.issues[0].message).toEqual("bound");
});
test("bound error map overrides contextual", () => {
// support contextual override
const result = stringWithCustomError.safeParse(undefined, {
error: () => ({ message: "override" }),
});
expect(result.success).toBe(false);
expect(result.error!.issues[0].message).toEqual("bound");
});
test("z.config customError ", () => {
// support overrideErrorMap
z.config({ customError: () => ({ message: "override" }) });
const result = stringWithCustomError.min(10).safeParse("tooshort");
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 10,
"inclusive": true,
"path": [],
"message": "override"
}
]]
`);
expect(result.error!.issues[0].message).toEqual("override");
z.config({ customError: undefined });
});
// 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).toBe(false);
// if (!result1.success) {
// expect(result1.error.issues[0].message).toEqual("Invalid name");
// }
// const result2 = str.safeParse(undefined);
// expect(result2.success).toBe(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).toBe(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).toBe(false);
// if (!result.success) {
// expect(result.error.issues[0].message).toEqual(errorMsg);
// }
// });
test("empty string error message", () => {
const schema = z.string().max(1, { message: "" });
const result = schema.safeParse("asdf");
expect(result.success).toBe(false);
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" });
expect(result.success).toBe(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 6,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=6 characters"
},
{
"code": "custom",
"path": [
"confirm"
],
"message": "Passwords don't match"
}
]]
`);
// expect(result.error!.issues.length).toEqual(2);
});
test("string error params", () => {
const a = z.string("Bad!");
expect(a.safeParse(123).error!.issues[0].message).toBe("Bad!");
const b = z.string().min(5, "Too short!");
expect(b.safeParse("abc").error!.issues[0].message).toBe("Too short!");
const c = z.uuid("Bad UUID!");
expect(c.safeParse("not-a-uuid").error!.issues[0].message).toBe("Bad UUID!");
const d = z.string().datetime({ message: "Bad date!" });
expect(d.safeParse("not-a-date").error!.issues[0].message).toBe("Bad date!");
const e = z.array(z.string(), "Bad array!");
expect(e.safeParse("not-an-array").error!.issues[0].message).toBe("Bad array!");
const f = z.array(z.string()).min(5, "Too few items!");
expect(f.safeParse(["a", "b"]).error!.issues[0].message).toBe("Too few items!");
const g = z.set(z.string(), "Bad set!");
expect(g.safeParse("not-a-set").error!.issues[0].message).toBe("Bad set!");
const h = z.array(z.string(), "Bad array!");
expect(h.safeParse(123).error!.issues[0].message).toBe("Bad array!");
const i = z.set(z.string(), "Bad set!");
expect(i.safeParse(123).error!.issues[0].message).toBe("Bad set!");
const j = z.array(z.string(), "Bad array!");
expect(j.safeParse(null).error!.issues[0].message).toBe("Bad array!");
});
test("error inheritance", () => {
const e1 = z.string().safeParse(123).error!;
expect(e1).toBeInstanceOf(z.core.$ZodError);
expect(e1).toBeInstanceOf(z.ZodError);
expect(e1).toBeInstanceOf(z.ZodRealError);
// expect(e1).not.toBeInstanceOf(Error);
try {
z.string().parse(123);
} catch (e2) {
expect(e1).toBeInstanceOf(z.core.$ZodError);
expect(e2).toBeInstanceOf(z.ZodError);
expect(e2).toBeInstanceOf(z.ZodRealError);
// expect(e2).toBeInstanceOf(Error);
}
});
test("error serialization", () => {
try {
z.string().parse(123);
} catch (e) {
expect(e).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]]
`);
expect(inspect(e).split("\n").slice(0, 8).join("\n")).toMatchInlineSnapshot(`
"ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]"
`);
}
});

View File

@@ -0,0 +1,96 @@
import { File as WebFile } from "@web-std/file";
import { afterEach, beforeEach, expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const minCheck = z.file().min(5);
const maxCheck = z.file().max(8);
const mimeCheck = z.file().mime(["text/plain", "application/json"]);
const originalFile = global.File;
beforeEach(async () => {
if (!globalThis.File) globalThis.File = WebFile;
});
afterEach(() => {
if (globalThis.File !== originalFile) {
globalThis.File = originalFile;
}
});
test("passing validations", () => {
minCheck.safeParse(new File(["12345"], "test.txt"));
maxCheck.safeParse(new File(["12345678"], "test.txt"));
mimeCheck.safeParse(new File([""], "test.csv", { type: "text/plain" }));
expect(() => mimeCheck.parse(new File([""], "test.txt"))).toThrow();
expect(() => mimeCheck.parse(new File([""], "test.txt", { type: "text/csv" }))).toThrow();
});
test("types", () => {
expectTypeOf(z.file().parse(new File([], "test.txt"))).toEqualTypeOf(new File([], "test.txt"));
});
test("failing validations", () => {
expect(minCheck.safeParse(new File(["1234"], "test.txt"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "file",
"code": "too_small",
"minimum": 5,
"inclusive": true,
"path": [],
"message": "Too small: expected file to have >=5 bytes"
}
]],
"success": false,
}
`);
expect(maxCheck.safeParse(new File(["123456789"], "test.txt"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "file",
"code": "too_big",
"maximum": 8,
"inclusive": true,
"path": [],
"message": "Too big: expected file to have <=8 bytes"
}
]],
"success": false,
}
`);
expect(mimeCheck.safeParse(new File([""], "test.csv"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_value",
"values": [
"text/plain",
"application/json"
],
"path": [],
"message": "Invalid option: expected one of \\"text/plain\\"|\\"application/json\\""
}
]],
"success": false,
}
`);
expect(mimeCheck.safeParse(new File([""], "test.csv", { type: "text/csv" }))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_value",
"values": [
"text/plain",
"application/json"
],
"path": [],
"message": "Invalid option: expected one of \\"text/plain\\"|\\"application/json\\""
}
]],
"success": false,
}
`);
});

View File

@@ -0,0 +1,179 @@
import { expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import type * as core from "zod/v4/core";
test("first party switch", () => {
const myType = z.string() as core.$ZodTypes;
const def = myType._zod.def;
switch (def.type) {
case "string":
break;
case "number":
break;
case "bigint":
break;
case "boolean":
break;
case "date":
break;
case "symbol":
break;
case "undefined":
break;
case "null":
break;
case "any":
break;
case "unknown":
break;
case "never":
break;
case "void":
break;
case "array":
break;
case "object":
break;
case "union":
break;
case "intersection":
break;
case "tuple":
break;
case "record":
break;
case "map":
break;
case "set":
break;
case "literal":
break;
case "enum":
break;
case "promise":
break;
case "optional":
break;
case "nonoptional":
break;
case "nullable":
break;
case "default":
break;
case "prefault":
break;
case "template_literal":
break;
case "custom":
break;
case "transform":
break;
case "readonly":
break;
case "nan":
break;
case "pipe":
break;
case "success":
break;
case "catch":
break;
case "file":
break;
case "lazy":
break;
case "function":
break;
default:
expectTypeOf(def).toEqualTypeOf<never>();
}
});
test("$ZodSchemaTypes", () => {
const type = "string" as core.$ZodTypeDef["type"];
switch (type) {
case "string":
break;
case "number":
break;
case "int":
break;
case "bigint":
break;
case "boolean":
break;
case "date":
break;
case "symbol":
break;
case "undefined":
break;
case "null":
break;
case "any":
break;
case "unknown":
break;
case "never":
break;
case "void":
break;
case "array":
break;
case "object":
break;
case "union":
break;
case "intersection":
break;
case "tuple":
break;
case "record":
break;
case "map":
break;
case "set":
break;
case "literal":
break;
case "enum":
break;
case "promise":
break;
case "optional":
break;
case "nonoptional":
break;
case "nullable":
break;
case "default":
break;
case "prefault":
break;
case "template_literal":
break;
case "custom":
break;
case "transform":
break;
case "readonly":
break;
case "nan":
break;
case "pipe":
break;
case "success":
break;
case "catch":
break;
case "file":
break;
case "lazy":
break;
case "function":
break;
default:
expectTypeOf(type).toEqualTypeOf<never>();
}
});

View File

@@ -0,0 +1,26 @@
import { expect, test } from "vitest";
import { type infer as _infer, json, nullable, object, pipe, transform } from "../../mini/index.js";
// biome-ignore lint/correctness/noUnusedImports: This import verifies the type is exported
import type { _ZodMiniJSONSchema } from "../../mini/schemas.js";
const DataType = object({
data: json(),
});
type DataType = _infer<typeof DataType>;
// biome-ignore lint/suspicious/noExportsInTest: This export is required to reproduce TS4023
export const Container = object({
contained: pipe(
nullable(DataType),
transform<DataType | null>(
(v) =>
v ?? {
data: "",
}
)
),
});
test("issue reproduction should compile without type errors", () => {
expect(Container).toBeDefined();
});

View File

@@ -0,0 +1,734 @@
import { expect, test } from "vitest";
import { fromJSONSchema } from "../from-json-schema.js";
import * as z from "../index.js";
test("basic string schema", () => {
const schema = fromJSONSchema({ type: "string" });
expect(schema.parse("hello")).toBe("hello");
expect(() => schema.parse(123)).toThrow();
});
test("string with constraints", () => {
const schema = fromJSONSchema({
type: "string",
minLength: 3,
maxLength: 10,
pattern: "^[a-z]+$",
});
expect(schema.parse("hello")).toBe("hello");
expect(schema.parse("helloworld")).toBe("helloworld"); // exactly 10 chars - valid
expect(() => schema.parse("hi")).toThrow(); // too short
expect(() => schema.parse("helloworld1")).toThrow(); // too long (11 chars)
expect(() => schema.parse("Hello")).toThrow(); // pattern mismatch
});
test("pattern is not implicitly anchored", () => {
// JSON Schema patterns match anywhere in the string, not just the full string
const schema = fromJSONSchema({
type: "string",
pattern: "foo",
});
expect(schema.parse("foo")).toBe("foo");
expect(schema.parse("foobar")).toBe("foobar"); // matches at start
expect(schema.parse("barfoo")).toBe("barfoo"); // matches at end
expect(schema.parse("barfoobar")).toBe("barfoobar"); // matches in middle
expect(() => schema.parse("bar")).toThrow(); // no match
});
test("number schema", () => {
const schema = fromJSONSchema({ type: "number" });
expect(schema.parse(42)).toBe(42);
expect(() => schema.parse("42")).toThrow();
});
test("number with constraints", () => {
const schema = fromJSONSchema({
type: "number",
minimum: 0,
maximum: 100,
multipleOf: 5,
});
expect(schema.parse(50)).toBe(50);
expect(() => schema.parse(-1)).toThrow();
expect(() => schema.parse(101)).toThrow();
expect(() => schema.parse(47)).toThrow(); // not multiple of 5
});
test("integer schema", () => {
const schema = fromJSONSchema({ type: "integer" });
expect(schema.parse(42)).toBe(42);
expect(() => schema.parse(42.5)).toThrow();
});
test("boolean schema", () => {
const schema = fromJSONSchema({ type: "boolean" });
expect(schema.parse(true)).toBe(true);
expect(schema.parse(false)).toBe(false);
expect(() => schema.parse("true")).toThrow();
});
test("null schema", () => {
const schema = fromJSONSchema({ type: "null" });
expect(schema.parse(null)).toBe(null);
expect(() => schema.parse(undefined)).toThrow();
});
test("object schema", () => {
const schema = fromJSONSchema({
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name"],
});
expect(schema.parse({ name: "John", age: 30 })).toEqual({ name: "John", age: 30 });
expect(schema.parse({ name: "John" })).toEqual({ name: "John" });
expect(() => schema.parse({ age: 30 })).toThrow(); // missing required
});
test("object with additionalProperties false", () => {
const schema = fromJSONSchema({
type: "object",
properties: {
name: { type: "string" },
},
additionalProperties: false,
});
expect(schema.parse({ name: "John" })).toEqual({ name: "John" });
expect(() => schema.parse({ name: "John", extra: "field" })).toThrow();
});
test("array schema", () => {
const schema = fromJSONSchema({
type: "array",
items: { type: "string" },
});
expect(schema.parse(["a", "b", "c"])).toEqual(["a", "b", "c"]);
expect(() => schema.parse([1, 2, 3])).toThrow();
});
test("array with constraints", () => {
const schema = fromJSONSchema({
type: "array",
items: { type: "number" },
minItems: 2,
maxItems: 4,
});
expect(schema.parse([1, 2])).toEqual([1, 2]);
expect(schema.parse([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
expect(() => schema.parse([1])).toThrow();
expect(() => schema.parse([1, 2, 3, 4, 5])).toThrow();
});
test("tuple with prefixItems (draft-2020-12)", () => {
const schema = fromJSONSchema({
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "array",
prefixItems: [{ type: "string" }, { type: "number" }],
});
expect(schema.parse(["hello", 42])).toEqual(["hello", 42]);
expect(() => schema.parse(["hello"])).toThrow();
expect(() => schema.parse(["hello", "world"])).toThrow();
});
test("tuple with items array (draft-7)", () => {
const schema = fromJSONSchema({
$schema: "http://json-schema.org/draft-07/schema#",
type: "array",
items: [{ type: "string" }, { type: "number" }],
additionalItems: false,
});
expect(schema.parse(["hello", 42])).toEqual(["hello", 42]);
expect(() => schema.parse(["hello", 42, "extra"])).toThrow();
});
test("enum schema", () => {
const schema = fromJSONSchema({
enum: ["red", "green", "blue"],
});
expect(schema.parse("red")).toBe("red");
expect(() => schema.parse("yellow")).toThrow();
});
test("const schema", () => {
const schema = fromJSONSchema({
const: "hello",
});
expect(schema.parse("hello")).toBe("hello");
expect(() => schema.parse("world")).toThrow();
});
test("anyOf schema", () => {
const schema = fromJSONSchema({
anyOf: [{ type: "string" }, { type: "number" }],
});
expect(schema.parse("hello")).toBe("hello");
expect(schema.parse(42)).toBe(42);
expect(() => schema.parse(true)).toThrow();
});
test("allOf schema", () => {
const schema = fromJSONSchema({
allOf: [
{ type: "object", properties: { name: { type: "string" } }, required: ["name"] },
{ type: "object", properties: { age: { type: "number" } }, required: ["age"] },
],
});
const result = schema.parse({ name: "John", age: 30 }) as { name: string; age: number };
expect(result.name).toBe("John");
expect(result.age).toBe(30);
});
test("allOf with empty array", () => {
// Empty allOf without explicit type returns any
const schema1 = fromJSONSchema({
allOf: [],
});
expect(schema1.parse("hello")).toBe("hello");
expect(schema1.parse(123)).toBe(123);
expect(schema1.parse({})).toEqual({});
// Empty allOf with explicit type returns base schema
const schema2 = fromJSONSchema({
type: "string",
allOf: [],
});
expect(schema2.parse("hello")).toBe("hello");
expect(() => schema2.parse(123)).toThrow();
});
test("oneOf schema (exclusive union)", () => {
const schema = fromJSONSchema({
oneOf: [{ type: "string" }, { type: "number" }],
});
expect(schema.parse("hello")).toBe("hello");
expect(schema.parse(42)).toBe(42);
expect(() => schema.parse(true)).toThrow();
});
test("type with anyOf creates intersection", () => {
// type: string AND (type:string,minLength:5 OR type:string,pattern:^a)
const schema = fromJSONSchema({
type: "string",
anyOf: [
{ type: "string", minLength: 5 },
{ type: "string", pattern: "^a" },
],
});
// Should pass: string AND (minLength:5 OR pattern:^a) - matches minLength
expect(schema.parse("hello")).toBe("hello");
// Should pass: string AND (minLength:5 OR pattern:^a) - matches pattern
expect(schema.parse("abc")).toBe("abc");
// Should fail: string but neither minLength nor pattern match
expect(() => schema.parse("hi")).toThrow();
// Should fail: not a string
expect(() => schema.parse(123)).toThrow();
});
test("type with oneOf creates intersection", () => {
// type: string AND (exactly one of: type:string,minLength:5 OR type:string,pattern:^a)
const schema = fromJSONSchema({
type: "string",
oneOf: [
{ type: "string", minLength: 5 },
{ type: "string", pattern: "^a" },
],
});
// Should pass: string AND minLength:5 (exactly one match - "hello" length 5 >= 5, doesn't start with 'a')
expect(schema.parse("hello")).toBe("hello");
// Should pass: string AND pattern:^a (exactly one match - "abc" starts with 'a', length 3 < 5)
expect(schema.parse("abc")).toBe("abc");
// Should fail: string but neither match
expect(() => schema.parse("hi")).toThrow();
// Should fail: not a string
expect(() => schema.parse(123)).toThrow();
// Should fail: matches both (length >= 5 AND starts with 'a') - exclusive union fails
expect(() => schema.parse("apple")).toThrow();
});
test("unevaluatedItems throws error", () => {
expect(() => {
fromJSONSchema({
type: "array",
unevaluatedItems: false,
});
}).toThrow("unevaluatedItems is not supported");
});
test("unevaluatedProperties throws error", () => {
expect(() => {
fromJSONSchema({
type: "object",
unevaluatedProperties: false,
});
}).toThrow("unevaluatedProperties is not supported");
});
test("if/then/else throws error", () => {
expect(() => {
fromJSONSchema({
if: { type: "string" },
then: { type: "number" },
});
}).toThrow("Conditional schemas");
});
test("external $ref throws error", () => {
expect(() => {
fromJSONSchema({
$ref: "https://example.com/schema#/definitions/User",
});
}).toThrow("External $ref is not supported");
});
test("local $ref resolution", () => {
const schema = fromJSONSchema({
$defs: {
User: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
},
},
$ref: "#/$defs/User",
});
expect(schema.parse({ name: "John" })).toEqual({ name: "John" });
expect(() => schema.parse({})).toThrow();
});
test("circular $ref with lazy", () => {
const schema = fromJSONSchema({
$defs: {
Node: {
type: "object",
properties: {
value: { type: "string" },
children: {
type: "array",
items: { $ref: "#/$defs/Node" },
},
},
},
},
$ref: "#/$defs/Node",
});
type Node = { value: string; children: Node[] };
const result = schema.parse({
value: "root",
children: [{ value: "child", children: [] }],
}) as Node;
expect(result.value).toBe("root");
expect(result.children[0]?.value).toBe("child");
});
test("patternProperties", () => {
const schema = fromJSONSchema({
type: "object",
patternProperties: {
"^S_": { type: "string" },
},
});
const result = schema.parse({ S_name: "John", S_age: "30" }) as Record<string, string>;
expect(result.S_name).toBe("John");
expect(result.S_age).toBe("30");
});
test("patternProperties with regular properties", () => {
// Note: When patternProperties is combined with properties, the intersection
// validates all keys against the pattern. This test uses a pattern that
// matches the regular property name as well.
const schema = fromJSONSchema({
type: "object",
properties: {
S_name: { type: "string" },
},
patternProperties: {
"^S_": { type: "string" },
},
required: ["S_name"],
});
const result = schema.parse({ S_name: "John", S_extra: "value" }) as Record<string, string>;
expect(result.S_name).toBe("John");
expect(result.S_extra).toBe("value");
});
test("multiple patternProperties", () => {
const schema = fromJSONSchema({
type: "object",
patternProperties: {
"^S_": { type: "string" },
"^N_": { type: "number" },
},
});
const result = schema.parse({ S_name: "John", N_count: 123 }) as Record<string, string | number>;
expect(result.S_name).toBe("John");
expect(result.N_count).toBe(123);
// Keys not matching any pattern should pass through
const result2 = schema.parse({ S_name: "John", N_count: 123, other: "value" }) as Record<string, string | number>;
expect(result2.other).toBe("value");
});
test("multiple overlapping patternProperties", () => {
// If a key matches multiple patterns, value must satisfy all schemas
const schema = fromJSONSchema({
type: "object",
patternProperties: {
"^S_": { type: "string" },
"^S_N": { type: "string", minLength: 3 },
},
});
// S_name matches ^S_ but not ^S_N
expect(schema.parse({ S_name: "John" })).toEqual({ S_name: "John" });
// S_N matches both patterns - must satisfy both (string with minLength 3)
expect(schema.parse({ S_N: "abc" })).toEqual({ S_N: "abc" });
expect(() => schema.parse({ S_N: "ab" })).toThrow(); // too short for ^S_N pattern
});
test("default value", () => {
const schema = fromJSONSchema({
type: "string",
default: "hello",
});
// Default is applied during parsing if value is missing/undefined
// This depends on Zod's default behavior
expect(schema.parse("world")).toBe("world");
});
test("description metadata", () => {
const schema = fromJSONSchema({
type: "string",
description: "A string value",
});
expect(schema.parse("hello")).toBe("hello");
});
test("version detection - draft-2020-12", () => {
const schema = fromJSONSchema({
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "array",
prefixItems: [{ type: "string" }],
});
expect(schema.parse(["hello"])).toEqual(["hello"]);
});
test("version detection - draft-7", () => {
const schema = fromJSONSchema({
$schema: "http://json-schema.org/draft-07/schema#",
type: "array",
items: [{ type: "string" }],
});
expect(schema.parse(["hello"])).toEqual(["hello"]);
});
test("version detection - draft-4", () => {
const schema = fromJSONSchema({
$schema: "http://json-schema.org/draft-04/schema#",
type: "array",
items: [{ type: "string" }],
});
expect(schema.parse(["hello"])).toEqual(["hello"]);
});
test("default version (draft-2020-12)", () => {
const schema = fromJSONSchema({
type: "array",
prefixItems: [{ type: "string" }],
});
expect(schema.parse(["hello"])).toEqual(["hello"]);
});
test("string format - email", () => {
const schema = fromJSONSchema({
type: "string",
format: "email",
});
expect(schema.parse("test@example.com")).toBe("test@example.com");
});
test("string format - uuid", () => {
const schema = fromJSONSchema({
type: "string",
format: "uuid",
});
const uuid = "550e8400-e29b-41d4-a716-446655440000";
expect(schema.parse(uuid)).toBe(uuid);
});
test("exclusiveMinimum and exclusiveMaximum", () => {
const schema = fromJSONSchema({
type: "number",
exclusiveMinimum: 0,
exclusiveMaximum: 100,
});
expect(schema.parse(50)).toBe(50);
expect(() => schema.parse(0)).toThrow();
expect(() => schema.parse(100)).toThrow();
});
test("boolean schema (true/false)", () => {
const trueSchema = fromJSONSchema(true);
expect(trueSchema.parse("anything")).toBe("anything");
const falseSchema = fromJSONSchema(false);
expect(() => falseSchema.parse("anything")).toThrow();
});
test("empty object schema", () => {
const schema = fromJSONSchema({
type: "object",
});
expect(schema.parse({})).toEqual({});
expect(schema.parse({ extra: "field" })).toEqual({ extra: "field" });
});
test("array without items", () => {
const schema = fromJSONSchema({
type: "array",
});
expect(schema.parse([1, "string", true])).toEqual([1, "string", true]);
});
test("mixed enum types", () => {
const schema = fromJSONSchema({
enum: ["string", 42, true, null],
});
expect(schema.parse("string")).toBe("string");
expect(schema.parse(42)).toBe(42);
expect(schema.parse(true)).toBe(true);
expect(schema.parse(null)).toBe(null);
});
test("nullable in OpenAPI 3.0", () => {
// General nullable case (not just enum: [null])
const stringSchema = fromJSONSchema(
{
type: "string",
nullable: true,
},
{ defaultTarget: "openapi-3.0" }
);
expect(stringSchema.parse("hello")).toBe("hello");
expect(stringSchema.parse(null)).toBe(null);
expect(() => stringSchema.parse(123)).toThrow();
const numberSchema = fromJSONSchema(
{
type: "number",
nullable: true,
},
{ defaultTarget: "openapi-3.0" }
);
expect(numberSchema.parse(42)).toBe(42);
expect(numberSchema.parse(null)).toBe(null);
expect(() => numberSchema.parse("string")).toThrow();
const objectSchema = fromJSONSchema(
{
type: "object",
properties: { name: { type: "string" } },
nullable: true,
},
{ defaultTarget: "openapi-3.0" }
);
expect(objectSchema.parse({ name: "John" })).toEqual({ name: "John" });
expect(objectSchema.parse(null)).toBe(null);
});
// Metadata extraction tests
test("unrecognized keys stored in globalRegistry by default", () => {
const schema = fromJSONSchema({
type: "string",
title: "My String",
deprecated: true,
examples: ["hello", "world"],
"x-custom": "custom value",
});
const meta = z.globalRegistry.get(schema);
expect(meta).toBeDefined();
expect(meta?.title).toBe("My String");
expect(meta?.deprecated).toBe(true);
expect(meta?.examples).toEqual(["hello", "world"]);
expect((meta as any)?.["x-custom"]).toBe("custom value");
// Clean up
z.globalRegistry.remove(schema);
});
test("unrecognized keys stored in custom registry", () => {
const customRegistry = z.registry<{ title?: string; deprecated?: boolean }>();
const schema = fromJSONSchema(
{
type: "number",
title: "Age",
deprecated: true,
},
{ registry: customRegistry }
);
// Should be in custom registry
const meta = customRegistry.get(schema);
expect(meta).toBeDefined();
expect(meta?.title).toBe("Age");
expect(meta?.deprecated).toBe(true);
// Should NOT be in globalRegistry
expect(z.globalRegistry.get(schema)).toBeUndefined();
});
test("$id and id are captured as metadata", () => {
const customRegistry = z.registry<{ $id?: string; id?: string }>();
const schema1 = fromJSONSchema(
{
$id: "https://example.com/schemas/user",
type: "object",
},
{ registry: customRegistry }
);
expect(customRegistry.get(schema1)?.$id).toBe("https://example.com/schemas/user");
const schema2 = fromJSONSchema(
{
id: "legacy-id",
type: "string",
},
{ registry: customRegistry }
);
expect(customRegistry.get(schema2)?.id).toBe("legacy-id");
});
test("x-* extension keys are captured as metadata", () => {
const customRegistry = z.registry<Record<string, unknown>>();
const schema = fromJSONSchema(
{
type: "string",
"x-openapi-example": "example value",
"x-internal": true,
"x-tags": ["api", "public"],
},
{ registry: customRegistry }
);
const meta = customRegistry.get(schema);
expect(meta?.["x-openapi-example"]).toBe("example value");
expect(meta?.["x-internal"]).toBe(true);
expect(meta?.["x-tags"]).toEqual(["api", "public"]);
});
test("metadata on nested schemas", () => {
const customRegistry = z.registry<Record<string, unknown>>();
const parentSchema = fromJSONSchema(
{
type: "object",
title: "User",
properties: {
name: {
type: "string",
title: "Name",
"x-field-order": 1,
},
age: {
type: "number",
title: "Age",
deprecated: true,
},
},
},
{ registry: customRegistry }
);
// Verify parent schema has its metadata
expect(customRegistry.get(parentSchema)?.title).toBe("User");
// We can't easily access nested schemas directly, but we can verify
// the registry is being used correctly by checking a separate schema
const simpleSchema = fromJSONSchema(
{
type: "string",
title: "Simple",
},
{ registry: customRegistry }
);
expect(customRegistry.get(simpleSchema)?.title).toBe("Simple");
});
test("no metadata added when no unrecognized keys", () => {
const customRegistry = z.registry<Record<string, unknown>>();
const schema = fromJSONSchema(
{
type: "string",
minLength: 1,
maxLength: 100,
description: "A regular string",
},
{ registry: customRegistry }
);
// description is handled via .describe(), so it shouldn't be in metadata
// All other keys are recognized, so no metadata should be added
expect(customRegistry.get(schema)).toBeUndefined();
});
test("writeOnly and examples are captured as metadata", () => {
const customRegistry = z.registry<{ writeOnly?: boolean; examples?: unknown[] }>();
const schema = fromJSONSchema(
{
type: "string",
writeOnly: true,
examples: ["password123", "secret"],
},
{ registry: customRegistry }
);
const meta = customRegistry.get(schema);
expect(meta?.writeOnly).toBe(true);
expect(meta?.examples).toEqual(["password123", "secret"]);
});
test("$comment and $anchor are captured as metadata", () => {
const customRegistry = z.registry<{ $comment?: string; $anchor?: string }>();
const schema = fromJSONSchema(
{
type: "string",
$comment: "This is a developer note",
$anchor: "my-anchor",
},
{ registry: customRegistry }
);
const meta = customRegistry.get(schema);
expect(meta?.$comment).toBe("This is a developer note");
expect(meta?.$anchor).toBe("my-anchor");
});
test("contentEncoding and contentMediaType are stored as metadata", () => {
const customRegistry = z.registry<{ contentEncoding?: string; contentMediaType?: string }>();
const schema = fromJSONSchema(
{
type: "string",
contentEncoding: "base64",
contentMediaType: "image/png",
},
{ registry: customRegistry }
);
// Should just be a string schema
expect(schema.parse("aGVsbG8gd29ybGQ=")).toBe("aGVsbG8gd29ybGQ=");
// Content keywords should be in metadata
const meta = customRegistry.get(schema);
expect(meta?.contentEncoding).toBe("base64");
expect(meta?.contentMediaType).toBe("image/png");
});

View File

@@ -0,0 +1,360 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const args1 = z.tuple([z.string()]);
const returns1 = z.number();
const func1 = z.function({
input: args1,
output: returns1,
});
test("function parsing", () => {
const parsed = func1.implement((arg: any) => arg.length);
const result = parsed("asdf");
expect(result).toBe(4);
});
test("parsed function fail 1", () => {
// @ts-expect-error
const parsed = func1.implement((x: string) => x);
expect(() => parsed("asdf")).toThrow();
});
test("parsed function fail 2", () => {
// @ts-expect-error
const parsed = func1.implement((x: string) => x);
expect(() => parsed(13 as any)).toThrow();
});
test("function inference 1", () => {
type func1 = (typeof func1)["_input"];
expectTypeOf<func1>().toEqualTypeOf<(k: string) => number>();
});
test("method parsing", () => {
const methodObject = z.object({
property: z.number(),
method: z
.function()
.input(z.tuple([z.string()]))
.output(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().input([z.string()]).output(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 = (typeof t1)["_input"];
expectTypeOf<t1>().toEqualTypeOf<(...args_1: never[]) => unknown>();
t1._input;
const t2args = z.tuple([z.string()], z.unknown());
const t2 = t1.input(t2args);
type t2 = (typeof t2)["_input"];
expectTypeOf<t2>().toEqualTypeOf<(arg: string, ...args_1: unknown[]) => unknown>();
const t3 = t2.output(z.boolean());
type t3 = (typeof t3)["_input"];
expectTypeOf<t3>().toEqualTypeOf<(arg: string, ...args_1: unknown[]) => boolean>();
});
// test("custom args", () => {
// const fn = z.function().implement((_a: string, _b: number) => {
// return new Date();
// });
// expectTypeOf(fn).toEqualTypeOf<(a: string, b: number) => Date>();
// });
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({
input: args2,
output: returns2,
});
test("function inference 2", () => {
type func2 = (typeof func2)["_input"];
expectTypeOf<func2>().toEqualTypeOf<
(arg: {
f3?: (boolean | undefined)[] | undefined;
f1: number;
f2: string | null;
}) => string | number
>();
});
test("valid function run", () => {
const validFunc2Instance = func2.implement((_x) => {
_x.f2;
_x.f3![0];
return "adf" as any;
});
validFunc2Instance({
f1: 21,
f2: "asdf",
f3: [true, false],
});
});
const args3 = [
z.object({
f1: z.number(),
f2: z.string().nullable(),
f3: z.array(z.boolean().optional()).optional(),
}),
] as const;
const returns3 = z.union([z.string(), z.number()]);
const func3 = z.function({
input: args3,
output: returns3,
});
test("function inference 3", () => {
type func3 = (typeof func3)["_input"];
expectTypeOf<func3>().toEqualTypeOf<
(arg: {
f3?: (boolean | undefined)[] | undefined;
f1: number;
f2: string | null;
}) => string | number
>();
});
test("valid function run", () => {
const validFunc3Instance = func3.implement((_x) => {
_x.f2;
_x.f3![0];
return "adf" as any;
});
validFunc3Instance({
f1: 21,
f2: "asdf",
f3: [true, false],
});
});
test("input validation error", () => {
const schema = z.function({
input: z.tuple([z.string()]),
output: z.void(),
});
const fn = schema.implement(() => 1234 as any);
// @ts-expect-error
const checker = () => fn();
try {
checker();
} catch (e: any) {
expect(e.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received undefined",
"path": [
0,
],
},
]
`);
}
});
test("array inputs", () => {
const a = z.function({
input: [
z.object({
name: z.string(),
age: z.number().int(),
}),
],
output: z.string(),
});
a.implement((args) => {
return `${args.age}`;
});
const b = z.function({
input: [
z.object({
name: z.string(),
age: z.number().int(),
}),
],
});
b.implement((args) => {
return `${args.age}`;
});
});
test("output validation error", () => {
const schema = z.function({
input: z.tuple([]),
output: z.string(),
});
const fn = schema.implement(() => 1234 as any);
try {
fn();
} catch (e: any) {
expect(e.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [],
},
]
`);
}
});
test("function with async refinements", async () => {
const schema = z
.function()
.input([z.string().refine(async (val) => val.length > 10)])
.output(z.promise(z.number().refine(async (val) => val > 10)));
const func = schema.implementAsync(async (val) => {
return val.length;
});
const results = [];
try {
await func("asdfasdf");
results.push("success");
} catch (_) {
results.push("fail");
}
try {
await func("asdflkjasdflkjsf");
results.push("success");
} catch (_) {
results.push("fail");
}
expect(results).toEqual(["fail", "success"]);
});
test("implement async with transforms", async () => {
const typeGuard = (data: string): data is "1234" => data === "1234";
const codeSchema = z.string().transform((data, ctx) => {
if (typeGuard(data)) {
return data;
} else {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid code",
});
return z.NEVER;
}
});
const inputSchema = z.object({
code: codeSchema,
});
const outputSchema = z.object({
data: z.array(z.string()).default([]),
});
const fnImplementation = async (data: z.infer<typeof inputSchema>): Promise<z.infer<typeof outputSchema>> => {
return {
data: [data.code],
};
};
const schema = z.function().input([inputSchema]).output(outputSchema);
const func = schema.implementAsync(fnImplementation);
type TheInterface = {
myFunction: (data: z.infer<typeof inputSchema>) => Promise<z.infer<typeof outputSchema>>;
};
const theImplementation: TheInterface = {
myFunction: func,
};
const results = [];
try {
await theImplementation.myFunction({
code: "1234",
});
results.push("success");
} catch (_) {
results.push("fail");
}
try {
await func({ data: "asdflkjasdflkjsf" } as any);
results.push("success");
} catch (_) {
results.push("fail");
}
expect(results).toEqual(["success", "fail"]);
});
test("non async function with async refinements should fail", async () => {
const func = z
.function()
.input([z.string().refine(async (val) => val.length > 10)])
.output(z.number().refine(async (val) => val > 10))
.implement((val) => {
return val.length;
});
const results = [];
try {
await func("asdasdfasdffasdf");
results.push("success");
} catch (_) {
results.push("fail");
}
expect(results).toEqual(["fail"]);
});
test("extra parameters with rest", () => {
const maxLength5 = z
.function()
.input([z.string()], z.unknown())
.output(z.boolean())
.implement((str, _arg, _qewr) => {
return str.length <= 5;
});
const filteredList = ["apple", "orange", "pear", "banana", "strawberry"].filter(maxLength5);
expect(filteredList.length).toEqual(2);
});

View File

@@ -0,0 +1,72 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
function nest<TData extends z.ZodType>(schema: TData) {
return z.object({
nested: schema,
});
}
test("generics", () => {
const a = nest(z.object({ a: z.string() }));
type a = z.infer<typeof a>;
expectTypeOf<a>().toEqualTypeOf<{ nested: { a: string } }>();
const b = nest(z.object({ a: z.string().optional() }));
type b = z.infer<typeof b>;
expectTypeOf<b>().toEqualTypeOf<{ nested: { a?: string | undefined } }>();
});
test("generics with optional", () => {
async function stripOuter<TData extends z.ZodType>(schema: TData, data: unknown) {
return z
.object({
nested: schema.optional(),
})
.transform((data) => {
return data.nested;
})
.parse({ nested: data });
}
const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
expectTypeOf<typeof result>().toEqualTypeOf<Promise<{ a: string } | undefined>>();
});
// test("assignability", () => {
// const createSchemaAndParse = <K extends string, VS extends z.ZodString>(key: K, valueSchema: VS, data: unknown) => {
// const schema = z.object({
// [key]: valueSchema,
// });
// // 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: "" });
// expectTypeOf<typeof parsed>().toEqualTypeOf<{ foo: string }>();
// });
test("nested no undefined", () => {
const inner = z.string().or(z.array(z.string()));
const outer = z.object({ inner });
type outerSchema = z.infer<typeof outer>;
expectTypeOf<outerSchema>().toEqualTypeOf<{ inner: string | string[] }>();
expect(outer.safeParse({ inner: undefined }).success).toEqual(false);
});
test("generic on output type", () => {
const createV4Schema = <Output>(opts: {
schema: z.ZodType<Output>;
}) => {
return opts.schema;
};
createV4Schema({
schema: z.object({
name: z.string(),
}),
})?._zod?.output?.name;
});

View File

@@ -0,0 +1,68 @@
import { expect, expectTypeOf, test } from "vitest";
import { type ZodCustomStringFormat, hash } from "zod"; // adjust path as needed
test("hash() API — types and runtime across all alg/enc combinations", async () => {
const { createHash } = await import("node:crypto");
type Alg = "md5" | "sha1" | "sha256" | "sha384" | "sha512";
// type Enc = "hex" | "base64" | "base64url";
const toB64Url = (b64: string) => b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
const makeDigests = (alg: Alg, input: string) => {
const buf = createHash(alg).update(input).digest();
const hex = buf.toString("hex");
const base64 = buf.toString("base64");
const base64url = toB64Url(base64);
return { hex, base64, base64url };
};
const algs: ReadonlyArray<Alg> = ["md5", "sha1", "sha256", "sha384", "sha512"];
const input = "zodasklfjaasdf";
// --- Type-level checks (ensure the literal format string is encoded in the return type)
expectTypeOf(hash("md5")).toEqualTypeOf<ZodCustomStringFormat<"md5_hex">>();
expectTypeOf(hash("sha1")).toEqualTypeOf<ZodCustomStringFormat<"sha1_hex">>();
expectTypeOf(hash("sha256", { enc: "base64" as const })).toEqualTypeOf<ZodCustomStringFormat<"sha256_base64">>();
expectTypeOf(hash("sha384", { enc: "base64url" as const })).toEqualTypeOf<
ZodCustomStringFormat<"sha384_base64url">
>();
// Test generic format types are correctly inferred and Enc defaults to "hex"
expectTypeOf(hash("sha256")).toEqualTypeOf<ZodCustomStringFormat<"sha256_hex">>();
// --- Runtime matrix (success + a few sharp-edged failures per combo)
for (const alg of algs) {
const { hex, base64, base64url } = makeDigests(alg, input);
// Success cases
expect(hash(alg).parse(hex)).toBe(hex); // default enc=hex
expect(hash(alg, { enc: "hex" }).parse(hex)).toBe(hex);
expect(hash(alg, { enc: "base64" }).parse(base64)).toBe(base64);
expect(hash(alg, { enc: "base64url" }).parse(base64url)).toBe(base64url);
// Failure cases (wrong encoding to schema)
expect(() => hash(alg, { enc: "hex" }).parse(base64)).toThrow();
expect(() => hash(alg, { enc: "base64" }).parse(hex)).toThrow();
expect(() => hash(alg, { enc: "base64url" }).parse(base64)).toThrow();
// Encoding-specific failures
// hex: uppercase allowed, wrong length should fail
hash(alg, { enc: "hex" }).parse(hex.toUpperCase());
expect(() => hash(alg, { enc: "hex" }).parse(hex.slice(0, -1))).toThrow();
// base64: missing required padding should fail (only for algorithms that require padding)
if (base64.includes("=")) {
const base64NoPad = base64.replace(/=+$/g, "");
expect(() => hash(alg, { enc: "base64" }).parse(base64NoPad)).toThrow();
}
// base64url: adding padding or using invalid characters should fail
expect(() => hash(alg, { enc: "base64url" }).parse(base64url + "=")).toThrow();
expect(() => hash(alg, { enc: "base64url" }).parse(base64url + "!")).toThrow();
// Param object present but enc omitted should still default to hex at runtime
const schemaWithEmptyParams = hash(alg, {} as any);
expect(schemaWithEmptyParams.parse(hex)).toBe(hex);
}
});

View File

@@ -0,0 +1,939 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import type { util } from "zod/v4/core";
test("z.boolean", () => {
const a = z.boolean();
expect(z.parse(a, true)).toEqual(true);
expect(z.parse(a, false)).toEqual(false);
expect(() => z.parse(a, 123)).toThrow();
expect(() => z.parse(a, "true")).toThrow();
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<boolean>();
});
test("z.bigint", () => {
const a = z.bigint();
expect(z.parse(a, BigInt(123))).toEqual(BigInt(123));
expect(() => z.parse(a, 123)).toThrow();
expect(() => z.parse(a, "123")).toThrow();
});
test("z.symbol", () => {
const a = z.symbol();
const sym = Symbol();
expect(z.parse(a, sym)).toEqual(sym);
expect(() => z.parse(a, "symbol")).toThrow();
});
test("z.date", () => {
const a = z.date();
const date = new Date();
expect(z.parse(a, date)).toEqual(date);
expect(() => z.parse(a, "date")).toThrow();
});
test("z.coerce.string", () => {
const a = z.coerce.string();
expect(z.parse(a, 123)).toEqual("123");
expect(z.parse(a, true)).toEqual("true");
expect(z.parse(a, null)).toEqual("null");
expect(z.parse(a, undefined)).toEqual("undefined");
});
test("z.coerce.number", () => {
const a = z.coerce.number();
expect(z.parse(a, "123")).toEqual(123);
expect(z.parse(a, "123.45")).toEqual(123.45);
expect(z.parse(a, true)).toEqual(1);
expect(z.parse(a, false)).toEqual(0);
expect(() => z.parse(a, "abc")).toThrow();
});
test("z.coerce.boolean", () => {
const a = z.coerce.boolean();
// test booleans
expect(z.parse(a, true)).toEqual(true);
expect(z.parse(a, false)).toEqual(false);
expect(z.parse(a, "true")).toEqual(true);
expect(z.parse(a, "false")).toEqual(true);
expect(z.parse(a, 1)).toEqual(true);
expect(z.parse(a, 0)).toEqual(false);
expect(z.parse(a, {})).toEqual(true);
expect(z.parse(a, [])).toEqual(true);
expect(z.parse(a, undefined)).toEqual(false);
expect(z.parse(a, null)).toEqual(false);
expect(z.parse(a, "")).toEqual(false);
});
test("z.coerce.bigint", () => {
const a = z.coerce.bigint();
expect(z.parse(a, "123")).toEqual(BigInt(123));
expect(z.parse(a, 123)).toEqual(BigInt(123));
expect(() => z.parse(a, "abc")).toThrow();
});
test("z.coerce.date", () => {
const a = z.coerce.date();
const date = new Date();
expect(z.parse(a, date.toISOString())).toEqual(date);
expect(z.parse(a, date.getTime())).toEqual(date);
expect(() => z.parse(a, "invalid date")).toThrow();
});
test("z.iso.datetime", () => {
const d1 = "2021-01-01T00:00:00Z";
const d2 = "2021-01-01T00:00:00.123Z";
const d3 = "2021-01-01T00:00:00";
const d4 = "2021-01-01T00:00:00+07:00";
const d5 = "bad data";
// local: false, offset: false, precision: null
const a = z.iso.datetime();
expect(z.safeParse(a, d1).success).toEqual(true);
expect(z.safeParse(a, d2).success).toEqual(true);
expect(z.safeParse(a, d3).success).toEqual(false);
expect(z.safeParse(a, d4).success).toEqual(false);
expect(z.safeParse(a, d5).success).toEqual(false);
const b = z.iso.datetime({ local: true });
expect(z.safeParse(b, d1).success).toEqual(true);
expect(z.safeParse(b, d2).success).toEqual(true);
expect(z.safeParse(b, d3).success).toEqual(true);
expect(z.safeParse(b, d4).success).toEqual(false);
expect(z.safeParse(b, d5).success).toEqual(false);
const c = z.iso.datetime({ offset: true });
expect(z.safeParse(c, d1).success).toEqual(true);
expect(z.safeParse(c, d2).success).toEqual(true);
expect(z.safeParse(c, d3).success).toEqual(false);
expect(z.safeParse(c, d4).success).toEqual(true);
expect(z.safeParse(c, d5).success).toEqual(false);
const d = z.iso.datetime({ precision: 3 });
expect(z.safeParse(d, d1).success).toEqual(false);
expect(z.safeParse(d, d2).success).toEqual(true);
expect(z.safeParse(d, d3).success).toEqual(false);
expect(z.safeParse(d, d4).success).toEqual(false);
expect(z.safeParse(d, d5).success).toEqual(false);
});
test("z.iso.date", () => {
const d1 = "2021-01-01";
const d2 = "bad data";
const a = z.iso.date();
expect(z.safeParse(a, d1).success).toEqual(true);
expect(z.safeParse(a, d2).success).toEqual(false);
const b = z.string().check(z.iso.date());
expect(z.safeParse(b, d1).success).toEqual(true);
expect(z.safeParse(b, d2).success).toEqual(false);
});
test("z.iso.time", () => {
const d1 = "00:00:00";
const d2 = "00:00:00.123";
const d3 = "bad data";
const a = z.iso.time();
expect(z.safeParse(a, d1).success).toEqual(true);
expect(z.safeParse(a, d2).success).toEqual(true);
expect(z.safeParse(a, d3).success).toEqual(false);
const b = z.iso.time({ precision: 3 });
expect(z.safeParse(b, d1).success).toEqual(false);
expect(z.safeParse(b, d2).success).toEqual(true);
expect(z.safeParse(b, d3).success).toEqual(false);
const c = z.string().check(z.iso.time());
expect(z.safeParse(c, d1).success).toEqual(true);
expect(z.safeParse(c, d2).success).toEqual(true);
expect(z.safeParse(c, d3).success).toEqual(false);
});
test("z.iso.duration", () => {
const d1 = "P3Y6M4DT12H30M5S";
const d2 = "bad data";
const a = z.iso.duration();
expect(z.safeParse(a, d1).success).toEqual(true);
expect(z.safeParse(a, d2).success).toEqual(false);
const b = z.string().check(z.iso.duration());
expect(z.safeParse(b, d1).success).toEqual(true);
expect(z.safeParse(b, d2).success).toEqual(false);
});
test("z.undefined", () => {
const a = z.undefined();
expect(z.parse(a, undefined)).toEqual(undefined);
expect(() => z.parse(a, "undefined")).toThrow();
});
test("z.null", () => {
const a = z.null();
expect(z.parse(a, null)).toEqual(null);
expect(() => z.parse(a, "null")).toThrow();
});
test("z.any", () => {
const a = z.any();
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, 123)).toEqual(123);
expect(z.parse(a, true)).toEqual(true);
expect(z.parse(a, null)).toEqual(null);
expect(z.parse(a, undefined)).toEqual(undefined);
z.parse(a, {});
z.parse(a, []);
z.parse(a, Symbol());
z.parse(a, new Date());
});
test("z.unknown", () => {
const a = z.unknown();
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, 123)).toEqual(123);
expect(z.parse(a, true)).toEqual(true);
expect(z.parse(a, null)).toEqual(null);
expect(z.parse(a, undefined)).toEqual(undefined);
z.parse(a, {});
z.parse(a, []);
z.parse(a, Symbol());
z.parse(a, new Date());
});
test("z.never", () => {
const a = z.never();
expect(() => z.parse(a, "hello")).toThrow();
});
test("z.void", () => {
const a = z.void();
expect(z.parse(a, undefined)).toEqual(undefined);
expect(() => z.parse(a, null)).toThrow();
});
test("z.array", () => {
const a = z.array(z.string());
expect(z.parse(a, ["hello", "world"])).toEqual(["hello", "world"]);
expect(() => z.parse(a, [123])).toThrow();
expect(() => z.parse(a, "hello")).toThrow();
});
test("z.union", () => {
const a = z.union([z.string(), z.number()]);
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, 123)).toEqual(123);
expect(() => z.parse(a, true)).toThrow();
});
test("z.intersection", () => {
const a = z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() }));
expect(z.parse(a, { a: "hello", b: 123 })).toEqual({ a: "hello", b: 123 });
expect(() => z.parse(a, { a: "hello" })).toThrow();
expect(() => z.parse(a, { b: 123 })).toThrow();
expect(() => z.parse(a, "hello")).toThrow();
});
test("z.tuple", () => {
const a = z.tuple([z.string(), z.number()]);
expect(z.parse(a, ["hello", 123])).toEqual(["hello", 123]);
expect(() => z.parse(a, ["hello", "world"])).toThrow();
expect(() => z.parse(a, [123, 456])).toThrow();
expect(() => z.parse(a, "hello")).toThrow();
// tuple with rest
const b = z.tuple([z.string(), z.number(), z.optional(z.string())], z.boolean());
type b = z.output<typeof b>;
expectTypeOf<b>().toEqualTypeOf<[string, number, string?, ...boolean[]]>();
const datas = [
["hello", 123],
["hello", 123, "world"],
["hello", 123, "world", true],
["hello", 123, "world", true, false, true],
];
for (const data of datas) {
expect(z.parse(b, data)).toEqual(data);
}
expect(() => z.parse(b, ["hello", 123, 123])).toThrow();
expect(() => z.parse(b, ["hello", 123, "world", 123])).toThrow();
// tuple with readonly args
const cArgs = [z.string(), z.number(), z.optional(z.string())] as const;
const c = z.tuple(cArgs, z.boolean());
type c = z.output<typeof c>;
expectTypeOf<c>().toEqualTypeOf<[string, number, string?, ...boolean[]]>();
// type c = z.output<typeof c>;
});
test("z.record", () => {
// record schema with enum keys
const a = z.record(z.string(), z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<Record<string, string>>();
const b = z.record(z.union([z.string(), z.number(), z.symbol()]), z.string());
type b = z.output<typeof b>;
expectTypeOf<b>().toEqualTypeOf<Record<string | number | symbol, string>>();
expect(z.parse(b, { a: "hello", 1: "world", [Symbol.for("asdf")]: "symbol" })).toEqual({
a: "hello",
1: "world",
[Symbol.for("asdf")]: "symbol",
});
// enum keys
const c = z.record(z.enum(["a", "b", "c"]), z.string());
type c = z.output<typeof c>;
expectTypeOf<c>().toEqualTypeOf<Record<"a" | "b" | "c", string>>();
expect(z.parse(c, { a: "hello", b: "world", c: "world" })).toEqual({
a: "hello",
b: "world",
c: "world",
});
// missing keys
expect(() => z.parse(c, { a: "hello", b: "world" })).toThrow();
// extra keys
expect(() => z.parse(c, { a: "hello", b: "world", c: "world", d: "world" })).toThrow();
// partial enum
const d = z.record(z.enum(["a", "b"]).or(z.never()), z.string());
type d = z.output<typeof d>;
expectTypeOf<d>().toEqualTypeOf<Record<"a" | "b", string>>();
// literal union keys
const e = z.record(z.union([z.literal("a"), z.literal(0)]), z.string());
type e = z.output<typeof e>;
expectTypeOf<e>().toEqualTypeOf<Record<"a" | 0, string>>();
expect(z.parse(e, { a: "hello", 0: "world" })).toEqual({
a: "hello",
0: "world",
});
// TypeScript enum keys
enum Enum {
A = 0,
B = "hi",
}
const f = z.record(z.enum(Enum), z.string());
type f = z.output<typeof f>;
expectTypeOf<f>().toEqualTypeOf<Record<Enum, string>>();
expect(z.parse(f, { [Enum.A]: "hello", [Enum.B]: "world" })).toEqual({
[Enum.A]: "hello",
[Enum.B]: "world",
});
});
test("z.map", () => {
const a = z.map(z.string(), z.number());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<Map<string, number>>();
expect(z.parse(a, new Map([["hello", 123]]))).toEqual(new Map([["hello", 123]]));
expect(() => z.parse(a, new Map([["hello", "world"]]))).toThrow();
expect(() => z.parse(a, new Map([[1243, "world"]]))).toThrow();
expect(() => z.parse(a, "hello")).toThrow();
const r1 = z.safeParse(a, new Map([[123, 123]]));
expect(r1.error?.issues[0].code).toEqual("invalid_type");
expect(r1.error?.issues[0].path).toEqual([123]);
const r2: any = z.safeParse(a, new Map([[BigInt(123), 123]]));
expect(r2.error!.issues[0].code).toEqual("invalid_key");
expect(r2.error!.issues[0].path).toEqual([]);
const r3: any = z.safeParse(a, new Map([["hello", "world"]]));
expect(r3.error!.issues[0].code).toEqual("invalid_type");
expect(r3.error!.issues[0].path).toEqual(["hello"]);
});
test("z.map invalid_element", () => {
const a = z.map(z.bigint(), z.number());
const r1 = z.safeParse(a, new Map([[BigInt(123), BigInt(123)]]));
expect(r1.error!.issues[0].code).toEqual("invalid_element");
expect(r1.error!.issues[0].path).toEqual([]);
});
test("z.map async", async () => {
const a = z.map(z.string().check(z.refine(async () => true)), z.number().check(z.refine(async () => true)));
const d1 = new Map([["hello", 123]]);
expect(await z.parseAsync(a, d1)).toEqual(d1);
await expect(z.parseAsync(a, new Map([[123, 123]]))).rejects.toThrow();
await expect(z.parseAsync(a, new Map([["hi", "world"]]))).rejects.toThrow();
await expect(z.parseAsync(a, new Map([[1243, "world"]]))).rejects.toThrow();
await expect(z.parseAsync(a, "hello")).rejects.toThrow();
const r = await z.safeParseAsync(a, new Map([[123, 123]]));
expect(r.success).toEqual(false);
expect(r.error!.issues[0].code).toEqual("invalid_type");
expect(r.error!.issues[0].path).toEqual([123]);
});
test("z.set", () => {
const a = z.set(z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<Set<string>>();
expect(z.parse(a, new Set(["hello", "world"]))).toEqual(new Set(["hello", "world"]));
expect(() => z.parse(a, new Set([123]))).toThrow();
expect(() => z.parse(a, ["hello", "world"])).toThrow();
expect(() => z.parse(a, "hello")).toThrow();
const b = z.set(z.number());
expect(z.parse(b, new Set([1, 2, 3]))).toEqual(new Set([1, 2, 3]));
expect(() => z.parse(b, new Set(["hello"]))).toThrow();
expect(() => z.parse(b, [1, 2, 3])).toThrow();
expect(() => z.parse(b, 123)).toThrow();
});
test("z.enum", () => {
const a = z.enum(["A", "B", "C"]);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<"A" | "B" | "C">();
expect(z.parse(a, "A")).toEqual("A");
expect(z.parse(a, "B")).toEqual("B");
expect(z.parse(a, "C")).toEqual("C");
expect(() => z.parse(a, "D")).toThrow();
expect(() => z.parse(a, 123)).toThrow();
expect(a.enum.A).toEqual("A");
expect(a.enum.B).toEqual("B");
expect(a.enum.C).toEqual("C");
expect((a.enum as any).D).toEqual(undefined);
});
test("z.enum - native", () => {
enum NativeEnum {
A = "A",
B = "B",
C = "C",
}
const a = z.enum(NativeEnum);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<NativeEnum>();
expect(z.parse(a, NativeEnum.A)).toEqual(NativeEnum.A);
expect(z.parse(a, NativeEnum.B)).toEqual(NativeEnum.B);
expect(z.parse(a, NativeEnum.C)).toEqual(NativeEnum.C);
expect(() => z.parse(a, "D")).toThrow();
expect(() => z.parse(a, 123)).toThrow();
// test a.enum
a;
expect(a.enum.A).toEqual(NativeEnum.A);
expect(a.enum.B).toEqual(NativeEnum.B);
expect(a.enum.C).toEqual(NativeEnum.C);
});
test("z.nativeEnum", () => {
enum NativeEnum {
A = "A",
B = "B",
C = "C",
}
const a = z.nativeEnum(NativeEnum);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<NativeEnum>();
expect(z.parse(a, NativeEnum.A)).toEqual(NativeEnum.A);
expect(z.parse(a, NativeEnum.B)).toEqual(NativeEnum.B);
expect(z.parse(a, NativeEnum.C)).toEqual(NativeEnum.C);
expect(() => z.parse(a, "D")).toThrow();
expect(() => z.parse(a, 123)).toThrow();
// test a.enum
a;
expect(a.enum.A).toEqual(NativeEnum.A);
expect(a.enum.B).toEqual(NativeEnum.B);
expect(a.enum.C).toEqual(NativeEnum.C);
});
test("z.literal", () => {
const a = z.literal("hello");
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<"hello">();
expect(z.parse(a, "hello")).toEqual("hello");
expect(() => z.parse(a, "world")).toThrow();
expect(() => z.parse(a, 123)).toThrow();
});
test("z.file", () => {
const a = z.file();
const file = new File(["content"], "filename.txt", { type: "text/plain" });
expect(z.parse(a, file)).toEqual(file);
expect(() => z.parse(a, "file")).toThrow();
expect(() => z.parse(a, 123)).toThrow();
});
test("z.transform", () => {
const a = z.pipe(
z.string(),
z.transform((val) => val.toUpperCase())
);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string>();
expect(z.parse(a, "hello")).toEqual("HELLO");
expect(() => z.parse(a, 123)).toThrow();
});
test("z.transform async", async () => {
const a = z.pipe(
z.string(),
z.transform(async (val) => val.toUpperCase())
);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string>();
expect(await z.parseAsync(a, "hello")).toEqual("HELLO");
await expect(() => z.parseAsync(a, 123)).rejects.toThrow();
});
test("z.preprocess", () => {
const a = z.pipe(
z.transform((val) => String(val).toUpperCase()),
z.string()
);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string>();
expect(z.parse(a, 123)).toEqual("123");
expect(z.parse(a, true)).toEqual("TRUE");
expect(z.parse(a, BigInt(1234))).toEqual("1234");
// expect(() => z.parse(a, Symbol("asdf"))).toThrow();
});
// test("z.preprocess async", () => {
// const a = z.preprocess(async (val) => String(val), z.string());
// type a = z.output<typeof a>;
// expectTypeOf<a>().toEqualTypeOf<string>();
// expect(z.parse(a, 123)).toEqual("123");
// expect(z.parse(a, true)).toEqual("true");
// expect(() => z.parse(a, {})).toThrow();
// });
test("z.optional", () => {
const a = z.optional(z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string | undefined>();
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, undefined)).toEqual(undefined);
expect(() => z.parse(a, 123)).toThrow();
});
test("z.nullable", () => {
const a = z.nullable(z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string | null>();
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, null)).toEqual(null);
expect(() => z.parse(a, 123)).toThrow();
});
test("z.default", () => {
const a = z._default(z.string(), "default");
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string>();
expect(z.parse(a, undefined)).toEqual("default");
expect(z.parse(a, "hello")).toEqual("hello");
expect(() => z.parse(a, 123)).toThrow();
const b = z._default(z.string(), () => "default");
expect(z.parse(b, undefined)).toEqual("default");
expect(z.parse(b, "hello")).toEqual("hello");
expect(() => z.parse(b, 123)).toThrow();
});
test("z.catch", () => {
const a = z.catch(z.string(), "default");
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string>();
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, 123)).toEqual("default");
const b = z.catch(z.string(), () => "default");
expect(z.parse(b, "hello")).toEqual("hello");
expect(z.parse(b, 123)).toEqual("default");
const c = z.catch(z.string(), (ctx) => {
return `${ctx.error.issues.length}issues`;
});
expect(z.parse(c, 1234)).toEqual("1issues");
});
test("z.nan", () => {
const a = z.nan();
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<number>();
expect(z.parse(a, Number.NaN)).toEqual(Number.NaN);
expect(() => z.parse(a, 123)).toThrow();
expect(() => z.parse(a, "NaN")).toThrow();
});
test("z.pipe", () => {
const a = z.pipe(
z.pipe(
z.string(),
z.transform((val) => val.length)
),
z.number()
);
type a_in = z.input<typeof a>;
expectTypeOf<a_in>().toEqualTypeOf<string>();
type a_out = z.output<typeof a>;
expectTypeOf<a_out>().toEqualTypeOf<number>();
expect(z.parse(a, "123")).toEqual(3);
expect(z.parse(a, "hello")).toEqual(5);
expect(() => z.parse(a, 123)).toThrow();
});
test("z.readonly", () => {
const a = z.readonly(z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<Readonly<string>>();
expect(z.parse(a, "hello")).toEqual("hello");
expect(() => z.parse(a, 123)).toThrow();
});
test("z.templateLiteral", () => {
const a = z.templateLiteral([z.string(), z.number()]);
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<`${string}${number}`>();
expect(z.parse(a, "hello123")).toEqual("hello123");
expect(() => z.parse(a, "hello")).toThrow();
expect(() => z.parse(a, 123)).toThrow();
// multipart
const b = z.templateLiteral([z.string(), z.number(), z.string()]);
type b = z.output<typeof b>;
expectTypeOf<b>().toEqualTypeOf<`${string}${number}${string}`>();
expect(z.parse(b, "hello123world")).toEqual("hello123world");
expect(z.parse(b, "123")).toEqual("123");
expect(() => z.parse(b, "hello")).toThrow();
expect(() => z.parse(b, 123)).toThrow();
// include boolean
const c = z.templateLiteral([z.string(), z.boolean()]);
type c = z.output<typeof c>;
expectTypeOf<c>().toEqualTypeOf<`${string}${boolean}`>();
expect(z.parse(c, "hellotrue")).toEqual("hellotrue");
expect(z.parse(c, "hellofalse")).toEqual("hellofalse");
expect(() => z.parse(c, "hello")).toThrow();
expect(() => z.parse(c, 123)).toThrow();
// include literal prefix
const d = z.templateLiteral([z.literal("hello"), z.number()]);
type d = z.output<typeof d>;
expectTypeOf<d>().toEqualTypeOf<`hello${number}`>();
expect(z.parse(d, "hello123")).toEqual("hello123");
expect(() => z.parse(d, 123)).toThrow();
expect(() => z.parse(d, "world123")).toThrow();
// include literal union
const e = z.templateLiteral([z.literal(["aa", "bb"]), z.number()]);
type e = z.output<typeof e>;
expectTypeOf<e>().toEqualTypeOf<`aa${number}` | `bb${number}`>();
expect(z.parse(e, "aa123")).toEqual("aa123");
expect(z.parse(e, "bb123")).toEqual("bb123");
expect(() => z.parse(e, "cc123")).toThrow();
expect(() => z.parse(e, 123)).toThrow();
});
// this returns both a schema and a check
test("z.custom schema", () => {
const a = z.custom((val) => {
return typeof val === "string";
});
expect(z.parse(a, "hello")).toEqual("hello");
expect(() => z.parse(a, 123)).toThrow();
});
test("z.custom check", () => {
// @ts-expect-error Inference not possible, use z.refine()
z.date().check(z.custom((val) => val.getTime() > 0));
});
test("z.check", () => {
// this is a more flexible version of z.custom that accepts an arbitrary _parse logic
// the function should return base.$ZodResult
const a = z.any().check(
z.check<string>((ctx) => {
if (typeof ctx.value === "string") return;
ctx.issues.push({
code: "custom",
origin: "custom",
message: "Expected a string",
input: ctx.value,
});
})
);
expect(z.safeParse(a, "hello")).toMatchObject({
success: true,
data: "hello",
});
expect(z.safeParse(a, 123)).toMatchObject({
success: false,
error: { issues: [{ code: "custom", message: "Expected a string" }] },
});
});
test("z.with (alias for z.check)", () => {
// .with() should work exactly the same as .check()
const a = z.any().with(
z.check<string>((ctx) => {
if (typeof ctx.value === "string") return;
ctx.issues.push({
code: "custom",
origin: "custom",
message: "Expected a string",
input: ctx.value,
});
})
);
expect(z.safeParse(a, "hello")).toMatchObject({
success: true,
data: "hello",
});
expect(z.safeParse(a, 123)).toMatchObject({
success: false,
error: { issues: [{ code: "custom", message: "Expected a string" }] },
});
// Test with refine
const b = z.string().with(z.refine((val) => val.length > 3, "Must be longer than 3"));
expect(z.safeParse(b, "hello").success).toBe(true);
expect(z.safeParse(b, "hi").success).toBe(false);
// Test with function
const c = z.string().with(({ value, issues }) => {
if (value.length <= 3) {
issues.push({
code: "custom",
input: value,
message: "Must be longer than 3",
});
}
});
expect(z.safeParse(c, "hello").success).toBe(true);
expect(z.safeParse(c, "hi").success).toBe(false);
});
test("z.instanceof", () => {
class A {}
const a = z.instanceof(A);
expect(z.parse(a, new A())).toBeInstanceOf(A);
expect(() => z.parse(a, {})).toThrow();
});
test("z.refine", () => {
const a = z.number().check(
z.refine((val) => val > 3),
z.refine((val) => val < 10)
);
expect(z.parse(a, 5)).toEqual(5);
expect(() => z.parse(a, 2)).toThrow();
expect(() => z.parse(a, 11)).toThrow();
expect(() => z.parse(a, "hi")).toThrow();
});
// test("z.superRefine", () => {
// const a = z.number([
// z.superRefine((val, ctx) => {
// if (val < 3) {
// return ctx.addIssue({
// code: "custom",
// origin: "custom",
// message: "Too small",
// input: val,
// });
// }
// if (val > 10) {
// return ctx.addIssue("Too big");
// }
// }),
// ]);
// expect(z.parse(a, 5)).toEqual(5);
// expect(() => z.parse(a, 2)).toThrow();
// expect(() => z.parse(a, 11)).toThrow();
// expect(() => z.parse(a, "hi")).toThrow();
// });
test("z.transform", () => {
const a = z.transform((val: number) => {
return `${val}`;
});
type a_in = z.input<typeof a>;
expectTypeOf<a_in>().toEqualTypeOf<number>();
type a_out = z.output<typeof a>;
expectTypeOf<a_out>().toEqualTypeOf<string>();
expect(z.parse(a, 123)).toEqual("123");
});
test("z.$brand()", () => {
const a = z.string().brand<"my-brand">();
type a = z.output<typeof a>;
const branded = (_: a) => {};
// @ts-expect-error
branded("asdf");
});
test("z.lazy", () => {
const a = z.lazy(() => z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<string>();
expect(z.parse(a, "hello")).toEqual("hello");
expect(() => z.parse(a, 123)).toThrow();
});
// schema that validates JSON-like data
test("z.json", () => {
const a = z.json();
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<util.JSONType>();
expect(z.parse(a, "hello")).toEqual("hello");
expect(z.parse(a, 123)).toEqual(123);
expect(z.parse(a, true)).toEqual(true);
expect(z.parse(a, null)).toEqual(null);
expect(z.parse(a, {})).toEqual({});
expect(z.parse(a, { a: "hello" })).toEqual({ a: "hello" });
expect(z.parse(a, [1, 2, 3])).toEqual([1, 2, 3]);
expect(z.parse(a, [{ a: "hello" }])).toEqual([{ a: "hello" }]);
// fail cases
expect(() => z.parse(a, new Date())).toThrow();
expect(() => z.parse(a, Symbol())).toThrow();
expect(() => z.parse(a, { a: new Date() })).toThrow();
expect(() => z.parse(a, undefined)).toThrow();
expect(() => z.parse(a, { a: undefined })).toThrow();
});
// promise
test("z.promise", async () => {
const a = z.promise(z.string());
type a = z.output<typeof a>;
expectTypeOf<a>().toEqualTypeOf<Promise<string>>();
expect(await z.safeParseAsync(a, Promise.resolve("hello"))).toMatchObject({
success: true,
data: "hello",
});
expect(await z.safeParseAsync(a, Promise.resolve(123))).toMatchObject({
success: false,
});
const b = z.string();
expect(() => z.parse(b, Promise.resolve("hello"))).toThrow();
});
// test("type assertions", () => {
// const schema = z.pipe(
// z.string(),
// z.transform((val) => val.length)
// );
// schema.assertInput<string>();
// // @ts-expect-error
// schema.assertInput<number>();
// schema.assertOutput<number>();
// // @ts-expect-error
// schema.assertOutput<string>();
// });
test("isPlainObject", () => {
expect(z.core.util.isPlainObject({})).toEqual(true);
expect(z.core.util.isPlainObject(Object.create(null))).toEqual(true);
expect(z.core.util.isPlainObject([])).toEqual(false);
expect(z.core.util.isPlainObject(new Date())).toEqual(false);
expect(z.core.util.isPlainObject(null)).toEqual(false);
expect(z.core.util.isPlainObject(undefined)).toEqual(false);
expect(z.core.util.isPlainObject("string")).toEqual(false);
expect(z.core.util.isPlainObject(123)).toEqual(false);
expect(z.core.util.isPlainObject(Symbol())).toEqual(false);
expect(z.core.util.isPlainObject({ constructor: "string" })).toEqual(true);
expect(z.core.util.isPlainObject({ constructor: 123 })).toEqual(true);
expect(z.core.util.isPlainObject({ constructor: null })).toEqual(true);
expect(z.core.util.isPlainObject({ constructor: undefined })).toEqual(true);
expect(z.core.util.isPlainObject({ constructor: true })).toEqual(true);
expect(z.core.util.isPlainObject({ constructor: {} })).toEqual(true);
expect(z.core.util.isPlainObject({ constructor: [] })).toEqual(true);
});
test("shallowClone with constructor field", () => {
const objWithConstructor = { constructor: "string", key: "value" };
const cloned = z.core.util.shallowClone(objWithConstructor);
expect(cloned).toEqual(objWithConstructor);
expect(cloned).not.toBe(objWithConstructor);
expect(cloned.constructor).toBe("string");
expect(cloned.key).toBe("value");
const testCases = [
{ constructor: 123, data: "test" },
{ constructor: null, data: "test" },
{ constructor: true, data: "test" },
{ constructor: {}, data: "test" },
{ constructor: [], data: "test" },
];
for (const testCase of testCases) {
const clonedCase = z.core.util.shallowClone(testCase);
expect(clonedCase).toEqual(testCase);
expect(clonedCase).not.toBe(testCase);
}
});
test("def typing", () => {
z.string().def.type satisfies "string";
z.number().def.type satisfies "number";
z.bigint().def.type satisfies "bigint";
z.boolean().def.type satisfies "boolean";
z.date().def.type satisfies "date";
z.symbol().def.type satisfies "symbol";
z.undefined().def.type satisfies "undefined";
z.string().nullable().def.type satisfies "nullable";
z.null().def.type satisfies "null";
z.any().def.type satisfies "any";
z.unknown().def.type satisfies "unknown";
z.never().def.type satisfies "never";
z.void().def.type satisfies "void";
z.array(z.string()).def.type satisfies "array";
z.object({ key: z.string() }).def.type satisfies "object";
z.union([z.string(), z.number()]).def.type satisfies "union";
z.intersection(z.string(), z.number()).def.type satisfies "intersection";
z.tuple([z.string(), z.number()]).def.type satisfies "tuple";
z.record(z.string(), z.number()).def.type satisfies "record";
z.map(z.string(), z.number()).def.type satisfies "map";
z.set(z.string()).def.type satisfies "set";
z.literal("example").def.type satisfies "literal";
z.enum(["a", "b", "c"]).def.type satisfies "enum";
z.promise(z.string()).def.type satisfies "promise";
z.lazy(() => z.string()).def.type satisfies "lazy";
z.string().optional().def.type satisfies "optional";
z.string().default("default").def.type satisfies "default";
z.templateLiteral([z.literal("a"), z.literal("b")]).def.type satisfies "template_literal";
z.custom<string>((val) => typeof val === "string").def.type satisfies "custom";
z.transform((val) => val as string).def.type satisfies "transform";
z.string().optional().nonoptional().def.type satisfies "nonoptional";
z.object({ key: z.string() }).readonly().def.type satisfies "readonly";
z.nan().def.type satisfies "nan";
z.unknown().pipe(z.number()).def.type satisfies "pipe";
z.success(z.string()).def.type satisfies "success";
z.string().catch("fallback").def.type satisfies "catch";
z.file().def.type satisfies "file";
});
test("runtime type property exists and returns correct values", () => {
const stringSchema = z.string();
expect(stringSchema.type).toBe("string");
});
test("type narrowing works with type property", () => {
type ArrayOrRecord = z.ZodArray<z.ZodString> | z.ZodRecord<z.ZodString, z.ZodAny>;
const arraySchema = z.array(z.string()) as ArrayOrRecord;
if (arraySchema.type === "array") {
expectTypeOf(arraySchema).toEqualTypeOf<z.ZodArray<z.ZodString>>();
expect(arraySchema.element).toBeDefined();
}
});

View File

@@ -0,0 +1,60 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
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();
await expect(() => TestSchema.parse(12)).toThrow();
expectTypeOf<Test>().toEqualTypeOf<z.infer<typeof TestSchema>>();
});
test("instanceof fatal", () => {
const schema = z.instanceof(Date).refine((d) => d.toString());
const res = schema.safeParse(null);
expect(res.success).toBe(false);
});
test("instanceof respects customError", () => {
class Test {
name!: string;
}
z.config({
customError: () => {
return "This is invalid!";
},
});
const TestSchema = z.instanceof(Test);
const result = TestSchema.safeParse("whatever");
expect(result.success).toBe(false);
if (!result.success) {
const issue = result.error.issues[0];
expect(issue.code).toBe("invalid_type");
if (issue.code === "invalid_type") {
expect(issue.expected).toBe("Test");
}
expect(issue.message).toBe("This is invalid!");
}
z.config({ customError: undefined });
});

View File

@@ -0,0 +1,198 @@
import { expect, expectTypeOf, test } from "vitest";
import type { util } from "zod/v4/core";
import * as z from "zod/v4";
test("object intersection", () => {
const A = z.object({ a: z.string() });
const B = z.object({ b: z.string() });
const C = z.intersection(A, B); // BaseC.merge(HasID);
type C = z.infer<typeof C>;
expectTypeOf<C>().toEqualTypeOf<{ a: string } & { b: string }>();
const data = { a: "foo", b: "foo" };
expect(C.parse(data)).toEqual(data);
expect(() => C.parse({ a: "foo" })).toThrow();
});
test("object intersection: loose", () => {
const A = z.looseObject({ a: z.string() });
const B = z.object({ b: z.string() });
const C = z.intersection(A, B); // BaseC.merge(HasID);
type C = z.infer<typeof C>;
expectTypeOf<C>().toEqualTypeOf<{ a: string; [x: string]: unknown } & { b: string }>();
const data = { a: "foo", b: "foo", c: "extra" };
expect(C.parse(data)).toEqual(data);
expect(() => C.parse({ a: "foo" })).toThrow();
});
test("object intersection: strict + strip", () => {
const A = z.strictObject({ a: z.string() });
const B = z.object({ b: z.string() });
const C = z.intersection(A, B);
type C = z.infer<typeof C>;
expectTypeOf<C>().toEqualTypeOf<{ a: string } & { b: string }>();
// Keys recognized by either side should work
expect(C.parse({ a: "foo", b: "bar" })).toEqual({ a: "foo", b: "bar" });
// Extra keys are stripped (follows strip behavior from B)
expect(C.parse({ a: "foo", b: "bar", c: "extra" })).toEqual({ a: "foo", b: "bar" });
});
test("object intersection: strict + strict", () => {
const A = z.strictObject({ a: z.string() });
const B = z.strictObject({ b: z.string() });
const C = z.intersection(A, B);
// Keys recognized by either side should work
expect(C.parse({ a: "foo", b: "bar" })).toEqual({ a: "foo", b: "bar" });
// Keys unrecognized by BOTH sides should error
const result = C.safeParse({ a: "foo", b: "bar", c: "extra" });
expect(result.error?.issues).toMatchInlineSnapshot(`
[
{
"code": "unrecognized_keys",
"keys": [
"c",
],
"message": "Unrecognized key: "c"",
"path": [],
},
]
`);
});
test("deep intersection", () => {
const Animal = z.object({
properties: z.object({
is_animal: z.boolean(),
}),
});
const Cat = z.intersection(
z.object({
properties: z.object({
jumped: z.boolean(),
}),
}),
Animal
);
type Cat = util.Flatten<z.infer<typeof Cat>>;
expectTypeOf<Cat>().toEqualTypeOf<{ properties: { is_animal: boolean } & { jumped: boolean } }>();
const a = Cat.safeParse({ properties: { is_animal: true, jumped: true } });
expect(a.data!.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.intersection(
Author,
z.object({
posts: z.array(
z.object({
title: z.string(),
})
),
})
);
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)
);
expect(() => {
numberIntersection.parse(1234);
}).toThrowErrorMatchingInlineSnapshot(`[Error: Unmergable intersection. Error path: []]`);
});
test("invalid array merge (incompatible lengths)", async () => {
const stringArrInt = z.intersection(
z.string().array(),
z
.string()
.array()
.transform((val) => [...val, "asdf"])
);
expect(() => stringArrInt.safeParse(["asdf", "qwer"])).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: []]`
);
});
test("invalid array merge (incompatible elements)", async () => {
const stringArrInt = z.intersection(
z.string().array(),
z
.string()
.array()
.transform((val) => [...val.slice(0, -1), "asdf"])
);
expect(() => stringArrInt.safeParse(["asdf", "qwer"])).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: [1]]`
);
});
test("invalid object merge", async () => {
const Cat = z.object({
phrase: z.string().transform((val) => `${val} Meow`),
});
const Dog = z.object({
phrase: z.string().transform((val) => `${val} Woof`),
});
const CatDog = z.intersection(Cat, Dog);
expect(() => CatDog.parse({ phrase: "Hello, my name is CatDog." })).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: ["phrase"]]`
);
});
test("invalid deep merge of object and array combination", async () => {
const University = z.object({
students: z.array(
z.object({
name: z.string().transform((val) => `Student name: ${val}`),
})
),
});
const Registry = z.intersection(
University,
z.object({
students: z.array(
z.object({
name: z.string(),
surname: z.string(),
})
),
})
);
const students = [{ name: "John", surname: "Doe" }];
expect(() => Registry.parse({ students })).toThrowErrorMatchingInlineSnapshot(
`[Error: Unmergable intersection. Error path: ["students",0,"name"]]`
);
});

View File

@@ -0,0 +1,109 @@
import { expect, test } from "vitest";
test("placeholder test", () => {
expect(2).toBe(2);
});
// test("overload types", () => {
// const schema = z.string().json();
// util.assertEqual<typeof schema, z.ZodString>(true);
// const schema2 = z.string().json(z.number());
// util.assertEqual<typeof schema2, z.ZodPipe<z.ZodTransform<any, string>, z.ZodNumber>>(true);
// const r2 = schema2.parse("12");
// util.assertEqual<number, typeof r2>(true);
// });
// test("parse string to json", async () => {
// const Env = z.object({
// myJsonConfig: z.string().jsonString(z.object({ foo: z.number() })),
// someOtherValue: z.string(),
// });
// expect(
// Env.parse({
// myJsonConfig: '{ "foo": 123 }',
// someOtherValue: "abc",
// })
// ).toEqual({
// myJsonConfig: { foo: 123 },
// someOtherValue: "abc",
// });
// const invalidValues = Env.safeParse({
// myJsonConfig: '{"foo": "not a number!"}',
// someOtherValue: null,
// });
// expect(JSON.parse(JSON.stringify(invalidValues))).toEqual({
// success: false,
// error: {
// name: "ZodError",
// issues: [
// {
// code: "invalid_type",
// expected: "number",
// input: "not a number!",
// received: "string",
// path: ["myJsonConfig", "foo"],
// message: "Expected number, received string",
// },
// {
// code: "invalid_type",
// expected: "string",
// input: null,
// received: "null",
// path: ["someOtherValue"],
// message: "Expected string, received null",
// },
// ],
// },
// });
// const invalidJsonSyntax = Env.safeParse({
// myJsonConfig: "This is not valid json",
// someOtherValue: null,
// });
// expect(JSON.parse(JSON.stringify(invalidJsonSyntax))).toMatchObject({
// success: false,
// error: {
// name: "ZodError",
// issues: [
// {
// code: "invalid_string",
// input: {
// _def: {
// catchall: {
// _def: {
// typeName: "ZodNever",
// },
// },
// typeName: "ZodObject",
// unknownKeys: "strip",
// },
// },
// validation: "json",
// message: "Invalid json",
// path: ["myJsonConfig"],
// },
// {
// code: "invalid_type",
// expected: "string",
// input: null,
// received: "null",
// path: ["someOtherValue"],
// message: "Expected string, received null",
// },
// ],
// },
// });
// });
// test("no argument", () => {
// const schema = z.string().json();
// util.assertEqual<typeof schema, z.ZodString>(true);
// z.string().json().parse(`{}`);
// z.string().json().parse(`null`);
// z.string().json().parse(`12`);
// z.string().json().parse(`{ "test": "test"}`);
// expect(() => z.string().json().parse(`asdf`)).toThrow();
// expect(() => z.string().json().parse(`{ "test": undefined }`)).toThrow();
// expect(() => z.string().json().parse(`{ "test": 12n }`)).toThrow();
// expect(() => z.string().json().parse(`{ test: "test" }`)).toThrow();
// });

View File

@@ -0,0 +1,227 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("opt passthrough", () => {
const object = z.object({
a: z.lazy(() => z.string()),
b: z.lazy(() => z.string().optional()),
c: z.lazy(() => z.string().default("default")),
});
type ObjectTypeIn = z.input<typeof object>;
expectTypeOf<ObjectTypeIn>().toEqualTypeOf<{
a: string;
b?: string | undefined;
c?: string | undefined;
}>();
type ObjectTypeOut = z.output<typeof object>;
expectTypeOf<ObjectTypeOut>().toEqualTypeOf<{
a: string;
b?: string | undefined;
c: string;
}>();
const result = object.parse(
{
a: "hello",
b: undefined,
},
{ jitless: true }
);
expect(result).toEqual({
a: "hello",
// b: undefined,
c: "default",
});
expect(z.lazy(() => z.string())._zod.optin).toEqual(undefined);
expect(z.lazy(() => z.string())._zod.optout).toEqual(undefined);
expect(z.lazy(() => z.string().optional())._zod.optin).toEqual("optional");
expect(z.lazy(() => z.string().optional())._zod.optout).toEqual("optional");
expect(z.lazy(() => z.string().default("asdf"))._zod.optin).toEqual("optional");
expect(z.lazy(() => z.string().default("asdf"))._zod.optout).toEqual(undefined);
});
////////////// LAZY //////////////
test("schema getter", () => {
z.lazy(() => z.string()).parse("asdf");
});
test("lazy proxy", () => {
const schema = z.lazy(() => z.string())._zod.innerType.min(6);
schema.parse("123456");
expect(schema.safeParse("12345").success).toBe(false);
});
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.lazy", () => {
const Category: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
subcategories: z.array(Category),
})
);
Category.parse(testCategory);
});
type LinkedList = null | { value: number; next: LinkedList };
const linkedListExample = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null,
},
},
},
};
test("recursive union wit z.lazy", () => {
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 | undefined;
}
test("mutual recursion with lazy", () => {
const Alazy: z.ZodType<A> = z.lazy(() =>
z.object({
val: z.number(),
b: Blazy,
})
);
const Blazy: z.ZodType<B> = z.lazy(() =>
z.object({
val: z.number(),
a: Alazy.optional(),
})
);
const testData = {
val: 1,
b: {
val: 5,
a: {
val: 3,
b: {
val: 4,
a: {
val: 2,
b: {
val: 1,
},
},
},
},
},
};
Alazy.parse(testData);
Blazy.parse(testData.b);
expect(() => Alazy.parse({ val: "asdf" })).toThrow();
});
// TODO
test("mutual recursion with cyclical data", () => {
const a: any = { val: 1 };
const b: any = { val: 2 };
a.b = b;
b.a = a;
});
test("complicated self-recursion", () => {
const Category = z.object({
name: z.string(),
age: z.optional(z.number()),
get nullself() {
return Category.nullable();
},
get optself() {
return Category.optional();
},
get self() {
return Category;
},
get subcategories() {
return z.array(Category);
},
nested: z.object({
get sub() {
return Category;
},
}),
});
type _Category = z.output<typeof Category>;
});
test("lazy initialization", () => {
const a: any = z.lazy(() => a).optional();
const b: any = z.lazy(() => b).nullable();
const c: any = z.lazy(() => c).default({} as any);
const d: any = z.lazy(() => d).prefault({} as any);
const e: any = z.lazy(() => e).nonoptional();
const f: any = z.lazy(() => f).catch({} as any);
const g: any = z.lazy(() => z.object({ g })).readonly();
const baseCategorySchema = z.object({
name: z.string(),
});
type Category = z.infer<typeof baseCategorySchema> & {
subcategories: Category[];
};
const categorySchema: z.ZodType<Category> = baseCategorySchema.extend({
subcategories: z.lazy(() => categorySchema.array()),
});
});

View File

@@ -0,0 +1,117 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
const literalTuna = z.literal("tuna");
const literalTunaCustomMessage = z.literal("tuna", {
message: "That's not a tuna",
});
const literalFortyTwo = z.literal(42);
const literalTrue = z.literal(true);
test("passing validations", () => {
literalTuna.parse("tuna");
literalFortyTwo.parse(42);
literalTrue.parse(true);
});
test("failing validations", () => {
expect(() => literalTuna.parse("shark")).toThrow();
expect(() => literalFortyTwo.parse(43)).toThrow();
expect(() => literalTrue.parse(false)).toThrow();
});
test("invalid_literal should have `input` field with data", () => {
const data = "shark";
const result = literalTuna.safeParse(data);
const issue = result.error!.issues[0];
expect(issue.code).toBe("invalid_value");
expect(issue).toMatchInlineSnapshot(`
{
"code": "invalid_value",
"message": "Invalid input: expected "tuna"",
"path": [],
"values": [
"tuna",
],
}
`);
});
test("invalid_literal should return default message", () => {
const data = "shark";
const result = literalTuna.safeParse(data);
const issue = result.error!.issues[0];
expect(issue.message).toEqual(`Invalid input: expected \"tuna\"`);
});
test("invalid_literal should return custom message", () => {
const data = "shark";
const result = literalTunaCustomMessage.safeParse(data);
const issue = result.error!.issues[0];
expect(issue.message).toEqual(`That's not a tuna`);
});
test("literal default error message", () => {
const result = z.literal("Tuna").safeParse("Trout");
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_value",
"values": [
"Tuna"
],
"path": [],
"message": "Invalid input: expected \\"Tuna\\""
}
]]
`);
});
test("literal bigint default error message", () => {
const result = z.literal(BigInt(12)).safeParse(BigInt(13));
expect(result.success).toBe(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].message).toEqual(`Invalid input: expected 12n`);
});
test(".value getter", () => {
expect(z.literal("tuna").value).toEqual("tuna");
expect(() => z.literal([1, 2, 3]).value).toThrow();
});
test("readonly", () => {
const a = ["asdf"] as const;
z.literal(a);
});
test("literal pattern", () => {
expect(z.literal(1.1)._zod.pattern).toMatchInlineSnapshot(`/\\^\\(1\\\\\\.1\\)\\$/`);
expect(z.templateLiteral([z.literal(1.1)]).safeParse("1.1")).toMatchInlineSnapshot(`
{
"data": "1.1",
"success": true,
}
`);
expect(z.templateLiteral([z.literal(1.1)]).safeParse("1n1")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^(1\\\\.1)$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});

View File

@@ -0,0 +1,330 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const stringMap = z.map(z.string(), z.string());
type stringMap = z.infer<typeof stringMap>;
const minTwo = stringMap.min(2);
const maxTwo = stringMap.max(2);
const justTwo = stringMap.size(2);
const nonEmpty = stringMap.nonempty();
const nonEmptyMax = stringMap.nonempty().max(2);
test("type inference", () => {
expectTypeOf<stringMap>().toEqualTypeOf<Map<string, string>>();
});
test("valid parse", () => {
const result = stringMap.safeParse(
new Map([
["first", "foo"],
["second", "bar"],
])
);
expect(result.success).toEqual(true);
expect(result.data).toMatchInlineSnapshot(`
Map {
"first" => "foo",
"second" => "bar",
}
`);
});
test("valid parse: size-related methods", () => {
expect(() => {
minTwo.parse(
new Map([
["a", "b"],
["c", "d"],
])
);
minTwo.parse(
new Map([
["a", "b"],
["c", "d"],
["e", "f"],
])
);
maxTwo.parse(
new Map([
["a", "b"],
["c", "d"],
])
);
maxTwo.parse(new Map([["a", "b"]]));
justTwo.parse(
new Map([
["a", "b"],
["c", "d"],
])
);
nonEmpty.parse(new Map([["a", "b"]]));
nonEmptyMax.parse(
new Map([
["a", "b"],
["c", "d"],
])
);
}).not.toThrow();
const sizeZeroResult = stringMap.parse(new Map());
expect(sizeZeroResult.size).toBe(0);
const sizeTwoResult = minTwo.parse(
new Map([
["a", "b"],
["c", "d"],
])
);
expect(sizeTwoResult.size).toBe(2);
});
test("failing when parsing empty map in nonempty ", () => {
const result = nonEmpty.safeParse(new Map());
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_small");
});
test("failing when map is bigger than max() ", () => {
const result = maxTwo.safeParse(
new Map([
["a", "b"],
["c", "d"],
["e", "f"],
])
);
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_big");
});
test("valid parse async", async () => {
const asyncMap = z.map(
z.string().refine(async () => false, "bad key"),
z.string().refine(async () => false, "bad value")
);
const result = await asyncMap.safeParseAsync(new Map([["first", "foo"]]));
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"first"
],
"message": "bad key"
},
{
"code": "custom",
"path": [
"first"
],
"message": "bad value"
}
]]
`);
});
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("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).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
42
],
"message": "Invalid input: expected string, received number"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
42
],
"message": "Invalid input: expected string, received symbol"
}
]]
`);
}
});
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).toMatchInlineSnapshot(`
[
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [
1,
],
},
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [
"bar",
],
},
]
`);
}
});
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).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"first"
],
"message": "Keys must be uppercase"
},
{
"code": "custom",
"path": [
"second"
],
"message": "Keys must be uppercase"
}
]]
`);
}
});
test("map with object keys", () => {
const map = z.map(
z.object({
name: z.string(),
age: z.number(),
}),
z.string()
);
const data = new Map([
[{ name: "John", age: 30 }, "foo"],
[{ name: "Jane", age: 25 }, "bar"],
]);
const result = map.safeParse(data);
expect(result.success).toEqual(true);
expect(result.data!).toEqual(data);
const badData = new Map([["bad", "foo"]]);
const badResult = map.safeParse(badData);
expect(badResult.success).toEqual(false);
expect(badResult.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [
"bad"
],
"message": "Invalid input: expected object, received string"
}
]]
`);
});
test("min/max", async () => {
const schema = stringMap.min(4).max(5);
const r1 = schema.safeParse(
new Map([
["a", "a"],
["b", "b"],
["c", "c"],
["d", "d"],
])
);
expect(r1.success).toEqual(true);
const r2 = schema.safeParse(
new Map([
["a", "a"],
["b", "b"],
["c", "c"],
])
);
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected map to have >=4 entries",
"minimum": 4,
"origin": "map",
"path": [],
},
]
`);
const r3 = schema.safeParse(
new Map([
["a", "a"],
["b", "b"],
["c", "c"],
["d", "d"],
["e", "e"],
["f", "f"],
])
);
expect(r3.success).toEqual(false);
expect(r3.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 5,
"message": "Too big: expected map to have <=5 entries",
"origin": "map",
"path": [],
},
]
`);
});

View File

@@ -0,0 +1,21 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const schema = z.nan();
test("passing validations", () => {
schema.parse(Number.NaN);
schema.parse(Number("Not a number"));
expectTypeOf<typeof schema._output>().toEqualTypeOf<number>();
});
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();
});

View File

@@ -0,0 +1,168 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("nested refinements", () => {
const zodSchema = z
.object({
password: z.string().min(1),
nested: z
.object({
confirm: z
.string()
.min(1)
.refine((value) => value.length > 2, {
message: "Confirm length should be > 2",
}),
})
.refine(
(data) => {
return data.confirm === "bar";
},
{
path: ["confirm"],
error: 'Value must be "bar"',
}
),
})
.refine(
(data) => {
return data.nested.confirm === data.password;
},
{
path: ["nested", "confirm"],
error: "Password and confirm must match",
}
);
const DATA = {
password: "bar",
nested: { confirm: "" },
};
expect(zodSchema.safeParse(DATA)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"nested",
"confirm"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Confirm length should be > 2"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Value must be \\"bar\\""
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Password and confirm must match"
}
]],
"success": false,
}
`);
expect(zodSchema.safeParse(DATA, { jitless: true })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"nested",
"confirm"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Confirm length should be > 2"
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Value must be \\"bar\\""
},
{
"code": "custom",
"path": [
"nested",
"confirm"
],
"message": "Password and confirm must match"
}
]],
"success": false,
}
`);
expect(zodSchema["~standard"].validate(DATA)).toMatchInlineSnapshot(`
{
"issues": [
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected string to have >=1 characters",
"minimum": 1,
"origin": "string",
"path": [
"nested",
"confirm",
],
},
{
"code": "custom",
"message": "Confirm length should be > 2",
"path": [
"nested",
"confirm",
],
},
{
"code": "custom",
"message": "Value must be "bar"",
"path": [
"nested",
"confirm",
],
},
{
"code": "custom",
"message": "Password and confirm must match",
"path": [
"nested",
"confirm",
],
},
],
}
`);
});

View File

@@ -0,0 +1,101 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("nonoptional", () => {
const schema = z.string().nonoptional();
expectTypeOf<typeof schema._input>().toEqualTypeOf<string>();
expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
const result = schema.safeParse(undefined);
expect(result.success).toBe(false);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("nonoptional with default", () => {
const schema = z.string().optional().nonoptional();
expectTypeOf<typeof schema._input>().toEqualTypeOf<string>();
expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
const result = schema.safeParse(undefined);
expect(result.success).toBe(false);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [],
"message": "Invalid input: expected nonoptional, received undefined"
}
]],
"success": false,
}
`);
});
test("nonoptional in object", () => {
const schema = z.object({ hi: z.string().optional().nonoptional() });
expectTypeOf<typeof schema._input>().toEqualTypeOf<{ hi: string }>();
expectTypeOf<typeof schema._output>().toEqualTypeOf<{ hi: string }>();
const r1 = schema.safeParse({ hi: "asdf" });
expect(r1.success).toEqual(true);
const r2 = schema.safeParse({ hi: undefined });
// expect(schema.safeParse({ hi: undefined }).success).toEqual(false);
expect(r2.success).toEqual(false);
expect(r2.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [
"hi"
],
"message": "Invalid input: expected nonoptional, received undefined"
}
]]
`);
const r3 = schema.safeParse({});
expect(r3.success).toEqual(false);
expect(r3.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [
"hi"
],
"message": "Invalid input: expected nonoptional, received undefined"
}
]]
`);
});
test("encoding", () => {
const schema = z.string().optional().nonoptional();
expect(z.encode(schema, "hello")).toEqual("hello");
expect(() => z.encode(schema, undefined as any)).toThrowErrorMatchingInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "nonoptional",
"path": [],
"message": "Invalid input: expected nonoptional, received undefined"
}
]]
`);
});

View File

@@ -0,0 +1,22 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test(".nullable()", () => {
const nullable = z.string().nullable();
expect(nullable.parse(null)).toBe(null);
expect(nullable.parse("asdf")).toBe("asdf");
expect(() => nullable.parse(123)).toThrow();
});
test(".nullable unwrap", () => {
const schema = z.string().nullable();
expect(schema).toBeInstanceOf(z.ZodNullable);
expect(schema.unwrap()).toBeInstanceOf(z.ZodString);
});
test("z.null", () => {
const n = z.null();
expect(n.parse(null)).toBe(null);
expect(() => n.parse("asdf")).toThrow();
});

View File

@@ -0,0 +1,270 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("z.number() basic validation", () => {
const schema = z.number();
expect(schema.parse(1234)).toEqual(1234);
});
test("NaN validation", () => {
const schema = z.number();
expect(() => schema.parse(Number.NaN)).toThrow();
});
test("Infinity validation", () => {
const schema = z.number();
expect(schema.safeParse(Number.POSITIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
expect(schema.safeParse(Number.NEGATIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
});
test(".gt() validation", () => {
const schema = z.number().gt(0).gt(5);
expect(schema.parse(6)).toEqual(6);
expect(() => schema.parse(5)).toThrow();
});
test(".gte() validation", () => {
const schema = z.number().gt(0).gte(1).gte(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(4)).toThrow();
});
test(".min() validation", () => {
const schema = z.number().min(0).min(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(4)).toThrow();
});
test(".lt() validation", () => {
const schema = z.number().lte(10).lt(5);
expect(schema.parse(4)).toEqual(4);
expect(() => schema.parse(5)).toThrow();
});
test(".lte() validation", () => {
const schema = z.number().lte(10).lte(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(6)).toThrow();
});
test(".max() validation", () => {
const schema = z.number().max(10).max(5);
expect(schema.parse(5)).toEqual(5);
expect(() => schema.parse(6)).toThrow();
});
test(".int() validation", () => {
const schema = z.number().int();
expect(schema.parse(4)).toEqual(4);
expect(() => schema.parse(3.14)).toThrow();
});
test(".positive() validation", () => {
const schema = z.number().positive();
expect(schema.parse(1)).toEqual(1);
expect(() => schema.parse(0)).toThrow();
expect(() => schema.parse(-1)).toThrow();
});
test(".negative() validation", () => {
const schema = z.number().negative();
expect(schema.parse(-1)).toEqual(-1);
expect(() => schema.parse(0)).toThrow();
expect(() => schema.parse(1)).toThrow();
});
test(".nonpositive() validation", () => {
const schema = z.number().nonpositive();
expect(schema.parse(0)).toEqual(0);
expect(schema.parse(-1)).toEqual(-1);
expect(() => schema.parse(1)).toThrow();
});
test(".nonnegative() validation", () => {
const schema = z.number().nonnegative();
expect(schema.parse(0)).toEqual(0);
expect(schema.parse(1)).toEqual(1);
expect(() => schema.parse(-1)).toThrow();
});
test("multipleOf", () => {
const numbers = {
number3: 5.123,
number6: 5.123456,
number7: 5.1234567,
number8: 5.12345678,
};
const schemas = {
schema6: z.number().multipleOf(0.000001),
schema7: z.number().multipleOf(0.0000001),
};
expect(() => schemas.schema6.parse(numbers.number3)).not.toThrow();
expect(() => schemas.schema6.parse(numbers.number6)).not.toThrow();
expect(() => schemas.schema6.parse(numbers.number7)).toThrow();
expect(() => schemas.schema6.parse(numbers.number8)).toThrow();
expect(() => schemas.schema7.parse(numbers.number3)).not.toThrow();
expect(() => schemas.schema7.parse(numbers.number6)).not.toThrow();
expect(() => schemas.schema7.parse(numbers.number7)).not.toThrow();
expect(() => schemas.schema7.parse(numbers.number8)).toThrow();
});
test(".multipleOf() with positive divisor", () => {
const schema = z.number().multipleOf(5);
expect(schema.parse(15)).toEqual(15);
expect(schema.parse(-15)).toEqual(-15);
expect(() => schema.parse(7.5)).toThrow();
expect(() => schema.parse(-7.5)).toThrow();
});
test(".multipleOf() with negative divisor", () => {
const schema = z.number().multipleOf(-5);
expect(schema.parse(-15)).toEqual(-15);
expect(schema.parse(15)).toEqual(15);
expect(() => schema.parse(-7.5)).toThrow();
expect(() => schema.parse(7.5)).toThrow();
});
test(".step() validation", () => {
const schemaPointOne = z.number().step(0.1);
const schemaPointZeroZeroZeroOne = z.number().step(0.0001);
const schemaSixPointFour = z.number().step(6.4);
expect(schemaPointOne.parse(6)).toEqual(6);
expect(schemaPointOne.parse(6.1)).toEqual(6.1);
expect(schemaSixPointFour.parse(12.8)).toEqual(12.8);
expect(schemaPointZeroZeroZeroOne.parse(3.01)).toEqual(3.01);
expect(() => schemaPointOne.parse(6.11)).toThrow();
expect(() => schemaPointOne.parse(6.1000000001)).toThrow();
expect(() => schemaSixPointFour.parse(6.41)).toThrow();
});
test(".finite() validation", () => {
const schema = z.number().finite();
expect(schema.parse(123)).toEqual(123);
expect(schema.safeParse(Number.POSITIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
expect(schema.safeParse(Number.NEGATIVE_INFINITY)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "number",
"code": "invalid_type",
"received": "Infinity",
"path": [],
"message": "Invalid input: expected number, received number"
}
]],
"success": false,
}
`);
});
test(".safe() validation", () => {
const schema = z.number().safe();
expect(schema.parse(Number.MIN_SAFE_INTEGER)).toEqual(Number.MIN_SAFE_INTEGER);
expect(schema.parse(Number.MAX_SAFE_INTEGER)).toEqual(Number.MAX_SAFE_INTEGER);
expect(() => schema.parse(Number.MIN_SAFE_INTEGER - 1)).toThrow();
expect(() => schema.parse(Number.MAX_SAFE_INTEGER + 1)).toThrow();
});
test("min value getters", () => {
expect(z.number().minValue).toBeNull;
expect(z.number().lt(5).minValue).toBeNull;
expect(z.number().lte(5).minValue).toBeNull;
expect(z.number().max(5).minValue).toBeNull;
expect(z.number().negative().minValue).toBeNull;
expect(z.number().nonpositive().minValue).toBeNull;
expect(z.number().int().minValue).toBeNull;
expect(z.number().multipleOf(5).minValue).toBeNull;
expect(z.number().finite().minValue).toBeNull;
expect(z.number().gt(5).minValue).toEqual(5);
expect(z.number().gte(5).minValue).toEqual(5);
expect(z.number().min(5).minValue).toEqual(5);
expect(z.number().min(5).min(10).minValue).toEqual(10);
expect(z.number().positive().minValue).toEqual(0);
expect(z.number().nonnegative().minValue).toEqual(0);
expect(z.number().safe().minValue).toEqual(Number.MIN_SAFE_INTEGER);
});
test("max value getters", () => {
expect(z.number().maxValue).toBeNull;
expect(z.number().gt(5).maxValue).toBeNull;
expect(z.number().gte(5).maxValue).toBeNull;
expect(z.number().min(5).maxValue).toBeNull;
expect(z.number().positive().maxValue).toBeNull;
expect(z.number().nonnegative().maxValue).toBeNull;
expect(z.number().int().minValue).toBeNull;
expect(z.number().multipleOf(5).minValue).toBeNull;
expect(z.number().finite().minValue).toBeNull;
expect(z.number().lt(5).maxValue).toEqual(5);
expect(z.number().lte(5).maxValue).toEqual(5);
expect(z.number().max(5).maxValue).toEqual(5);
expect(z.number().max(5).max(1).maxValue).toEqual(1);
expect(z.number().negative().maxValue).toEqual(0);
expect(z.number().nonpositive().maxValue).toEqual(0);
expect(z.number().safe().maxValue).toEqual(Number.MAX_SAFE_INTEGER);
});
test("int getter", () => {
expect(z.number().isInt).toEqual(false);
expect(z.number().int().isInt).toEqual(true);
expect(z.number().safe().isInt).toEqual(true);
expect(z.number().multipleOf(5).isInt).toEqual(true);
});
/** In Zod 4, number schemas don't accept infinite values. */
test("finite getter", () => {
expect(z.number().isFinite).toEqual(true);
});
test("string format methods", () => {
const a = z.int32().min(5);
expect(a.parse(6)).toEqual(6);
expect(() => a.parse(1)).toThrow();
});
test("error customization", () => {
z.number().gte(5, { error: (iss) => "Min: " + iss.minimum.valueOf() });
z.number().lte(5, { error: (iss) => "Max: " + iss.maximum.valueOf() });
});

View File

@@ -0,0 +1,640 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
import * as core from "zod/v4/core";
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 }[];
};
expectTypeOf<z.TypeOf<typeof Test>>().toEqualTypeOf<TestType>();
});
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("nonstrict by default", () => {
z.object({ points: z.number() }).parse({
points: 2314,
unknown: "asdf",
});
});
test("parse optional keys ", () => {
const schema = z.object({
a: z.string().optional(),
});
expect(schema.parse({ a: "asdf" })).toEqual({ a: "asdf" });
});
test("empty object", () => {
const schema = z.object({});
expect(schema.parse({})).toEqual({});
expect(schema.parse({ name: "asdf" })).toEqual({});
expect(schema.safeParse(null).success).toEqual(false);
expect(schema.safeParse("asdf").success).toEqual(false);
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<Record<string, never>>();
});
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().passthrough().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 });
// expectTypeOf<(typeof d1)["asdf"]>().toEqualTypeOf<number>();
expectTypeOf<(typeof d1)["first"]>().toEqualTypeOf<string>();
});
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("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,
});
expect(Object.keys(result)).toEqual(["id", "set"]);
});
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("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>;
expectTypeOf<asdf>().toEqualTypeOf<{ a: number }>();
});
test("inferred type with Record shape", () => {
type A = z.ZodObject<Record<string, z.ZodType<string, number>>>;
expectTypeOf<z.infer<A>>().toEqualTypeOf<Record<string, string>>();
expectTypeOf<z.input<A>>().toEqualTypeOf<Record<string, number>>();
type B = z.ZodObject;
expectTypeOf<z.infer<B>>().toEqualTypeOf<Record<string, unknown>>();
expectTypeOf<z.input<B>>().toEqualTypeOf<Record<string, unknown>>();
});
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>;
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string; b: string }>();
expectTypeOf<Merged>().toEqualTypeOf<{ a?: string; b: string }>();
});
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>;
expectTypeOf<Unioned>().toEqualTypeOf<{ a: string; b?: string } | { a?: string; b: string }>();
});
test("inferred enum type", async () => {
const Enum = z.object({ a: z.string(), b: z.string().optional() }).keyof();
expect(Enum.enum).toEqual({
a: "a",
b: "b",
});
expect(Enum._zod.def.entries).toEqual({
a: "a",
b: "b",
});
type Enum = z.infer<typeof Enum>;
expectTypeOf<Enum>().toEqualTypeOf<"a" | "b">();
});
test("z.keyof returns enum", () => {
const User = z.object({ name: z.string(), age: z.number() });
const keysSchema = z.keyof(User);
expect(keysSchema.enum).toEqual({
name: "name",
age: "age",
});
expect(keysSchema._zod.def.entries).toEqual({
name: "name",
age: "age",
});
type Keys = z.infer<typeof keysSchema>;
expectTypeOf<Keys>().toEqualTypeOf<"name" | "age">();
});
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>;
expectTypeOf<Partial>().toEqualTypeOf<{ a?: string; b?: string }>();
});
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>;
expectTypeOf<Picked>().toEqualTypeOf<{ b?: string }>();
});
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>;
expectTypeOf<myType>().toEqualTypeOf<{
anyOptional?: any;
anyRequired: any;
unknownOptional?: unknown;
unknownRequired: unknown;
}>();
});
test("strictObject", 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(z.intersection(schema, 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(z.intersection(schema, 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>;
expectTypeOf<keyof Example>().toEqualTypeOf<"prop" | "opt" | "arr">();
});
test("catchall", () => {
const a = z.object({});
expect(a._zod.def.catchall).toBeUndefined();
const b = z.strictObject({});
expect(b._zod.def.catchall).toBeInstanceOf(core.$ZodNever);
const c = z.looseObject({});
expect(c._zod.def.catchall).toBeInstanceOf(core.$ZodUnknown);
const d = z.object({}).catchall(z.number());
expect(d._zod.def.catchall).toBeInstanceOf(core.$ZodNumber);
});
test("unknownkeys merging", () => {
// This one is "strict"
const a = z.looseObject({
a: z.string(),
});
const b = z.strictObject({ b: z.string() });
// incoming object overrides
const c = a.merge(b);
expect(c._zod.def.catchall).toBeInstanceOf(core.$ZodNever);
});
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);
expectTypeOf<keyof PersonWithNickname>().toEqualTypeOf<"firstName" | "lastName" | "nickName">();
expectTypeOf<PersonWithNickname>().toEqualTypeOf<{ firstName: string; lastName: string; nickName: string }>();
});
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);
expectTypeOf<PersonWithNumberAsLastName>().toEqualTypeOf<{ firstName: string; lastName: number }>();
});
test("safeExtend() should have power to override existing key", () => {
const PersonWithMinLastName = personToExtend.safeExtend({
lastName: z.string().min(3),
});
type PersonWithMinLastName = z.infer<typeof PersonWithMinLastName>;
const expected = { firstName: "f", lastName: "abc" };
const actual = PersonWithMinLastName.parse(expected);
expect(actual).toEqual(expected);
expect(() => PersonWithMinLastName.parse({ firstName: "f", lastName: "ab" })).toThrow();
expectTypeOf<PersonWithMinLastName>().toEqualTypeOf<{ firstName: string; lastName: string }>();
});
test("safeExtend() maintains refinements", () => {
const schema = z.object({ name: z.string().min(1) });
const extended = schema.safeExtend({ name: z.string().min(2) });
expect(() => extended.parse({ name: "" })).toThrow();
expect(extended.parse({ name: "ab" })).toEqual({ name: "ab" });
type Extended = z.infer<typeof extended>;
expectTypeOf<Extended>().toEqualTypeOf<{ name: string }>();
// @ts-expect-error
schema.safeExtend({ name: z.number() });
});
test("passthrough index signature", () => {
const a = z.object({ a: z.string() });
type a = z.infer<typeof a>;
expectTypeOf<a>().toEqualTypeOf<{ a: string }>();
const b = a.passthrough();
type b = z.infer<typeof b>;
expectTypeOf<b>().toEqualTypeOf<{ a: string; [k: string]: unknown }>();
});
// 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.object({
// data: z.union([z.object({ name: z.string(), a: z.number() }), z.object({ name: z.string(), b: z.number() })]),
// }) satisfies z.ZodType<Outer, any>;
// });
test("assignability", () => {
z.object({ a: z.string() }) satisfies z.ZodObject<{ a: z.ZodString }>;
z.object({ a: z.string() }).catchall(z.number()) satisfies z.ZodObject<{ a: z.ZodString }>;
z.object({ a: z.string() }).strict() satisfies z.ZodObject;
z.object({}) satisfies z.ZodObject;
z.looseObject({ name: z.string() }) satisfies z.ZodObject<
{
name: z.ZodString;
},
z.core.$loose
>;
z.looseObject({ name: z.string() }) satisfies z.ZodObject<{
name: z.ZodString;
}>;
z.strictObject({ name: z.string() }) satisfies z.ZodObject<
{
name: z.ZodString;
},
z.core.$loose
>;
z.strictObject({ name: z.string() }) satisfies z.ZodObject<
{
name: z.ZodString;
},
z.core.$strict
>;
z.object({ name: z.string() }) satisfies z.ZodObject<{
name: z.ZodString;
}>;
z.object({
a: z.string(),
b: z.number(),
c: z.boolean(),
}) satisfies z.core.$ZodObject;
});
test("null prototype", () => {
const schema = z.object({ a: z.string() });
const obj = Object.create(null);
obj.a = "foo";
expect(schema.parse(obj)).toEqual({ a: "foo" });
});
test("empty objects", () => {
const A = z.looseObject({});
type Ain = z.input<typeof A>;
expectTypeOf<Ain>().toEqualTypeOf<Record<string, unknown>>();
type Aout = z.output<typeof A>;
expectTypeOf<Aout>().toEqualTypeOf<Record<string, unknown>>();
const B = z.object({});
type Bout = z.output<typeof B>;
expectTypeOf<Bout>().toEqualTypeOf<Record<string, never>>();
type Bin = z.input<typeof B>;
expectTypeOf<Bin>().toEqualTypeOf<Record<string, never>>();
const C = z.strictObject({});
type Cout = z.output<typeof C>;
expectTypeOf<Cout>().toEqualTypeOf<Record<string, never>>();
type Cin = z.input<typeof C>;
expectTypeOf<Cin>().toEqualTypeOf<Record<string, never>>();
});
test("preserve key order", () => {
const schema = z.object({
a: z.string().optional(),
b: z.string(),
});
const r1 = schema.safeParse({ a: "asdf", b: "qwer" });
const r2 = schema.safeParse({ a: "asdf", b: "qwer" }, { jitless: true });
expect(Object.keys(r1.data!)).toMatchInlineSnapshot(`
[
"a",
"b",
]
`);
expect(Object.keys(r1.data!)).toEqual(Object.keys(r2.data!));
});
test("empty shape", () => {
const a = z.object({});
a.parse({});
a.parse({}, { jitless: true });
a.parse(Object.create(null));
a.parse(Object.create(null), { jitless: true });
expect(() => a.parse([])).toThrow();
expect(() => a.parse([], { jitless: true })).toThrow();
});
test("zodtype assignability", () => {
// Does not error
z.object({ hello: z.string().optional() }) satisfies z.ZodType<{ hello?: string | undefined }>;
z.object({ hello: z.string() }) satisfies z.ZodType<{ hello?: string | undefined }>;
// @ts-expect-error
z.object({}) satisfies z.ZodType<{ hello: string | undefined }>;
// @ts-expect-error
z.object({ hello: z.string().optional() }) satisfies z.ZodType<{ hello: string | undefined }>;
// @ts-expect-error
z.object({ hello: z.string().optional() }) satisfies z.ZodType<{ hello: string }>;
// @ts-expect-error
z.object({ hello: z.number() }) satisfies z.ZodType<{ hello?: string | undefined }>;
});
test("index signature in shape", () => {
function makeZodObj<const T extends string>(key: T) {
return z.looseObject({
[key]: z.string(),
});
}
const schema = makeZodObj("foo");
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<Record<string, string>>();
});
test("extend() on object with refinements should throw when overwriting properties", () => {
const schema = z
.object({
a: z.string(),
})
.refine(() => true);
expect(() => schema.extend({ a: z.number() })).toThrow();
});
test("extend() on object with refinements should not throw when adding new properties", () => {
const schema = z
.object({
a: z.string(),
})
.refine((data) => data.a.length > 0);
// Should not throw since 'b' doesn't overlap with 'a'
const extended = schema.extend({ b: z.number() });
// Verify the extended schema works correctly
expect(extended.parse({ a: "hello", b: 42 })).toEqual({ a: "hello", b: 42 });
// Verify the original refinement still applies
expect(() => extended.parse({ a: "", b: 42 })).toThrow();
});
test("safeExtend() on object with refinements should not throw", () => {
const schema = z
.object({
a: z.string(),
})
.refine(() => true);
expect(() => schema.safeExtend({ b: z.string() })).not.toThrow();
});

View File

@@ -0,0 +1,223 @@
// @ts-ignore TS6133
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test(".optional()", () => {
const schema = z.string().optional();
expect(schema.parse("adsf")).toEqual("adsf");
expect(schema.parse(undefined)).toEqual(undefined);
expect(schema.safeParse(null).success).toEqual(false);
expectTypeOf<typeof schema._output>().toEqualTypeOf<string | undefined>();
});
test("unwrap", () => {
const unwrapped = z.string().optional().unwrap();
expect(unwrapped).toBeInstanceOf(z.ZodString);
});
test("optionality", () => {
const a = z.string();
expect(a._zod.optin).toEqual(undefined);
expect(a._zod.optout).toEqual(undefined);
const b = z.string().optional();
expect(b._zod.optin).toEqual("optional");
expect(b._zod.optout).toEqual("optional");
const c = z.string().default("asdf");
expect(c._zod.optin).toEqual("optional");
expect(c._zod.optout).toEqual(undefined);
const d = z.string().optional().nullable();
expect(d._zod.optin).toEqual("optional");
expect(d._zod.optout).toEqual("optional");
const e = z.string().default("asdf").nullable();
expect(e._zod.optin).toEqual("optional");
expect(e._zod.optout).toEqual(undefined);
// z.undefined should NOT be optional
const f = z.undefined();
expect(f._zod.optin).toEqual("optional");
expect(f._zod.optout).toEqual("optional");
expectTypeOf<typeof f._zod.optin>().toEqualTypeOf<"optional" | undefined>();
expectTypeOf<typeof f._zod.optout>().toEqualTypeOf<"optional" | undefined>();
// z.union should be optional if any of the types are optional
const g = z.union([z.string(), z.undefined()]);
expect(g._zod.optin).toEqual("optional");
expect(g._zod.optout).toEqual("optional");
expectTypeOf<typeof g._zod.optin>().toEqualTypeOf<"optional" | undefined>();
expectTypeOf<typeof g._zod.optout>().toEqualTypeOf<"optional" | undefined>();
const h = z.union([z.string(), z.optional(z.string())]);
expect(h._zod.optin).toEqual("optional");
expect(h._zod.optout).toEqual("optional");
expectTypeOf<typeof h._zod.optin>().toEqualTypeOf<"optional">();
expectTypeOf<typeof h._zod.optout>().toEqualTypeOf<"optional">();
});
test("pipe optionality", () => {
z.string().optional()._zod.optin;
const a = z.string().optional().pipe(z.string());
expect(a._zod.optin).toEqual("optional");
expect(a._zod.optout).toEqual(undefined);
expectTypeOf<typeof a._zod.optin>().toEqualTypeOf<"optional">();
expectTypeOf<typeof a._zod.optout>().toEqualTypeOf<"optional" | undefined>();
const b = z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().optional());
expect(b._zod.optin).toEqual(undefined);
expect(b._zod.optout).toEqual("optional");
expectTypeOf<typeof b._zod.optin>().toEqualTypeOf<"optional" | undefined>();
expectTypeOf<typeof b._zod.optout>().toEqualTypeOf<"optional">();
const c = z.string().default("asdf").pipe(z.string());
expect(c._zod.optin).toEqual("optional");
expect(c._zod.optout).toEqual(undefined);
const d = z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().default("asdf"));
expect(d._zod.optin).toEqual(undefined);
expect(d._zod.optout).toEqual(undefined);
});
test("pipe optionality inside objects", () => {
const schema = z.object({
a: z.string().optional(),
b: z.string().optional().pipe(z.string()),
c: z.string().default("asdf").pipe(z.string()),
d: z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().optional()),
e: z
.string()
.transform((val) => (Math.random() ? val : undefined))
.pipe(z.string().default("asdf")),
});
type SchemaIn = z.input<typeof schema>;
expectTypeOf<SchemaIn>().toEqualTypeOf<{
a?: string | undefined;
b?: string | undefined;
c?: string | undefined;
d: string;
e: string;
}>();
type SchemaOut = z.output<typeof schema>;
expectTypeOf<SchemaOut>().toEqualTypeOf<{
a?: string | undefined;
b: string;
c: string;
d?: string | undefined;
e: string;
}>();
});
test("optional prop with pipe", () => {
const schema = z.object({
id: z
.union([z.number(), z.string().nullish()])
.transform((val) => (val === null || val === undefined ? val : Number(val)))
.pipe(z.number())
.optional(),
});
schema.parse({});
schema.parse({}, { jitless: true });
});
// exactOptional tests
test(".exactOptional()", () => {
const schema = z.string().exactOptional();
expect(schema.parse("asdf")).toEqual("asdf");
expect(schema.safeParse(undefined).success).toEqual(false);
expect(schema.safeParse(null).success).toEqual(false);
// Type should NOT include undefined
expectTypeOf<typeof schema._output>().toEqualTypeOf<string>();
expectTypeOf<typeof schema._input>().toEqualTypeOf<string>();
});
test("exactOptional unwrap", () => {
const unwrapped = z.string().exactOptional().unwrap();
expect(unwrapped).toBeInstanceOf(z.ZodString);
});
test("exactOptional optionality", () => {
const a = z.string().exactOptional();
expect(a._zod.optin).toEqual("optional");
expect(a._zod.optout).toEqual("optional");
expectTypeOf<typeof a._zod.optin>().toEqualTypeOf<"optional">();
expectTypeOf<typeof a._zod.optout>().toEqualTypeOf<"optional">();
});
test("exactOptional in objects - absent keys", () => {
const schema = z.object({
a: z.string().exactOptional(),
});
// Absent key should pass
expect(schema.parse({})).toEqual({});
expect(schema.parse({}, { jitless: true })).toEqual({});
// Present key with valid value should pass
expect(schema.parse({ a: "hello" })).toEqual({ a: "hello" });
expect(schema.parse({ a: "hello" }, { jitless: true })).toEqual({ a: "hello" });
});
test("exactOptional in objects - explicit undefined rejected", () => {
const schema = z.object({
a: z.string().exactOptional(),
});
// Explicit undefined should fail
expect(schema.safeParse({ a: undefined }).success).toEqual(false);
expect(schema.safeParse({ a: undefined }, { jitless: true }).success).toEqual(false);
});
test("exactOptional type inference in objects", () => {
const schema = z.object({
a: z.string().exactOptional(),
b: z.string().optional(),
});
type SchemaIn = z.input<typeof schema>;
expectTypeOf<SchemaIn>().toEqualTypeOf<{
a?: string;
b?: string | undefined;
}>();
type SchemaOut = z.output<typeof schema>;
expectTypeOf<SchemaOut>().toEqualTypeOf<{
a?: string;
b?: string | undefined;
}>();
});
test("exactOptional vs optional comparison", () => {
const optionalSchema = z.object({ a: z.string().optional() });
const exactOptionalSchema = z.object({ a: z.string().exactOptional() });
// Both accept absent keys
expect(optionalSchema.parse({})).toEqual({});
expect(exactOptionalSchema.parse({})).toEqual({});
// Both accept valid values
expect(optionalSchema.parse({ a: "hi" })).toEqual({ a: "hi" });
expect(exactOptionalSchema.parse({ a: "hi" })).toEqual({ a: "hi" });
// optional() accepts explicit undefined
expect(optionalSchema.parse({ a: undefined })).toEqual({ a: undefined });
// exactOptional() rejects explicit undefined
expect(exactOptionalSchema.safeParse({ a: undefined }).success).toEqual(false);
});

View File

@@ -0,0 +1,427 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
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>;
expectTypeOf<shallow>().toEqualTypeOf<{
name?: string | undefined;
age?: number | undefined;
outer?: { inner: string } | undefined;
array?: { asdf: string }[] | undefined;
}>();
});
test("shallow partial parse", () => {
const shallow = nested.partial();
shallow.parse({});
shallow.parse({
name: "asdf",
age: 23143,
});
});
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.ZodNonOptional);
expect(requiredObject.shape.name.unwrap()).toBeInstanceOf(z.ZodString);
expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.age.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(requiredObject.shape.field).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.field.unwrap()).toBeInstanceOf(z.ZodDefault);
expect(requiredObject.shape.nullableField).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.nullableField.unwrap()).toBeInstanceOf(z.ZodNullable);
expect(requiredObject.shape.nullishField).toBeInstanceOf(z.ZodNonOptional);
expect(requiredObject.shape.nullishField.unwrap()).toBeInstanceOf(z.ZodOptional);
expect(requiredObject.shape.nullishField.unwrap().unwrap()).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;
};
expectTypeOf<expected>().toEqualTypeOf<required>();
});
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.ZodNonOptional);
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.ZodNonOptional);
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("catch/prefault/default", () => {
const mySchema = z.object({
a: z.string().catch("catch value").optional(),
b: z.string().default("default value").optional(),
c: z.string().prefault("prefault value").optional(),
d: z.string().catch("catch value"),
e: z.string().default("default value"),
f: z.string().prefault("prefault value"),
});
expect(mySchema.parse({})).toMatchInlineSnapshot(`
{
"b": "default value",
"c": "prefault value",
"d": "catch value",
"e": "default value",
"f": "prefault value",
}
`);
expect(mySchema.parse({}, { jitless: true })).toMatchInlineSnapshot(`
{
"b": "default value",
"c": "prefault value",
"d": "catch value",
"e": "default value",
"f": "prefault value",
}
`);
});
test("handleOptionalObjectResult branches", () => {
const mySchema = z.object({
// Branch: input[key] === undefined, key not in input, caught error
caughtMissing: z.string().catch("caught").optional(),
// Branch: input[key] === undefined, key in input, caught error
caughtUndefined: z.string().catch("caught").optional(),
// Branch: input[key] === undefined, key not in input, validation issues
issueMissing: z.string().min(5).optional(),
// Branch: input[key] === undefined, key in input, validation issues
issueUndefined: z.string().min(5).optional(),
// Branch: input[key] === undefined, validation returns undefined
validUndefined: z.string().optional(),
// Branch: input[key] === undefined, non-undefined result (default/transform)
defaultValue: z.string().default("default").optional(),
// Branch: input[key] defined, caught error
caughtDefined: z.string().catch("caught").optional(),
// Branch: input[key] defined, validation issues
issueDefined: z.string().min(5).optional(),
// Branch: input[key] defined, validation returns undefined
validDefinedUndefined: z
.string()
.transform(() => undefined)
.optional(),
// Branch: input[key] defined, non-undefined value
validDefined: z.string().optional(),
});
// Test input[key] === undefined cases
const result1 = mySchema.parse(
{
// caughtMissing: not present (key not in input)
caughtUndefined: undefined, // key in input
// issueMissing: not present (key not in input)
issueUndefined: undefined, // key in input
validUndefined: undefined,
// defaultValue: not present, will get default
},
{ jitless: true }
);
expect(result1).toEqual({
caughtUndefined: undefined,
issueUndefined: undefined,
validUndefined: undefined,
defaultValue: "default",
});
// Test input[key] defined cases (successful)
const result2 = mySchema.parse(
{
caughtDefined: 123, // invalid type, should catch
validDefinedUndefined: "test", // transforms to undefined
validDefined: "valid", // valid value
},
{ jitless: true }
);
expect(result2).toEqual({
caughtDefined: "caught",
validDefinedUndefined: undefined,
validDefined: "valid",
defaultValue: "default",
});
// Test validation issues are properly reported (input[key] defined, validation fails)
expect(() =>
mySchema.parse(
{
issueDefined: "abc", // too short
},
{ jitless: true }
)
).toThrow();
});
test("fastpass vs non-fastpass consistency", () => {
const mySchema = z.object({
caughtMissing: z.string().catch("caught").optional(),
caughtUndefined: z.string().catch("caught").optional(),
issueMissing: z.string().min(5).optional(),
issueUndefined: z.string().min(5).optional(),
validUndefined: z.string().optional(),
defaultValue: z.string().default("default").optional(),
caughtDefined: z.string().catch("caught").optional(),
validDefinedUndefined: z
.string()
.transform(() => undefined)
.optional(),
validDefined: z.string().optional(),
});
const input = {
caughtUndefined: undefined,
issueUndefined: undefined,
validUndefined: undefined,
caughtDefined: 123,
validDefinedUndefined: "test",
validDefined: "valid",
};
// Test both paths produce identical results
const jitlessResult = mySchema.parse(input, { jitless: true });
const fastpassResult = mySchema.parse(input);
expect(jitlessResult).toEqual(fastpassResult);
expect(jitlessResult).toEqual({
caughtUndefined: undefined,
issueUndefined: undefined,
validUndefined: undefined,
defaultValue: "default",
caughtDefined: "caught",
validDefinedUndefined: undefined,
validDefined: "valid",
});
});
test("optional with check", () => {
const baseSchema = z
.string()
.optional()
.check(({ value, ...ctx }) => {
ctx.issues.push({
code: "custom",
input: value,
message: "message",
});
});
// this correctly fails
expect(baseSchema.safeParse(undefined)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "message",
"path": []
}
]],
"success": false,
}
`);
const schemaObject = z.object({
date: baseSchema,
});
expect(schemaObject.safeParse({ date: undefined })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "message",
"path": [
"date"
]
}
]],
"success": false,
}
`);
});
test("partial - throws error on schema with refinements", () => {
const baseSchema = z.object({
id: z.string(),
name: z.string(),
items: z.string().array(),
});
const refinedSchema = baseSchema.superRefine((val, ctx) => {
if (val.items.length === 0) {
ctx.addIssue({
message: "Must have at least one item",
code: "custom",
path: ["items"],
});
}
});
expect(() => refinedSchema.partial()).toThrow(".partial() cannot be used on object schemas containing refinements");
});
test("partial - throws error on schema with refine", () => {
const baseSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
});
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
message: "Passwords must match",
});
expect(() => refinedSchema.partial()).toThrow(".partial() cannot be used on object schemas containing refinements");
});
test("required - preserves refinements", () => {
const baseSchema = z.object({
name: z.string().optional(),
age: z.number().optional(),
});
const refinedSchema = baseSchema.superRefine((val, ctx) => {
if (val.name === "admin") {
ctx.addIssue({
message: "Name cannot be admin",
code: "custom",
path: ["name"],
});
}
});
const requiredSchema = refinedSchema.required();
// The refinement should still be applied
const result = requiredSchema.safeParse({ name: "admin", age: 25 });
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toBe("Name cannot be admin");
}
// Valid data should pass
const validResult = requiredSchema.safeParse({ name: "user", age: 25 });
expect(validResult.success).toBe(true);
});
test("required - refinement is executed on required schema", () => {
const baseSchema = z.object({
password: z.string().optional(),
confirmPassword: z.string().optional(),
});
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
message: "Passwords must match",
});
const requiredSchema = refinedSchema.required();
// Mismatched passwords should fail refinement
const result = requiredSchema.safeParse({ password: "abc", confirmPassword: "xyz" });
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toBe("Passwords must match");
}
// Matching passwords should pass
const validResult = requiredSchema.safeParse({ password: "abc", confirmPassword: "abc" });
expect(validResult.success).toBe(true);
});

View File

@@ -0,0 +1,211 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
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>;
expectTypeOf<nameonlyFish>().toEqualTypeOf<{ name: string }>();
});
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("pick - remove optional", () => {
const schema = z.object({ a: z.string(), b: z.string().optional() });
expect("a" in schema._zod.def.shape).toEqual(true);
expect("b" in schema._zod.def.shape!).toEqual(true);
const picked = schema.pick({ a: true });
expect("a" in picked._zod.def.shape).toEqual(true);
expect("b" in picked._zod.def.shape!).toEqual(false);
});
test("omit type inference", () => {
const nonameFish = fish.omit({ name: true });
type nonameFish = z.infer<typeof nonameFish>;
expectTypeOf<nonameFish>().toEqualTypeOf<{ age: number; nested: Record<string, never> }>();
});
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("omit - remove optional", () => {
const schema = z.object({ a: z.string(), b: z.string().optional() });
expect("a" in schema._zod.def.shape).toEqual(true);
const omitted = schema.omit({ a: true });
expect("a" in omitted._zod.def.shape).toEqual(false);
});
test("nonstrict inference", () => {
const laxfish = fish.pick({ name: true }).catchall(z.any());
type laxfish = z.infer<typeof laxfish>;
expectTypeOf<laxfish>().toEqualTypeOf<{ name: string; [k: string]: any }>();
});
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(),
});
// Mixed valid + invalid keys
// @ts-expect-error
expect(() => schema.pick({ name: true, asdf: true }).safeParse({})).toThrow();
// @ts-expect-error
expect(() => schema.omit({ name: true, asdf: true }).safeParse({})).toThrow();
// @ts-expect-error
expect(() => schema.partial({ name: true, asdf: true }).safeParse({})).toThrow();
// @ts-expect-error
expect(() => schema.required({ name: true, asdf: true }).safeParse({})).toThrow();
// Only invalid keys
// @ts-expect-error
expect(() => schema.pick({ $unknown: true }).safeParse({})).toThrow();
// @ts-expect-error
expect(() => schema.omit({ $unknown: true }).safeParse({})).toThrow();
// @ts-expect-error
expect(() => schema.required({ $unknown: true }).safeParse({})).toThrow();
// @ts-expect-error
expect(() => schema.partial({ $unknown: true }).safeParse({})).toThrow();
});
test("pick - throws error on schema with refinements", () => {
const baseSchema = z.object({
id: z.string(),
name: z.string(),
items: z.string().array(),
});
const refinedSchema = baseSchema.superRefine((val, ctx) => {
if (val.items.length === 0) {
ctx.addIssue({
message: "Must have at least one item",
code: "custom",
path: ["items"],
});
}
});
expect(() => refinedSchema.pick({ name: true })).toThrow(
".pick() cannot be used on object schemas containing refinements"
);
});
test("omit - throws error on schema with refinements", () => {
const baseSchema = z.object({
id: z.string(),
name: z.string(),
items: z.string().array(),
});
const refinedSchema = baseSchema.superRefine((val, ctx) => {
if (val.items.length === 0) {
ctx.addIssue({
message: "Must have at least one item",
code: "custom",
path: ["items"],
});
}
});
expect(() => refinedSchema.omit({ id: true })).toThrow(
".omit() cannot be used on object schemas containing refinements"
);
});
test("pick - throws error on schema with refine", () => {
const baseSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
});
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
message: "Passwords must match",
});
expect(() => refinedSchema.pick({ password: true })).toThrow(
".pick() cannot be used on object schemas containing refinements"
);
});
test("omit - throws error on schema with refine", () => {
const baseSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
email: z.string(),
});
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
message: "Passwords must match",
});
expect(() => refinedSchema.omit({ email: true })).toThrow(
".omit() cannot be used on object schemas containing refinements"
);
});

View File

@@ -0,0 +1,101 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("string to number pipe", () => {
const schema = z.string().transform(Number).pipe(z.number());
expect(schema.parse("1234")).toEqual(1234);
});
test("string to number pipe async", async () => {
const schema = z
.string()
.transform(async (val) => Number(val))
.pipe(z.number());
expect(await schema.parseAsync("1234")).toEqual(1234);
});
test("string with default fallback", () => {
const stringWithDefault = z
.pipe(
z.transform((v) => (v === "none" ? undefined : v)),
z.string()
)
.catch("default");
expect(stringWithDefault.parse("ok")).toBe("ok");
expect(stringWithDefault.parse(undefined)).toBe("default");
expect(stringWithDefault.parse("none")).toBe("default");
expect(stringWithDefault.parse(15)).toBe("default");
});
test("continue on non-fatal errors", () => {
const schema = z
.string()
.refine((c) => c === "1234", "A")
.transform((val) => Number(val))
.refine((c) => c === 1234, "B");
schema.parse("1234");
expect(schema.safeParse("4321")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "A"
}
]],
"success": false,
}
`);
});
test("break on fatal errors", () => {
const schema = z
.string()
.refine((c) => c === "1234", { message: "A", abort: true })
.transform((val) => Number(val))
.refine((c) => c === 1234, "B");
schema.parse("1234");
expect(schema.safeParse("4321")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "A"
}
]],
"success": false,
}
`);
});
test("reverse parsing with pipe", () => {
const schema = z.string().pipe(z.string());
// Reverse direction: default should NOT be applied
expect(z.safeDecode(schema, "asdf")).toMatchInlineSnapshot(`
{
"data": "asdf",
"success": true,
}
`);
expect(z.safeEncode(schema, "asdf")).toMatchInlineSnapshot(`
{
"data": "asdf",
"success": true,
}
`);
});
test("reverse parsing with pipe", () => {
const schema = z.string().transform((val) => val.length);
// should throw
expect(() => z.encode(schema, 1234)).toThrow();
});

View File

@@ -0,0 +1,74 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("basic prefault", () => {
const a = z.prefault(z.string().trim(), " default ");
expect(a).toBeInstanceOf(z.ZodPrefault);
expect(a.parse(" asdf ")).toEqual("asdf");
expect(a.parse(undefined)).toEqual("default");
type inp = z.input<typeof a>;
expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
type out = z.output<typeof a>;
expectTypeOf<out>().toEqualTypeOf<string>();
});
test("prefault inside object", () => {
// test optinality
const a = z.object({
name: z.string().optional(),
age: z.number().default(1234),
email: z.string().prefault("1234"),
});
type inp = z.input<typeof a>;
expectTypeOf<inp>().toEqualTypeOf<{
name?: string | undefined;
age?: number | undefined;
email?: string | undefined;
}>();
type out = z.output<typeof a>;
expectTypeOf<out>().toEqualTypeOf<{
name?: string | undefined;
age: number;
email: string;
}>();
});
test("object schema with prefault should return shallow clone", () => {
const schema = z
.object({
a: z.string(),
})
.default({ a: "x" });
const result1 = schema.parse(undefined);
const result2 = schema.parse(undefined);
expect(result1).not.toBe(result2);
expect(result1).toEqual(result2);
});
test("direction-aware prefault", () => {
const schema = z.string().prefault("hello");
// Forward direction (regular parse): prefault should be applied
expect(schema.parse(undefined)).toBe("hello");
// Reverse direction (encode): prefault should NOT be applied, undefined should fail validation
expect(z.safeEncode(schema, undefined as any)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
// But valid values should still work in reverse
expect(z.encode(schema, "world")).toBe("world");
});

View File

@@ -0,0 +1,282 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("preprocess", () => {
const schema = z.preprocess((data) => [data], z.string().array());
const value = schema.parse("asdf");
expect(value).toEqual(["asdf"]);
expectTypeOf<(typeof schema)["_input"]>().toEqualTypeOf<unknown>();
});
test("async preprocess", async () => {
const schema = z.preprocess(async (data) => {
return [data];
}, z.string().array());
const value = await schema.safeParseAsync("asdf");
expect(value.data).toEqual(["asdf"]);
expect(value).toMatchInlineSnapshot(`
{
"data": [
"asdf",
],
"success": true,
}
`);
});
test("ctx.addIssue accepts string", () => {
const schema = z.preprocess((_, ctx) => {
ctx.addIssue("bad stuff");
}, z.string());
const result = schema.safeParse("asdf");
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"message": "bad stuff",
"code": "custom",
"path": []
}
]],
"success": false,
}
`);
});
test("preprocess ctx.addIssue with parse", () => {
const a = z.preprocess((data, ctx) => {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string());
const result = a.safeParse("asdf");
// expect(result.error!.toJSON()).toContain("not one of our allowed strings");
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]],
"success": false,
}
`);
});
test("preprocess ctx.addIssue fatal by default", () => {
const schema = z.preprocess((data, ctx) => {
ctx.addIssue({
code: "custom",
message: `custom error`,
});
return data;
}, z.string());
const result = schema.safeParse(1234);
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "custom error",
"path": []
}
]],
"success": false,
}
`);
});
test("preprocess ctx.addIssue fatal true", () => {
const schema = z.preprocess((data, ctx) => {
ctx.addIssue({
input: data,
code: "custom",
origin: "custom",
message: `custom error`,
fatal: true,
});
return data;
}, z.string());
const result = schema.safeParse(1234);
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"origin": "custom",
"message": "custom error",
"fatal": true,
"path": []
}
]],
"success": false,
}
`);
});
test("async preprocess ctx.addIssue with parseAsync", async () => {
const schema = z.preprocess(async (data, ctx) => {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
return data;
}, z.string());
const result = await schema.safeParseAsync("asdf");
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]],
"success": false,
}
`);
});
test("z.NEVER in preprocess", () => {
const foo = z.preprocess((val, ctx) => {
if (!val) {
ctx.addIssue({ input: val, code: "custom", message: "bad" });
return z.NEVER;
}
return val;
}, z.number());
type foo = z.infer<typeof foo>;
expectTypeOf<foo>().toEqualTypeOf<number>();
const result = foo.safeParse(undefined);
expect(result.error!.issues).toHaveLength(1);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "bad",
"path": []
}
]],
"success": false,
}
`);
});
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.error!.issues).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 1,
"inclusive": true,
"path": [
"nonEmptyStr"
],
"message": "Too small: expected string to have >=1 characters"
},
{
"origin": "number",
"code": "too_small",
"minimum": 0,
"inclusive": false,
"path": [
"positiveNum"
],
"message": "Too small: expected number to be >0"
}
]],
"success": false,
}
`);
});
test("preprocess validates with sibling errors", () => {
const schema = z.object({
missing: z.string().refine(() => false),
preprocess: z.preprocess((data: any) => data?.trim(), z.string().regex(/ asdf/)),
});
const result = schema.safeParse({ preprocess: " asdf" });
expect(result.error!.issues).toHaveLength(2);
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"missing"
],
"message": "Invalid input: expected string, received undefined"
},
{
"origin": "string",
"code": "invalid_format",
"format": "regex",
"pattern": "/ asdf/",
"path": [
"preprocess"
],
"message": "Invalid string: must match pattern / asdf/"
}
]],
"success": false,
}
`);
});
test("perform transform with non-fatal issues", () => {
const A = z
.string()
.refine((_) => false)
.min(4)
.transform((val) => val.length)
.pipe(z.number())
.refine((_) => false);
expect(A.safeParse("asdfasdf").error!.issues).toHaveLength(1);
expect(A.safeParse("asdfasdf").error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});

View File

@@ -0,0 +1,175 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const literalStringSchema = z.literal("asdf");
const literalNumberSchema = z.literal(12);
const literalBooleanSchema = z.literal(true);
const literalBigIntSchema = z.literal(BigInt(42));
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();
test("literal string schema", () => {
expect(literalStringSchema.parse("asdf")).toBe("asdf");
expect(() => literalStringSchema.parse("not_asdf")).toThrow();
expect(() => literalStringSchema.parse(123)).toThrow();
expect(() => literalStringSchema.parse(true)).toThrow();
expect(() => literalStringSchema.parse({})).toThrow();
});
test("literal number schema", () => {
expect(literalNumberSchema.parse(12)).toBe(12);
expect(() => literalNumberSchema.parse(13)).toThrow();
expect(() => literalNumberSchema.parse("foo")).toThrow();
expect(() => literalNumberSchema.parse(true)).toThrow();
expect(() => literalNumberSchema.parse({})).toThrow();
});
test("literal boolean schema", () => {
expect(literalBooleanSchema.parse(true)).toBe(true);
expect(() => literalBooleanSchema.parse(false)).toThrow();
expect(() => literalBooleanSchema.parse("asdf")).toThrow();
expect(() => literalBooleanSchema.parse(123)).toThrow();
expect(() => literalBooleanSchema.parse({})).toThrow();
});
test("literal bigint schema", () => {
expect(literalBigIntSchema.parse(BigInt(42))).toBe(BigInt(42));
expect(() => literalBigIntSchema.parse(BigInt(43))).toThrow();
expect(() => literalBigIntSchema.parse("asdf")).toThrow();
expect(() => literalBigIntSchema.parse(123)).toThrow();
expect(() => literalBigIntSchema.parse({})).toThrow();
});
test("string schema", () => {
stringSchema.parse("foo");
expect(() => stringSchema.parse(Math.random())).toThrow();
expect(() => stringSchema.parse(true)).toThrow();
expect(() => stringSchema.parse(undefined)).toThrow();
expect(() => stringSchema.parse(null)).toThrow();
});
test("number schema", () => {
numberSchema.parse(Math.random());
expect(() => numberSchema.parse("foo")).toThrow();
expect(() => numberSchema.parse(BigInt(17))).toThrow();
expect(() => numberSchema.parse(true)).toThrow();
expect(() => numberSchema.parse(undefined)).toThrow();
expect(() => numberSchema.parse(null)).toThrow();
});
test("bigint schema", () => {
bigintSchema.parse(BigInt(17));
expect(() => bigintSchema.parse("foo")).toThrow();
expect(() => bigintSchema.parse(Math.random())).toThrow();
expect(() => bigintSchema.parse(true)).toThrow();
expect(() => bigintSchema.parse(undefined)).toThrow();
expect(() => bigintSchema.parse(null)).toThrow();
});
test("boolean schema", () => {
booleanSchema.parse(true);
expect(() => booleanSchema.parse("foo")).toThrow();
expect(() => booleanSchema.parse(Math.random())).toThrow();
expect(() => booleanSchema.parse(undefined)).toThrow();
expect(() => booleanSchema.parse(null)).toThrow();
});
test("date schema", async () => {
dateSchema.parse(new Date());
expect(() => dateSchema.parse("foo")).toThrow();
expect(() => dateSchema.parse(Math.random())).toThrow();
expect(() => dateSchema.parse(true)).toThrow();
expect(() => dateSchema.parse(undefined)).toThrow();
expect(() => dateSchema.parse(null)).toThrow();
expect(await dateSchema.safeParseAsync(new Date("invalid"))).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "date",
"code": "invalid_type",
"received": "Invalid Date",
"path": [],
"message": "Invalid input: expected date, received Date"
}
]],
"success": false,
}
`);
});
test("symbol schema", () => {
symbolSchema.parse(Symbol("foo"));
expect(() => symbolSchema.parse("foo")).toThrow();
expect(() => symbolSchema.parse(Math.random())).toThrow();
expect(() => symbolSchema.parse(true)).toThrow();
expect(() => symbolSchema.parse(new Date())).toThrow();
expect(() => symbolSchema.parse(undefined)).toThrow();
expect(() => symbolSchema.parse(null)).toThrow();
});
test("undefined schema", () => {
undefinedSchema.parse(undefined);
expect(() => undefinedSchema.parse("foo")).toThrow();
expect(() => undefinedSchema.parse(Math.random())).toThrow();
expect(() => undefinedSchema.parse(true)).toThrow();
expect(() => undefinedSchema.parse(null)).toThrow();
});
test("null schema", () => {
nullSchema.parse(null);
expect(() => nullSchema.parse("foo")).toThrow();
expect(() => nullSchema.parse(Math.random())).toThrow();
expect(() => nullSchema.parse(true)).toThrow();
expect(() => nullSchema.parse(undefined)).toThrow();
});
test("primitive inference", () => {
expectTypeOf<z.TypeOf<typeof literalStringSchema>>().toEqualTypeOf<"asdf">();
expectTypeOf<z.TypeOf<typeof literalNumberSchema>>().toEqualTypeOf<12>();
expectTypeOf<z.TypeOf<typeof literalBooleanSchema>>().toEqualTypeOf<true>();
expectTypeOf<z.TypeOf<typeof literalBigIntSchema>>().toEqualTypeOf<bigint>();
expectTypeOf<z.TypeOf<typeof stringSchema>>().toEqualTypeOf<string>();
expectTypeOf<z.TypeOf<typeof numberSchema>>().toEqualTypeOf<number>();
expectTypeOf<z.TypeOf<typeof bigintSchema>>().toEqualTypeOf<bigint>();
expectTypeOf<z.TypeOf<typeof booleanSchema>>().toEqualTypeOf<boolean>();
expectTypeOf<z.TypeOf<typeof dateSchema>>().toEqualTypeOf<Date>();
expectTypeOf<z.TypeOf<typeof symbolSchema>>().toEqualTypeOf<symbol>();
expectTypeOf<z.TypeOf<typeof nullSchema>>().toEqualTypeOf<null>();
expectTypeOf<z.TypeOf<typeof undefinedSchema>>().toEqualTypeOf<undefined>();
expectTypeOf<z.TypeOf<typeof stringSchemaOptional>>().toEqualTypeOf<string | undefined>();
expectTypeOf<z.TypeOf<typeof stringSchemaNullable>>().toEqualTypeOf<string | null>();
expectTypeOf<z.TypeOf<typeof numberSchemaOptional>>().toEqualTypeOf<number | undefined>();
expectTypeOf<z.TypeOf<typeof numberSchemaNullable>>().toEqualTypeOf<number | null>();
expectTypeOf<z.TypeOf<typeof bigintSchemaOptional>>().toEqualTypeOf<bigint | undefined>();
expectTypeOf<z.TypeOf<typeof bigintSchemaNullable>>().toEqualTypeOf<bigint | null>();
expectTypeOf<z.TypeOf<typeof booleanSchemaOptional>>().toEqualTypeOf<boolean | undefined>();
expectTypeOf<z.TypeOf<typeof booleanSchemaNullable>>().toEqualTypeOf<boolean | null>();
expectTypeOf<z.TypeOf<typeof dateSchemaOptional>>().toEqualTypeOf<Date | undefined>();
expectTypeOf<z.TypeOf<typeof dateSchemaNullable>>().toEqualTypeOf<Date | null>();
expectTypeOf<z.TypeOf<typeof symbolSchemaOptional>>().toEqualTypeOf<symbol | undefined>();
expectTypeOf<z.TypeOf<typeof symbolSchemaNullable>>().toEqualTypeOf<symbol | null>();
});
test("get literal values", () => {
expect(literalStringSchema.values).toEqual(new Set(["asdf"]));
expect(literalStringSchema._zod.def.values).toEqual(["asdf"]);
});

View File

@@ -0,0 +1,81 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const promSchema = z.promise(
z.object({
name: z.string(),
age: z.number(),
})
);
test("promise inference", () => {
type promSchemaType = z.infer<typeof promSchema>;
expectTypeOf<promSchemaType>().toEqualTypeOf<Promise<{ name: string; age: number }>>();
});
test("promise parsing success", async () => {
// expect(() => promSchema.parse(Promise.resolve({ name: "Bobby", age: 10 }))).toThrow();
const pr = promSchema.parseAsync(Promise.resolve({ name: "Bobby", age: 10 }));
expect(pr).toBeInstanceOf(Promise);
const result = await pr;
expect(result).toMatchInlineSnapshot(`
{
"age": 10,
"name": "Bobby",
}
`);
});
test("promise parsing fail", async () => {
const bad = await promSchema.safeParseAsync(Promise.resolve({ name: "Bobby", age: "10" }));
expect(bad.success).toBe(false);
expect(bad.error).toBeInstanceOf(z.ZodError);
});
test("promise parsing fail 2", async () => {
const result = await promSchema.safeParseAsync(Promise.resolve({ name: "Bobby", age: "10" }));
expect(result.success).toBe(false);
expect(result.error).toBeInstanceOf(z.ZodError);
});
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({
input: z.tuple([]),
output: promSchema,
});
test("async function pass", async () => {
const validatedFunction = asyncFunction.implementAsync(async () => {
return { name: "jimmy", age: 14 };
});
await expect(validatedFunction()).resolves.toEqual({
name: "jimmy",
age: 14,
});
});
test("async function fail", async () => {
const validatedFunction = asyncFunction.implementAsync(() => {
return Promise.resolve("asdf" as any);
});
await expect(validatedFunction()).rejects.toBeInstanceOf(z.core.$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);
});

View File

@@ -0,0 +1,23 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
declare module "zod/v4" {
interface ZodType {
/** @deprecated */
_classic(): string;
}
}
test("prototype extension", () => {
z.ZodType.prototype._classic = function () {
return "_classic";
};
// should pass
const result = z.string()._classic();
expect(result).toBe("_classic");
// expectTypeOf<typeof result>().toEqualTypeOf<string>();
// clean up
z.ZodType.prototype._classic = undefined;
});

View File

@@ -0,0 +1,252 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
enum testEnum {
A = 0,
B = 1,
}
test("flat inference", () => {
const readonlyString = z.string().readonly();
const readonlyNumber = z.number().readonly();
const readonlyNaN = z.nan().readonly();
const readonlyBigInt = z.bigint().readonly();
const readonlyBoolean = z.boolean().readonly();
const readonlyDate = z.date().readonly();
const readonlyUndefined = z.undefined().readonly();
const readonlyNull = z.null().readonly();
const readonlyAny = z.any().readonly();
const readonlyUnknown = z.unknown().readonly();
const readonlyVoid = z.void().readonly();
const readonlyStringArray = z.array(z.string()).readonly();
const readonlyTuple = z.tuple([z.string(), z.number()]).readonly();
const readonlyMap = z.map(z.string(), z.date()).readonly();
const readonlySet = z.set(z.string()).readonly();
const readonlyStringRecord = z.record(z.string(), z.string()).readonly();
const readonlyNumberRecord = z.record(z.string(), z.number()).readonly();
const readonlyObject = z.object({ a: z.string(), 1: z.number() }).readonly();
const readonlyEnum = z.nativeEnum(testEnum).readonly();
const readonlyPromise = z.promise(z.string()).readonly();
expectTypeOf<typeof readonlyString._output>().toEqualTypeOf<string>();
expectTypeOf<typeof readonlyNumber._output>().toEqualTypeOf<number>();
expectTypeOf<typeof readonlyNaN._output>().toEqualTypeOf<number>();
expectTypeOf<typeof readonlyBigInt._output>().toEqualTypeOf<bigint>();
expectTypeOf<typeof readonlyBoolean._output>().toEqualTypeOf<boolean>();
expectTypeOf<typeof readonlyDate._output>().toEqualTypeOf<Date>();
expectTypeOf<typeof readonlyUndefined._output>().toEqualTypeOf<undefined>();
expectTypeOf<typeof readonlyNull._output>().toEqualTypeOf<null>();
expectTypeOf<typeof readonlyAny._output>().toEqualTypeOf<any>();
expectTypeOf<typeof readonlyUnknown._output>().toEqualTypeOf<Readonly<unknown>>();
expectTypeOf<typeof readonlyVoid._output>().toEqualTypeOf<void>();
expectTypeOf<typeof readonlyStringArray._output>().toEqualTypeOf<readonly string[]>();
expectTypeOf<typeof readonlyTuple._output>().toEqualTypeOf<readonly [string, number]>();
expectTypeOf<typeof readonlyMap._output>().toEqualTypeOf<ReadonlyMap<string, Date>>();
expectTypeOf<typeof readonlySet._output>().toEqualTypeOf<ReadonlySet<string>>();
expectTypeOf<typeof readonlyStringRecord._output>().toEqualTypeOf<Readonly<Record<string, string>>>();
expectTypeOf<typeof readonlyNumberRecord._output>().toEqualTypeOf<Readonly<Record<string, number>>>();
expectTypeOf<typeof readonlyObject._output>().toEqualTypeOf<{ readonly a: string; readonly 1: number }>();
expectTypeOf<typeof readonlyEnum._output>().toEqualTypeOf<Readonly<testEnum>>();
expectTypeOf<typeof readonlyPromise._output>().toEqualTypeOf<Promise<string>>();
});
// test("deep inference", () => {
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[0]>>().toEqualTypeOf<string>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[1]>>().toEqualTypeOf<number>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[2]>>().toEqualTypeOf<number>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[3]>>().toEqualTypeOf<bigint>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[4]>>().toEqualTypeOf<boolean>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[5]>>().toEqualTypeOf<Date>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[6]>>().toEqualTypeOf<undefined>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[7]>>().toEqualTypeOf<null>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[8]>>().toEqualTypeOf<any>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[9]>
// >().toEqualTypeOf<Readonly<unknown>>();
// expectTypeOf<z.infer<(typeof deepReadonlySchemas_0)[10]>>().toEqualTypeOf<void>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[11]>
// >().toEqualTypeOf<(args_0: string, args_1: number, ...args_2: unknown[]) => unknown>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[12]>
// >().toEqualTypeOf<readonly string[]>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[13]>
// >().toEqualTypeOf<readonly [string, number]>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[14]>
// >().toEqualTypeOf<ReadonlyMap<string, Date>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[15]>
// >().toEqualTypeOf<ReadonlySet<Promise<string>>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[16]>
// >().toEqualTypeOf<Readonly<Record<string, string>>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[17]>
// >().toEqualTypeOf<Readonly<Record<string, number>>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[18]>
// >().toEqualTypeOf<{ readonly a: string; readonly 1: number }>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[19]>
// >().toEqualTypeOf<Readonly<testEnum>>();
// expectTypeOf<
// z.infer<(typeof deepReadonlySchemas_0)[20]>
// >().toEqualTypeOf<Promise<string>>();
// expectTypeOf<
// z.infer<typeof crazyDeepReadonlySchema>
// >().toEqualTypeOf<ReadonlyMap<
// ReadonlySet<readonly [string, number]>,
// {
// readonly a: {
// readonly [x: string]: readonly any[];
// };
// readonly b: {
// readonly c: {
// readonly d: {
// readonly e: {
// readonly f: {
// readonly g?: {};
// };
// };
// };
// };
// };
// }
// >>();
// });
test("object freezing", async () => {
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.record(z.string(), 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(
await z
.set(z.promise(z.string()))
.readonly()
.parseAsync(new Set([Promise.resolve("a")]))
)
).toBe(true);
expect(Object.isFrozen(await z.promise(z.string()).readonly().parseAsync(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(), 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);
});
test("readonly inference", () => {
const readonlyStringArray = z.string().array().readonly();
const readonlyStringTuple = z.tuple([z.string()]).readonly();
const deepReadonly = z.object({ a: z.string() }).readonly();
type readonlyStringArray = z.infer<typeof readonlyStringArray>;
type readonlyStringTuple = z.infer<typeof readonlyStringTuple>;
type deepReadonly = z.infer<typeof deepReadonly>;
expectTypeOf<readonlyStringArray>().toEqualTypeOf<readonly string[]>();
expectTypeOf<readonlyStringTuple>().toEqualTypeOf<readonly [string]>();
expectTypeOf<deepReadonly>().toEqualTypeOf<{ readonly a: string }>();
});
test("readonly parse", () => {
const schema = z.array(z.string()).readonly();
const readonlyArray = ["a", "b", "c"] as const;
const mutableArray = ["a", "b", "c"];
const result1 = schema.parse(readonlyArray);
const result2 = schema.parse(mutableArray);
expect(result1).toEqual(readonlyArray);
expect(result2).toEqual(mutableArray);
});
test("readonly parse with tuples", () => {
const schema = z.tuple([z.string(), z.number()]).readonly();
schema.parse(["a", 1]);
});
test("readonly and the get method", () => {
const readonlyString = z.string().readonly();
const readonlyNumber1 = z.number().readonly();
const readonlyNumber2 = z.number().readonly();
const readonlyBigInt = z.bigint().readonly();
const readonlyBoolean = z.boolean().readonly();
const readonlyDate = z.date().readonly();
const readonlyUndefined = z.undefined().readonly();
const readonlyNull = z.null().readonly();
const readonlyAny = z.any().readonly();
const readonlyUnknown = z.unknown().readonly();
const readonlyVoid = z.void().readonly();
// const readonlyFunction = z.function(z.tuple([z.string(), z.number()]), z.unknown()).readonly();
const readonlyStringArray = z.string().array().readonly();
const readonlyTuple = z.tuple([z.string(), z.number()]).readonly();
expectTypeOf<z.infer<typeof readonlyString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof readonlyNumber1>>().toEqualTypeOf<number>();
expectTypeOf<z.infer<typeof readonlyNumber2>>().toEqualTypeOf<number>();
expectTypeOf<z.infer<typeof readonlyBigInt>>().toEqualTypeOf<bigint>();
expectTypeOf<z.infer<typeof readonlyBoolean>>().toEqualTypeOf<boolean>();
expectTypeOf<z.infer<typeof readonlyDate>>().toEqualTypeOf<Date>();
expectTypeOf<z.infer<typeof readonlyUndefined>>().toEqualTypeOf<undefined>();
expectTypeOf<z.infer<typeof readonlyNull>>().toEqualTypeOf<null>();
expectTypeOf<z.infer<typeof readonlyAny>>().toEqualTypeOf<any>();
expectTypeOf<z.infer<typeof readonlyUnknown>>().toEqualTypeOf<Readonly<unknown>>();
expectTypeOf<z.infer<typeof readonlyVoid>>().toEqualTypeOf<void>();
// expectTypeOf<z.infer<typeof readonlyFunction>>().toEqualTypeOf<
// (args_0: string, args_1: number, ...args_2: unknown[]) => unknown
// >();
expectTypeOf<z.infer<typeof readonlyStringArray>>().toEqualTypeOf<readonly string[]>();
expectTypeOf<z.infer<typeof readonlyTuple>>().toEqualTypeOf<readonly [string, number]>();
expect(readonlyString.parse("asdf")).toEqual("asdf");
expect(readonlyNumber1.parse(1234)).toEqual(1234);
expect(readonlyNumber2.parse(1234)).toEqual(1234);
const bigIntVal = BigInt(1);
expect(readonlyBigInt.parse(bigIntVal)).toEqual(bigIntVal);
expect(readonlyBoolean.parse(true)).toEqual(true);
const dateVal = new Date();
expect(readonlyDate.parse(dateVal)).toEqual(dateVal);
expect(readonlyUndefined.parse(undefined)).toEqual(undefined);
expect(readonlyNull.parse(null)).toEqual(null);
expect(readonlyAny.parse("whatever")).toEqual("whatever");
expect(readonlyUnknown.parse("whatever")).toEqual("whatever");
expect(readonlyVoid.parse(undefined)).toEqual(undefined);
// expect(readonlyFunction.parse(() => void 0)).toEqual(() => void 0);
expect(readonlyStringArray.parse(["asdf"])).toEqual(["asdf"]);
expect(readonlyTuple.parse(["asdf", 1234])).toEqual(["asdf", 1234]);
});

View File

@@ -0,0 +1,632 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("type inference", () => {
const booleanRecord = z.record(z.string(), z.boolean());
type booleanRecord = typeof booleanRecord._output;
const recordWithEnumKeys = z.record(z.enum(["Tuna", "Salmon"]), z.string());
type recordWithEnumKeys = z.infer<typeof recordWithEnumKeys>;
const recordWithLiteralKey = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
type recordWithLiteralKey = z.infer<typeof recordWithLiteralKey>;
const recordWithLiteralUnionKeys = z.record(
z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]),
z.string()
);
type recordWithLiteralUnionKeys = z.infer<typeof recordWithLiteralUnionKeys>;
enum Enum {
Tuna = 0,
Salmon = "Shark",
}
const recordWithTypescriptEnum = z.record(z.enum(Enum), z.string());
type recordWithTypescriptEnum = z.infer<typeof recordWithTypescriptEnum>;
expectTypeOf<booleanRecord>().toEqualTypeOf<Record<string, boolean>>();
expectTypeOf<recordWithEnumKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon", string>>();
expectTypeOf<recordWithLiteralKey>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
expectTypeOf<recordWithLiteralUnionKeys>().toEqualTypeOf<Record<"Tuna" | "Salmon" | 21, string>>();
expectTypeOf<recordWithTypescriptEnum>().toEqualTypeOf<Record<Enum, string>>();
});
test("enum exhaustiveness", () => {
const schema = z.record(z.enum(["Tuna", "Salmon"]), z.string());
expect(
schema.parse({
Tuna: "asdf",
Salmon: "asdf",
})
).toEqual({
Tuna: "asdf",
Salmon: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("typescript enum exhaustiveness", () => {
enum BigFish {
Tuna = 0,
Salmon = "Shark",
}
const schema = z.record(z.enum(BigFish), z.string());
const value = {
[BigFish.Tuna]: "asdf",
[BigFish.Salmon]: "asdf",
};
expect(schema.parse(value)).toEqual(value);
expect(schema.safeParse({ [BigFish.Tuna]: "asdf", [BigFish.Salmon]: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ [BigFish.Tuna]: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Shark"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
expect(schema.safeParse({ [BigFish.Salmon]: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
0
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("literal exhaustiveness", () => {
const schema = z.record(z.literal(["Tuna", "Salmon", 21]), z.string());
schema.parse({
Tuna: "asdf",
Salmon: "asdf",
21: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
21
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("pipe exhaustiveness", () => {
const schema = z.record(z.enum(["Tuna", "Salmon"]).pipe(z.any()), z.string());
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf" })).toEqual({
Tuna: "asdf",
Salmon: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("union exhaustiveness", () => {
const schema = z.record(z.union([z.literal("Tuna"), z.literal("Salmon"), z.literal(21)]), z.string());
expect(schema.parse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf" })).toEqual({
Tuna: "asdf",
Salmon: "asdf",
21: "asdf",
});
expect(schema.safeParse({ Tuna: "asdf", Salmon: "asdf", 21: "asdf", Trout: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "unrecognized_keys",
"keys": [
"Trout"
],
"path": [],
"message": "Unrecognized key: \\"Trout\\""
}
]],
"success": false,
}
`);
expect(schema.safeParse({ Tuna: "asdf" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"Salmon"
],
"message": "Invalid input: expected string, received undefined"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
21
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
});
test("string record parse - pass", () => {
const schema = z.record(z.string(), z.boolean());
schema.parse({
k1: true,
k2: false,
1234: false,
});
expect(schema.safeParse({ asdf: 1234 }).success).toEqual(false);
expect(schema.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "record",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected record, received string"
}
]],
"success": false,
}
`);
});
test("key and value getters", () => {
const rec = z.record(z.string(), z.number());
rec.keyType.parse("asdf");
rec.valueType.parse(1234);
});
test("is not vulnerable to prototype pollution", async () => {
const rec = z.record(
z.string(),
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 remove undefined values", () => {
const result1 = z.record(z.string(), z.any()).parse({ foo: undefined });
expect(result1).toEqual({
foo: undefined,
});
});
test("allow undefined values", () => {
const schema = z.record(z.string(), z.undefined());
expect(
Object.keys(
schema.parse({
_test: undefined,
})
)
).toEqual(["_test"]);
});
test("async parsing", async () => {
const schema = z
.record(
z.string(),
z
.string()
.optional()
.refine(async () => true)
)
.refine(async () => true);
const data = {
foo: "bar",
baz: "qux",
};
const result = await schema.safeParseAsync(data);
expect(result.data).toEqual(data);
});
test("async parsing", async () => {
const schema = z
.record(
z.string(),
z
.string()
.optional()
.refine(async () => false)
)
.refine(async () => false);
const data = {
foo: "bar",
baz: "qux",
};
const result = await schema.safeParseAsync(data);
expect(result.success).toEqual(false);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [
"foo"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [
"baz"
],
"message": "Invalid input"
},
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
});
test("partial record", () => {
const schema = z.partialRecord(z.string(), z.string());
type schema = z.infer<typeof schema>;
expectTypeOf<schema>().toEqualTypeOf<Partial<Record<string, string>>>();
const Keys = z.enum(["id", "name", "email"]); //.or(z.never());
const Person = z.partialRecord(Keys, z.string());
expectTypeOf<z.infer<typeof Person>>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
Person.parse({
id: "123",
// name: "John",
// email: "john@example.com",
});
Person.parse({
// id: "123",
// name: "John",
email: "john@example.com",
});
expect(Person.def.keyType._zod.def.type).toEqual("enum");
});
test("partialRecord with z.literal([key, ...])", () => {
const Keys = z.literal(["id", "name", "email"]);
const schema = z.partialRecord(Keys, z.string());
type Schema = z.infer<typeof schema>;
expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
// Should parse valid partials
expect(schema.parse({})).toEqual({});
expect(schema.parse({ id: "1" })).toEqual({ id: "1" });
expect(schema.parse({ name: "n", email: "e@example.com" })).toEqual({ name: "n", email: "e@example.com" });
// Should fail with unrecognized key, error checked via inline snapshot
expect(schema.safeParse({ foo: "bar" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_key",
"origin": "record",
"issues": [
{
"code": "invalid_value",
"values": [
"id",
"name",
"email"
],
"path": [],
"message": "Invalid option: expected one of \\"id\\"|\\"name\\"|\\"email\\""
}
],
"path": [
"foo"
],
"message": "Invalid key in record"
}
]],
"success": false,
}
`);
});
test("partialRecord with numeric literal keys", () => {
const Keys = z.literal([1, 2, 3]);
const schema = z.partialRecord(Keys, z.string());
type Schema = z.infer<typeof schema>;
expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<1 | 2 | 3, string>>>();
// Should parse valid partials with numeric keys (as strings in JS objects)
expect(schema.parse({})).toEqual({});
expect(schema.parse({ 1: "one" })).toEqual({ 1: "one" });
expect(schema.parse({ 2: "two", 3: "three" })).toEqual({ 2: "two", 3: "three" });
// Should fail with unrecognized key
expect(schema.safeParse({ 4: "four" }).success).toBe(false);
});
test("partialRecord with union of string and numeric literal keys", () => {
const StringKeys = z.literal(["a", "b", "c"]);
const NumericKeys = z.literal([1, 2, 3]);
const schema = z.partialRecord(z.union([StringKeys, NumericKeys]), z.string());
type Schema = z.infer<typeof schema>;
expectTypeOf<Schema>().toEqualTypeOf<Partial<Record<"a" | "b" | "c" | 1 | 2 | 3, string>>>();
// Should parse valid partials with mixed keys
expect(schema.parse({})).toEqual({});
expect(schema.parse({ a: "1", 2: "4" })).toEqual({ a: "1", 2: "4" });
expect(schema.parse({ a: "a", b: "b", 1: "1", 2: "2" })).toEqual({ a: "a", b: "b", 1: "1", 2: "2" });
// Should fail with unrecognized key
expect(schema.safeParse({ d: "d" }).success).toBe(false);
expect(schema.safeParse({ 4: "4" }).success).toBe(false);
});
test("looseRecord passes through non-matching keys", () => {
const schema = z.looseRecord(z.string().regex(/^S_/), z.string());
// Keys matching pattern are validated
expect(schema.parse({ S_name: "John" })).toEqual({ S_name: "John" });
expect(() => schema.parse({ S_name: 123 })).toThrow(); // wrong value type
// Keys not matching pattern pass through unchanged
expect(schema.parse({ S_name: "John", other: "value" })).toEqual({ S_name: "John", other: "value" });
expect(schema.parse({ S_name: "John", count: 123 })).toEqual({ S_name: "John", count: 123 });
expect(schema.parse({ other: "value" })).toEqual({ other: "value" });
});
test("intersection of loose records", () => {
const schema = z.intersection(
z.object({ name: z.string() }).passthrough(),
z.intersection(
z.looseRecord(z.string().regex(/^S_/), z.string()),
z.looseRecord(z.string().regex(/^N_/), z.number())
)
);
// Each pattern validates its matching keys
const result = schema.parse({ name: "John", S_foo: "bar", N_count: 123 });
expect(result.name).toBe("John");
expect(result.S_foo).toBe("bar");
expect(result.N_count).toBe(123);
// Keys not matching any pattern pass through
const result2 = schema.parse({ name: "John", S_foo: "bar", N_count: 123, other: "value" });
expect(result2.other).toBe("value");
// Validation errors still occur for matching keys
expect(() => schema.parse({ name: "John", S_foo: 123 })).toThrow(); // S_foo should be string
expect(() => schema.parse({ name: "John", N_count: "abc" })).toThrow(); // N_count should be number
});
test("object with looseRecord index signature", () => {
// Simulates TypeScript index signature: { label: string; [key: `label:${string}`]: string }
const schema = z.object({ label: z.string() }).and(z.looseRecord(z.string().regex(/^label:[a-z]{2}$/), z.string()));
type Schema = z.infer<typeof schema>;
expectTypeOf<Schema>().toEqualTypeOf<{ label: string } & Record<string, string>>();
// Valid: has required property and matching pattern keys
expect(schema.parse({ label: "Purple", "label:en": "Purple", "label:ru": "Пурпурный" })).toEqual({
label: "Purple",
"label:en": "Purple",
"label:ru": "Пурпурный",
});
// Valid: just required property
expect(schema.parse({ label: "Purple" })).toEqual({ label: "Purple" });
// Invalid: missing required property
expect(schema.safeParse({ "label:en": "Purple" })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"label"
],
"message": "Invalid input: expected string, received undefined"
}
]],
"success": false,
}
`);
// Invalid: pattern key with wrong value type
expect(schema.safeParse({ label: "Purple", "label:en": 123 })).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [
"label:en"
],
"message": "Invalid input: expected string, received number"
}
]],
"success": false,
}
`);
});
test("numeric string keys", () => {
const schema = z.record(z.number(), z.number());
// Numeric string keys work
expect(schema.parse({ 1: 100, 2: 200 })).toEqual({ 1: 100, 2: 200 });
expect(schema.parse({ "1.5": 100, "-3": 200 })).toEqual({ "1.5": 100, "-3": 200 });
// Non-numeric keys fail
expect(schema.safeParse({ abc: 100 }).success).toBe(false);
// Integer constraint is respected
const intSchema = z.record(z.number().int(), z.number());
expect(intSchema.parse({ 1: 100 })).toEqual({ 1: 100 });
expect(intSchema.safeParse({ "1.5": 100 }).success).toBe(false);
// Transforms on numeric keys work
const transformedSchema = z.record(
z.number().overwrite((n) => n * 2),
z.string()
);
expect(transformedSchema.parse({ 5: "five", 10: "ten" })).toEqual({ 10: "five", 20: "ten" });
});

View File

@@ -0,0 +1,582 @@
import { expect, expectTypeOf, test } from "vitest";
import { z } from "zod/v4";
test("recursion with z.lazy", () => {
const data = {
name: "I",
subcategories: [
{
name: "A",
subcategories: [
{
name: "1",
subcategories: [
{
name: "a",
subcategories: [],
},
],
},
],
},
],
};
const Category = z.object({
name: z.string(),
get subcategories() {
return z.array(Category).optional().nullable();
},
});
type Category = z.infer<typeof Category>;
interface _Category {
name: string;
subcategories?: _Category[] | undefined | null;
}
expectTypeOf<Category>().toEqualTypeOf<_Category>();
Category.parse(data);
});
test("recursion involving union type", () => {
const data = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: {
value: 4,
next: null,
},
},
},
};
const LL = z.object({
value: z.number(),
get next() {
return LL.nullable();
},
});
type LL = z.infer<typeof LL>;
type _LL = {
value: number;
next: _LL | null;
};
expectTypeOf<LL>().toEqualTypeOf<_LL>();
LL.parse(data);
});
test("mutual recursion - native", () => {
const Alazy = z.object({
val: z.number(),
get b() {
return Blazy;
},
});
const Blazy = z.object({
val: z.number(),
get a() {
return Alazy.optional();
},
});
const testData = {
val: 1,
b: {
val: 5,
a: {
val: 3,
b: {
val: 4,
a: {
val: 2,
b: {
val: 1,
},
},
},
},
},
};
type Alazy = z.infer<typeof Alazy>;
type Blazy = z.infer<typeof Blazy>;
interface _Alazy {
val: number;
b: _Blazy;
}
interface _Blazy {
val: number;
a?: _Alazy | undefined;
}
expectTypeOf<Alazy>().toEqualTypeOf<_Alazy>();
expectTypeOf<Blazy>().toEqualTypeOf<_Blazy>();
Alazy.parse(testData);
Blazy.parse(testData.b);
expect(() => Alazy.parse({ val: "asdf" })).toThrow();
});
test("pick and omit with getter", () => {
const Category = z.strictObject({
name: z.string(),
get subcategories() {
return z.array(Category);
},
});
type Category = z.infer<typeof Category>;
interface _Category {
name: string;
subcategories: _Category[];
}
expectTypeOf<Category>().toEqualTypeOf<_Category>();
const PickedCategory = Category.pick({ name: true });
const OmittedCategory = Category.omit({ subcategories: true });
const picked = { name: "test" };
const omitted = { name: "test" };
PickedCategory.parse(picked);
OmittedCategory.parse(omitted);
expect(() => PickedCategory.parse({ name: "test", subcategories: [] })).toThrow();
expect(() => OmittedCategory.parse({ name: "test", subcategories: [] })).toThrow();
});
test("deferred self-recursion", () => {
const Feature = z.object({
title: z.string(),
get features(): z.ZodOptional<z.ZodArray<typeof Feature>> {
return z.optional(z.array(Feature)); //.optional();
},
});
// type Feature = z.infer<typeof Feature>;
const Output = z.object({
id: z.int(), //.nonnegative(),
name: z.string(),
get features(): z.ZodArray<typeof Feature> {
return Feature.array();
},
});
type Output = z.output<typeof Output>;
type _Feature = {
title: string;
features?: _Feature[] | undefined;
};
type _Output = {
id: number;
name: string;
features: _Feature[];
};
// expectTypeOf<Feature>().toEqualTypeOf<_Feature>();
expectTypeOf<Output>().toEqualTypeOf<_Output>();
});
test("deferred mutual recursion", () => {
const Slot = z.object({
slotCode: z.string(),
get blocks() {
return z.array(Block);
},
});
type Slot = z.infer<typeof Slot>;
const Block = z.object({
blockCode: z.string(),
get slots() {
return z.array(Slot).optional();
},
});
type Block = z.infer<typeof Block>;
const Page = z.object({
slots: z.array(Slot),
});
type Page = z.infer<typeof Page>;
type _Slot = {
slotCode: string;
blocks: _Block[];
};
type _Block = {
blockCode: string;
slots?: _Slot[] | undefined;
};
type _Page = {
slots: _Slot[];
};
expectTypeOf<Slot>().toEqualTypeOf<_Slot>();
expectTypeOf<Block>().toEqualTypeOf<_Block>();
expectTypeOf<Page>().toEqualTypeOf<_Page>();
});
test("mutual recursion with meta", () => {
const A = z
.object({
name: z.string(),
get b() {
return B;
},
})
.readonly()
.meta({ id: "A" })
.optional();
const B = z
.object({
name: z.string(),
get a() {
return A;
},
})
.readonly()
.meta({ id: "B" });
type A = z.infer<typeof A>;
type B = z.infer<typeof B>;
type _A =
| Readonly<{
name: string;
b: _B;
}>
| undefined;
// | undefined;
type _B = Readonly<{
name: string;
a?: _A;
}>;
expectTypeOf<A>().toEqualTypeOf<_A>();
expectTypeOf<B>().toEqualTypeOf<_B>();
});
test("intersection with recursive types", () => {
const A = z.discriminatedUnion("type", [
z.object({
type: z.literal("CONTAINER"),
}),
z.object({
type: z.literal("SCREEN"),
config: z.object({ x: z.number(), y: z.number() }),
}),
]);
// type A = z.infer<typeof A>;
const B = z.object({
get children() {
return z.array(C).optional();
},
});
// type B = z.infer<typeof B>;
const C = z.intersection(A, B);
type C = z.infer<typeof C>;
type _C = (
| {
type: "CONTAINER";
}
| {
type: "SCREEN";
config: {
x: number;
y: number;
};
}
) & {
children?: _C[] | undefined;
};
expectTypeOf<C>().toEqualTypeOf<_C>();
});
test("object utilities with recursive types", () => {
const NodeBase = z.object({
id: z.string(),
name: z.string(),
get children() {
return z.array(Node).optional();
},
});
// Test extend with new keys (extend throws when overwriting existing keys)
const NodeOne = NodeBase.extend({
name: z.literal("nodeOne"),
get children() {
return z.array(Node);
},
});
const NodeTwo = NodeBase.extend({
name: z.literal("nodeTwo"),
get children() {
return z.array(Node);
},
});
// Test pick
const PickedNode = NodeBase.pick({ id: true, name: true });
// Test omit
const OmittedNode = NodeBase.omit({ children: true });
// Test merge
const ExtraProps = {
metadata: z.string(),
get parent() {
return Node.optional();
},
};
const MergedNode = NodeBase.extend(ExtraProps);
// Test partial
const PartialNode = NodeBase.partial();
const PartialMaskedNode = NodeBase.partial({ name: true });
// Test required (assuming NodeBase has optional fields)
const OptionalNodeBase = z.object({
id: z.string().optional(),
name: z.string().optional(),
get children() {
return z.array(Node).optional();
},
});
const RequiredNode = OptionalNodeBase.required();
const RequiredMaskedNode = OptionalNodeBase.required({ id: true });
const Node = z.union([
NodeOne,
NodeTwo,
PickedNode,
OmittedNode,
MergedNode,
PartialNode,
PartialMaskedNode,
RequiredNode,
RequiredMaskedNode,
]);
});
test("tuple with recursive types", () => {
const TaskListNodeSchema = z.strictObject({
type: z.literal("taskList"),
get content() {
return z.array(z.tuple([TaskListNodeSchema, z.union([TaskListNodeSchema])])).min(1);
},
});
type TaskListNodeSchema = z.infer<typeof TaskListNodeSchema>;
type _TaskListNodeSchema = {
type: "taskList";
content: [_TaskListNodeSchema, _TaskListNodeSchema][];
};
expectTypeOf<TaskListNodeSchema>().toEqualTypeOf<_TaskListNodeSchema>();
});
test("recursion compatibility", () => {
// array
const A = z.object({
get array() {
return A.array();
},
get optional() {
return A.optional();
},
get nullable() {
return A.nullable();
},
get nonoptional() {
return A.nonoptional();
},
get readonly() {
return A.readonly();
},
get describe() {
return A.describe("A recursive type");
},
get meta() {
return A.meta({ description: "A recursive type" });
},
get pipe() {
return A.pipe(z.any());
},
get strict() {
return A.strict();
},
get tuple() {
return z.tuple([A, A]);
},
get object() {
return z
.object({
subcategories: A,
})
.strict()
.loose();
},
get union() {
return z.union([A, A]);
},
get intersection() {
return z.intersection(A, A);
},
get record() {
return z.record(z.string(), A);
},
get map() {
return z.map(z.string(), A);
},
get set() {
return z.set(A);
},
get lazy() {
return z.lazy(() => A);
},
get promise() {
return z.promise(A);
},
});
});
test("recursive object with .check()", () => {
const Category = z
.object({
id: z.string(),
name: z.string(),
get subcategories() {
return z.array(Category).optional();
},
})
.check((ctx) => {
// Check for duplicate IDs among direct subcategories
if (ctx.value.subcategories) {
const siblingIds = new Set<string>();
ctx.value.subcategories.forEach((sub, index) => {
if (siblingIds.has(sub.id)) {
ctx.issues.push({
code: "custom",
message: `Duplicate sibling ID found: ${sub.id}`,
path: ["subcategories", index, "id"],
input: ctx.value,
});
}
siblingIds.add(sub.id);
});
}
});
// Valid - siblings have unique IDs
const validData = {
id: "electronics",
name: "Electronics",
subcategories: [
{
id: "computers",
name: "Computers",
subcategories: [
{ id: "laptops", name: "Laptops" },
{ id: "desktops", name: "Desktops" },
],
},
{
id: "phones",
name: "Phones",
},
],
};
// Invalid - duplicate sibling IDs
const invalidData = {
id: "electronics",
name: "Electronics",
subcategories: [
{ id: "computers", name: "Computers" },
{ id: "phones", name: "Phones" },
{ id: "computers", name: "Computers Again" }, // Duplicate at index 2
],
};
expect(() => Category.parse(validData)).not.toThrow();
expect(() => Category.parse(invalidData)).toThrow();
});
// biome-ignore lint: sadf
export type RecursiveA = z.ZodUnion<
[
z.ZodObject<{
a: z.ZodDefault<RecursiveA>;
b: z.ZodPrefault<RecursiveA>;
c: z.ZodNonOptional<RecursiveA>;
d: z.ZodOptional<RecursiveA>;
e: z.ZodNullable<RecursiveA>;
g: z.ZodReadonly<RecursiveA>;
h: z.ZodPipe<RecursiveA, z.ZodString>;
i: z.ZodArray<RecursiveA>;
j: z.ZodSet<RecursiveA>;
k: z.ZodMap<RecursiveA, RecursiveA>;
l: z.ZodRecord<z.ZodString, RecursiveA>;
m: z.ZodUnion<[RecursiveA, RecursiveA]>;
n: z.ZodIntersection<RecursiveA, RecursiveA>;
o: z.ZodLazy<RecursiveA>;
p: z.ZodPromise<RecursiveA>;
q: z.ZodCatch<RecursiveA>;
r: z.ZodSuccess<RecursiveA>;
s: z.ZodTransform<RecursiveA, string>;
t: z.ZodTuple<[RecursiveA, RecursiveA]>;
u: z.ZodObject<{
a: RecursiveA;
}>;
}>,
]
>;
test("recursive type with `id` meta", () => {
const AType = z.object({
type: z.literal("a"),
name: z.string(),
});
const BType = z.object({
type: z.literal("b"),
name: z.string(),
});
const CType = z.object({
type: z.literal("c"),
name: z.string(),
});
const Schema = z.object({
type: z.literal("special").meta({ description: "Type" }),
config: z.object({
title: z.string().meta({ description: "Title" }),
get elements() {
return z.array(z.discriminatedUnion("type", [AType, BType, CType])).meta({
id: "SpecialElements",
title: "SpecialElements",
description: "Array of elements",
});
},
}),
});
Schema.parse({
type: "special",
config: {
title: "Special",
elements: [
{ type: "a", name: "John" },
{ type: "b", name: "Jane" },
{ type: "c", name: "Jim" },
],
},
});
});

View File

@@ -0,0 +1,570 @@
import { describe, expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
describe("basic refinement functionality", () => {
test("should create a new schema instance when refining", () => {
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);
});
test("should validate according to refinement logic", () => {
const schema = z
.object({
first: z.string(),
second: z.string(),
})
.partial()
.strict()
.refine((data) => data.first || data.second, "Either first or second should be filled in.");
// Should fail on empty object
expect(() => schema.parse({})).toThrow();
// Should pass with first property
expect(schema.parse({ first: "a" })).toEqual({ first: "a" });
// Should pass with second property
expect(schema.parse({ second: "a" })).toEqual({ second: "a" });
// Should pass with both properties
expect(schema.parse({ first: "a", second: "a" })).toEqual({ first: "a", second: "a" });
});
test("should validate strict mode correctly", () => {
const schema = z
.object({
first: z.string(),
second: z.string(),
})
.partial()
.strict();
// Should throw on extra properties
expect(() => schema.parse({ third: "adsf" })).toThrow();
});
});
describe("refinement with custom error messages", () => {
test("should use custom error message when validation fails", () => {
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");
const result = validationSchema.safeParse({
email: "aaaa@gmail.com",
password: "aaaaaaaa",
confirmPassword: "bbbbbbbb",
});
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].message).toEqual("Both password and confirmation must match");
}
});
});
describe("async refinements", () => {
test("should support async refinement functions", 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"
);
// Should pass with matching passwords
const validData = {
email: "aaaa@gmail.com",
password: "password",
confirmPassword: "password",
};
await expect(validationSchema.parseAsync(validData)).resolves.toEqual(validData);
// Should fail with non-matching passwords
await expect(
validationSchema.parseAsync({
email: "aaaa@gmail.com",
password: "password",
confirmPassword: "different",
})
).rejects.toThrow();
});
});
describe("early termination options", () => {
test("should abort early with continue: false", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val.length < 2) {
ctx.addIssue({
code: "custom",
message: "BAD",
continue: false,
});
}
})
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("BAD");
}
});
test("should abort early with fatal: true", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val.length < 2) {
ctx.addIssue({
code: "custom",
fatal: true,
message: "BAD",
});
}
})
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("BAD");
}
});
test("should abort early with abort flag", () => {
const schema = z
.string()
.refine((_) => false, { abort: true })
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
}
});
});
describe("custom error paths", () => {
test("should use custom path in error message", () => {
const result = z
.object({ password: z.string(), confirm: z.string() })
.refine((data) => data.confirm === data.password, { path: ["confirm"] })
.safeParse({ password: "asdf", confirm: "qewr" });
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].path).toEqual(["confirm"]);
}
});
});
describe("superRefine functionality", () => {
test("should support multiple validation rules", () => {
const Strings = z.array(z.string()).superRefine((val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
input: val,
code: "too_big",
origin: "array",
maximum: 3,
inclusive: true,
exact: true,
message: "Too many items 😡",
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
input: val,
code: "custom",
message: `No duplicates allowed.`,
});
}
});
// Should fail with too many items and duplicates
const result = Strings.safeParse(["asfd", "asfd", "asfd", "asfd"]);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
expect(result.error.issues[0].message).toEqual("Too many items 😡");
expect(result.error.issues[1].message).toEqual("No duplicates allowed.");
}
// Should pass with valid input
const validArray = ["asfd", "qwer"];
expect(Strings.parse(validArray)).toEqual(validArray);
});
test("should support async superRefine", async () => {
const Strings = z.array(z.string()).superRefine(async (val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
input: val,
code: "too_big",
origin: "array",
maximum: 3,
inclusive: true,
message: "Too many items 😡",
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
input: val,
code: "custom",
message: `No duplicates allowed.`,
});
}
});
// Should fail with too many items and duplicates
const result = await Strings.safeParseAsync(["asfd", "asfd", "asfd", "asfd"]);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
}
// Should pass with valid input
const validArray = ["asfd", "qwer"];
await expect(Strings.parseAsync(validArray)).resolves.toEqual(validArray);
});
test("should test continuability of custom issues", () => {
// Default continue behavior - allows subsequent refinements
const defaultContinue = z
.string()
.superRefine((_, ctx) => {
ctx.addIssue({ code: "custom", message: "First issue" });
})
.refine(() => false, "Second issue");
expect(defaultContinue.safeParse("test")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "First issue",
"path": []
},
{
"code": "custom",
"path": [],
"message": "Second issue"
}
]],
"success": false,
}
`);
// Explicit continue: false - prevents subsequent refinements
const explicitContinueFalse = z
.string()
.superRefine((_, ctx) => {
ctx.addIssue({ code: "custom", message: "First issue", continue: false });
})
.refine(() => false, "Second issue");
expect(explicitContinueFalse.safeParse("test")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "First issue",
"path": []
}
]],
"success": false,
}
`);
// Multiple issues in same refinement - both always added regardless of continue
const multipleInSame = z.string().superRefine((_, ctx) => {
ctx.addIssue({ code: "custom", message: "First", continue: false });
ctx.addIssue({ code: "custom", message: "Second" });
});
expect(multipleInSame.safeParse("test")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "First",
"path": []
},
{
"code": "custom",
"message": "Second",
"path": []
}
]],
"success": false,
}
`);
});
test("should accept string as shorthand for custom error message", () => {
const schema = z.string().superRefine((_, ctx) => {
ctx.addIssue("bad stuff");
});
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues).toHaveLength(1);
expect(result.error.issues[0].message).toEqual("bad stuff");
}
});
test("should respect fatal flag in superRefine", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val === "") {
ctx.addIssue({
input: val,
code: "custom",
message: "foo",
fatal: true,
});
}
})
.superRefine((val, ctx) => {
if (val !== " ") {
ctx.addIssue({
input: val,
code: "custom",
message: "bar",
});
}
});
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("foo");
}
});
});
describe("chained refinements", () => {
test("should collect all validation errors when appropriate", () => {
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",
});
// Should fail with one error
const r1 = objectSchema.safeParse({
length: 4,
size: 9,
});
expect(r1.success).toEqual(false);
if (!r1.success) {
expect(r1.error.issues.length).toEqual(1);
expect(r1.error.issues[0].path).toEqual(["length"]);
}
// Should fail with two errors
const r2 = objectSchema.safeParse({
length: 4,
size: 3,
});
expect(r2.success).toEqual(false);
if (!r2.success) {
expect(r2.error.issues.length).toEqual(2);
}
// Should pass with valid input
const validData = {
length: 6,
size: 8,
};
expect(objectSchema.parse(validData)).toEqual(validData);
});
});
describe("type refinement with type guards", () => {
test("type guard narrows output type", () => {
const schema = z.string().refine((s): s is "a" => s === "a");
expectTypeOf<z.input<typeof schema>>().toEqualTypeOf<string>();
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<"a">();
});
test("non-type-guard refine does not narrow", () => {
const schema = z.string().refine((s) => s.length > 0);
expectTypeOf<z.input<typeof schema>>().toEqualTypeOf<string>();
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<string>();
});
// TODO: Implement type narrowing for superRefine
// 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) {
// ctx.addIssue({
// input: arg,
// code: "custom",
// message: "cannot be null",
// fatal: true,
// });
// return false;
// }
// return true;
// });
//
// expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<NarrowType>();
//
// expect(schema.safeParse({ type: "test", age: 0 }).success).toEqual(true);
// expect(schema.safeParse(null).success).toEqual(false);
// });
});
test("when", () => {
const schema = z
.strictObject({
password: z.string().min(8),
confirmPassword: z.string(),
other: z.string(),
})
.refine(
(data) => {
// console.log("running check...");
// console.log(data);
// console.log(data.password);
return data.password === data.confirmPassword;
},
{
message: "Passwords do not match",
path: ["confirmPassword"],
when(payload) {
if (payload.value === undefined) return false;
if (payload.value === null) return false;
// no issues with confirmPassword or password
return payload.issues.every((iss) => iss.path?.[0] !== "confirmPassword" && iss.path?.[0] !== "password");
},
}
);
expect(schema.safeParse(undefined)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]],
"success": false,
}
`);
expect(schema.safeParse(null)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received null"
}
]],
"success": false,
}
`);
expect(
schema.safeParse({
password: "asdf",
confirmPassword: "asdfg",
other: "qwer",
})
).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 8,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=8 characters"
}
]],
"success": false,
}
`);
expect(
schema.safeParse({
password: "asdf",
confirmPassword: "asdfg",
other: 1234,
})
).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 8,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=8 characters"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"other"
],
"message": "Invalid input: expected string, received number"
}
]],
"success": false,
}
`);
});

View File

@@ -0,0 +1,243 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("globalRegistry", () => {
const reg = z.registry();
const a = z.string();
reg.add(a);
expect(reg.has(a)).toEqual(true);
reg.remove(a);
expect(reg.has(a)).toEqual(false);
a.register(z.globalRegistry, { field: "sup" });
expect(z.globalRegistry.has(a)).toEqual(true);
expect(z.globalRegistry.get(a)).toEqual({ field: "sup" });
z.globalRegistry.remove(a);
expect(z.globalRegistry.has(a)).toEqual(false);
});
test("globalRegistry is singleton and attached to globalThis", () => {
expect(z.globalRegistry).toBe((globalThis as any).__zod_globalRegistry);
});
test("z.registry", () => {
const fieldRegistry = z.registry<{ name: string; description: string }>();
const a = z.string();
fieldRegistry.add(a, { name: "hello", description: "world" });
const a_meta = fieldRegistry.get(a);
expect(a_meta).toEqual({ name: "hello", description: "world" });
fieldRegistry.remove(a);
expect(fieldRegistry.has(a)).toEqual(false);
expect(fieldRegistry.get(a)).toEqual(undefined);
});
test("z.registry no metadata", () => {
const fieldRegistry = z.registry();
const a = z.string();
fieldRegistry.add(a);
fieldRegistry.add(z.number());
expect(fieldRegistry.get(a)).toEqual(undefined);
expect(fieldRegistry.has(a)).toEqual(true);
});
test("z.registry with schema constraints", () => {
const fieldRegistry = z.registry<{ name: string; description: string }, z.ZodString>();
const a = z.string();
fieldRegistry.add(a, { name: "hello", description: "world" });
// @ts-expect-error
fieldRegistry.add(z.number(), { name: "test" });
// @ts-expect-error
z.number().register(fieldRegistry, { name: "test", description: "test" });
});
// test("z.namedRegistry", () => {
// const namedReg = z
// .namedRegistry<{ name: string; description: string }>()
// .add(z.string(), { name: "hello", description: "world" })
// .add(z.number(), { name: "number", description: "number" });
// expect(namedReg.get("hello")).toEqual({
// name: "hello",
// description: "world",
// });
// expect(namedReg.has("hello")).toEqual(true);
// expect(namedReg.get("number")).toEqual({
// name: "number",
// description: "number",
// });
// // @ts-expect-error
// namedReg.get("world");
// // @ts-expect-error
// expect(namedReg.get("world")).toEqual(undefined);
// const hello = namedReg.get("hello");
// expect(hello).toEqual({ name: "hello", description: "world" });
// expectTypeOf<typeof hello>().toEqualTypeOf<{
// name: "hello";
// description: "world";
// }>();
// expectTypeOf<typeof namedReg.items>().toEqualTypeOf<{
// hello: { name: "hello"; description: "world" };
// number: { name: "number"; description: "number" };
// }>();
// });
test("output type in registry meta", () => {
const reg = z.registry<{ out: z.$output }>();
const a = z.string();
reg.add(a, { out: "asdf" });
// @ts-expect-error
reg.add(a, 1234);
expectTypeOf(reg.get(a)).toEqualTypeOf<{ out: string } | undefined>();
});
test("output type in registry meta - objects and arrays", () => {
const reg = z.registry<{ name: string; examples: z.$output[] }>();
const a = z.string();
reg.add(a, { name: "hello", examples: ["world"] });
// @ts-expect-error
reg.add(a, { name: "hello", examples: "world" });
expectTypeOf(reg.get(a)).toEqualTypeOf<{ name: string; examples: string[] } | undefined>();
});
test("input type in registry meta", () => {
const reg = z.registry<{ in: z.$input }>();
const a = z.pipe(z.number(), z.transform(String));
reg.add(a, { in: 1234 });
// @ts-expect-error
reg.add(a, "1234");
expectTypeOf(reg.get(a)).toEqualTypeOf<{ in: number } | undefined>();
});
test("input type in registry meta - objects and arrays", () => {
const reg = z.registry<{ name: string; examples: z.$input[] }>();
const a = z.pipe(z.number(), z.transform(String));
reg.add(a, { name: "hello", examples: [1234] });
// @ts-expect-error
reg.add(a, { name: "hello", examples: "world" });
expectTypeOf(reg.get(a)).toEqualTypeOf<{ name: string; examples: number[] } | undefined>();
});
test(".meta method", () => {
const a1 = z.string();
const a2 = a1.meta({ name: "hello" });
expect(a1.meta()).toEqual(undefined);
expect(a2.meta()).toEqual({ name: "hello" });
expect(a1 === a2).toEqual(false);
});
test(".meta metadata does not bubble up", () => {
const a1 = z.string().meta({ name: "hello" });
const a2 = a1.optional();
expect(a1.meta()).toEqual({ name: "hello" });
expect(a2.meta()).toEqual(undefined);
});
test(".describe", () => {
const a1 = z.string();
const a2 = a1.describe("Hello");
expect(a1.description).toEqual(undefined);
expect(a2.description).toEqual("Hello");
});
test("inherit across clone", () => {
const A = z.string().meta({ a: true });
expect(A.meta()).toEqual({ a: true });
const B = A.meta({ b: true });
expect(B.meta()).toEqual({ a: true, b: true });
const C = B.describe("hello");
expect(C.meta()).toEqual({ a: true, b: true, description: "hello" });
});
test("loose examples", () => {
z.string().register(z.globalRegistry, {
examples: ["example"],
});
});
test("function meta without replacement", () => {
const myReg = z.registry<{
defaulter: (arg: string, test: boolean) => number;
}>();
const mySchema = z.date();
myReg.add(mySchema, {
defaulter: (arg, _test) => {
return arg.length;
},
});
expect(myReg.get(mySchema)!.defaulter("hello", true)).toEqual(5);
});
test("function meta with replacement", () => {
const myReg = z.registry<{
defaulter: (arg: z.$input, test: boolean) => z.$output;
}>();
const mySchema = z.string().transform((val) => val.length);
myReg.add(mySchema, {
defaulter: (arg, _test) => {
return arg.length;
},
});
expect(myReg.get(mySchema)!.defaulter("hello", true)).toEqual(5);
});
test("test .clear()", () => {
const reg = z.registry();
const a = z.string();
reg.add(a);
expect(reg.has(a)).toEqual(true);
reg.clear();
expect(reg.has(a)).toEqual(false);
});
test("re-registering same id silently overwrites", () => {
const reg = z.registry<z.core.GlobalMeta>();
const a = z.string();
const b = z.number();
reg.add(a, { id: "shared-id" });
reg.add(b, { id: "shared-id" });
// No error thrown, b now owns the id
expect(reg._idmap.get("shared-id")).toBe(b);
});
test("toJSONSchema throws on duplicate id across different schemas", () => {
const reg = z.registry<z.core.GlobalMeta>();
const a = z.string().register(reg, { id: "duplicate-id" });
const b = z.number().register(reg, { id: "duplicate-id" });
const wrapper = z.object({ a, b });
expect(() => z.toJSONSchema(wrapper, { metadata: reg })).toThrow(
'Duplicate schema id "duplicate-id" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.'
);
});
test("toJSONSchema allows same schema with same id", () => {
const reg = z.registry<z.core.GlobalMeta>();
const shared = z.string().register(reg, { id: "shared-id" });
const wrapper = z.object({ a: shared, b: shared });
// Should not throw - same schema instance used twice
const result = z.toJSONSchema(wrapper, { metadata: reg });
expect(result.$defs?.["shared-id"]).toBeDefined();
});

View File

@@ -0,0 +1,181 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
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", () => {
expectTypeOf<stringSet>().toEqualTypeOf<Set<string>>();
});
test("valid parse", () => {
const result = stringSet.safeParse(new Set(["first", "second"]));
expect(result.success).toEqual(true);
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);
expect(result.data!.has("first")).toEqual(true);
expect(result.data!.has("second")).toEqual(true);
expect(result.data!.has("third")).toEqual(false);
const asyncResult = stringSet.safeParse(new Set(["first", "second"]));
expect(asyncResult.success).toEqual(true);
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);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_small");
});
test("failing when set is smaller than min() ", () => {
const result = minTwo.safeParse(new Set(["just_one"]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_small");
});
test("failing when set is bigger than max() ", () => {
const result = maxTwo.safeParse(new Set(["one", "two", "three"]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error!.issues[0].code).toEqual("too_big");
});
test("doesnt 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);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "set",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected set, received Map"
}
]]
`);
});
test("throws when the given set has invalid input", () => {
const result = stringSet.safeParse(new Set([Symbol()]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(1);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received symbol"
}
]]
`);
});
test("throws when the given set has multiple invalid entries", () => {
const result = stringSet.safeParse(new Set([1, 2] as any[]));
expect(result.success).toEqual(false);
expect(result.error!.issues.length).toEqual(2);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
},
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]]
`);
});
test("min/max", async () => {
const schema = z.set(z.string()).min(4).max(5);
const r1 = schema.safeParse(new Set(["a", "b", "c", "d"]));
expect(r1.success).toEqual(true);
const r2 = schema.safeParse(new Set(["a", "b", "c"]));
expect(r2.success).toEqual(false);
expect(r2.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected set to have >=4 items",
"minimum": 4,
"origin": "set",
"path": [],
},
]
`);
const r3 = schema.safeParse(new Set(["a", "b", "c", "d", "e", "f"]));
expect(r3.success).toEqual(false);
expect(r3.error!.issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 5,
"message": "Too big: expected set to have <=5 items",
"origin": "set",
"path": [],
},
]
`);
});

View File

@@ -0,0 +1,134 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("length checks", async () => {
const schema = z.string();
const result = await schema["~standard"].validate(12);
expect(result).toMatchInlineSnapshot(`
{
"issues": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [],
},
],
}
`);
});
test("length checks", async () => {
const schema = z.string();
const result = await schema["~standard"].validate("asdf");
expect(result).toMatchInlineSnapshot(`
{
"value": "asdf",
}
`);
});
test("length checks", async () => {
const schema = z.string().refine(async (val) => val.length > 5);
const result = await schema["~standard"].validate(12);
expect(result).toMatchInlineSnapshot(`
{
"issues": [
{
"code": "invalid_type",
"expected": "string",
"message": "Invalid input: expected string, received number",
"path": [],
},
],
}
`);
});
test("length checks", async () => {
const schema = z.string().refine(async (val) => val.length > 5);
const result = await schema["~standard"].validate("234134134");
expect(result).toMatchInlineSnapshot(`
{
"value": "234134134",
}
`);
});
test("schemas conform to StandardJSONSchemaV1", async () => {
const schema = z.codec(z.string(), z.number(), {
decode: (str) => Number.parseFloat(str),
encode: (num) => num.toString(),
});
expect(schema["~standard"].validate).toBeTypeOf("function");
expect(await schema["~standard"].validate("42")).toMatchInlineSnapshot(`
{
"value": 42,
}
`);
expect(schema["~standard"].jsonSchema.input({ target: "draft-2020-12" })).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
}
`);
expect(schema["~standard"].jsonSchema.output({ target: "draft-2020-12" })).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number",
}
`);
});
test(".toJSONSchema() returns StandardJSONSchemaV1", async () => {
const codec = z.codec(z.string(), z.number(), {
decode: (str) => Number.parseFloat(str),
encode: (num) => num.toString(),
});
const result = codec.toJSONSchema();
expect(result["~standard"].validate).toBeTypeOf("function");
expect(await result["~standard"].validate("42")).toMatchInlineSnapshot(`
{
"value": 42,
}
`);
expect(result["~standard"].jsonSchema.input({ target: "draft-2020-12" })).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
}
`);
expect(result["~standard"].jsonSchema.output({ target: "draft-2020-12" })).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number",
}
`);
});
test("z.toJSONSchema() returns StandardJSONSchemaV1", async () => {
const codec = z.codec(z.string(), z.number(), {
decode: (str) => Number.parseFloat(str),
encode: (num) => num.toString(),
});
const result = z.toJSONSchema(codec);
expect(result["~standard"].validate).toBeTypeOf("function");
expect(await result["~standard"].validate("42")).toMatchInlineSnapshot(`
{
"value": 42,
}
`);
expect(result["~standard"].jsonSchema.input({ target: "draft-2020-12" })).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "string",
}
`);
expect(result["~standard"].jsonSchema.output({ target: "draft-2020-12" })).toMatchInlineSnapshot(`
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "number",
}
`);
});

View File

@@ -0,0 +1,125 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("string format methods", () => {
const a = z.email().min(10);
const b = z.email().max(10);
const c = z.email().length(10);
const d = z.email().uppercase();
const e = z.email().lowercase();
// Positive and negative cases for `a`
expect(a.safeParse("longemail@example.com").success).toBe(true); // Positive
expect(a.safeParse("ort@e.co").success).toBe(false); // Negative
// Positive and negative cases for `b`
expect(b.safeParse("sho@e.co").success).toBe(true); // Positive
expect(b.safeParse("longemail@example.com").success).toBe(false); // Negative
// Positive and negative cases for `c`
expect(c.safeParse("56780@e.co").success).toBe(true); // Positive
expect(c.safeParse("shoasdfasdfrt@e.co").success).toBe(false); // Negative
// Positive and negative cases for `d`
expect(d.safeParse("EMAIL@EXAMPLE.COM").success).toBe(true); // Positive
expect(d.safeParse("email@example.com").success).toBe(false); // Negative
// Positive and negative cases for `e`
expect(e.safeParse("email@example.com").success).toBe(true); // Positive
expect(e.safeParse("EMAIL@EXAMPLE.COM").success).toBe(false); // Negative
});
test("z.stringFormat", () => {
const ccRegex = /^(?:\d{14,19}|\d{4}(?: \d{3,6}){2,4}|\d{4}(?:-\d{3,6}){2,4})$/u;
const a = z
.stringFormat("creditCard", (val) => ccRegex.test(val), {
error: `Invalid credit card number`,
})
.refine((_) => false, "Also bad");
expect(a.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "creditCard",
"path": [],
"message": "Invalid credit card number"
},
{
"code": "custom",
"path": [],
"message": "Also bad"
}
]],
"success": false,
}
`);
expect(a.safeParse("1234-5678-9012-3456")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "Also bad"
}
]],
"success": false,
}
`);
expect(a.def.pattern).toMatchInlineSnapshot(`undefined`);
const b = z
.stringFormat("creditCard", ccRegex, {
abort: true,
error: `Invalid credit card number`,
})
.refine((_) => false, "Also bad");
expect(b.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "creditCard",
"path": [],
"message": "Invalid credit card number"
}
]],
"success": false,
}
`);
expect(b.safeParse("1234-5678-9012-3456")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [],
"message": "Also bad"
}
]],
"success": false,
}
`);
expect(b.def.pattern).toMatchInlineSnapshot(
`/\\^\\(\\?:\\\\d\\{14,19\\}\\|\\\\d\\{4\\}\\(\\?: \\\\d\\{3,6\\}\\)\\{2,4\\}\\|\\\\d\\{4\\}\\(\\?:-\\\\d\\{3,6\\}\\)\\{2,4\\}\\)\\$/u`
);
});
test("z.hex", () => {
const hexSchema = z.hex();
// Valid hex strings
expect(hexSchema.safeParse("").success).toBe(true); // Empty string is valid hex
expect(hexSchema.safeParse("123abc").success).toBe(true);
expect(hexSchema.safeParse("DEADBEEF").success).toBe(true);
expect(hexSchema.safeParse("0123456789abcdefABCDEF").success).toBe(true);
// Invalid hex strings
expect(hexSchema.safeParse("xyz").success).toBe(false);
expect(hexSchema.safeParse("123g").success).toBe(false);
expect(hexSchema.safeParse("hello world").success).toBe(false);
expect(hexSchema.safeParse("123-abc").success).toBe(false);
});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,106 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("z.stringbool", () => {
const a = z.stringbool();
type a = z.infer<typeof a>;
expectTypeOf<a>().toEqualTypeOf<boolean>();
type a_in = z.input<typeof a>;
expectTypeOf<a_in>().toEqualTypeOf<string>();
expect(z.parse(a, "true")).toEqual(true);
expect(z.parse(a, "yes")).toEqual(true);
expect(z.parse(a, "1")).toEqual(true);
expect(z.parse(a, "on")).toEqual(true);
expect(z.parse(a, "y")).toEqual(true);
expect(z.parse(a, "enabled")).toEqual(true);
expect(z.parse(a, "TRUE")).toEqual(true);
expect(z.parse(a, "false")).toEqual(false);
expect(z.parse(a, "no")).toEqual(false);
expect(z.parse(a, "0")).toEqual(false);
expect(z.parse(a, "off")).toEqual(false);
expect(z.parse(a, "n")).toEqual(false);
expect(z.parse(a, "disabled")).toEqual(false);
expect(z.parse(a, "FALSE")).toEqual(false);
expect(z.safeParse(a, "other")).toMatchObject({ success: false });
expect(z.safeParse(a, "")).toMatchObject({ success: false });
expect(z.safeParse(a, undefined)).toMatchObject({ success: false });
expect(z.safeParse(a, {})).toMatchObject({ success: false });
expect(z.safeParse(a, true)).toMatchObject({ success: false });
expect(z.safeParse(a, false)).toMatchObject({ success: false });
});
test("custom values", () => {
const b = z.stringbool({
truthy: ["y"],
falsy: ["N"],
});
expect(z.parse(b, "y")).toEqual(true);
expect(z.parse(b, "Y")).toEqual(true);
expect(z.parse(b, "n")).toEqual(false);
expect(z.parse(b, "N")).toEqual(false);
expect(z.safeParse(b, "true")).toMatchObject({ success: false });
expect(z.safeParse(b, "false")).toMatchObject({ success: false });
});
test("custom values - case sensitive", () => {
const c = z.stringbool({
truthy: ["y"],
falsy: ["N"],
case: "sensitive",
});
expect(z.parse(c, "y")).toEqual(true);
expect(z.safeParse(c, "Y")).toMatchObject({ success: false });
expect(z.parse(c, "N")).toEqual(false);
expect(z.safeParse(c, "n")).toMatchObject({ success: false });
expect(z.safeParse(c, "TRUE")).toMatchObject({ success: false });
});
// test custom error messages
test("z.stringbool with custom error messages", () => {
const a = z.stringbool("wrong!");
expect(() => a.parse("")).toThrowError("wrong!");
});
test("z.stringbool codec encoding", () => {
const schema = z.stringbool();
// Test encoding with default values
expect(z.encode(schema, true)).toEqual("true");
expect(z.encode(schema, false)).toEqual("false");
});
test("z.stringbool codec encoding with custom values", () => {
const schema = z.stringbool({
truthy: ["yes", "on", "1"],
falsy: ["no", "off", "0"],
});
// Should return first element of custom arrays
expect(z.encode(schema, true)).toEqual("yes");
expect(z.encode(schema, false)).toEqual("no");
});
test("z.stringbool codec round trip", () => {
const schema = z.stringbool({
truthy: ["enabled", "active"],
falsy: ["disabled", "inactive"],
});
// Test round trip: string -> boolean -> string
const decoded = z.decode(schema, "enabled");
expect(decoded).toEqual(true);
const encoded = z.encode(schema, decoded);
expect(encoded).toEqual("enabled"); // First element of truthy array
// Test with falsy value
const decodedFalse = z.decode(schema, "inactive");
expect(decodedFalse).toEqual(false);
const encodedFalse = z.encode(schema, decodedFalse);
expect(encodedFalse).toEqual("disabled"); // First element of falsy array
});

View File

@@ -0,0 +1,771 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
const empty = z.templateLiteral([]);
const hello = z.templateLiteral(["hello"]);
const world = z.templateLiteral(["", z.literal("world")]);
const one = z.templateLiteral([1]);
const two = z.templateLiteral(["", z.literal(2)]);
const onePointOne = z.templateLiteral([z.literal(1.1)]);
const truee = z.templateLiteral([true]);
const anotherTrue = z.templateLiteral(["", z.literal(true)]);
const falsee = z.templateLiteral([false]);
const anotherFalse = z.templateLiteral(["", z.literal(false)]);
const nulll = z.templateLiteral([null]);
const anotherNull = z.templateLiteral(["", z.null()]);
const undefinedd = z.templateLiteral([undefined]);
const anotherUndefined = z.templateLiteral(["", z.undefined()]);
const anyString = z.templateLiteral(["", z.string()]);
const lazyString = z.templateLiteral(["", z.lazy(() => z.string())]);
const anyNumber = z.templateLiteral(["", z.number()]);
const anyInt = z.templateLiteral(["", z.number().int()]);
// const anyFiniteNumber = z.templateLiteral(["", z.number().finite()]);
// const anyNegativeNumber = z.templateLiteral(["", z.number().negative()]);
// const anyPositiveNumber = z.templateLiteral(["", z.number().positive()]);
// const zeroButInADumbWay = z.templateLiteral(["", z.number().nonnegative().nonpositive()]);
// const finiteButInADumbWay = z.templateLiteral(["", z.number().min(5).max(10)]);
const bool = z.templateLiteral(["", z.boolean()]);
const bigone = z.templateLiteral(["", z.literal(BigInt(1))]);
const anyBigint = z.templateLiteral(["", z.bigint()]);
const nullableYo = z.templateLiteral(["", z.nullable(z.literal("yo"))]);
const nullableString = z.templateLiteral(["", z.nullable(z.string())]);
const optionalYeah = z.templateLiteral(["", z.literal("yeah").optional()]);
const optionalString = z.templateLiteral(["", z.string().optional()]);
const optionalNumber = z.templateLiteral(["", z.number().optional()]);
const nullishBruh = z.templateLiteral(["", z.literal("bruh").nullish()]);
const nullishString = z.templateLiteral(["", z.string().nullish()]);
const cuid = z.templateLiteral(["", z.string().cuid()]);
const cuidZZZ = z.templateLiteral(["", z.string().cuid(), "ZZZ"]);
const cuid2 = z.templateLiteral(["", z.string().cuid2()]);
const datetime = z.templateLiteral(["", z.string().datetime()]);
const email = z.templateLiteral(["", z.string().email()]);
// const ip = z.templateLiteral(["", z.string().ip()]);
const ipv4 = z.templateLiteral(["", z.string().ipv4()]);
const ipv6 = z.templateLiteral(["", z.string().ipv6()]);
const mac = z.templateLiteral(["", z.mac()]);
const ulid = z.templateLiteral(["", z.string().ulid()]);
const uuid = z.templateLiteral(["", z.string().uuid()]);
const stringAToZ = z.templateLiteral(["", z.string().regex(/^[a-z]+$/)]);
const stringStartsWith = z.templateLiteral(["", z.string().startsWith("hello")]);
const stringEndsWith = z.templateLiteral(["", z.string().endsWith("world")]);
const stringMax5 = z.templateLiteral(["", z.string().max(5)]);
const stringMin5 = z.templateLiteral(["", z.string().min(5)]);
const stringLen5 = z.templateLiteral(["", z.string().length(5)]);
const stringMin5Max10 = z.templateLiteral(["", z.string().min(5).max(10)]);
const stringStartsWithMax5 = z.templateLiteral(["", z.string().startsWith("hello").max(5)]);
const brandedString = z.templateLiteral(["", z.string().min(1).brand("myBrand")]);
// const anything = z.templateLiteral(["", z.any()]);
const url = z.templateLiteral(["https://", z.string().regex(/\w+/), ".", z.enum(["com", "net"])]);
const measurement = z.templateLiteral([
"",
z.number().finite(),
z.enum(["px", "em", "rem", "vh", "vw", "vmin", "vmax"]).optional(),
]);
const connectionString = z.templateLiteral([
"mongodb://",
z
.templateLiteral([
"",
z.string().regex(/\w+/).describe("username"),
":",
z.string().regex(/\w+/).describe("password"),
"@",
])
.optional(),
z.string().regex(/\w+/).describe("host"),
":",
z.number().finite().int().positive().describe("port"),
z
.templateLiteral([
"/",
z.string().regex(/\w+/).optional().describe("defaultauthdb"),
z
.templateLiteral([
"?",
z
.string()
.regex(/^\w+=\w+(&\w+=\w+)*$/)
.optional()
.describe("options"),
])
.optional(),
])
.optional(),
]);
test("template literal type inference", () => {
expectTypeOf<z.infer<typeof empty>>().toEqualTypeOf<``>();
expectTypeOf<z.infer<typeof hello>>().toEqualTypeOf<`hello`>();
expectTypeOf<z.infer<typeof world>>().toEqualTypeOf<`world`>();
expectTypeOf<z.infer<typeof one>>().toEqualTypeOf<`1`>();
expectTypeOf<z.infer<typeof two>>().toEqualTypeOf<`2`>();
expectTypeOf<z.infer<typeof truee>>().toEqualTypeOf<`true`>();
expectTypeOf<z.infer<typeof anotherTrue>>().toEqualTypeOf<`true`>();
expectTypeOf<z.infer<typeof falsee>>().toEqualTypeOf<`false`>();
expectTypeOf<z.infer<typeof anotherFalse>>().toEqualTypeOf<`false`>();
expectTypeOf<z.infer<typeof nulll>>().toEqualTypeOf<`null`>();
expectTypeOf<z.infer<typeof anotherNull>>().toEqualTypeOf<`null`>();
expectTypeOf<z.infer<typeof undefinedd>>().toEqualTypeOf<``>();
expectTypeOf<z.infer<typeof anotherUndefined>>().toEqualTypeOf<``>();
expectTypeOf<z.infer<typeof anyString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof lazyString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof anyNumber>>().toEqualTypeOf<`${number}`>();
expectTypeOf<z.infer<typeof anyInt>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof anyFiniteNumber>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof anyNegativeNumber>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof anyPositiveNumber>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof zeroButInADumbWay>>().toEqualTypeOf<`${number}`>();
// expectTypeOf<z.infer<typeof finiteButInADumbWay>>().toEqualTypeOf<`${number}`>();
expectTypeOf<z.infer<typeof bool>>().toEqualTypeOf<`true` | `false`>();
expectTypeOf<z.infer<typeof bigone>>().toEqualTypeOf<`${bigint}`>();
expectTypeOf<z.infer<typeof anyBigint>>().toEqualTypeOf<`${bigint}`>();
expectTypeOf<z.infer<typeof nullableYo>>().toEqualTypeOf<`yo` | `null`>();
expectTypeOf<z.infer<typeof nullableString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof optionalYeah>>().toEqualTypeOf<`yeah` | ``>();
expectTypeOf<z.infer<typeof optionalString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof optionalNumber>>().toEqualTypeOf<`${number}` | ``>();
expectTypeOf<z.infer<typeof nullishBruh>>().toEqualTypeOf<`bruh` | `null` | ``>();
expectTypeOf<z.infer<typeof nullishString>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof cuid>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof cuidZZZ>>().toEqualTypeOf<`${string}ZZZ`>();
expectTypeOf<z.infer<typeof cuid2>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof datetime>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof email>>().toEqualTypeOf<string>();
// expectTypeOf<z.infer<typeof ip>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof ipv4>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof ipv6>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof mac>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof ulid>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof uuid>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringAToZ>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringStartsWith>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringEndsWith>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringMax5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringMin5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringLen5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringMin5Max10>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof stringStartsWithMax5>>().toEqualTypeOf<string>();
expectTypeOf<z.infer<typeof brandedString>>().toEqualTypeOf<`${string & z.core.$brand<"myBrand">}`>();
// expectTypeOf<z.infer<typeof anything>>().toEqualTypeOf<`${any}`>();
expectTypeOf<z.infer<typeof url>>().toEqualTypeOf<`https://${string}.com` | `https://${string}.net`>();
expectTypeOf<z.infer<typeof measurement>>().toEqualTypeOf<
| `${number}`
| `${number}px`
| `${number}em`
| `${number}rem`
| `${number}vh`
| `${number}vw`
| `${number}vmin`
| `${number}vmax`
>();
expectTypeOf<z.infer<typeof connectionString>>().toEqualTypeOf<
| `mongodb://${string}:${number}`
| `mongodb://${string}:${number}/${string}`
| `mongodb://${string}:${number}/${string}?${string}`
| `mongodb://${string}:${string}@${string}:${number}`
| `mongodb://${string}:${string}@${string}:${number}/${string}`
| `mongodb://${string}:${string}@${string}:${number}/${string}?${string}`
>();
});
test("template literal unsupported args", () => {
expect(() =>
// @ts-expect-error
z.templateLiteral([z.object({})])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.array(z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.union([z.object({}), z.string()])])
).toThrow();
// @ts-expect-error
expect(() => z.templateLiteral([z.date()])).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.custom<object>((_) => true)])
).toThrow();
expect(() =>
z.templateLiteral([
// @ts-expect-error
z.discriminatedUnion("discriminator", [z.object({}), z.object({})]),
])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.function()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.instanceof(class MyClass {})])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.intersection(z.object({}), z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.map(z.string(), z.string())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.nullable(z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.optional(z.object({}))])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.promise()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.record(z.unknown())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.set(z.string())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.symbol()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.tuple([z.string()])])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.unknown()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.void()])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.never()])
).toThrow();
// @ts-expect-error
expect(() => z.templateLiteral([z.nan()])).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.pipe(z.string(), z.string())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.preprocess(() => true, z.boolean())])
).toThrow();
expect(() =>
// @ts-expect-error
z.templateLiteral([z.object({}).brand("brand")])
).toThrow();
// these constraints aren't enforced but they shouldn't throw
z.templateLiteral([z.number().multipleOf(2)]);
z.templateLiteral([z.string().emoji()]);
z.templateLiteral([z.string().url()]);
z.templateLiteral([z.string().url()]);
z.templateLiteral([z.string().trim()]);
z.templateLiteral([z.string().includes("train")]);
z.templateLiteral([z.string().toLowerCase()]);
z.templateLiteral([z.string().toUpperCase()]);
});
test("template literal parsing - success - basic cases", () => {
expect(() => z.templateLiteral([]).parse(7)).toThrow();
empty.parse("");
hello.parse("hello");
world.parse("world");
one.parse("1");
two.parse("2");
onePointOne.parse("1.1");
truee.parse("true");
anotherTrue.parse("true");
falsee.parse("false");
anotherFalse.parse("false");
nulll.parse("null");
anotherNull.parse("null");
undefinedd.parse("undefined");
anotherUndefined.parse("undefined");
anyString.parse("blahblahblah");
anyString.parse("");
lazyString.parse("blahblahblah");
lazyString.parse("");
anyNumber.parse("123");
anyNumber.parse("1.23");
anyNumber.parse("0");
anyNumber.parse("-1.23");
anyNumber.parse("-123");
// anyNumber.parse("Infinity");
// anyNumber.parse("-Infinity");
anyInt.parse("123");
// anyInt.parse("-123");
// anyFiniteNumber.parse("123");
// anyFiniteNumber.parse("1.23");
// anyFiniteNumber.parse("0");
// anyFiniteNumber.parse("-1.23");
// anyFiniteNumber.parse("-123");
// anyNegativeNumber.parse("-123");
// anyNegativeNumber.parse("-1.23");
// anyNegativeNumber.parse("-Infinity");
// anyPositiveNumber.parse("123");
// anyPositiveNumber.parse("1.23");
// anyPositiveNumber.parse("Infinity");
// zeroButInADumbWay.parse("0");
// zeroButInADumbWay.parse("00000");
// finiteButInADumbWay.parse("5");
// finiteButInADumbWay.parse("10");
// finiteButInADumbWay.parse("6.66");
bool.parse("true");
bool.parse("false");
bigone.parse("1");
anyBigint.parse("123456");
anyBigint.parse("0");
// anyBigint.parse("-123456");
nullableYo.parse("yo");
nullableYo.parse("null");
nullableString.parse("abc");
nullableString.parse("null");
optionalYeah.parse("yeah");
optionalYeah.parse("");
optionalString.parse("abc");
optionalString.parse("");
optionalNumber.parse("123");
optionalNumber.parse("1.23");
optionalNumber.parse("0");
optionalNumber.parse("-1.23");
optionalNumber.parse("-123");
// optionalNumber.parse("Infinity");
// optionalNumber.parse("-Infinity");
nullishBruh.parse("bruh");
nullishBruh.parse("null");
nullishBruh.parse("");
cuid.parse("cjld2cyuq0000t3rmniod1foy");
cuidZZZ.parse("cjld2cyuq0000t3rmniod1foyZZZ");
cuid2.parse("tz4a98xxat96iws9zmbrgj3a");
datetime.parse(new Date().toISOString());
email.parse("info@example.com");
// ip.parse("213.174.246.205");
// ip.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
ipv4.parse("213.174.246.205");
ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452");
mac.parse("00:1A:2B:3C:4D:5E");
ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW");
uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6");
stringAToZ.parse("asudgaskhdgashd");
stringStartsWith.parse("hello world");
stringEndsWith.parse("hello world");
stringMax5.parse("hello");
stringMin5.parse("hello");
stringLen5.parse("hello");
stringMin5Max10.parse("hello worl");
stringStartsWithMax5.parse("hello");
brandedString.parse("branded string");
});
test("template literal parsing - failure - basic cases", () => {
expect(() => empty.parse("a")).toThrow();
expect(() => hello.parse("hello!")).toThrow();
expect(() => hello.parse("!hello")).toThrow();
expect(() => world.parse("world!")).toThrow();
expect(() => world.parse("!world")).toThrow();
expect(() => one.parse("2")).toThrow();
expect(() => one.parse("12")).toThrow();
expect(() => one.parse("21")).toThrow();
expect(() => onePointOne.parse("1s1")).toThrow();
expect(() => two.parse("1")).toThrow();
expect(() => two.parse("21")).toThrow();
expect(() => two.parse("12")).toThrow();
expect(() => truee.parse("false")).toThrow();
expect(() => truee.parse("1true")).toThrow();
expect(() => truee.parse("true1")).toThrow();
expect(() => anotherTrue.parse("false")).toThrow();
expect(() => anotherTrue.parse("1true")).toThrow();
expect(() => anotherTrue.parse("true1")).toThrow();
expect(() => falsee.parse("true")).toThrow();
expect(() => falsee.parse("1false")).toThrow();
expect(() => falsee.parse("false1")).toThrow();
expect(() => anotherFalse.parse("true")).toThrow();
expect(() => anotherFalse.parse("1false")).toThrow();
expect(() => anotherFalse.parse("false1")).toThrow();
expect(() => nulll.parse("123")).toThrow();
expect(() => nulll.parse("null1")).toThrow();
expect(() => nulll.parse("1null")).toThrow();
expect(() => anotherNull.parse("123")).toThrow();
expect(() => anotherNull.parse("null1")).toThrow();
expect(() => anotherNull.parse("1null")).toThrow();
expect(() => undefinedd.parse("123")).toThrow();
expect(() => undefinedd.parse("undefined1")).toThrow();
expect(() => undefinedd.parse("1undefined")).toThrow();
expect(() => anotherUndefined.parse("123")).toThrow();
expect(() => anotherUndefined.parse("undefined1")).toThrow();
expect(() => anotherUndefined.parse("1undefined")).toThrow();
expect(() => anyNumber.parse("2a")).toThrow();
expect(() => anyNumber.parse("a2")).toThrow();
expect(() => anyNumber.parse("-2a")).toThrow();
expect(() => anyNumber.parse("a-2")).toThrow();
expect(() => anyNumber.parse("2.5a")).toThrow();
expect(() => anyNumber.parse("a2.5")).toThrow();
expect(() => anyNumber.parse("Infinitya")).toThrow();
expect(() => anyNumber.parse("aInfinity")).toThrow();
expect(() => anyNumber.parse("-Infinitya")).toThrow();
expect(() => anyNumber.parse("a-Infinity")).toThrow();
expect(() => anyNumber.parse("2e5")).toThrow();
expect(() => anyNumber.parse("2e-5")).toThrow();
expect(() => anyNumber.parse("2e+5")).toThrow();
expect(() => anyNumber.parse("-2e5")).toThrow();
expect(() => anyNumber.parse("-2e-5")).toThrow();
expect(() => anyNumber.parse("-2e+5")).toThrow();
expect(() => anyNumber.parse("2.1e5")).toThrow();
expect(() => anyNumber.parse("2.1e-5")).toThrow();
expect(() => anyNumber.parse("2.1e+5")).toThrow();
expect(() => anyNumber.parse("-2.1e5")).toThrow();
expect(() => anyNumber.parse("-2.1e-5")).toThrow();
expect(() => anyNumber.parse("-2.1e+5")).toThrow();
expect(() => anyNumber.parse("-Infinity")).toThrow();
expect(() => anyNumber.parse("Infinity")).toThrow();
expect(() => anyInt.parse("1.23")).toThrow();
expect(() => anyInt.parse("-1.23")).toThrow();
expect(() => anyInt.parse("d1")).toThrow();
expect(() => anyInt.parse("1d")).toThrow();
// expect(() => anyFiniteNumber.parse("Infinity")).toThrow();
// expect(() => anyFiniteNumber.parse("-Infinity")).toThrow();
// expect(() => anyFiniteNumber.parse("123a")).toThrow();
// expect(() => anyFiniteNumber.parse("a123")).toThrow();
// expect(() => anyNegativeNumber.parse("0")).toThrow();
// expect(() => anyNegativeNumber.parse("1")).toThrow();
// expect(() => anyNegativeNumber.parse("Infinity")).toThrow();
// expect(() => anyPositiveNumber.parse("0")).toThrow();
// expect(() => anyPositiveNumber.parse("-1")).toThrow();
// expect(() => anyPositiveNumber.parse("-Infinity")).toThrow();
// expect(() => zeroButInADumbWay.parse("1")).toThrow();
// expect(() => zeroButInADumbWay.parse("-1")).toThrow();
// expect(() => finiteButInADumbWay.parse("Infinity")).toThrow();
// expect(() => finiteButInADumbWay.parse("-Infinity")).toThrow();
// expect(() => finiteButInADumbWay.parse("-5")).toThrow();
// expect(() => finiteButInADumbWay.parse("10a")).toThrow();
// expect(() => finiteButInADumbWay.parse("a10")).toThrow();
expect(() => bool.parse("123")).toThrow();
expect(() => bigone.parse("2")).toThrow();
expect(() => bigone.parse("c1")).toThrow();
expect(() => anyBigint.parse("1.23")).toThrow();
expect(() => anyBigint.parse("-1.23")).toThrow();
expect(() => anyBigint.parse("c123")).toThrow();
expect(() => nullableYo.parse("yo1")).toThrow();
expect(() => nullableYo.parse("1yo")).toThrow();
expect(() => nullableYo.parse("null1")).toThrow();
expect(() => nullableYo.parse("1null")).toThrow();
expect(() => optionalYeah.parse("yeah1")).toThrow();
expect(() => optionalYeah.parse("1yeah")).toThrow();
expect(() => optionalYeah.parse("undefined")).toThrow();
expect(() => optionalNumber.parse("123a")).toThrow();
expect(() => optionalNumber.parse("a123")).toThrow();
// expect(() => optionalNumber.parse("Infinitya")).toThrow();
// expect(() => optionalNumber.parse("aInfinity")).toThrow();
expect(() => nullishBruh.parse("bruh1")).toThrow();
expect(() => nullishBruh.parse("1bruh")).toThrow();
expect(() => nullishBruh.parse("null1")).toThrow();
expect(() => nullishBruh.parse("1null")).toThrow();
expect(() => nullishBruh.parse("undefined")).toThrow();
expect(() => cuid.parse("bjld2cyuq0000t3rmniod1foy")).toThrow();
expect(() => cuid.parse("cjld2cyu")).toThrow();
expect(() => cuid.parse("cjld2 cyu")).toThrow();
expect(() => cuid.parse("cjld2cyuq0000t3rmniod1foy ")).toThrow();
expect(() => cuid.parse("1cjld2cyuq0000t3rmniod1foy")).toThrow();
expect(() => cuidZZZ.parse("cjld2cyuq0000t3rmniod1foy")).toThrow();
expect(() => cuidZZZ.parse("cjld2cyuq0000t3rmniod1foyZZY")).toThrow();
expect(() => cuidZZZ.parse("cjld2cyuq0000t3rmniod1foyZZZ1")).toThrow();
expect(() => cuidZZZ.parse("1cjld2cyuq0000t3rmniod1foyZZZ")).toThrow();
expect(() => cuid2.parse("A9z4a98xxat96iws9zmbrgj3a")).toThrow();
expect(() => cuid2.parse("tz4a98xxat96iws9zmbrgj3!")).toThrow();
expect(() => datetime.parse("2022-01-01 00:00:00")).toThrow();
expect(() => email.parse("info@example.com@")).toThrow();
// expect(() => ip.parse("213.174.246:205")).toThrow();
// expect(() => ip.parse("c359.f57c:21e5:39eb:1187:e501:f936:b452")).toThrow();
expect(() => ipv4.parse("1213.174.246.205")).toThrow();
expect(() => ipv4.parse("c359:f57c:21e5:39eb:1187:e501:f936:b452")).toThrow();
expect(() => ipv6.parse("c359:f57c:21e5:39eb:1187:e501:f936:b4521")).toThrow();
expect(() => ipv6.parse("213.174.246.205")).toThrow();
expect(() => mac.parse("00:1A:2B:3C:4D:5E:6A:7B")).toThrow();
expect(() => mac.parse("00:1A:2B:3C")).toThrow();
expect(() => ulid.parse("01GW3D2QZJBYB6P1Z1AE997VPW!")).toThrow();
expect(() => uuid.parse("808989fd-3a6e-4af2-b607-737323a176f6Z")).toThrow();
expect(() => uuid.parse("Z808989fd-3a6e-4af2-b607-737323a176f6")).toThrow();
expect(() => stringAToZ.parse("asdasdasd1")).toThrow();
expect(() => stringAToZ.parse("1asdasdasd")).toThrow();
expect(() => stringStartsWith.parse("ahello")).toThrow();
expect(() => stringEndsWith.parse("worlda")).toThrow();
expect(() => stringMax5.parse("123456")).toThrow();
expect(() => stringMin5.parse("1234")).toThrow();
expect(() => stringLen5.parse("123456")).toThrow();
expect(() => stringLen5.parse("1234")).toThrow();
expect(() => stringMin5Max10.parse("1234")).toThrow();
expect(() => stringMin5Max10.parse("12345678901")).toThrow();
// the "startswith" overrides the max length
// expect(() => stringStartsWithMax5.parse("hello1")).toThrow();
expect(() => stringStartsWithMax5.parse("1hell")).toThrow();
expect(() => brandedString.parse("")).toThrow();
});
test("regexes", () => {
expect(empty._zod.pattern.source).toMatchInlineSnapshot(`"^$"`);
expect(hello._zod.pattern.source).toMatchInlineSnapshot(`"^hello$"`);
expect(world._zod.pattern.source).toMatchInlineSnapshot(`"^(world)$"`);
expect(one._zod.pattern.source).toMatchInlineSnapshot(`"^1$"`);
expect(two._zod.pattern.source).toMatchInlineSnapshot(`"^(2)$"`);
expect(truee._zod.pattern.source).toMatchInlineSnapshot(`"^true$"`);
expect(anotherTrue._zod.pattern.source).toMatchInlineSnapshot(`"^(true)$"`);
expect(falsee._zod.pattern.source).toMatchInlineSnapshot(`"^false$"`);
expect(anotherFalse._zod.pattern.source).toMatchInlineSnapshot(`"^(false)$"`);
expect(nulll._zod.pattern.source).toMatchInlineSnapshot(`"^null$"`);
expect(anotherNull._zod.pattern.source).toMatchInlineSnapshot(`"^null$"`);
expect(undefinedd._zod.pattern.source).toMatchInlineSnapshot(`"^undefined$"`);
expect(anotherUndefined._zod.pattern.source).toMatchInlineSnapshot(`"^undefined$"`);
expect(anyString._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{0,}$"`);
expect(lazyString._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{0,}$"`);
expect(anyNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
expect(anyInt._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+$"`);
// expect(anyFiniteNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(anyNegativeNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(anyPositiveNumber._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(zeroButInADumbWay._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
// expect(finiteButInADumbWay._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?$"`);
expect(bool._zod.pattern.source).toMatchInlineSnapshot(`"^(?:true|false)$"`);
expect(bigone._zod.pattern.source).toMatchInlineSnapshot(`"^(1)$"`);
expect(anyBigint._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+n?$"`);
expect(nullableYo._zod.pattern.source).toMatchInlineSnapshot(`"^((yo)|null)$"`);
expect(nullableString._zod.pattern.source).toMatchInlineSnapshot(`"^([\\s\\S]{0,}|null)$"`);
expect(optionalYeah._zod.pattern.source).toMatchInlineSnapshot(`"^((yeah))?$"`);
expect(optionalString._zod.pattern.source).toMatchInlineSnapshot(`"^([\\s\\S]{0,})?$"`);
expect(optionalNumber._zod.pattern.source).toMatchInlineSnapshot(`"^(-?\\d+(?:\\.\\d+)?)?$"`);
expect(nullishBruh._zod.pattern.source).toMatchInlineSnapshot(`"^(((bruh)|null))?$"`);
expect(nullishString._zod.pattern.source).toMatchInlineSnapshot(`"^(([\\s\\S]{0,}|null))?$"`);
expect(cuid._zod.pattern.source).toMatchInlineSnapshot(`"^[cC][^\\s-]{8,}$"`);
expect(cuidZZZ._zod.pattern.source).toMatchInlineSnapshot(`"^[cC][^\\s-]{8,}ZZZ$"`);
expect(cuid2._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9a-z]+$"`);
expect(datetime._zod.pattern.source).toMatchInlineSnapshot(
`"^(?:(?:\\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])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"`
);
expect(email._zod.pattern.source).toMatchInlineSnapshot(
`"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"`
);
// expect(ip._zod.pattern.source).toMatchInlineSnapshot(
// `"^(^(?:(?: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])$)|(^(([a-fA-F0-9]{1,4}:){7}|::([a-fA-F0-9]{1,4}:){0,6}|([a-fA-F0-9]{1,4}:){1}:([a-fA-F0-9]{1,4}:){0,5}|([a-fA-F0-9]{1,4}:){2}:([a-fA-F0-9]{1,4}:){0,4}|([a-fA-F0-9]{1,4}:){3}:([a-fA-F0-9]{1,4}:){0,3}|([a-fA-F0-9]{1,4}:){4}:([a-fA-F0-9]{1,4}:){0,2}|([a-fA-F0-9]{1,4}:){5}:([a-fA-F0-9]{1,4}:){0,1})([a-fA-F0-9]{1,4}|(((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})))$)$"`
// );
expect(ipv4._zod.pattern.source).toMatchInlineSnapshot(
`"^(?:(?: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])$"`
);
expect(ipv6._zod.pattern.source).toMatchInlineSnapshot(
`"^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$"`
);
expect(mac._zod.pattern.source).toMatchInlineSnapshot(
`"^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$"`
);
expect(ulid._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"`);
expect(uuid._zod.pattern.source).toMatchInlineSnapshot(
`"^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"`
);
expect(stringAToZ._zod.pattern.source).toMatchInlineSnapshot(`"^[a-z]+$"`);
expect(stringStartsWith._zod.pattern.source).toMatchInlineSnapshot(`"^hello.*$"`);
expect(stringEndsWith._zod.pattern.source).toMatchInlineSnapshot(`"^.*world$"`);
expect(stringMax5._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{0,5}$"`);
expect(stringMin5._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{5,}$"`);
expect(stringLen5._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{5,5}$"`);
expect(stringMin5Max10._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{5,10}$"`);
expect(brandedString._zod.pattern.source).toMatchInlineSnapshot(`"^[\\s\\S]{1,}$"`);
expect(url._zod.pattern.source).toMatchInlineSnapshot(`"^https:\\/\\/\\w+\\.(com|net)$"`);
expect(measurement._zod.pattern.source).toMatchInlineSnapshot(`"^-?\\d+(?:\\.\\d+)?((px|em|rem|vh|vw|vmin|vmax))?$"`);
expect(connectionString._zod.pattern.source).toMatchInlineSnapshot(
`"^mongodb:\\/\\/(\\w+:\\w+@)?\\w+:-?\\d+(\\/(\\w+)?(\\?(\\w+=\\w+(&\\w+=\\w+)*)?)?)?$"`
);
});
test("template literal parsing - success - complex cases", () => {
url.parse("https://example.com");
url.parse("https://speedtest.net");
// measurement.parse(1);
// measurement.parse(1.1);
// measurement.parse(0);
// measurement.parse(-1.1);
// measurement.parse(-1);
measurement.parse("1");
measurement.parse("1.1");
measurement.parse("0");
measurement.parse("-1");
measurement.parse("-1.1");
measurement.parse("1px");
measurement.parse("1.1px");
measurement.parse("0px");
measurement.parse("-1px");
measurement.parse("-1.1px");
measurement.parse("1em");
measurement.parse("1.1em");
measurement.parse("0em");
measurement.parse("-1em");
measurement.parse("-1.1em");
measurement.parse("1rem");
measurement.parse("1.1rem");
measurement.parse("0rem");
measurement.parse("-1rem");
measurement.parse("-1.1rem");
measurement.parse("1vh");
measurement.parse("1.1vh");
measurement.parse("0vh");
measurement.parse("-1vh");
measurement.parse("-1.1vh");
measurement.parse("1vw");
measurement.parse("1.1vw");
measurement.parse("0vw");
measurement.parse("-1vw");
measurement.parse("-1.1vw");
measurement.parse("1vmin");
measurement.parse("1.1vmin");
measurement.parse("0vmin");
measurement.parse("-1vmin");
measurement.parse("-1.1vmin");
measurement.parse("1vmax");
measurement.parse("1.1vmax");
measurement.parse("0vmax");
measurement.parse("-1vmax");
measurement.parse("-1.1vmax");
connectionString.parse("mongodb://host:1234");
connectionString.parse("mongodb://host:1234/");
connectionString.parse("mongodb://host:1234/defaultauthdb");
connectionString.parse("mongodb://host:1234/defaultauthdb?authSource=admin");
connectionString.parse("mongodb://host:1234/defaultauthdb?authSource=admin&connectTimeoutMS=300000");
connectionString.parse("mongodb://host:1234/?authSource=admin");
connectionString.parse("mongodb://host:1234/?authSource=admin&connectTimeoutMS=300000");
connectionString.parse("mongodb://username:password@host:1234");
connectionString.parse("mongodb://username:password@host:1234/");
connectionString.parse("mongodb://username:password@host:1234/defaultauthdb");
connectionString.parse("mongodb://username:password@host:1234/defaultauthdb?authSource=admin");
connectionString.parse(
"mongodb://username:password@host:1234/defaultauthdb?authSource=admin&connectTimeoutMS=300000"
);
connectionString.parse("mongodb://username:password@host:1234/?authSource=admin");
connectionString.parse("mongodb://username:password@host:1234/?authSource=admin&connectTimeoutMS=300000");
});
test("template literal parsing - failure - complex cases", () => {
expect(() => url.parse("http://example.com")).toThrow();
expect(() => url.parse("https://.com")).toThrow();
expect(() => url.parse("https://examplecom")).toThrow();
expect(() => url.parse("https://example.org")).toThrow();
expect(() => url.parse("https://example.net.il")).toThrow();
expect(() => measurement.parse("1.1.1")).toThrow();
expect(() => measurement.parse("Infinity")).toThrow();
expect(() => measurement.parse("-Infinity")).toThrow();
expect(() => measurement.parse("NaN")).toThrow();
expect(() => measurement.parse("1%")).toThrow();
expect(() => connectionString.parse("mongod://host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://:1234")).toThrow();
expect(() => connectionString.parse("mongodb://host1234")).toThrow();
expect(() => connectionString.parse("mongodb://host:d234")).toThrow();
expect(() => connectionString.parse("mongodb://host:12.34")).toThrow();
// Note: template literal regex currently allows negative numbers despite .positive() constraint
// This is a known limitation where template literals use regex patterns directly
// expect(() => connectionString.parse("mongodb://host:-1234")).toThrow();
// expect(() => connectionString.parse("mongodb://host:-12.34")).toThrow();
expect(() => connectionString.parse("mongodb://host:")).toThrow();
expect(() => connectionString.parse("mongodb://:password@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://usernamepassword@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://username:@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://@host:1234")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/defaultauthdb?authSourceadmin")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/?authSourceadmin")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/defaultauthdb?&authSource=admin")).toThrow();
expect(() => connectionString.parse("mongodb://host:1234/?&authSource=admin")).toThrow();
});
test("template literal parsing - failure - issue format", () => {
expect(anotherNull.safeParse("1null")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^null$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(cuidZZZ.safeParse("1cjld2cyuq0000t3rmniod1foyZZZ")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^[cC][^\\\\s-]{8,}ZZZ$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(stringMin5Max10.safeParse("1234")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^[\\\\s\\\\S]{5,10}$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(connectionString.safeParse("mongodb://host:1234/defaultauthdb?authSourceadmin")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^mongodb:\\\\/\\\\/(\\\\w+:\\\\w+@)?\\\\w+:-?\\\\d+(\\\\/(\\\\w+)?(\\\\?(\\\\w+=\\\\w+(&\\\\w+=\\\\w+)*)?)?)?$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
expect(stringStartsWithMax5.safeParse("1hell")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_format",
"format": "template_literal",
"pattern": "^hello.*$",
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});

View File

@@ -0,0 +1,438 @@
import { describe, expect, test } from "vitest";
import * as z from "zod/v4";
// Utility functions
function expectMethodMatch(schema: z.ZodType, params?: z.core.ToJSONSchemaParams): void {
const staticResult = z.toJSONSchema(schema, params);
const methodResult = schema.toJSONSchema(params);
expect(methodResult).toEqual(staticResult);
}
describe("toJSONSchema method", () => {
describe("primitive types", () => {
test("string", () => {
expectMethodMatch(z.string());
});
test("number", () => {
expectMethodMatch(z.number());
});
test("boolean", () => {
expectMethodMatch(z.boolean());
});
test("bigint", () => {
expectMethodMatch(z.bigint(), { unrepresentable: "any" });
});
test("symbol", () => {
expectMethodMatch(z.symbol(), { unrepresentable: "any" });
});
test("null", () => {
expectMethodMatch(z.null());
});
test("undefined", () => {
expectMethodMatch(z.undefined(), { unrepresentable: "any" });
});
test("void", () => {
expectMethodMatch(z.void(), { unrepresentable: "any" });
});
test("never", () => {
expectMethodMatch(z.never());
});
test("any", () => {
expectMethodMatch(z.any());
});
test("unknown", () => {
expectMethodMatch(z.unknown());
});
test("date", () => {
expectMethodMatch(z.date(), { unrepresentable: "any" });
});
test("nan", () => {
expectMethodMatch(z.nan(), { unrepresentable: "any" });
});
});
describe("string formats", () => {
test("email", () => {
expectMethodMatch(z.email());
});
test("url", () => {
expectMethodMatch(z.url());
});
test("uuid", () => {
expectMethodMatch(z.uuid());
});
test("datetime", () => {
expectMethodMatch(z.iso.datetime());
});
test("date", () => {
expectMethodMatch(z.iso.date());
});
test("guid", () => {
expectMethodMatch(z.guid());
});
test("cuid", () => {
expectMethodMatch(z.cuid());
});
test("cuid2", () => {
expectMethodMatch(z.cuid2());
});
test("ulid", () => {
expectMethodMatch(z.ulid());
});
test("base64", () => {
expectMethodMatch(z.base64());
});
test("ipv4", () => {
expectMethodMatch(z.ipv4());
});
test("ipv6", () => {
expectMethodMatch(z.ipv6());
});
});
describe("string validations", () => {
test("min length", () => {
expectMethodMatch(z.string().min(5));
});
test("max length", () => {
expectMethodMatch(z.string().max(10));
});
test("length", () => {
expectMethodMatch(z.string().length(5));
});
test("regex", () => {
expectMethodMatch(z.string().regex(/^[A-Z]+$/));
});
test("multiple patterns", () => {
expectMethodMatch(
z
.string()
.regex(/^[A-Z]+$/)
.regex(/^[0-9]+$/)
);
});
test("startsWith", () => {
expectMethodMatch(z.string().startsWith("hello"));
});
test("endsWith", () => {
expectMethodMatch(z.string().endsWith("world"));
});
test("includes", () => {
expectMethodMatch(z.string().includes("test"));
});
});
describe("number validations", () => {
test("min", () => {
expectMethodMatch(z.number().min(5));
});
test("max", () => {
expectMethodMatch(z.number().max(10));
});
test("int", () => {
expectMethodMatch(z.int());
});
test("positive", () => {
expectMethodMatch(z.number().positive());
});
test("negative", () => {
expectMethodMatch(z.number().negative());
});
test("multipleOf", () => {
expectMethodMatch(z.number().multipleOf(2));
});
test("gte", () => {
expectMethodMatch(z.number().gte(5));
});
test("lte", () => {
expectMethodMatch(z.number().lte(10));
});
test("gt", () => {
expectMethodMatch(z.number().gt(5));
});
test("lt", () => {
expectMethodMatch(z.number().lt(10));
});
});
describe("literals and enums", () => {
test("literal string", () => {
expectMethodMatch(z.literal("hello"));
});
test("literal number", () => {
expectMethodMatch(z.literal(42));
});
test("literal boolean", () => {
expectMethodMatch(z.literal(true));
});
test("literal null", () => {
expectMethodMatch(z.literal(null));
});
test("multiple literals", () => {
expectMethodMatch(z.literal(["a", "b", "c"]));
});
test("enum", () => {
expectMethodMatch(z.enum(["red", "green", "blue"]));
});
test("nativeEnum", () => {
enum Colors {
Red = "red",
Green = "green",
Blue = "blue",
}
expectMethodMatch(z.nativeEnum(Colors));
});
});
describe("composite types", () => {
test("array", () => {
expectMethodMatch(z.array(z.string()));
});
test("array with min", () => {
expectMethodMatch(z.array(z.string()).min(2));
});
test("array with max", () => {
expectMethodMatch(z.array(z.string()).max(10));
});
test("object", () => {
expectMethodMatch(z.object({ name: z.string(), age: z.number() }));
});
test("object with optional", () => {
expectMethodMatch(z.object({ name: z.string(), age: z.number().optional() }));
});
test("strict object", () => {
expectMethodMatch(z.strictObject({ name: z.string() }));
});
test("loose object", () => {
expectMethodMatch(z.looseObject({ name: z.string() }));
});
test("object with catchall", () => {
expectMethodMatch(z.object({ name: z.string() }).catchall(z.string()));
});
test("tuple", () => {
expectMethodMatch(z.tuple([z.string(), z.number()]));
});
test("tuple with rest", () => {
expectMethodMatch(z.tuple([z.string()], z.number()));
});
test("record", () => {
expectMethodMatch(z.record(z.string(), z.number()));
});
test("union", () => {
expectMethodMatch(z.union([z.string(), z.number()]));
});
test("discriminated union", () => {
expectMethodMatch(
z.discriminatedUnion("type", [
z.object({ type: z.literal("a"), value: z.string() }),
z.object({ type: z.literal("b"), value: z.number() }),
])
);
});
test("intersection", () => {
expectMethodMatch(z.intersection(z.object({ a: z.string() }), z.object({ b: z.number() })));
});
});
describe("wrapper types", () => {
test("optional", () => {
expectMethodMatch(z.string().optional());
});
test("nullable", () => {
expectMethodMatch(z.string().nullable());
});
test("nullish", () => {
expectMethodMatch(z.string().nullish());
});
test("default", () => {
expectMethodMatch(z.string().default("hello"));
});
test("default function", () => {
expectMethodMatch(z.string().default(() => "hello"));
});
test("prefault", () => {
expectMethodMatch(z.string().prefault("hello"));
});
test("prefault function", () => {
expectMethodMatch(z.string().prefault(() => "hello"));
});
test("catch", () => {
expectMethodMatch(z.string().catch("hello"));
});
test("readonly", () => {
expectMethodMatch(z.string().readonly());
});
test("nonoptional", () => {
expectMethodMatch(z.string().optional().nonoptional());
});
});
describe("special types", () => {
test("lazy", () => {
type Node = {
value: string;
children?: Node[] | undefined;
};
const Node: z.ZodType<Node> = z.lazy(() =>
z.object({
value: z.string(),
children: z.array(Node).optional(),
})
) as z.ZodType<Node>;
expectMethodMatch(Node);
});
test("promise", () => {
expectMethodMatch(z.promise(z.string()));
});
test("pipe", () => {
expectMethodMatch(
z
.string()
.transform((val) => val.length)
.pipe(z.number())
);
});
test("transform", () => {
expectMethodMatch(
z.string().transform((val) => val.length),
{ unrepresentable: "any" }
);
});
test("file", () => {
expectMethodMatch(z.file());
});
test("file with mime", () => {
expectMethodMatch(z.file().mime("image/png"));
});
});
describe("parameters", () => {
test("target draft-7", () => {
expectMethodMatch(z.string(), { target: "draft-7" });
});
test("target draft-4", () => {
expectMethodMatch(z.string(), { target: "draft-4" });
});
test("target openapi-3.0", () => {
expectMethodMatch(z.string(), { target: "openapi-3.0" });
});
test("io input", () => {
expectMethodMatch(z.string().default("hello"), { io: "input" });
});
test("cycles throw", () => {
const schema = z.object({
name: z.string(),
get subcategories() {
return z.array(schema);
},
});
// Both should throw the same error
expect(() => z.toJSONSchema(schema, { cycles: "throw" })).toThrow();
expect(() => schema.toJSONSchema({ cycles: "throw" })).toThrow();
});
test("reused ref", () => {
const shared = z.string();
const schema = z.object({
a: shared,
b: shared,
});
expectMethodMatch(schema, { reused: "ref" });
});
});
describe("edge cases with metadata", () => {
test("schema with id metadata", () => {
const a = z.string().meta({ id: "hi" });
expectMethodMatch(a);
});
test("schema with id then additional metadata", () => {
const a = z.string().meta({ id: "hi2" });
const b = a.meta({ name: "asdf" });
expectMethodMatch(b);
});
test("nested schema with id", () => {
const inner = z.string().meta({ id: "inner" });
const outer = z.object({ value: inner });
expectMethodMatch(outer);
});
});
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,361 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("transform ctx.addIssue with parse", () => {
const strs = ["foo", "bar"];
const schema = z.string().transform((data, ctx) => {
const i = strs.indexOf(data);
if (i === -1) {
ctx.addIssue({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
}
return data.length;
});
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
expect(result.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]]
`);
});
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({
input: data,
code: "custom",
message: `${data} is not one of our allowed strings`,
});
}
return data.length;
})
.safeParseAsync("asdf");
expect(result).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "asdf is not one of our allowed strings",
"path": []
}
]],
"success": false,
}
`);
});
test("z.NEVER in transform", () => {
const foo = z
.number()
.optional()
.transform((val, ctx) => {
if (!val) {
ctx.addIssue({
input: val,
code: z.ZodIssueCode.custom,
message: "bad",
});
return z.NEVER;
}
return val;
});
type foo = z.infer<typeof foo>;
expectTypeOf<foo>().toEqualTypeOf<number>();
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 () => {
const asyncNumberToString = z.number().transform(async (n) => String(n));
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 stringToNumber = z.string().transform((arg) => Number.parseFloat(arg));
const t1 = z.object({
stringToNumber,
});
type t1 = z.input<typeof t1>;
type t2 = z.output<typeof t1>;
expectTypeOf<t1>().toEqualTypeOf<{ stringToNumber: string }>();
expectTypeOf<t2>().toEqualTypeOf<{ stringToNumber: number }>();
});
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 stringToNumber = z.string().transform((arg) => Number.parseFloat(arg));
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);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
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);
expect(result.error).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "custom",
"path": [],
"message": "Invalid input"
}
]]
`);
const result2 = await schema.spa(1234);
expect(result2.success).toEqual(false);
expect(result2.error).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "string",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected string, received number"
}
]]
`);
});
test("do not continue by default", () => {
const A = z
.string()
.transform((val, ctx) => {
ctx.addIssue({
code: "custom",
message: `custom error`,
});
ctx.addIssue({
code: "custom",
message: `custom error`,
});
return val;
})
.pipe(z.number() as any);
expect(A.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "custom error",
"path": []
},
{
"code": "custom",
"message": "custom error",
"path": []
}
]],
"success": false,
}
`);
const B = z
.string()
.transform((val, ctx) => {
ctx.issues.push({
code: "custom",
message: `custom error`,
input: val,
});
ctx.issues.push({
code: "custom",
message: `custom error`,
input: val,
});
return val;
})
.pipe(z.number() as any);
expect(B.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "custom error",
"path": []
},
{
"code": "custom",
"message": "custom error",
"path": []
}
]],
"success": false,
}
`);
const C = z
.string()
.transform((val, ctx) => {
ctx.issues.push({
code: "custom",
message: `custom error`,
input: val,
continue: true,
});
ctx.issues.push({
code: "custom",
message: `custom error`,
input: val,
continue: true,
});
return val;
})
.pipe(z.number() as any);
expect(C.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "custom error",
"path": []
},
{
"code": "custom",
"message": "custom error",
"path": []
}
]],
"success": false,
}
`);
});
test("encode error", () => {
const schema = z.string().transform((val) => val.length);
expect(() => z.encode(schema, 1234)).toThrowErrorMatchingInlineSnapshot(
`[ZodEncodeError: Encountered unidirectional transform during encode: ZodTransform]`
);
});

View File

@@ -0,0 +1,183 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("successful validation", () => {
const testTuple = z.tuple([z.string(), z.number()]);
expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();
const val = testTuple.parse(["asdf", 1234]);
expect(val).toEqual(val);
const r1 = testTuple.safeParse(["asdf", "asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
1
],
"message": "Invalid input: expected number, received string"
}
]]
`);
const r2 = testTuple.safeParse(["asdf", 1234, true]);
expect(r2.success).toEqual(false);
expect(r2.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "too_big",
"maximum": 2,
"inclusive": true,
"origin": "array",
"path": [],
"message": "Too big: expected array to have <=2 items"
}
]]
`);
const r3 = testTuple.safeParse({});
expect(r3.success).toEqual(false);
expect(r3.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "tuple",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected tuple, received object"
}
]]
`);
});
test("async validation", async () => {
const testTuple = z
.tuple([z.string().refine(async () => true), z.number().refine(async () => true)])
.refine(async () => true);
expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();
const val = await testTuple.parseAsync(["asdf", 1234]);
expect(val).toEqual(val);
const r1 = await testTuple.safeParseAsync(["asdf", "asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "number",
"code": "invalid_type",
"path": [
1
],
"message": "Invalid input: expected number, received string"
}
]]
`);
const r2 = await testTuple.safeParseAsync(["asdf", 1234, true]);
expect(r2.success).toEqual(false);
expect(r2.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "too_big",
"maximum": 2,
"inclusive": true,
"origin": "array",
"path": [],
"message": "Too big: expected array to have <=2 items"
}
]]
`);
const r3 = await testTuple.safeParseAsync({});
expect(r3.success).toEqual(false);
expect(r3.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"expected": "tuple",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected tuple, received object"
}
]]
`);
});
test("tuple with optional elements", () => {
const myTuple = z.tuple([z.string(), z.number().optional(), z.string().optional()]).rest(z.boolean());
expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number?, string?, ...boolean[]]>();
const goodData = [["asdf"], ["asdf", 1234], ["asdf", 1234, "asdf"], ["asdf", 1234, "asdf", true, false, true]];
for (const data of goodData) {
expect(myTuple.parse(data)).toEqual(data);
}
const badData = [
["asdf", "asdf"],
["asdf", 1234, "asdf", "asdf"],
["asdf", 1234, "asdf", true, false, "asdf"],
];
for (const data of badData) {
expect(() => myTuple.parse(data)).toThrow();
}
});
test("tuple with optional elements followed by required", () => {
const myTuple = z.tuple([z.string(), z.number().optional(), z.string()]).rest(z.boolean());
expectTypeOf<typeof myTuple._output>().toEqualTypeOf<[string, number | undefined, string, ...boolean[]]>();
const goodData = [
["asdf", 1234, "asdf"],
["asdf", 1234, "asdf", true, false, true],
];
for (const data of goodData) {
expect(myTuple.parse(data)).toEqual(data);
}
const badData = [
["asdf"],
["asdf", 1234],
["asdf", 1234, "asdf", "asdf"],
["asdf", 1234, "asdf", true, false, "asdf"],
];
for (const data of badData) {
expect(() => myTuple.parse(data)).toThrow();
}
});
test("tuple with all optional elements", () => {
const allOptionalTuple = z.tuple([z.string().optional(), z.number().optional(), z.boolean().optional()]);
expectTypeOf<typeof allOptionalTuple._output>().toEqualTypeOf<[string?, number?, boolean?]>();
// Empty array should be valid (all items optional)
expect(allOptionalTuple.parse([])).toEqual([]);
// Partial arrays should be valid
expect(allOptionalTuple.parse(["hello"])).toEqual(["hello"]);
expect(allOptionalTuple.parse(["hello", 42])).toEqual(["hello", 42]);
// Full array should be valid
expect(allOptionalTuple.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
// Array that's too long should fail
expect(() => allOptionalTuple.parse(["hello", 42, true, "extra"])).toThrow();
});
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>;
expectTypeOf<t1>().toEqualTypeOf<[string, number, ...boolean[]]>();
});
test("sparse array input", () => {
const schema = z.tuple([z.string(), z.number()]);
expect(() => schema.parse(new Array(2))).toThrow();
});

View File

@@ -0,0 +1,219 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
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 errors from both union arms", () => {
const result = z.union([z.number(), z.boolean()]).safeParse("a");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues).toMatchInlineSnapshot(`
[
{
"code": "invalid_union",
"errors": [
[
{
"code": "invalid_type",
"expected": "number",
"message": "Invalid input: expected number, received string",
"path": [],
},
],
[
{
"code": "invalid_type",
"expected": "boolean",
"message": "Invalid input: expected boolean, received string",
"path": [],
},
],
],
"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);
});
test("union inferred types", () => {
const test = z.object({}).or(z.array(z.object({})));
type Test = z.output<typeof test>; // <— any
expectTypeOf<Test>().toEqualTypeOf<Record<string, never> | Array<Record<string, never>>>();
});
test("union values", () => {
const schema = z.union([z.literal("a"), z.literal("b"), z.literal("c")]);
expect(schema._zod.values).toMatchInlineSnapshot(`
Set {
"a",
"b",
"c",
}
`);
});
test("non-aborted errors", () => {
const zItemTest = z.union([
z.object({
date: z.number(),
startDate: z.optional(z.null()),
endDate: z.optional(z.null()),
}),
z
.object({
date: z.optional(z.null()),
startDate: z.number(),
endDate: z.number(),
})
.refine((data) => data.startDate !== data.endDate, {
error: "startDate and endDate must be different",
path: ["endDate"],
}),
]);
const res = zItemTest.safeParse({
date: null,
startDate: 1,
endDate: 1,
});
expect(res).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"path": [
"endDate"
],
"message": "startDate and endDate must be different"
}
]],
"success": false,
}
`);
});
test("surface continuable errors only if they exist", () => {
const schema = z.union([z.boolean(), z.uuid(), z.jwt()]);
expect(schema.safeParse("asdf")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "invalid_union",
"errors": [
[
{
"expected": "boolean",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected boolean, received string"
}
],
[
{
"origin": "string",
"code": "invalid_format",
"format": "uuid",
"pattern": "/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/",
"path": [],
"message": "Invalid UUID"
}
],
[
{
"code": "invalid_format",
"format": "jwt",
"path": [],
"message": "Invalid JWT"
}
]
],
"path": [],
"message": "Invalid input"
}
]],
"success": false,
}
`);
});
// z.xor() tests
test("z.xor() - exactly one match succeeds", () => {
const schema = z.xor([z.string(), z.number()]);
expect(schema.parse("hello")).toBe("hello");
expect(schema.parse(42)).toBe(42);
});
test("z.xor() - zero matches fails", () => {
const schema = z.xor([z.string(), z.number()]);
const result = schema.safeParse(true);
expect(result.success).toBe(false);
});
test("z.xor() - multiple matches fails", () => {
const schema = z.xor([z.string(), z.any()]);
const result = schema.safeParse("hello");
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].code).toBe("invalid_union");
expect((result.error.issues[0] as any).inclusive).toBe(false);
}
});
test("z.xor() with custom error message", () => {
const schema = z.xor([z.string(), z.number()], "Expected exactly one of string or number");
const result = schema.safeParse(true);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toBe("Expected exactly one of string or number");
}
});
test("z.xor() type inference", () => {
const schema = z.xor([z.string(), z.number(), z.boolean()]);
type Result = z.infer<typeof schema>;
expectTypeOf<Result>().toEqualTypeOf<string | number | boolean>();
});

View File

@@ -0,0 +1,13 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("type inference", () => {
const schema = z.string().array();
expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<string[]>();
});
test("url regex", () => {
expect((z.url({ hostname: /^example\.com$/ }).safeParse("http://example.org/").error?.issues[0] as any).pattern).toBe(
"^example\\.com$"
);
});

View File

@@ -0,0 +1,283 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("string length", async () => {
try {
await z.string().length(4).parseAsync("asd");
} catch (err) {
// ("String must contain exactly 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"exact": true,
"inclusive": true,
"message": "Too small: expected string to have >=4 characters",
"minimum": 4,
"origin": "string",
"path": [],
},
]
`);
}
try {
await z.string().length(4).parseAsync("asdaa");
} catch (err) {
// ("String must contain exactly 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"exact": true,
"inclusive": true,
"maximum": 4,
"message": "Too big: expected string to have <=4 characters",
"origin": "string",
"path": [],
},
]
`);
}
});
test("string min/max", async () => {
try {
await z.string().min(4).parseAsync("asd");
} catch (err) {
// ("String must contain at least 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected string to have >=4 characters",
"minimum": 4,
"origin": "string",
"path": [],
},
]
`);
}
});
test("string max", async () => {
try {
await z.string().max(4).parseAsync("aasdfsdfsd");
} catch (err) {
// ("String must contain at most 4 character(s)");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 4,
"message": "Too big: expected string to have <=4 characters",
"origin": "string",
"path": [],
},
]
`);
}
});
test("number min", async () => {
try {
await z.number().min(3).parseAsync(2);
} catch (err) {
// ("Number must be greater than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected number to be >=3",
"minimum": 3,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number gte", async () => {
try {
await z.number().gte(3).parseAsync(2);
} catch (err) {
// ("Number must be greater than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected number to be >=3",
"minimum": 3,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number gt", async () => {
try {
await z.number().gt(3).parseAsync(3);
} catch (err) {
// ("Number must be greater than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": false,
"message": "Too small: expected number to be >3",
"minimum": 3,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number max", async () => {
try {
await z.number().max(3).parseAsync(4);
} catch (err) {
// ("Number must be less than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 3,
"message": "Too big: expected number to be <=3",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number lte", async () => {
try {
await z.number().lte(3).parseAsync(4);
} catch (err) {
// ("Number must be less than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 3,
"message": "Too big: expected number to be <=3",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number lt", async () => {
try {
await z.number().lt(3).parseAsync(3);
} catch (err) {
// ("Number must be less than or equal to 3");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": false,
"maximum": 3,
"message": "Too big: expected number to be <3",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number nonnegative", async () => {
try {
await z.number().nonnegative().parseAsync(-1);
} catch (err) {
// ("Number must be greater than or equal to 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": true,
"message": "Too small: expected number to be >=0",
"minimum": 0,
"origin": "number",
"path": [],
},
]
`);
}
});
test("number nonpositive", async () => {
try {
await z.number().nonpositive().parseAsync(1);
} catch (err) {
// ("Number must be less than or equal to 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": true,
"maximum": 0,
"message": "Too big: expected number to be <=0",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number negative", async () => {
try {
await z.number().negative().parseAsync(1);
} catch (err) {
// ("Number must be less than 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_big",
"inclusive": false,
"maximum": 0,
"message": "Too big: expected number to be <0",
"origin": "number",
"path": [],
},
]
`);
}
});
test("number positive", async () => {
try {
await z.number().positive().parseAsync(-1);
} catch (err) {
// ("Number must be greater than 0");
expect((err as z.ZodError).issues).toMatchInlineSnapshot(`
[
{
"code": "too_small",
"inclusive": false,
"message": "Too small: expected number to be >0",
"minimum": 0,
"origin": "number",
"path": [],
},
]
`);
}
});

View File

@@ -0,0 +1,12 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
test("void", () => {
const v = z.void();
v.parse(undefined);
expect(() => v.parse(null)).toThrow();
expect(() => v.parse("")).toThrow();
type v = z.infer<typeof v>;
expectTypeOf<v>().toEqualTypeOf<void>();
});

1798
mcp-server-ssh/node_modules/zod/src/v4/core/api.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

1293
mcp-server-ssh/node_modules/zod/src/v4/core/checks.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

15
mcp-server-ssh/node_modules/zod/src/v4/core/config.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
import type * as errors from "./errors.js";
export interface $ZodConfig {
/** Custom error map. Overrides `config().localeError`. */
customError?: errors.$ZodErrorMap | undefined;
/** Localized error map. Lowest priority. */
localeError?: errors.$ZodErrorMap | undefined;
}
export const globalConfig: $ZodConfig = {};
export function config(config?: Partial<$ZodConfig>): $ZodConfig {
if (config) Object.assign(globalConfig, config);
return globalConfig;
}

138
mcp-server-ssh/node_modules/zod/src/v4/core/core.ts generated vendored Normal file
View File

@@ -0,0 +1,138 @@
import type * as errors from "./errors.js";
import type * as schemas from "./schemas.js";
import type { Class } from "./util.js";
////////////////////////////// CONSTRUCTORS ///////////////////////////////////////
type ZodTrait = { _zod: { def: any; [k: string]: any } };
export interface $constructor<T extends ZodTrait, D = T["_zod"]["def"]> {
new (def: D): T;
init(inst: T, def: D): asserts inst is T;
}
/** A special constant with type `never` */
export const NEVER: never = Object.freeze({
status: "aborted",
}) as never;
export /*@__NO_SIDE_EFFECTS__*/ function $constructor<T extends ZodTrait, D = T["_zod"]["def"]>(
name: string,
initializer: (inst: T, def: D) => void,
params?: { Parent?: typeof Class }
): $constructor<T, D> {
function init(inst: T, def: D) {
if (!inst._zod) {
Object.defineProperty(inst, "_zod", {
value: {
def,
constr: _,
traits: new Set(),
},
enumerable: false,
});
}
if (inst._zod.traits.has(name)) {
return;
}
inst._zod.traits.add(name);
initializer(inst, def);
// support prototype modifications
const proto = _.prototype;
const keys = Object.keys(proto);
for (let i = 0; i < keys.length; i++) {
const k = keys[i]!;
if (!(k in inst)) {
(inst as any)[k] = proto[k].bind(inst);
}
}
}
// doesn't work if Parent has a constructor with arguments
const Parent = params?.Parent ?? Object;
class Definition extends Parent {}
Object.defineProperty(Definition, "name", { value: name });
function _(this: any, def: D) {
const inst = params?.Parent ? new Definition() : this;
init(inst, def);
inst._zod.deferred ??= [];
for (const fn of inst._zod.deferred) {
fn();
}
return inst;
}
Object.defineProperty(_, "init", { value: init });
Object.defineProperty(_, Symbol.hasInstance, {
value: (inst: any) => {
if (params?.Parent && inst instanceof params.Parent) return true;
return inst?._zod?.traits?.has(name);
},
});
Object.defineProperty(_, "name", { value: name });
return _ as any;
}
////////////////////////////// UTILITIES ///////////////////////////////////////
export const $brand: unique symbol = Symbol("zod_brand");
export type $brand<T extends string | number | symbol = string | number | symbol> = {
[$brand]: { [k in T]: true };
};
export type $ZodBranded<
T extends schemas.SomeType,
Brand extends string | number | symbol,
Dir extends "in" | "out" | "inout" = "out",
> = T &
(Dir extends "inout"
? { _zod: { input: input<T> & $brand<Brand>; output: output<T> & $brand<Brand> } }
: Dir extends "in"
? { _zod: { input: input<T> & $brand<Brand> } }
: { _zod: { output: output<T> & $brand<Brand> } });
export type $ZodNarrow<T extends schemas.SomeType, Out> = T & { _zod: { output: Out } };
export class $ZodAsyncError extends Error {
constructor() {
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
}
}
export class $ZodEncodeError extends Error {
constructor(name: string) {
super(`Encountered unidirectional transform during encode: ${name}`);
this.name = "ZodEncodeError";
}
}
//////////////////////////// TYPE HELPERS ///////////////////////////////////
// export type input<T extends schemas.$ZodType> = T["_zod"]["input"];
// export type output<T extends schemas.$ZodType> = T["_zod"]["output"];
// export type input<T extends schemas.$ZodType> = T["_zod"]["input"];
// export type output<T extends schemas.$ZodType> = T["_zod"]["output"];
export type input<T> = T extends { _zod: { input: any } } ? T["_zod"]["input"] : unknown;
export type output<T> = T extends { _zod: { output: any } } ? T["_zod"]["output"] : unknown;
export type { output as infer };
////////////////////////////// CONFIG ///////////////////////////////////////
export interface $ZodConfig {
/** Custom error map. Overrides `config().localeError`. */
customError?: errors.$ZodErrorMap | undefined;
/** Localized error map. Lowest priority. */
localeError?: errors.$ZodErrorMap | undefined;
/** Disable JIT schema compilation. Useful in environments that disallow `eval`. */
jitless?: boolean | undefined;
}
export const globalConfig: $ZodConfig = {};
export function config(newConfig?: Partial<$ZodConfig>): $ZodConfig {
if (newConfig) Object.assign(globalConfig, newConfig);
return globalConfig;
}

44
mcp-server-ssh/node_modules/zod/src/v4/core/doc.ts generated vendored Normal file
View File

@@ -0,0 +1,44 @@
type ModeWriter = (doc: Doc, modes: { execution: "sync" | "async" }) => void;
export class Doc {
args!: string[];
content: string[] = [];
indent = 0;
constructor(args: string[] = []) {
if (this) this.args = args;
}
indented(fn: (doc: Doc) => void) {
this.indent += 1;
fn(this);
this.indent -= 1;
}
write(fn: ModeWriter): void;
write(line: string): void;
write(arg: any) {
if (typeof arg === "function") {
(arg as ModeWriter)(this, { execution: "sync" });
(arg as ModeWriter)(this, { execution: "async" });
return;
}
const content = arg as string;
const lines = content.split("\n").filter((x) => x);
const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));
const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
for (const line of dedented) {
this.content.push(line);
}
}
compile(): any {
const F = Function;
const args = this?.args;
const content = this?.content ?? [``];
const lines = [...content.map((x) => ` ${x}`)];
// console.log(lines.join("\n"));
return new F(...args, lines.join("\n")) as any;
}
}

448
mcp-server-ssh/node_modules/zod/src/v4/core/errors.ts generated vendored Normal file
View File

@@ -0,0 +1,448 @@
import type { $ZodCheck, $ZodStringFormats } from "./checks.js";
import { $constructor } from "./core.js";
import type { $ZodType } from "./schemas.js";
import type { StandardSchemaV1 } from "./standard-schema.js";
import * as util from "./util.js";
///////////////////////////
//// base type ////
///////////////////////////
export interface $ZodIssueBase {
readonly code?: string;
readonly input?: unknown;
readonly path: PropertyKey[];
readonly message: string;
}
////////////////////////////////
//// issue subtypes ////
////////////////////////////////
export type $ZodInvalidTypeExpected =
| "string"
| "number"
| "int"
| "boolean"
| "bigint"
| "symbol"
| "undefined"
| "null"
| "never"
| "void"
| "date"
| "array"
| "object"
| "tuple"
| "record"
| "map"
| "set"
| "file"
| "nonoptional"
| "nan"
| "function"
| (string & {}); // class names for instanceof
export interface $ZodIssueInvalidType<Input = unknown> extends $ZodIssueBase {
readonly code: "invalid_type";
readonly expected: $ZodInvalidTypeExpected;
readonly input?: Input;
}
export interface $ZodIssueTooBig<Input = unknown> extends $ZodIssueBase {
readonly code: "too_big";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
readonly maximum: number | bigint;
readonly inclusive?: boolean;
readonly exact?: boolean;
readonly input?: Input;
}
export interface $ZodIssueTooSmall<Input = unknown> extends $ZodIssueBase {
readonly code: "too_small";
readonly origin: "number" | "int" | "bigint" | "date" | "string" | "array" | "set" | "file" | (string & {});
readonly minimum: number | bigint;
/** True if the allowable range includes the minimum */
readonly inclusive?: boolean;
/** True if the allowed value is fixed (e.g.` z.length(5)`), not a range (`z.minLength(5)`) */
readonly exact?: boolean;
readonly input?: Input;
}
export interface $ZodIssueInvalidStringFormat extends $ZodIssueBase {
readonly code: "invalid_format";
readonly format: $ZodStringFormats | (string & {});
readonly pattern?: string;
readonly input?: string;
}
export interface $ZodIssueNotMultipleOf<Input extends number | bigint = number | bigint> extends $ZodIssueBase {
readonly code: "not_multiple_of";
readonly divisor: number;
readonly input?: Input;
}
export interface $ZodIssueUnrecognizedKeys extends $ZodIssueBase {
readonly code: "unrecognized_keys";
readonly keys: string[];
readonly input?: Record<string, unknown>;
}
interface $ZodIssueInvalidUnionNoMatch extends $ZodIssueBase {
readonly code: "invalid_union";
readonly errors: $ZodIssue[][];
readonly input?: unknown;
readonly discriminator?: string | undefined;
readonly inclusive?: true;
}
interface $ZodIssueInvalidUnionMultipleMatch extends $ZodIssueBase {
readonly code: "invalid_union";
readonly errors: [];
readonly input?: unknown;
readonly discriminator?: string | undefined;
readonly inclusive: false;
}
export type $ZodIssueInvalidUnion = $ZodIssueInvalidUnionNoMatch | $ZodIssueInvalidUnionMultipleMatch;
export interface $ZodIssueInvalidKey<Input = unknown> extends $ZodIssueBase {
readonly code: "invalid_key";
readonly origin: "map" | "record";
readonly issues: $ZodIssue[];
readonly input?: Input;
}
export interface $ZodIssueInvalidElement<Input = unknown> extends $ZodIssueBase {
readonly code: "invalid_element";
readonly origin: "map" | "set";
readonly key: unknown;
readonly issues: $ZodIssue[];
readonly input?: Input;
}
export interface $ZodIssueInvalidValue<Input = unknown> extends $ZodIssueBase {
readonly code: "invalid_value";
readonly values: util.Primitive[];
readonly input?: Input;
}
export interface $ZodIssueCustom extends $ZodIssueBase {
readonly code: "custom";
readonly params?: Record<string, any> | undefined;
readonly input?: unknown;
}
////////////////////////////////////////////
//// first-party string formats ////
////////////////////////////////////////////
export interface $ZodIssueStringCommonFormats extends $ZodIssueInvalidStringFormat {
format: Exclude<$ZodStringFormats, "regex" | "jwt" | "starts_with" | "ends_with" | "includes">;
}
export interface $ZodIssueStringInvalidRegex extends $ZodIssueInvalidStringFormat {
format: "regex";
pattern: string;
}
export interface $ZodIssueStringInvalidJWT extends $ZodIssueInvalidStringFormat {
format: "jwt";
algorithm?: string;
}
export interface $ZodIssueStringStartsWith extends $ZodIssueInvalidStringFormat {
format: "starts_with";
prefix: string;
}
export interface $ZodIssueStringEndsWith extends $ZodIssueInvalidStringFormat {
format: "ends_with";
suffix: string;
}
export interface $ZodIssueStringIncludes extends $ZodIssueInvalidStringFormat {
format: "includes";
includes: string;
}
export type $ZodStringFormatIssues =
| $ZodIssueStringCommonFormats
| $ZodIssueStringInvalidRegex
| $ZodIssueStringInvalidJWT
| $ZodIssueStringStartsWith
| $ZodIssueStringEndsWith
| $ZodIssueStringIncludes;
////////////////////////
//// utils /////
////////////////////////
export type $ZodIssue =
| $ZodIssueInvalidType
| $ZodIssueTooBig
| $ZodIssueTooSmall
| $ZodIssueInvalidStringFormat
| $ZodIssueNotMultipleOf
| $ZodIssueUnrecognizedKeys
| $ZodIssueInvalidUnion
| $ZodIssueInvalidKey
| $ZodIssueInvalidElement
| $ZodIssueInvalidValue
| $ZodIssueCustom;
export type $ZodIssueCode = $ZodIssue["code"];
export type $ZodInternalIssue<T extends $ZodIssueBase = $ZodIssue> = T extends any ? RawIssue<T> : never;
type RawIssue<T extends $ZodIssueBase> = T extends any
? util.Flatten<
util.MakePartial<T, "message" | "path"> & {
/** The input data */
readonly input: unknown;
/** The schema or check that originated this issue. */
readonly inst?: $ZodType | $ZodCheck;
/** If `true`, Zod will continue executing checks/refinements after this issue. */
readonly continue?: boolean | undefined;
} & Record<string, unknown>
>
: never;
export type $ZodRawIssue<T extends $ZodIssueBase = $ZodIssue> = $ZodInternalIssue<T>;
export interface $ZodErrorMap<T extends $ZodIssueBase = $ZodIssue> {
// biome-ignore lint:
(issue: $ZodRawIssue<T>): { message: string } | string | undefined | null;
}
//////////////////////// ERROR CLASS ////////////////////////
// const ZOD_ERROR: symbol = Symbol.for("{{zod.error}}");
export interface $ZodError<T = unknown> extends Error {
type: T;
issues: $ZodIssue[];
_zod: {
output: T;
def: $ZodIssue[];
};
stack?: string;
name: string;
}
const initializer = (inst: $ZodError, def: $ZodIssue[]): void => {
inst.name = "$ZodError";
Object.defineProperty(inst, "_zod", {
value: inst._zod,
enumerable: false,
});
Object.defineProperty(inst, "issues", {
value: def,
enumerable: false,
});
inst.message = JSON.stringify(def, util.jsonStringifyReplacer, 2);
Object.defineProperty(inst, "toString", {
value: () => inst.message,
enumerable: false,
});
};
export const $ZodError: $constructor<$ZodError> = $constructor("$ZodError", initializer);
interface $ZodRealError<T = any> extends $ZodError<T> {}
export const $ZodRealError: $constructor<$ZodRealError> = $constructor("$ZodError", initializer, { Parent: Error });
/////////////////// ERROR UTILITIES ////////////////////////
// flatten
export type $ZodFlattenedError<T, U = string> = _FlattenedError<T, U>;
type _FlattenedError<T, U = string> = {
formErrors: U[];
fieldErrors: {
[P in keyof T]?: U[];
};
};
export function flattenError<T>(error: $ZodError<T>): _FlattenedError<T>;
export function flattenError<T, U>(error: $ZodError<T>, mapper?: (issue: $ZodIssue) => U): _FlattenedError<T, U>;
export function flattenError<T, U>(error: $ZodError<T>, mapper = (issue: $ZodIssue) => issue.message as U) {
const fieldErrors: Record<PropertyKey, any> = {};
const formErrors: U[] = [];
for (const sub of error.issues) {
if (sub.path.length > 0) {
fieldErrors[sub.path[0]!] = fieldErrors[sub.path[0]!] || [];
fieldErrors[sub.path[0]!].push(mapper(sub));
} else {
formErrors.push(mapper(sub));
}
}
return { formErrors, fieldErrors };
}
type _ZodFormattedError<T, U = string> = T extends [any, ...any[]]
? { [K in keyof T]?: $ZodFormattedError<T[K], U> }
: T extends any[]
? { [k: number]: $ZodFormattedError<T[number], U> }
: T extends object
? util.Flatten<{ [K in keyof T]?: $ZodFormattedError<T[K], U> }>
: any;
export type $ZodFormattedError<T, U = string> = {
_errors: U[];
} & util.Flatten<_ZodFormattedError<T, U>>;
export function formatError<T>(error: $ZodError<T>): $ZodFormattedError<T>;
export function formatError<T, U>(error: $ZodError<T>, mapper?: (issue: $ZodIssue) => U): $ZodFormattedError<T, U>;
export function formatError<T, U>(error: $ZodError<T>, mapper = (issue: $ZodIssue) => issue.message as U) {
const fieldErrors: $ZodFormattedError<T> = { _errors: [] } as any;
const processError = (error: { issues: $ZodIssue[] }) => {
for (const issue of error.issues) {
if (issue.code === "invalid_union" && issue.errors.length) {
issue.errors.map((issues) => processError({ issues }));
} else if (issue.code === "invalid_key") {
processError({ issues: issue.issues });
} else if (issue.code === "invalid_element") {
processError({ issues: issue.issues });
} 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;
if (!terminal) {
curr[el] = curr[el] || { _errors: [] };
} else {
curr[el] = curr[el] || { _errors: [] };
curr[el]._errors.push(mapper(issue));
}
curr = curr[el];
i++;
}
}
}
};
processError(error);
return fieldErrors;
}
export type $ZodErrorTree<T, U = string> = T extends util.Primitive
? { errors: U[] }
: T extends [any, ...any[]]
? { errors: U[]; items?: { [K in keyof T]?: $ZodErrorTree<T[K], U> } }
: T extends any[]
? { errors: U[]; items?: Array<$ZodErrorTree<T[number], U>> }
: T extends object
? { errors: U[]; properties?: { [K in keyof T]?: $ZodErrorTree<T[K], U> } }
: { errors: U[] };
export function treeifyError<T>(error: $ZodError<T>): $ZodErrorTree<T>;
export function treeifyError<T, U>(error: $ZodError<T>, mapper?: (issue: $ZodIssue) => U): $ZodErrorTree<T, U>;
export function treeifyError<T, U>(error: $ZodError<T>, mapper = (issue: $ZodIssue) => issue.message as U) {
const result: $ZodErrorTree<T, U> = { errors: [] } as any;
const processError = (error: { issues: $ZodIssue[] }, path: PropertyKey[] = []) => {
for (const issue of error.issues) {
if (issue.code === "invalid_union" && issue.errors.length) {
// regular union error
issue.errors.map((issues) => processError({ issues }, issue.path));
} else if (issue.code === "invalid_key") {
processError({ issues: issue.issues }, issue.path);
} else if (issue.code === "invalid_element") {
processError({ issues: issue.issues }, issue.path);
} else {
const fullpath = [...path, ...issue.path];
if (fullpath.length === 0) {
result.errors.push(mapper(issue));
continue;
}
let curr: any = result;
let i = 0;
while (i < fullpath.length) {
const el = fullpath[i]!;
const terminal = i === fullpath.length - 1;
if (typeof el === "string") {
curr.properties ??= {};
curr.properties[el] ??= { errors: [] };
curr = curr.properties[el];
} else {
curr.items ??= [];
curr.items[el] ??= { errors: [] };
curr = curr.items[el];
}
if (terminal) {
curr.errors.push(mapper(issue));
}
i++;
}
}
}
};
processError(error);
return result;
}
/** Format a ZodError as a human-readable string in the following form.
*
* From
*
* ```ts
* ZodError {
* issues: [
* {
* expected: 'string',
* code: 'invalid_type',
* path: [ 'username' ],
* message: 'Invalid input: expected string'
* },
* {
* expected: 'number',
* code: 'invalid_type',
* path: [ 'favoriteNumbers', 1 ],
* message: 'Invalid input: expected number'
* }
* ];
* }
* ```
*
* to
*
* ```
* username
* ✖ Expected number, received string at "username
* favoriteNumbers[0]
* ✖ Invalid input: expected number
* ```
*/
export function toDotPath(_path: readonly (string | number | symbol | StandardSchemaV1.PathSegment)[]): string {
const segs: string[] = [];
const path: PropertyKey[] = _path.map((seg: any) => (typeof seg === "object" ? seg.key : seg));
for (const seg of path) {
if (typeof seg === "number") segs.push(`[${seg}]`);
else if (typeof seg === "symbol") segs.push(`[${JSON.stringify(String(seg))}]`);
else if (/[^\w$]/.test(seg)) segs.push(`[${JSON.stringify(seg)}]`);
else {
if (segs.length) segs.push(".");
segs.push(seg);
}
}
return segs.join("");
}
export function prettifyError(error: StandardSchemaV1.FailureResult): string {
const lines: string[] = [];
// sort by path length
const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
// Process each issue
for (const issue of issues) {
lines.push(`${issue.message}`);
if (issue.path?.length) lines.push(` → at ${toDotPath(issue.path)}`);
}
// Convert Map to formatted string
return lines.join("\n");
}

16
mcp-server-ssh/node_modules/zod/src/v4/core/index.ts generated vendored Normal file
View File

@@ -0,0 +1,16 @@
export * from "./core.js";
export * from "./parse.js";
export * from "./errors.js";
export * from "./schemas.js";
export * from "./checks.js";
export * from "./versions.js";
export * as util from "./util.js";
export * as regexes from "./regexes.js";
export * as locales from "../locales/index.js";
export * from "./registries.js";
export * from "./doc.js";
export * from "./api.js";
export * from "./to-json-schema.js";
export { toJSONSchema } from "./json-schema-processors.js";
export { JSONSchemaGenerator } from "./json-schema-generator.js";
export * as JSONSchema from "./json-schema.js";

View File

@@ -0,0 +1,126 @@
import { allProcessors } from "./json-schema-processors.js";
import type * as JSONSchema from "./json-schema.js";
import type { $ZodRegistry } from "./registries.js";
import type * as schemas from "./schemas.js";
import {
type JSONSchemaGeneratorParams,
type ProcessParams,
type Seen,
type ToJSONSchemaContext,
extractDefs,
finalize,
initializeContext,
process,
} from "./to-json-schema.js";
/**
* Parameters for the emit method of JSONSchemaGenerator.
* @deprecated Use toJSONSchema function instead
*/
export type EmitParams = Pick<JSONSchemaGeneratorParams, "cycles" | "reused" | "external">;
/**
* Parameters for JSONSchemaGenerator constructor.
* @deprecated Use toJSONSchema function instead
*/
type JSONSchemaGeneratorConstructorParams = Pick<
JSONSchemaGeneratorParams,
"metadata" | "target" | "unrepresentable" | "override" | "io"
>;
/**
* Legacy class-based interface for JSON Schema generation.
* This class wraps the new functional implementation to provide backward compatibility.
*
* @deprecated Use the `toJSONSchema` function instead for new code.
*
* @example
* ```typescript
* // Legacy usage (still supported)
* const gen = new JSONSchemaGenerator({ target: "draft-07" });
* gen.process(schema);
* const result = gen.emit(schema);
*
* // Preferred modern usage
* const result = toJSONSchema(schema, { target: "draft-07" });
* ```
*/
export class JSONSchemaGenerator {
private ctx: ToJSONSchemaContext;
/** @deprecated Access via ctx instead */
get metadataRegistry(): $ZodRegistry<Record<string, any>> {
return this.ctx.metadataRegistry;
}
/** @deprecated Access via ctx instead */
get target() {
return this.ctx.target;
}
/** @deprecated Access via ctx instead */
get unrepresentable() {
return this.ctx.unrepresentable;
}
/** @deprecated Access via ctx instead */
get override() {
return this.ctx.override;
}
/** @deprecated Access via ctx instead */
get io() {
return this.ctx.io;
}
/** @deprecated Access via ctx instead */
get counter() {
return this.ctx.counter;
}
set counter(value: number) {
this.ctx.counter = value;
}
/** @deprecated Access via ctx instead */
get seen(): Map<schemas.$ZodType, Seen> {
return this.ctx.seen;
}
constructor(params?: JSONSchemaGeneratorConstructorParams) {
// Normalize target for internal context
let normalizedTarget: ToJSONSchemaContext["target"] = params?.target ?? "draft-2020-12";
if (normalizedTarget === "draft-4") normalizedTarget = "draft-04";
if (normalizedTarget === "draft-7") normalizedTarget = "draft-07";
this.ctx = initializeContext({
processors: allProcessors,
target: normalizedTarget,
...(params?.metadata && { metadata: params.metadata }),
...(params?.unrepresentable && { unrepresentable: params.unrepresentable }),
...(params?.override && { override: params.override as any }),
...(params?.io && { io: params.io }),
});
}
/**
* Process a schema to prepare it for JSON Schema generation.
* This must be called before emit().
*/
process(schema: schemas.$ZodType, _params: ProcessParams = { path: [], schemaPath: [] }): JSONSchema.BaseSchema {
return process(schema, this.ctx, _params);
}
/**
* Emit the final JSON Schema after processing.
* Must call process() first.
*/
emit(schema: schemas.$ZodType, _params?: EmitParams): JSONSchema.BaseSchema {
// Apply emit params to the context
if (_params) {
if (_params.cycles) this.ctx.cycles = _params.cycles;
if (_params.reused) this.ctx.reused = _params.reused;
if (_params.external) this.ctx.external = _params.external;
}
extractDefs(this.ctx, schema);
const result = finalize(this.ctx, schema);
// Strip ~standard property to match old implementation's return type
const { "~standard": _, ...plainResult } = result as any;
return plainResult as JSONSchema.BaseSchema;
}
}

View File

@@ -0,0 +1,667 @@
import type * as checks from "./checks.js";
import type * as JSONSchema from "./json-schema.js";
import type { $ZodRegistry } from "./registries.js";
import type * as schemas from "./schemas.js";
import {
type Processor,
type RegistryToJSONSchemaParams,
type ToJSONSchemaParams,
type ZodStandardJSONSchemaPayload,
extractDefs,
finalize,
initializeContext,
process,
} from "./to-json-schema.js";
import { getEnumValues } from "./util.js";
const formatMap: Partial<Record<checks.$ZodStringFormats, string | undefined>> = {
guid: "uuid",
url: "uri",
datetime: "date-time",
json_string: "json-string",
regex: "", // do not set
};
// ==================== SIMPLE TYPE PROCESSORS ====================
export const stringProcessor: Processor<schemas.$ZodString> = (schema, ctx, _json, _params) => {
const json = _json as JSONSchema.StringSchema;
json.type = "string";
const { minimum, maximum, format, patterns, contentEncoding } = schema._zod
.bag as schemas.$ZodStringInternals<unknown>["bag"];
if (typeof minimum === "number") json.minLength = minimum;
if (typeof maximum === "number") json.maxLength = maximum;
// custom pattern overrides format
if (format) {
json.format = formatMap[format as checks.$ZodStringFormats] ?? format;
if (json.format === "") delete json.format; // empty format is not valid
// JSON Schema format: "time" requires a full time with offset or Z
// z.iso.time() does not include timezone information, so format: "time" should never be used
if (format === "time") {
delete json.format;
}
}
if (contentEncoding) json.contentEncoding = contentEncoding;
if (patterns && patterns.size > 0) {
const regexes = [...patterns];
if (regexes.length === 1) json.pattern = regexes[0]!.source;
else if (regexes.length > 1) {
json.allOf = [
...regexes.map((regex) => ({
...(ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0"
? ({ type: "string" } as const)
: {}),
pattern: regex.source,
})),
];
}
}
};
export const numberProcessor: Processor<schemas.$ZodNumber> = (schema, ctx, _json, _params) => {
const json = _json as JSONSchema.NumberSchema | JSONSchema.IntegerSchema;
const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
if (typeof format === "string" && format.includes("int")) json.type = "integer";
else json.type = "number";
if (typeof exclusiveMinimum === "number") {
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
json.minimum = exclusiveMinimum;
json.exclusiveMinimum = true;
} else {
json.exclusiveMinimum = exclusiveMinimum;
}
}
if (typeof minimum === "number") {
json.minimum = minimum;
if (typeof exclusiveMinimum === "number" && ctx.target !== "draft-04") {
if (exclusiveMinimum >= minimum) delete json.minimum;
else delete json.exclusiveMinimum;
}
}
if (typeof exclusiveMaximum === "number") {
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
json.maximum = exclusiveMaximum;
json.exclusiveMaximum = true;
} else {
json.exclusiveMaximum = exclusiveMaximum;
}
}
if (typeof maximum === "number") {
json.maximum = maximum;
if (typeof exclusiveMaximum === "number" && ctx.target !== "draft-04") {
if (exclusiveMaximum <= maximum) delete json.maximum;
else delete json.exclusiveMaximum;
}
}
if (typeof multipleOf === "number") json.multipleOf = multipleOf;
};
export const booleanProcessor: Processor<schemas.$ZodBoolean> = (_schema, _ctx, json, _params) => {
(json as JSONSchema.BooleanSchema).type = "boolean";
};
export const bigintProcessor: Processor<schemas.$ZodBigInt> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("BigInt cannot be represented in JSON Schema");
}
};
export const symbolProcessor: Processor<schemas.$ZodSymbol> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Symbols cannot be represented in JSON Schema");
}
};
export const nullProcessor: Processor<schemas.$ZodNull> = (_schema, ctx, json, _params) => {
if (ctx.target === "openapi-3.0") {
json.type = "string";
json.nullable = true;
json.enum = [null];
} else {
json.type = "null";
}
};
export const undefinedProcessor: Processor<schemas.$ZodUndefined> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Undefined cannot be represented in JSON Schema");
}
};
export const voidProcessor: Processor<schemas.$ZodVoid> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Void cannot be represented in JSON Schema");
}
};
export const neverProcessor: Processor<schemas.$ZodNever> = (_schema, _ctx, json, _params) => {
json.not = {};
};
export const anyProcessor: Processor<schemas.$ZodAny> = (_schema, _ctx, _json, _params) => {
// empty schema accepts anything
};
export const unknownProcessor: Processor<schemas.$ZodUnknown> = (_schema, _ctx, _json, _params) => {
// empty schema accepts anything
};
export const dateProcessor: Processor<schemas.$ZodDate> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Date cannot be represented in JSON Schema");
}
};
export const enumProcessor: Processor<schemas.$ZodEnum> = (schema, _ctx, json, _params) => {
const def = schema._zod.def as schemas.$ZodEnumDef;
const values = getEnumValues(def.entries);
// Number enums can have both string and number values
if (values.every((v) => typeof v === "number")) json.type = "number";
if (values.every((v) => typeof v === "string")) json.type = "string";
json.enum = values;
};
export const literalProcessor: Processor<schemas.$ZodLiteral> = (schema, ctx, json, _params) => {
const def = schema._zod.def as schemas.$ZodLiteralDef<any>;
const vals: (string | number | boolean | null)[] = [];
for (const val of def.values) {
if (val === undefined) {
if (ctx.unrepresentable === "throw") {
throw new Error("Literal `undefined` cannot be represented in JSON Schema");
} else {
// do not add to vals
}
} else if (typeof val === "bigint") {
if (ctx.unrepresentable === "throw") {
throw new Error("BigInt literals cannot be represented in JSON Schema");
} else {
vals.push(Number(val));
}
} else {
vals.push(val);
}
}
if (vals.length === 0) {
// do nothing (an undefined literal was stripped)
} else if (vals.length === 1) {
const val = vals[0]!;
json.type = val === null ? ("null" as const) : (typeof val as any);
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
json.enum = [val];
} else {
json.const = val;
}
} else {
if (vals.every((v) => typeof v === "number")) json.type = "number";
if (vals.every((v) => typeof v === "string")) json.type = "string";
if (vals.every((v) => typeof v === "boolean")) json.type = "boolean";
if (vals.every((v) => v === null)) json.type = "null";
json.enum = vals;
}
};
export const nanProcessor: Processor<schemas.$ZodNaN> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("NaN cannot be represented in JSON Schema");
}
};
export const templateLiteralProcessor: Processor<schemas.$ZodTemplateLiteral> = (schema, _ctx, json, _params) => {
const _json = json as JSONSchema.StringSchema;
const pattern = schema._zod.pattern;
if (!pattern) throw new Error("Pattern not found in template literal");
_json.type = "string";
_json.pattern = pattern.source;
};
export const fileProcessor: Processor<schemas.$ZodFile> = (schema, _ctx, json, _params) => {
const _json = json as JSONSchema.StringSchema;
const file: JSONSchema.StringSchema = {
type: "string",
format: "binary",
contentEncoding: "binary",
};
const { minimum, maximum, mime } = schema._zod.bag as schemas.$ZodFileInternals["bag"];
if (minimum !== undefined) file.minLength = minimum;
if (maximum !== undefined) file.maxLength = maximum;
if (mime) {
if (mime.length === 1) {
file.contentMediaType = mime[0]!;
Object.assign(_json, file);
} else {
Object.assign(_json, file); // shared props at root
_json.anyOf = mime.map((m) => ({ contentMediaType: m })); // only contentMediaType differs
}
} else {
Object.assign(_json, file);
}
};
export const successProcessor: Processor<schemas.$ZodSuccess> = (_schema, _ctx, json, _params) => {
(json as JSONSchema.BooleanSchema).type = "boolean";
};
export const customProcessor: Processor<schemas.$ZodCustom> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Custom types cannot be represented in JSON Schema");
}
};
export const functionProcessor: Processor<schemas.$ZodFunction> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Function types cannot be represented in JSON Schema");
}
};
export const transformProcessor: Processor<schemas.$ZodTransform> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Transforms cannot be represented in JSON Schema");
}
};
export const mapProcessor: Processor<schemas.$ZodMap> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Map cannot be represented in JSON Schema");
}
};
export const setProcessor: Processor<schemas.$ZodSet> = (_schema, ctx, _json, _params) => {
if (ctx.unrepresentable === "throw") {
throw new Error("Set cannot be represented in JSON Schema");
}
};
// ==================== COMPOSITE TYPE PROCESSORS ====================
export const arrayProcessor: Processor<schemas.$ZodArray> = (schema, ctx, _json, params) => {
const json = _json as JSONSchema.ArraySchema;
const def = schema._zod.def as schemas.$ZodArrayDef;
const { minimum, maximum } = schema._zod.bag;
if (typeof minimum === "number") json.minItems = minimum;
if (typeof maximum === "number") json.maxItems = maximum;
json.type = "array";
json.items = process(def.element, ctx as any, { ...params, path: [...params.path, "items"] });
};
export const objectProcessor: Processor<schemas.$ZodObject> = (schema, ctx, _json, params) => {
const json = _json as JSONSchema.ObjectSchema;
const def = schema._zod.def as schemas.$ZodObjectDef;
json.type = "object";
json.properties = {};
const shape = def.shape;
for (const key in shape) {
json.properties[key] = process(shape[key]!, ctx as any, {
...params,
path: [...params.path, "properties", key],
});
}
// required keys
const allKeys = new Set(Object.keys(shape));
const requiredKeys = new Set(
[...allKeys].filter((key) => {
const v = def.shape[key]!._zod;
if (ctx.io === "input") {
return v.optin === undefined;
} else {
return v.optout === undefined;
}
})
);
if (requiredKeys.size > 0) {
json.required = Array.from(requiredKeys);
}
// catchall
if (def.catchall?._zod.def.type === "never") {
// strict
json.additionalProperties = false;
} else if (!def.catchall) {
// regular
if (ctx.io === "output") json.additionalProperties = false;
} else if (def.catchall) {
json.additionalProperties = process(def.catchall, ctx as any, {
...params,
path: [...params.path, "additionalProperties"],
});
}
};
export const unionProcessor: Processor<schemas.$ZodUnion> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodUnionDef;
// Exclusive unions (inclusive === false) use oneOf (exactly one match) instead of anyOf (one or more matches)
// This includes both z.xor() and discriminated unions
const isExclusive = def.inclusive === false;
const options = def.options.map((x, i) =>
process(x, ctx as any, {
...params,
path: [...params.path, isExclusive ? "oneOf" : "anyOf", i],
})
);
if (isExclusive) {
json.oneOf = options;
} else {
json.anyOf = options;
}
};
export const intersectionProcessor: Processor<schemas.$ZodIntersection> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodIntersectionDef;
const a = process(def.left, ctx as any, {
...params,
path: [...params.path, "allOf", 0],
});
const b = process(def.right, ctx as any, {
...params,
path: [...params.path, "allOf", 1],
});
const isSimpleIntersection = (val: any) => "allOf" in val && Object.keys(val).length === 1;
const allOf = [
...(isSimpleIntersection(a) ? (a.allOf as any[]) : [a]),
...(isSimpleIntersection(b) ? (b.allOf as any[]) : [b]),
];
json.allOf = allOf;
};
export const tupleProcessor: Processor<schemas.$ZodTuple> = (schema, ctx, _json, params) => {
const json = _json as JSONSchema.ArraySchema;
const def = schema._zod.def as schemas.$ZodTupleDef;
json.type = "array";
const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
const restPath =
ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
const prefixItems = def.items.map((x, i) =>
process(x, ctx as any, {
...params,
path: [...params.path, prefixPath, i],
})
);
const rest = def.rest
? process(def.rest, ctx as any, {
...params,
path: [...params.path, restPath, ...(ctx.target === "openapi-3.0" ? [def.items.length] : [])],
})
: null;
if (ctx.target === "draft-2020-12") {
json.prefixItems = prefixItems;
if (rest) {
json.items = rest;
}
} else if (ctx.target === "openapi-3.0") {
json.items = {
anyOf: prefixItems,
};
if (rest) {
json.items.anyOf!.push(rest);
}
json.minItems = prefixItems.length;
if (!rest) {
json.maxItems = prefixItems.length;
}
} else {
json.items = prefixItems;
if (rest) {
json.additionalItems = rest;
}
}
// length
const { minimum, maximum } = schema._zod.bag as {
minimum?: number;
maximum?: number;
};
if (typeof minimum === "number") json.minItems = minimum;
if (typeof maximum === "number") json.maxItems = maximum;
};
export const recordProcessor: Processor<schemas.$ZodRecord> = (schema, ctx, _json, params) => {
const json = _json as JSONSchema.ObjectSchema;
const def = schema._zod.def as schemas.$ZodRecordDef;
json.type = "object";
// For looseRecord with regex patterns, use patternProperties
// This correctly represents "only validate keys matching the pattern" semantics
// and composes well with allOf (intersections)
const keyType = def.keyType as schemas.$ZodTypes;
const keyBag = keyType._zod.bag as schemas.$ZodStringInternals<unknown>["bag"] | undefined;
const patterns = keyBag?.patterns;
if (def.mode === "loose" && patterns && patterns.size > 0) {
// Use patternProperties for looseRecord with regex patterns
const valueSchema = process(def.valueType, ctx as any, {
...params,
path: [...params.path, "patternProperties", "*"],
});
json.patternProperties = {};
for (const pattern of patterns) {
json.patternProperties[pattern.source] = valueSchema;
}
} else {
// Default behavior: use propertyNames + additionalProperties
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
json.propertyNames = process(def.keyType, ctx as any, {
...params,
path: [...params.path, "propertyNames"],
});
}
json.additionalProperties = process(def.valueType, ctx as any, {
...params,
path: [...params.path, "additionalProperties"],
});
}
// Add required for keys with discrete values (enum, literal, etc.)
const keyValues = keyType._zod.values;
if (keyValues) {
const validKeyValues = [...keyValues].filter(
(v): v is string | number => typeof v === "string" || typeof v === "number"
);
if (validKeyValues.length > 0) {
json.required = validKeyValues as string[];
}
}
};
export const nullableProcessor: Processor<schemas.$ZodNullable> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodNullableDef;
const inner = process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
if (ctx.target === "openapi-3.0") {
seen.ref = def.innerType;
json.nullable = true;
} else {
json.anyOf = [inner, { type: "null" }];
}
};
export const nonoptionalProcessor: Processor<schemas.$ZodNonOptional> = (schema, ctx, _json, params) => {
const def = schema._zod.def as schemas.$ZodNonOptionalDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
};
export const defaultProcessor: Processor<schemas.$ZodDefault> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodDefaultDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
json.default = JSON.parse(JSON.stringify(def.defaultValue));
};
export const prefaultProcessor: Processor<schemas.$ZodPrefault> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodPrefaultDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
};
export const catchProcessor: Processor<schemas.$ZodCatch> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodCatchDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
let catchValue: any;
try {
catchValue = def.catchValue(undefined as any);
} catch {
throw new Error("Dynamic catch values are not supported in JSON Schema");
}
json.default = catchValue;
};
export const pipeProcessor: Processor<schemas.$ZodPipe> = (schema, ctx, _json, params) => {
const def = schema._zod.def as schemas.$ZodPipeDef;
const innerType = ctx.io === "input" ? (def.in._zod.def.type === "transform" ? def.out : def.in) : def.out;
process(innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = innerType;
};
export const readonlyProcessor: Processor<schemas.$ZodReadonly> = (schema, ctx, json, params) => {
const def = schema._zod.def as schemas.$ZodReadonlyDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
json.readOnly = true;
};
export const promiseProcessor: Processor<schemas.$ZodPromise> = (schema, ctx, _json, params) => {
const def = schema._zod.def as schemas.$ZodPromiseDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
};
export const optionalProcessor: Processor<schemas.$ZodOptional> = (schema, ctx, _json, params) => {
const def = schema._zod.def as schemas.$ZodOptionalDef;
process(def.innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = def.innerType;
};
export const lazyProcessor: Processor<schemas.$ZodLazy> = (schema, ctx, _json, params) => {
const innerType = (schema as schemas.$ZodLazy)._zod.innerType;
process(innerType, ctx as any, params);
const seen = ctx.seen.get(schema)!;
seen.ref = innerType;
};
// ==================== ALL PROCESSORS ====================
export const allProcessors: Record<string, Processor<any>> = {
string: stringProcessor,
number: numberProcessor,
boolean: booleanProcessor,
bigint: bigintProcessor,
symbol: symbolProcessor,
null: nullProcessor,
undefined: undefinedProcessor,
void: voidProcessor,
never: neverProcessor,
any: anyProcessor,
unknown: unknownProcessor,
date: dateProcessor,
enum: enumProcessor,
literal: literalProcessor,
nan: nanProcessor,
template_literal: templateLiteralProcessor,
file: fileProcessor,
success: successProcessor,
custom: customProcessor,
function: functionProcessor,
transform: transformProcessor,
map: mapProcessor,
set: setProcessor,
array: arrayProcessor,
object: objectProcessor,
union: unionProcessor,
intersection: intersectionProcessor,
tuple: tupleProcessor,
record: recordProcessor,
nullable: nullableProcessor,
nonoptional: nonoptionalProcessor,
default: defaultProcessor,
prefault: prefaultProcessor,
catch: catchProcessor,
pipe: pipeProcessor,
readonly: readonlyProcessor,
promise: promiseProcessor,
optional: optionalProcessor,
lazy: lazyProcessor,
};
// ==================== TOP-LEVEL toJSONSchema ====================
export function toJSONSchema<T extends schemas.$ZodType>(
schema: T,
params?: ToJSONSchemaParams
): ZodStandardJSONSchemaPayload<T>;
export function toJSONSchema(
registry: $ZodRegistry<{ id?: string | undefined }>,
params?: RegistryToJSONSchemaParams
): { schemas: Record<string, ZodStandardJSONSchemaPayload<schemas.$ZodType>> };
export function toJSONSchema(
input: schemas.$ZodType | $ZodRegistry<{ id?: string | undefined }>,
params?: ToJSONSchemaParams | RegistryToJSONSchemaParams
): any {
if ("_idmap" in input) {
// Registry case
const registry = input as $ZodRegistry<{ id?: string | undefined }>;
const ctx = initializeContext({ ...params, processors: allProcessors });
const defs: any = {};
// First pass: process all schemas to build the seen map
for (const entry of registry._idmap.entries()) {
const [_, schema] = entry;
process(schema, ctx as any);
}
const schemas: Record<string, JSONSchema.BaseSchema> = {};
const external = {
registry,
uri: (params as RegistryToJSONSchemaParams)?.uri,
defs,
};
// Update the context with external configuration
ctx.external = external;
// Second pass: emit each schema
for (const entry of registry._idmap.entries()) {
const [key, schema] = entry;
extractDefs(ctx as any, schema);
schemas[key] = finalize(ctx as any, schema);
}
if (Object.keys(defs).length > 0) {
const defsSegment = ctx.target === "draft-2020-12" ? "$defs" : "definitions";
schemas.__shared = {
[defsSegment]: defs,
};
}
return { schemas };
}
// Single schema case
const ctx = initializeContext({ ...params, processors: allProcessors });
process(input, ctx as any);
extractDefs(ctx as any, input);
return finalize(ctx as any, input);
}

View File

@@ -0,0 +1,147 @@
export type Schema =
| ObjectSchema
| ArraySchema
| StringSchema
| NumberSchema
| IntegerSchema
| BooleanSchema
| NullSchema;
// export type JsonType = "object" | "array" | "string" | "number" | "boolean" | "null" | "integer";
// export interface JSONSchema {
// type?: string ;
// $id?: string ;
// id?: string ;
// $schema?: string ;
// $ref?: string ;
// $anchor?: string ;
// $defs?: { [key: string]: JSONSchema } ;
// definitions?: { [key: string]: JSONSchema } ;
// $comment?: string ;
// title?: string ;
// description?: string ;
// default?: unknown ;
// examples?: unknown[] ;
// readOnly?: boolean ;
// writeOnly?: boolean ;
// deprecated?: boolean ;
// allOf?: JSONSchema[] ;
// anyOf?: JSONSchema[] ;
// oneOf?: JSONSchema[] ;
// not?: JSONSchema ;
// if?: JSONSchema ;
// then?: JSONSchema ;
// else?: JSONSchema ;
// enum?: Array<string | number | boolean | null> ;
// const?: string | number | boolean | null ;
// [k: string]: unknown;
// /** A special key used as an intermediate representation of extends-style relationships. Omitted as a $ref with additional properties. */
// // _ref?: JSONSchema;
// _prefault?: unknown ;
// }
export type _JSONSchema = boolean | JSONSchema;
export type JSONSchema = {
[k: string]: unknown;
$schema?:
| "https://json-schema.org/draft/2020-12/schema"
| "http://json-schema.org/draft-07/schema#"
| "http://json-schema.org/draft-04/schema#";
$id?: string;
$anchor?: string;
$ref?: string;
$dynamicRef?: string;
$dynamicAnchor?: string;
$vocabulary?: Record<string, boolean>;
$comment?: string;
$defs?: Record<string, JSONSchema>;
type?: "object" | "array" | "string" | "number" | "boolean" | "null" | "integer";
additionalItems?: _JSONSchema;
unevaluatedItems?: _JSONSchema;
prefixItems?: _JSONSchema[];
items?: _JSONSchema | _JSONSchema[];
contains?: _JSONSchema;
additionalProperties?: _JSONSchema;
unevaluatedProperties?: _JSONSchema;
properties?: Record<string, _JSONSchema>;
patternProperties?: Record<string, _JSONSchema>;
dependentSchemas?: Record<string, _JSONSchema>;
propertyNames?: _JSONSchema;
if?: _JSONSchema;
then?: _JSONSchema;
else?: _JSONSchema;
allOf?: JSONSchema[];
anyOf?: JSONSchema[];
oneOf?: JSONSchema[];
not?: _JSONSchema;
multipleOf?: number;
maximum?: number;
exclusiveMaximum?: number | boolean;
minimum?: number;
exclusiveMinimum?: number | boolean;
maxLength?: number;
minLength?: number;
pattern?: string;
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
maxContains?: number;
minContains?: number;
maxProperties?: number;
minProperties?: number;
required?: string[];
dependentRequired?: Record<string, string[]>;
enum?: Array<string | number | boolean | null>;
const?: string | number | boolean | null;
// metadata
id?: string;
title?: string;
description?: string;
default?: unknown;
deprecated?: boolean;
readOnly?: boolean;
writeOnly?: boolean;
nullable?: boolean;
examples?: unknown[];
format?: string;
contentMediaType?: string;
contentEncoding?: string;
contentSchema?: JSONSchema;
// internal
_prefault?: unknown;
};
// for backwards compatibility
export type BaseSchema = JSONSchema;
export interface ObjectSchema extends JSONSchema {
type: "object";
}
export interface ArraySchema extends JSONSchema {
type: "array";
}
export interface StringSchema extends JSONSchema {
type: "string";
}
export interface NumberSchema extends JSONSchema {
type: "number";
}
export interface IntegerSchema extends JSONSchema {
type: "integer";
}
export interface BooleanSchema extends JSONSchema {
type: "boolean";
}
export interface NullSchema extends JSONSchema {
type: "null";
}

195
mcp-server-ssh/node_modules/zod/src/v4/core/parse.ts generated vendored Normal file
View File

@@ -0,0 +1,195 @@
import * as core from "./core.js";
import * as errors from "./errors.js";
import type * as schemas from "./schemas.js";
import * as util from "./util.js";
export type $ZodErrorClass = { new (issues: errors.$ZodIssue[]): errors.$ZodError };
/////////// METHODS ///////////
export type $Parse = <T extends schemas.$ZodType>(
schema: T,
value: unknown,
_ctx?: schemas.ParseContext<errors.$ZodIssue>,
_params?: { callee?: util.AnyFunc; Err?: $ZodErrorClass }
) => core.output<T>;
export const _parse: (_Err: $ZodErrorClass) => $Parse = (_Err) => (schema, value, _ctx, _params) => {
const ctx: schemas.ParseContextInternal = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
const result = schema._zod.run({ value, issues: [] }, ctx);
if (result instanceof Promise) {
throw new core.$ZodAsyncError();
}
if (result.issues.length) {
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())));
util.captureStackTrace(e, _params?.callee);
throw e;
}
return result.value as core.output<typeof schema>;
};
export const parse: $Parse = /* @__PURE__*/ _parse(errors.$ZodRealError);
export type $ParseAsync = <T extends schemas.$ZodType>(
schema: T,
value: unknown,
_ctx?: schemas.ParseContext<errors.$ZodIssue>,
_params?: { callee?: util.AnyFunc; Err?: $ZodErrorClass }
) => Promise<core.output<T>>;
export const _parseAsync: (_Err: $ZodErrorClass) => $ParseAsync = (_Err) => async (schema, value, _ctx, params) => {
const ctx: schemas.ParseContextInternal = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
let result = schema._zod.run({ value, issues: [] }, ctx);
if (result instanceof Promise) result = await result;
if (result.issues.length) {
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())));
util.captureStackTrace(e, params?.callee);
throw e;
}
return result.value as core.output<typeof schema>;
};
export const parseAsync: $ParseAsync = /* @__PURE__*/ _parseAsync(errors.$ZodRealError);
export type $SafeParse = <T extends schemas.$ZodType>(
schema: T,
value: unknown,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => util.SafeParseResult<core.output<T>>;
export const _safeParse: (_Err: $ZodErrorClass) => $SafeParse = (_Err) => (schema, value, _ctx) => {
const ctx: schemas.ParseContextInternal = _ctx ? { ..._ctx, async: false } : { async: false };
const result = schema._zod.run({ value, issues: [] }, ctx);
if (result instanceof Promise) {
throw new core.$ZodAsyncError();
}
return result.issues.length
? {
success: false,
error: new (_Err ?? errors.$ZodError)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config()))),
}
: ({ success: true, data: result.value } as any);
};
export const safeParse: $SafeParse = /* @__PURE__*/ _safeParse(errors.$ZodRealError);
export type $SafeParseAsync = <T extends schemas.$ZodType>(
schema: T,
value: unknown,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => Promise<util.SafeParseResult<core.output<T>>>;
export const _safeParseAsync: (_Err: $ZodErrorClass) => $SafeParseAsync = (_Err) => async (schema, value, _ctx) => {
const ctx: schemas.ParseContextInternal = _ctx ? Object.assign(_ctx, { async: true }) : { async: true };
let result = schema._zod.run({ value, issues: [] }, ctx);
if (result instanceof Promise) result = await result;
return result.issues.length
? {
success: false,
error: new _Err(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config()))),
}
: ({ success: true, data: result.value } as any);
};
export const safeParseAsync: $SafeParseAsync = /* @__PURE__*/ _safeParseAsync(errors.$ZodRealError);
// Codec functions
export type $Encode = <T extends schemas.$ZodType>(
schema: T,
value: core.output<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => core.input<T>;
export const _encode: (_Err: $ZodErrorClass) => $Encode = (_Err) => (schema, value, _ctx) => {
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" as const }) : { direction: "backward" as const };
return _parse(_Err)(schema, value, ctx as any) as any;
};
export const encode: $Encode = /* @__PURE__*/ _encode(errors.$ZodRealError);
export type $Decode = <T extends schemas.$ZodType>(
schema: T,
value: core.input<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => core.output<T>;
export const _decode: (_Err: $ZodErrorClass) => $Decode = (_Err) => (schema, value, _ctx) => {
return _parse(_Err)(schema, value, _ctx);
};
export const decode: $Decode = /* @__PURE__*/ _decode(errors.$ZodRealError);
export type $EncodeAsync = <T extends schemas.$ZodType>(
schema: T,
value: core.output<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => Promise<core.input<T>>;
export const _encodeAsync: (_Err: $ZodErrorClass) => $EncodeAsync = (_Err) => async (schema, value, _ctx) => {
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" as const }) : { direction: "backward" as const };
return _parseAsync(_Err)(schema, value, ctx as any) as any;
};
export const encodeAsync: $EncodeAsync = /* @__PURE__*/ _encodeAsync(errors.$ZodRealError);
export type $DecodeAsync = <T extends schemas.$ZodType>(
schema: T,
value: core.input<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => Promise<core.output<T>>;
export const _decodeAsync: (_Err: $ZodErrorClass) => $DecodeAsync = (_Err) => async (schema, value, _ctx) => {
return _parseAsync(_Err)(schema, value, _ctx);
};
export const decodeAsync: $DecodeAsync = /* @__PURE__*/ _decodeAsync(errors.$ZodRealError);
export type $SafeEncode = <T extends schemas.$ZodType>(
schema: T,
value: core.output<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => util.SafeParseResult<core.input<T>>;
export const _safeEncode: (_Err: $ZodErrorClass) => $SafeEncode = (_Err) => (schema, value, _ctx) => {
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" as const }) : { direction: "backward" as const };
return _safeParse(_Err)(schema, value, ctx as any) as any;
};
export const safeEncode: $SafeEncode = /* @__PURE__*/ _safeEncode(errors.$ZodRealError);
export type $SafeDecode = <T extends schemas.$ZodType>(
schema: T,
value: core.input<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => util.SafeParseResult<core.output<T>>;
export const _safeDecode: (_Err: $ZodErrorClass) => $SafeDecode = (_Err) => (schema, value, _ctx) => {
return _safeParse(_Err)(schema, value, _ctx);
};
export const safeDecode: $SafeDecode = /* @__PURE__*/ _safeDecode(errors.$ZodRealError);
export type $SafeEncodeAsync = <T extends schemas.$ZodType>(
schema: T,
value: core.output<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => Promise<util.SafeParseResult<core.input<T>>>;
export const _safeEncodeAsync: (_Err: $ZodErrorClass) => $SafeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
const ctx = _ctx ? Object.assign(_ctx, { direction: "backward" as const }) : { direction: "backward" as const };
return _safeParseAsync(_Err)(schema, value, ctx as any) as any;
};
export const safeEncodeAsync: $SafeEncodeAsync = /* @__PURE__*/ _safeEncodeAsync(errors.$ZodRealError);
export type $SafeDecodeAsync = <T extends schemas.$ZodType>(
schema: T,
value: core.input<T>,
_ctx?: schemas.ParseContext<errors.$ZodIssue>
) => Promise<util.SafeParseResult<core.output<T>>>;
export const _safeDecodeAsync: (_Err: $ZodErrorClass) => $SafeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
return _safeParseAsync(_Err)(schema, value, _ctx);
};
export const safeDecodeAsync: $SafeDecodeAsync = /* @__PURE__*/ _safeDecodeAsync(errors.$ZodRealError);

183
mcp-server-ssh/node_modules/zod/src/v4/core/regexes.ts generated vendored Normal file
View File

@@ -0,0 +1,183 @@
import * as util from "./util.js";
export const cuid: RegExp = /^[cC][^\s-]{8,}$/;
export const cuid2: RegExp = /^[0-9a-z]+$/;
export const ulid: RegExp = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
export const xid: RegExp = /^[0-9a-vA-V]{20}$/;
export const ksuid: RegExp = /^[A-Za-z0-9]{27}$/;
export const nanoid: RegExp = /^[a-zA-Z0-9_-]{21}$/;
/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */
export const duration: RegExp =
/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
/** Implements ISO 8601-2 extensions like explicit +- prefixes, mixing weeks with other units, and fractional/negative components. */
export const extendedDuration: RegExp =
/^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
/** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */
export const guid: RegExp = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
/** Returns a regex for validating an RFC 9562/4122 UUID.
*
* @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */
export const uuid = (version?: number | undefined): RegExp => {
if (!version)
return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
return new RegExp(
`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`
);
};
export const uuid4: RegExp = /*@__PURE__*/ uuid(4);
export const uuid6: RegExp = /*@__PURE__*/ uuid(6);
export const uuid7: RegExp = /*@__PURE__*/ uuid(7);
/** Practical email validation */
export const email: RegExp =
/^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
/** Equivalent to the HTML5 input[type=email] validation implemented by browsers. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email */
export const html5Email: RegExp =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
/** The classic emailregex.com regex for RFC 5322-compliant emails */
export const rfc5322Email =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
/** A loose regex that allows Unicode characters, enforces length limits, and that's about it. */
export const unicodeEmail = /^[^\s@"]{1,64}@[^\s@]{1,255}$/u;
export const idnEmail = unicodeEmail;
export const browserEmail: RegExp =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
const _emoji: string = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`;
export function emoji(): RegExp {
return new RegExp(_emoji, "u");
}
export const ipv4: RegExp =
/^(?:(?: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])$/;
export const ipv6: RegExp =
/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
export const mac = (delimiter?: string): RegExp => {
const escapedDelim = util.escapeRegex(delimiter ?? ":");
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
};
export const cidrv4: RegExp =
/^((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])\/([0-9]|[1-2][0-9]|3[0-2])$/;
export const cidrv6: RegExp =
/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
// https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
export const base64: RegExp = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
export const base64url: RegExp = /^[A-Za-z0-9_-]*$/;
// based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
// export const hostname: RegExp = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
export const hostname: RegExp =
/^(?=.{1,253}\.?$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[-0-9a-zA-Z]{0,61}[0-9a-zA-Z])?)*\.?$/;
export const domain: RegExp = /^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
// https://blog.stevenlevithan.com/archives/validate-phone-number#r4-3 (regex sans spaces)
// E.164: leading digit must be 1-9; total digits (excluding '+') between 7-15
export const e164: RegExp = /^\+[1-9]\d{6,14}$/;
// const dateSource = `((\\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])))`;
const dateSource = `(?:(?:\\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])))`;
export const date: RegExp = /*@__PURE__*/ new RegExp(`^${dateSource}$`);
function timeSource(args: { precision?: number | null | undefined }) {
const hhmm = `(?:[01]\\d|2[0-3]):[0-5]\\d`;
const regex =
typeof args.precision === "number"
? args.precision === -1
? `${hhmm}`
: args.precision === 0
? `${hhmm}:[0-5]\\d`
: `${hhmm}:[0-5]\\d\\.\\d{${args.precision}}`
: `${hhmm}(?::[0-5]\\d(?:\\.\\d+)?)?`;
return regex;
}
export function time(args: {
precision?: number | null;
// local?: boolean;
}): RegExp {
return new RegExp(`^${timeSource(args)}$`);
}
// Adapted from https://stackoverflow.com/a/3143231
export function datetime(args: {
precision?: number | null;
offset?: boolean;
local?: boolean;
}): RegExp {
const time = timeSource({ precision: args.precision });
const opts = ["Z"];
if (args.local) opts.push("");
// if (args.offset) opts.push(`([+-]\\d{2}:\\d{2})`);
if (args.offset) opts.push(`([+-](?:[01]\\d|2[0-3]):[0-5]\\d)`);
const timeRegex = `${time}(?:${opts.join("|")})`;
return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
}
export const string = (params?: { minimum?: number | undefined; maximum?: number | undefined }): RegExp => {
const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
return new RegExp(`^${regex}$`);
};
export const bigint: RegExp = /^-?\d+n?$/;
export const integer: RegExp = /^-?\d+$/;
export const number: RegExp = /^-?\d+(?:\.\d+)?$/;
export const boolean: RegExp = /^(?:true|false)$/i;
const _null: RegExp = /^null$/i;
export { _null as null };
const _undefined: RegExp = /^undefined$/i;
export { _undefined as undefined };
// regex for string with no uppercase letters
export const lowercase: RegExp = /^[^A-Z]*$/;
// regex for string with no lowercase letters
export const uppercase: RegExp = /^[^a-z]*$/;
// regex for hexadecimal strings (any length)
export const hex: RegExp = /^[0-9a-fA-F]*$/;
// Hash regexes for different algorithms and encodings
// Helper function to create base64 regex with exact length and padding
function fixedBase64(bodyLength: number, padding: "" | "=" | "=="): RegExp {
return new RegExp(`^[A-Za-z0-9+/]{${bodyLength}}${padding}$`);
}
// Helper function to create base64url regex with exact length (no padding)
function fixedBase64url(length: number): RegExp {
return new RegExp(`^[A-Za-z0-9_-]{${length}}$`);
}
// MD5 (16 bytes): base64 = 24 chars total (22 + "==")
export const md5_hex: RegExp = /^[0-9a-fA-F]{32}$/;
export const md5_base64: RegExp = /*@__PURE__*/ fixedBase64(22, "==");
export const md5_base64url: RegExp = /*@__PURE__*/ fixedBase64url(22);
// SHA1 (20 bytes): base64 = 28 chars total (27 + "=")
export const sha1_hex: RegExp = /^[0-9a-fA-F]{40}$/;
export const sha1_base64: RegExp = /*@__PURE__*/ fixedBase64(27, "=");
export const sha1_base64url: RegExp = /*@__PURE__*/ fixedBase64url(27);
// SHA256 (32 bytes): base64 = 44 chars total (43 + "=")
export const sha256_hex: RegExp = /^[0-9a-fA-F]{64}$/;
export const sha256_base64: RegExp = /*@__PURE__*/ fixedBase64(43, "=");
export const sha256_base64url: RegExp = /*@__PURE__*/ fixedBase64url(43);
// SHA384 (48 bytes): base64 = 64 chars total (no padding)
export const sha384_hex: RegExp = /^[0-9a-fA-F]{96}$/;
export const sha384_base64: RegExp = /*@__PURE__*/ fixedBase64(64, "");
export const sha384_base64url: RegExp = /*@__PURE__*/ fixedBase64url(64);
// SHA512 (64 bytes): base64 = 88 chars total (86 + "==")
export const sha512_hex: RegExp = /^[0-9a-fA-F]{128}$/;
export const sha512_base64: RegExp = /*@__PURE__*/ fixedBase64(86, "==");
export const sha512_base64url: RegExp = /*@__PURE__*/ fixedBase64url(86);

View File

@@ -0,0 +1,105 @@
import type * as core from "./core.js";
import type { $ZodType } from "./schemas.js";
export const $output: unique symbol = Symbol("ZodOutput");
export type $output = typeof $output;
export const $input: unique symbol = Symbol("ZodInput");
export type $input = typeof $input;
export type $replace<Meta, S extends $ZodType> = Meta extends $output
? core.output<S>
: Meta extends $input
? core.input<S>
: Meta extends (infer M)[]
? $replace<M, S>[]
: Meta extends (...args: infer P) => infer R
? (
...args: {
[K in keyof P]: $replace<P[K], S>; // tuple
}
) => $replace<R, S>
: // handle objects
Meta extends object
? { [K in keyof Meta]: $replace<Meta[K], S> }
: Meta;
type MetadataType = object | undefined;
export class $ZodRegistry<Meta extends MetadataType = MetadataType, Schema extends $ZodType = $ZodType> {
_meta!: Meta;
_schema!: Schema;
_map: WeakMap<Schema, $replace<Meta, Schema>> = new WeakMap();
_idmap: Map<string, Schema> = new Map();
add<S extends Schema>(
schema: S,
..._meta: undefined extends Meta ? [$replace<Meta, S>?] : [$replace<Meta, S>]
): this {
const meta: any = _meta[0];
this._map.set(schema, meta!);
if (meta && typeof meta === "object" && "id" in meta) {
this._idmap.set(meta.id!, schema);
}
return this as any;
}
clear(): this {
this._map = new WeakMap();
this._idmap = new Map();
return this;
}
remove(schema: Schema): this {
const meta: any = this._map.get(schema);
if (meta && typeof meta === "object" && "id" in meta) {
this._idmap.delete(meta.id!);
}
this._map.delete(schema);
return this;
}
get<S extends Schema>(schema: S): $replace<Meta, S> | undefined {
// return this._map.get(schema) as any;
// inherit metadata
const p = schema._zod.parent as Schema;
if (p) {
const pm: any = { ...(this.get(p) ?? {}) };
delete pm.id; // do not inherit id
const f = { ...pm, ...this._map.get(schema) } as any;
return Object.keys(f).length ? f : undefined;
}
return this._map.get(schema) as any;
}
has(schema: Schema): boolean {
return this._map.has(schema);
}
}
export interface JSONSchemaMeta {
id?: string | undefined;
title?: string | undefined;
description?: string | undefined;
deprecated?: boolean | undefined;
[k: string]: unknown;
}
export interface GlobalMeta extends JSONSchemaMeta {}
// registries
export function registry<T extends MetadataType = MetadataType, S extends $ZodType = $ZodType>(): $ZodRegistry<T, S> {
return new $ZodRegistry<T, S>();
}
interface GlobalThisWithRegistry {
/**
* The globalRegistry instance shared across both CommonJS and ESM builds.
* By attaching the registry to `globalThis`, this property ensures a single, deduplicated instance
* is used regardless of whether the package is loaded via `require` (CJS) or `import` (ESM).
* This prevents dual package hazards and keeps registry state consistent.
*/
__zod_globalRegistry?: $ZodRegistry<GlobalMeta>;
}
(globalThis as GlobalThisWithRegistry).__zod_globalRegistry ??= registry<GlobalMeta>();
export const globalRegistry: $ZodRegistry<GlobalMeta> = (globalThis as GlobalThisWithRegistry).__zod_globalRegistry!;

4538
mcp-server-ssh/node_modules/zod/src/v4/core/schemas.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
/** The Standard interface. */
export interface StandardTypedV1<Input = unknown, Output = Input> {
/** The Standard properties. */
readonly "~standard": StandardTypedV1.Props<Input, Output>;
}
export declare namespace StandardTypedV1 {
/** The Standard 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;
/** Inferred types associated with the schema. */
readonly types?: Types<Input, Output> | undefined;
}
/** The Standard 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. */
export type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
/** Infers the output type of a Standard. */
export type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
}
/** The Standard Schema interface. */
export interface 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> extends StandardTypedV1.Props<Input, Output> {
/** Validates unknown input values. */
readonly validate: (
value: unknown,
options?: StandardSchemaV1.Options | undefined
) => Result<Output> | Promise<Result<Output>>;
}
/** 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 absence of issues indicates success. */
readonly issues?: undefined;
}
export interface Options {
/** Implicit support for additional vendor-specific parameters, if needed. */
readonly libraryOptions?: Record<string, unknown> | 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 types interface. */
export interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
/** Infers the input type of a Standard. */
export type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
/** Infers the output type of a Standard. */
export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
}
/** The Standard JSON Schema interface. */
export interface StandardJSONSchemaV1<Input = unknown, Output = Input> {
/** The Standard JSON Schema properties. */
readonly "~standard": StandardJSONSchemaV1.Props<Input, Output>;
}
export declare namespace StandardJSONSchemaV1 {
/** The Standard JSON Schema properties interface. */
export interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
/** Methods for generating the input/output JSON Schema. */
readonly jsonSchema: Converter;
}
/** The Standard JSON Schema converter interface. */
export interface Converter {
/** Converts the input type to JSON Schema. May throw if conversion is not supported. */
readonly input: (options: StandardJSONSchemaV1.Options) => Record<string, unknown>;
/** Converts the output type to JSON Schema. May throw if conversion is not supported. */
readonly output: (options: StandardJSONSchemaV1.Options) => Record<string, unknown>;
}
/** The target version of the generated JSON Schema.
*
* It is *strongly recommended* that implementers support `"draft-2020-12"` and `"draft-07"`, as they are both in wide use.
*
* The `"openapi-3.0"` target is intended as a standardized specifier for OpenAPI 3.0 which is a superset of JSON Schema `"draft-04"`.
*
* All other targets can be implemented on a best-effort basis. Libraries should throw if they don't support a specified target.
*/
export type Target =
| "draft-2020-12"
| "draft-07"
| "openapi-3.0"
// Accepts any string for future targets while preserving autocomplete
| ({} & string);
/** The options for the input/output methods. */
export interface Options {
/** Specifies the target version of the generated JSON Schema. Support for all versions is on a best-effort basis. If a given version is not supported, the library should throw. */
readonly target: Target;
/** Implicit support for additional vendor-specific parameters, if needed. */
readonly libraryOptions?: Record<string, unknown> | undefined;
}
/** The Standard types interface. */
export interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
/** Infers the input type of a Standard. */
export type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
/** Infers the output type of a Standard. */
export type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
}
export interface StandardSchemaWithJSONProps<Input = unknown, Output = Input>
extends StandardSchemaV1.Props<Input, Output>,
StandardJSONSchemaV1.Props<Input, Output> {}
/**
* An interface that combines StandardJSONSchema and StandardSchema.
*/
export interface StandardSchemaWithJSON<Input = unknown, Output = Input> {
"~standard": StandardSchemaWithJSONProps<Input, Output>;
}

View File

@@ -0,0 +1,59 @@
import { expect, test } from "vitest";
import * as z from "zod/v4";
test("safeExtend chaining preserves and overrides properties", () => {
const schema1 = z.object({
email: z.string(),
});
const schema2 = schema1.safeExtend({
email: schema1.shape.email.check(z.email()),
});
const schema3 = schema2.safeExtend({
email: schema2.shape.email.or(z.literal("")),
});
schema3.parse({ email: "test@example.com" });
});
test("extend with constructor field in shape", () => {
const baseSchema = z.object({
name: z.string(),
});
const extendedSchema = baseSchema.extend({
constructor: z.string(),
age: z.number(),
});
const result = extendedSchema.parse({
name: "John",
constructor: "Person",
age: 30,
});
expect(result).toEqual({
name: "John",
constructor: "Person",
age: 30,
});
const testCases = [
{ name: "Test", constructor: 123, age: 25 },
{ name: "Test", constructor: null, age: 25 },
{ name: "Test", constructor: true, age: 25 },
{ name: "Test", constructor: {}, age: 25 },
];
for (const testCase of testCases) {
const anyConstructorSchema = baseSchema.extend({
constructor: z.any(),
age: z.number(),
});
expect(() => anyConstructorSchema.parse(testCase)).not.toThrow();
const parsed = anyConstructorSchema.parse(testCase);
expect(parsed).toEqual(testCase);
}
});

View File

@@ -0,0 +1,46 @@
import { expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v3";
test("test", () => {
expect(true).toBe(true);
});
test("test2", () => {
expect(() => z.string().parse(234)).toThrowErrorMatchingInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"path": [],
"message": "Expected string, received number"
}
]]
`);
});
test("async validation", async () => {
const testTuple = z
.tuple([z.string().refine(async () => true), z.number().refine(async () => true)])
.refine(async () => true);
expectTypeOf<typeof testTuple._output>().toEqualTypeOf<[string, number]>();
const val = await testTuple.parseAsync(["asdf", 1234]);
expect(val).toEqual(val);
const r1 = await testTuple.safeParseAsync(["asdf", "asdf"]);
expect(r1.success).toEqual(false);
expect(r1.error!).toMatchInlineSnapshot(`
[ZodError: [
{
"code": "invalid_type",
"expected": "number",
"received": "string",
"path": [
1
],
"message": "Expected number, received string"
}
]]
`);
});

Some files were not shown because too many files have changed in this diff Show More