zod
Version:
TypeScript-first schema declaration and validation library with static type inference
1,459 lines (1,335 loc) • 160 kB
text/typescript
import {
type IssueData,
type StringValidation,
type ZodCustomIssue,
ZodError,
type ZodErrorMap,
type ZodIssue,
ZodIssueCode,
} from "./ZodError.js";
import { defaultErrorMap, getErrorMap } from "./errors.js";
import type { enumUtil } from "./helpers/enumUtil.js";
import { errorUtil } from "./helpers/errorUtil.js";
import {
type AsyncParseReturnType,
DIRTY,
INVALID,
OK,
type ParseContext,
type ParseInput,
type ParseParams,
type ParsePath,
type ParseReturnType,
ParseStatus,
type SyncParseReturnType,
addIssueToContext,
isAborted,
isAsync,
isDirty,
isValid,
makeIssue,
} from "./helpers/parseUtil.js";
import type { partialUtil } from "./helpers/partialUtil.js";
import type { Primitive } from "./helpers/typeAliases.js";
import { util, ZodParsedType, getParsedType, type objectUtil } from "./helpers/util.js";
import type { StandardSchemaV1 } from "./standard-schema.js";
///////////////////////////////////////
///////////////////////////////////////
////////// //////////
////////// ZodType //////////
////////// //////////
///////////////////////////////////////
///////////////////////////////////////
export interface RefinementCtx {
addIssue: (arg: IssueData) => void;
path: (string | number)[];
}
export type ZodRawShape = { [k: string]: ZodTypeAny };
export type ZodTypeAny = ZodType<any, any, any>;
export type TypeOf<T extends ZodType<any, any, any>> = T["_output"];
export type input<T extends ZodType<any, any, any>> = T["_input"];
export type output<T extends ZodType<any, any, any>> = T["_output"];
export type { TypeOf as infer };
export type CustomErrorParams = Partial<util.Omit<ZodCustomIssue, "code">>;
export interface ZodTypeDef {
errorMap?: ZodErrorMap | undefined;
description?: string | undefined;
}
class ParseInputLazyPath implements ParseInput {
parent: ParseContext;
data: any;
_path: ParsePath;
_key: string | number | (string | number)[];
_cachedPath: ParsePath = [];
constructor(parent: ParseContext, value: any, path: ParsePath, key: string | number | (string | number)[]) {
this.parent = parent;
this.data = value;
this._path = path;
this._key = key;
}
get path() {
if (!this._cachedPath.length) {
if (Array.isArray(this._key)) {
this._cachedPath.push(...this._path, ...this._key);
} else {
this._cachedPath.push(...this._path, this._key);
}
}
return this._cachedPath;
}
}
const handleResult = <Input, Output>(
ctx: ParseContext,
result: SyncParseReturnType<Output>
): { success: true; data: Output } | { success: false; error: ZodError<Input> } => {
if (isValid(result)) {
return { success: true, data: result.value };
} else {
if (!ctx.common.issues.length) {
throw new Error("Validation failed but no issues detected.");
}
return {
success: false,
get error() {
if ((this as any)._error) return (this as any)._error as Error;
const error = new ZodError(ctx.common.issues);
(this as any)._error = error;
return (this as any)._error;
},
};
}
};
export type RawCreateParams =
| {
errorMap?: ZodErrorMap | undefined;
invalid_type_error?: string | undefined;
required_error?: string | undefined;
message?: string | undefined;
description?: string | undefined;
}
| undefined;
export type ProcessedCreateParams = {
errorMap?: ZodErrorMap | undefined;
description?: string | undefined;
};
function processCreateParams(params: RawCreateParams): ProcessedCreateParams {
if (!params) return {};
const { errorMap, invalid_type_error, required_error, description } = params;
if (errorMap && (invalid_type_error || required_error)) {
throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`);
}
if (errorMap) return { errorMap: errorMap, description };
const customMap: ZodErrorMap = (iss, ctx) => {
const { message } = params;
if (iss.code === "invalid_enum_value") {
return { message: message ?? ctx.defaultError };
}
if (typeof ctx.data === "undefined") {
return { message: message ?? required_error ?? ctx.defaultError };
}
if (iss.code !== "invalid_type") return { message: ctx.defaultError };
return { message: message ?? invalid_type_error ?? ctx.defaultError };
};
return { errorMap: customMap, description };
}
export type SafeParseSuccess<Output> = {
success: true;
data: Output;
error?: never;
};
export type SafeParseError<Input> = {
success: false;
error: ZodError<Input>;
data?: never;
};
export type SafeParseReturnType<Input, Output> = SafeParseSuccess<Output> | SafeParseError<Input>;
export abstract class ZodType<Output = any, Def extends ZodTypeDef = ZodTypeDef, Input = Output> {
readonly _type!: Output;
readonly _output!: Output;
readonly _input!: Input;
readonly _def!: Def;
get description(): string | undefined {
return this._def.description;
}
"~standard": StandardSchemaV1.Props<Input, Output>;
abstract _parse(input: ParseInput): ParseReturnType<Output>;
_getType(input: ParseInput): string {
return getParsedType(input.data);
}
_getOrReturnCtx(input: ParseInput, ctx?: ParseContext | undefined): ParseContext {
return (
ctx || {
common: input.parent.common,
data: input.data,
parsedType: getParsedType(input.data),
schemaErrorMap: this._def.errorMap,
path: input.path,
parent: input.parent,
}
);
}
_processInputParams(input: ParseInput): {
status: ParseStatus;
ctx: ParseContext;
} {
return {
status: new ParseStatus(),
ctx: {
common: input.parent.common,
data: input.data,
parsedType: getParsedType(input.data),
schemaErrorMap: this._def.errorMap,
path: input.path,
parent: input.parent,
},
};
}
_parseSync(input: ParseInput): SyncParseReturnType<Output> {
const result = this._parse(input);
if (isAsync(result)) {
throw new Error("Synchronous parse encountered promise.");
}
return result;
}
_parseAsync(input: ParseInput): AsyncParseReturnType<Output> {
const result = this._parse(input);
return Promise.resolve(result);
}
parse(data: unknown, params?: util.InexactPartial<ParseParams>): Output {
const result = this.safeParse(data, params);
if (result.success) return result.data;
throw result.error;
}
safeParse(data: unknown, params?: util.InexactPartial<ParseParams>): SafeParseReturnType<Input, Output> {
const ctx: ParseContext = {
common: {
issues: [],
async: params?.async ?? false,
contextualErrorMap: params?.errorMap,
},
path: params?.path || [],
schemaErrorMap: this._def.errorMap,
parent: null,
data,
parsedType: getParsedType(data),
};
const result = this._parseSync({ data, path: ctx.path, parent: ctx });
return handleResult(ctx, result);
}
"~validate"(data: unknown): StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>> {
const ctx: ParseContext = {
common: {
issues: [],
async: !!(this["~standard"] as any).async,
},
path: [],
schemaErrorMap: this._def.errorMap,
parent: null,
data,
parsedType: getParsedType(data),
};
if (!(this["~standard"] as any).async) {
try {
const result = this._parseSync({ data, path: [], parent: ctx });
return isValid(result)
? {
value: result.value,
}
: {
issues: ctx.common.issues,
};
} catch (err: any) {
if ((err as Error)?.message?.toLowerCase()?.includes("encountered")) {
(this["~standard"] as any).async = true;
}
(ctx as any).common = {
issues: [],
async: true,
};
}
}
return this._parseAsync({ data, path: [], parent: ctx }).then((result) =>
isValid(result)
? {
value: result.value,
}
: {
issues: ctx.common.issues,
}
);
}
async parseAsync(data: unknown, params?: util.InexactPartial<ParseParams>): Promise<Output> {
const result = await this.safeParseAsync(data, params);
if (result.success) return result.data;
throw result.error;
}
async safeParseAsync(
data: unknown,
params?: util.InexactPartial<ParseParams>
): Promise<SafeParseReturnType<Input, Output>> {
const ctx: ParseContext = {
common: {
issues: [],
contextualErrorMap: params?.errorMap,
async: true,
},
path: params?.path || [],
schemaErrorMap: this._def.errorMap,
parent: null,
data,
parsedType: getParsedType(data),
};
const maybeAsyncResult = this._parse({ data, path: ctx.path, parent: ctx });
const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult));
return handleResult(ctx, result);
}
/** Alias of safeParseAsync */
spa = this.safeParseAsync;
refine<RefinedOutput extends Output>(
check: (arg: Output) => arg is RefinedOutput,
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
): ZodEffects<this, RefinedOutput, Input>;
refine(
check: (arg: Output) => unknown | Promise<unknown>,
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
): ZodEffects<this, Output, Input>;
refine(
check: (arg: Output) => unknown,
message?: string | CustomErrorParams | ((arg: Output) => CustomErrorParams)
): ZodEffects<this, Output, Input> {
const getIssueProperties = (val: Output) => {
if (typeof message === "string" || typeof message === "undefined") {
return { message };
} else if (typeof message === "function") {
return message(val);
} else {
return message;
}
};
return this._refinement((val, ctx) => {
const result = check(val);
const setError = () =>
ctx.addIssue({
code: ZodIssueCode.custom,
...getIssueProperties(val),
});
if (typeof Promise !== "undefined" && result instanceof Promise) {
return result.then((data) => {
if (!data) {
setError();
return false;
} else {
return true;
}
});
}
if (!result) {
setError();
return false;
} else {
return true;
}
});
}
refinement<RefinedOutput extends Output>(
check: (arg: Output) => arg is RefinedOutput,
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
): ZodEffects<this, RefinedOutput, Input>;
refinement(
check: (arg: Output) => boolean,
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
): ZodEffects<this, Output, Input>;
refinement(
check: (arg: Output) => unknown,
refinementData: IssueData | ((arg: Output, ctx: RefinementCtx) => IssueData)
): ZodEffects<this, Output, Input> {
return this._refinement((val, ctx) => {
if (!check(val)) {
ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData);
return false;
} else {
return true;
}
});
}
_refinement(refinement: RefinementEffect<Output>["refinement"]): ZodEffects<this, Output, Input> {
return new ZodEffects({
schema: this,
typeName: ZodFirstPartyTypeKind.ZodEffects,
effect: { type: "refinement", refinement },
});
}
superRefine<RefinedOutput extends Output>(
refinement: (arg: Output, ctx: RefinementCtx) => arg is RefinedOutput
): ZodEffects<this, RefinedOutput, Input>;
superRefine(refinement: (arg: Output, ctx: RefinementCtx) => void): ZodEffects<this, Output, Input>;
superRefine(refinement: (arg: Output, ctx: RefinementCtx) => Promise<void>): ZodEffects<this, Output, Input>;
superRefine(
refinement: (arg: Output, ctx: RefinementCtx) => unknown | Promise<unknown>
): ZodEffects<this, Output, Input> {
return this._refinement(refinement);
}
constructor(def: Def) {
this._def = def;
this.parse = this.parse.bind(this);
this.safeParse = this.safeParse.bind(this);
this.parseAsync = this.parseAsync.bind(this);
this.safeParseAsync = this.safeParseAsync.bind(this);
this.spa = this.spa.bind(this);
this.refine = this.refine.bind(this);
this.refinement = this.refinement.bind(this);
this.superRefine = this.superRefine.bind(this);
this.optional = this.optional.bind(this);
this.nullable = this.nullable.bind(this);
this.nullish = this.nullish.bind(this);
this.array = this.array.bind(this);
this.promise = this.promise.bind(this);
this.or = this.or.bind(this);
this.and = this.and.bind(this);
this.transform = this.transform.bind(this);
this.brand = this.brand.bind(this);
this.default = this.default.bind(this);
this.catch = this.catch.bind(this);
this.describe = this.describe.bind(this);
this.pipe = this.pipe.bind(this);
this.readonly = this.readonly.bind(this);
this.isNullable = this.isNullable.bind(this);
this.isOptional = this.isOptional.bind(this);
this["~standard"] = {
version: 1,
vendor: "zod",
validate: (data) => this["~validate"](data),
};
}
optional(): ZodOptional<this> {
return ZodOptional.create(this, this._def) as any;
}
nullable(): ZodNullable<this> {
return ZodNullable.create(this, this._def) as any;
}
nullish(): ZodOptional<ZodNullable<this>> {
return this.nullable().optional();
}
array(): ZodArray<this> {
return ZodArray.create(this);
}
promise(): ZodPromise<this> {
return ZodPromise.create(this, this._def);
}
or<T extends ZodTypeAny>(option: T): ZodUnion<[this, T]> {
return ZodUnion.create([this, option], this._def) as any;
}
and<T extends ZodTypeAny>(incoming: T): ZodIntersection<this, T> {
return ZodIntersection.create(this, incoming, this._def);
}
transform<NewOut>(
transform: (arg: Output, ctx: RefinementCtx) => NewOut | Promise<NewOut>
): ZodEffects<this, NewOut> {
return new ZodEffects({
...processCreateParams(this._def),
schema: this,
typeName: ZodFirstPartyTypeKind.ZodEffects,
effect: { type: "transform", transform },
}) as any;
}
default(def: util.noUndefined<Input>): ZodDefault<this>;
default(def: () => util.noUndefined<Input>): ZodDefault<this>;
default(def: any) {
const defaultValueFunc = typeof def === "function" ? def : () => def;
return new ZodDefault({
...processCreateParams(this._def),
innerType: this,
defaultValue: defaultValueFunc,
typeName: ZodFirstPartyTypeKind.ZodDefault,
}) as any;
}
brand<B extends string | number | symbol>(brand?: B): ZodBranded<this, B>;
brand<B extends string | number | symbol>(): ZodBranded<this, B> {
return new ZodBranded({
typeName: ZodFirstPartyTypeKind.ZodBranded,
type: this,
...processCreateParams(this._def),
});
}
catch(def: Output): ZodCatch<this>;
catch(def: (ctx: { error: ZodError; input: Input }) => Output): ZodCatch<this>;
catch(def: any) {
const catchValueFunc = typeof def === "function" ? def : () => def;
return new ZodCatch({
...processCreateParams(this._def),
innerType: this,
catchValue: catchValueFunc,
typeName: ZodFirstPartyTypeKind.ZodCatch,
}) as any;
}
describe(description: string): this {
const This = (this as any).constructor;
return new This({
...this._def,
description,
});
}
pipe<T extends ZodTypeAny>(target: T): ZodPipeline<this, T> {
return ZodPipeline.create(this, target);
}
readonly(): ZodReadonly<this> {
return ZodReadonly.create(this);
}
isOptional(): boolean {
return this.safeParse(undefined).success;
}
isNullable(): boolean {
return this.safeParse(null).success;
}
}
/////////////////////////////////////////
/////////////////////////////////////////
////////// //////////
////////// ZodString //////////
////////// //////////
/////////////////////////////////////////
/////////////////////////////////////////
export type IpVersion = "v4" | "v6";
export type ZodStringCheck =
| { kind: "min"; value: number; message?: string | undefined }
| { kind: "max"; value: number; message?: string | undefined }
| { kind: "length"; value: number; message?: string | undefined }
| { kind: "email"; message?: string | undefined }
| { kind: "url"; message?: string | undefined }
| { kind: "emoji"; message?: string | undefined }
| { kind: "uuid"; message?: string | undefined }
| { kind: "nanoid"; message?: string | undefined }
| { kind: "cuid"; message?: string | undefined }
| { kind: "includes"; value: string; position?: number | undefined; message?: string | undefined }
| { kind: "cuid2"; message?: string | undefined }
| { kind: "ulid"; message?: string | undefined }
| { kind: "startsWith"; value: string; message?: string | undefined }
| { kind: "endsWith"; value: string; message?: string | undefined }
| { kind: "regex"; regex: RegExp; message?: string | undefined }
| { kind: "trim"; message?: string | undefined }
| { kind: "toLowerCase"; message?: string | undefined }
| { kind: "toUpperCase"; message?: string | undefined }
| { kind: "jwt"; alg?: string; message?: string | undefined }
| {
kind: "datetime";
offset: boolean;
local: boolean;
precision: number | null;
message?: string | undefined;
}
| {
kind: "date";
// withDate: true;
message?: string | undefined;
}
| {
kind: "time";
precision: number | null;
message?: string | undefined;
}
| { kind: "duration"; message?: string | undefined }
| { kind: "ip"; version?: IpVersion | undefined; message?: string | undefined }
| { kind: "cidr"; version?: IpVersion | undefined; message?: string | undefined }
| { kind: "base64"; message?: string | undefined }
| { kind: "base64url"; message?: string | undefined };
export interface ZodStringDef extends ZodTypeDef {
checks: ZodStringCheck[];
typeName: ZodFirstPartyTypeKind.ZodString;
coerce: boolean;
}
const cuidRegex = /^c[^\s-]{8,}$/i;
const cuid2Regex = /^[0-9a-z]+$/;
const ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
// const uuidRegex =
// /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i;
const uuidRegex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/i;
const nanoidRegex = /^[a-z0-9_-]{21}$/i;
const jwtRegex = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/;
const durationRegex =
/^[-+]?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)?)??$/;
// from https://stackoverflow.com/a/46181/1550155
// old version: too slow, didn't support unicode
// const emailRegex = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i;
//old email regex
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
// eslint-disable-next-line
// const emailRegex =
// /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((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}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-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})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/;
// const emailRegex =
// /^[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])?)*$/;
// const emailRegex =
// /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i;
const emailRegex = /^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
// const emailRegex =
// /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i;
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
const _emojiRegex = `^(\\p{Extended_Pictographic}|\\p{Emoji_Component})+$`;
let emojiRegex: RegExp;
// faster, simpler, safer
const ipv4Regex =
/^(?:(?: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])$/;
const ipv4CidrRegex =
/^(?:(?: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])\/(3[0-2]|[12]?[0-9])$/;
// const ipv6Regex =
// /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-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})))$/;
const ipv6Regex =
/^(([0-9a-fA-F]{1,4}:){7,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}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
const ipv6CidrRegex =
/^(([0-9a-fA-F]{1,4}:){7,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}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
// https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
const base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
// https://base64.guru/standards/base64url
const base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;
// simple
// const dateRegexSource = `\\d{4}-\\d{2}-\\d{2}`;
// no leap year validation
// const dateRegexSource = `\\d{4}-((0[13578]|10|12)-31|(0[13-9]|1[0-2])-30|(0[1-9]|1[0-2])-(0[1-9]|1\\d|2\\d))`;
// with leap year validation
const dateRegexSource = `((\\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 dateRegex = new RegExp(`^${dateRegexSource}$`);
function timeRegexSource(args: { precision?: number | null }) {
let secondsRegexSource = `[0-5]\\d`;
if (args.precision) {
secondsRegexSource = `${secondsRegexSource}\\.\\d{${args.precision}}`;
} else if (args.precision == null) {
secondsRegexSource = `${secondsRegexSource}(\\.\\d+)?`;
}
const secondsQuantifier = args.precision ? "+" : "?"; // require seconds if precision is nonzero
return `([01]\\d|2[0-3]):[0-5]\\d(:${secondsRegexSource})${secondsQuantifier}`;
}
function timeRegex(args: {
offset?: boolean;
local?: boolean;
precision?: number | null;
}) {
return new RegExp(`^${timeRegexSource(args)}$`);
}
// Adapted from https://stackoverflow.com/a/3143231
export function datetimeRegex(args: {
precision?: number | null;
offset?: boolean;
local?: boolean;
}) {
let regex = `${dateRegexSource}T${timeRegexSource(args)}`;
const opts: string[] = [];
opts.push(args.local ? `Z?` : `Z`);
if (args.offset) opts.push(`([+-]\\d{2}:?\\d{2})`);
regex = `${regex}(${opts.join("|")})`;
return new RegExp(`^${regex}$`);
}
function isValidIP(ip: string, version?: IpVersion) {
if ((version === "v4" || !version) && ipv4Regex.test(ip)) {
return true;
}
if ((version === "v6" || !version) && ipv6Regex.test(ip)) {
return true;
}
return false;
}
function isValidJWT(jwt: string, alg?: string): boolean {
if (!jwtRegex.test(jwt)) return false;
try {
const [header] = jwt.split(".");
if (!header) return false;
// Convert base64url to base64
const base64 = header
.replace(/-/g, "+")
.replace(/_/g, "/")
.padEnd(header.length + ((4 - (header.length % 4)) % 4), "=");
// @ts-ignore
const decoded = JSON.parse(atob(base64));
if (typeof decoded !== "object" || decoded === null) return false;
if ("typ" in decoded && decoded?.typ !== "JWT") return false;
if (!decoded.alg) return false;
if (alg && decoded.alg !== alg) return false;
return true;
} catch {
return false;
}
}
function isValidCidr(ip: string, version?: IpVersion) {
if ((version === "v4" || !version) && ipv4CidrRegex.test(ip)) {
return true;
}
if ((version === "v6" || !version) && ipv6CidrRegex.test(ip)) {
return true;
}
return false;
}
export class ZodString extends ZodType<string, ZodStringDef, string> {
_parse(input: ParseInput): ParseReturnType<string> {
if (this._def.coerce) {
input.data = String(input.data);
}
const parsedType = this._getType(input);
if (parsedType !== ZodParsedType.string) {
const ctx = this._getOrReturnCtx(input);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_type,
expected: ZodParsedType.string,
received: ctx.parsedType,
});
return INVALID;
}
const status = new ParseStatus();
let ctx: undefined | ParseContext = undefined;
for (const check of this._def.checks) {
if (check.kind === "min") {
if (input.data.length < check.value) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: check.value,
type: "string",
inclusive: true,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "max") {
if (input.data.length > check.value) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
maximum: check.value,
type: "string",
inclusive: true,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "length") {
const tooBig = input.data.length > check.value;
const tooSmall = input.data.length < check.value;
if (tooBig || tooSmall) {
ctx = this._getOrReturnCtx(input, ctx);
if (tooBig) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
maximum: check.value,
type: "string",
inclusive: true,
exact: true,
message: check.message,
});
} else if (tooSmall) {
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: check.value,
type: "string",
inclusive: true,
exact: true,
message: check.message,
});
}
status.dirty();
}
} else if (check.kind === "email") {
if (!emailRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "email",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "emoji") {
if (!emojiRegex) {
emojiRegex = new RegExp(_emojiRegex, "u");
}
if (!emojiRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "emoji",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "uuid") {
if (!uuidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "uuid",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "nanoid") {
if (!nanoidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "nanoid",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "cuid") {
if (!cuidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "cuid",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "cuid2") {
if (!cuid2Regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "cuid2",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "ulid") {
if (!ulidRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "ulid",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "url") {
try {
// @ts-ignore
new URL(input.data);
} catch {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "url",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "regex") {
check.regex.lastIndex = 0;
const testResult = check.regex.test(input.data);
if (!testResult) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "regex",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "trim") {
input.data = input.data.trim();
} else if (check.kind === "includes") {
if (!(input.data as string).includes(check.value, check.position)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { includes: check.value, position: check.position },
message: check.message,
});
status.dirty();
}
} else if (check.kind === "toLowerCase") {
input.data = input.data.toLowerCase();
} else if (check.kind === "toUpperCase") {
input.data = input.data.toUpperCase();
} else if (check.kind === "startsWith") {
if (!(input.data as string).startsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { startsWith: check.value },
message: check.message,
});
status.dirty();
}
} else if (check.kind === "endsWith") {
if (!(input.data as string).endsWith(check.value)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: { endsWith: check.value },
message: check.message,
});
status.dirty();
}
} else if (check.kind === "datetime") {
const regex = datetimeRegex(check);
if (!regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: "datetime",
message: check.message,
});
status.dirty();
}
} else if (check.kind === "date") {
const regex = dateRegex;
if (!regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: "date",
message: check.message,
});
status.dirty();
}
} else if (check.kind === "time") {
const regex = timeRegex(check);
if (!regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_string,
validation: "time",
message: check.message,
});
status.dirty();
}
} else if (check.kind === "duration") {
if (!durationRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "duration",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "ip") {
if (!isValidIP(input.data, check.version)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "ip",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "jwt") {
if (!isValidJWT(input.data, check.alg)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "jwt",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "cidr") {
if (!isValidCidr(input.data, check.version)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "cidr",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "base64") {
if (!base64Regex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "base64",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "base64url") {
if (!base64urlRegex.test(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
validation: "base64url",
code: ZodIssueCode.invalid_string,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
}
return { status: status.value, value: input.data };
}
protected _regex(regex: RegExp, validation: StringValidation, message?: errorUtil.ErrMessage) {
return this.refinement((data) => regex.test(data), {
validation,
code: ZodIssueCode.invalid_string,
...errorUtil.errToObj(message),
});
}
_addCheck(check: ZodStringCheck) {
return new ZodString({
...this._def,
checks: [...this._def.checks, check],
});
}
email(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) });
}
url(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) });
}
emoji(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "emoji", ...errorUtil.errToObj(message) });
}
uuid(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) });
}
nanoid(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "nanoid", ...errorUtil.errToObj(message) });
}
cuid(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) });
}
cuid2(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "cuid2", ...errorUtil.errToObj(message) });
}
ulid(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "ulid", ...errorUtil.errToObj(message) });
}
base64(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "base64", ...errorUtil.errToObj(message) });
}
base64url(message?: errorUtil.ErrMessage) {
// base64url encoding is a modification of base64 that can safely be used in URLs and filenames
return this._addCheck({
kind: "base64url",
...errorUtil.errToObj(message),
});
}
jwt(options?: { alg?: string; message?: string | undefined }) {
return this._addCheck({ kind: "jwt", ...errorUtil.errToObj(options) });
}
ip(options?: string | { version?: IpVersion; message?: string | undefined }) {
return this._addCheck({ kind: "ip", ...errorUtil.errToObj(options) });
}
cidr(options?: string | { version?: IpVersion; message?: string | undefined }) {
return this._addCheck({ kind: "cidr", ...errorUtil.errToObj(options) });
}
datetime(
options?:
| string
| {
message?: string | undefined;
precision?: number | null;
offset?: boolean;
local?: boolean;
}
) {
if (typeof options === "string") {
return this._addCheck({
kind: "datetime",
precision: null,
offset: false,
local: false,
message: options,
});
}
return this._addCheck({
kind: "datetime",
precision: typeof options?.precision === "undefined" ? null : options?.precision,
offset: options?.offset ?? false,
local: options?.local ?? false,
...errorUtil.errToObj(options?.message),
});
}
date(message?: string) {
return this._addCheck({ kind: "date", message });
}
time(
options?:
| string
| {
message?: string | undefined;
precision?: number | null;
}
) {
if (typeof options === "string") {
return this._addCheck({
kind: "time",
precision: null,
message: options,
});
}
return this._addCheck({
kind: "time",
precision: typeof options?.precision === "undefined" ? null : options?.precision,
...errorUtil.errToObj(options?.message),
});
}
duration(message?: errorUtil.ErrMessage) {
return this._addCheck({ kind: "duration", ...errorUtil.errToObj(message) });
}
regex(regex: RegExp, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "regex",
regex: regex,
...errorUtil.errToObj(message),
});
}
includes(value: string, options?: { message?: string; position?: number }) {
return this._addCheck({
kind: "includes",
value: value,
position: options?.position,
...errorUtil.errToObj(options?.message),
});
}
startsWith(value: string, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "startsWith",
value: value,
...errorUtil.errToObj(message),
});
}
endsWith(value: string, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "endsWith",
value: value,
...errorUtil.errToObj(message),
});
}
min(minLength: number, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "min",
value: minLength,
...errorUtil.errToObj(message),
});
}
max(maxLength: number, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "max",
value: maxLength,
...errorUtil.errToObj(message),
});
}
length(len: number, message?: errorUtil.ErrMessage) {
return this._addCheck({
kind: "length",
value: len,
...errorUtil.errToObj(message),
});
}
/**
* Equivalent to `.min(1)`
*/
nonempty(message?: errorUtil.ErrMessage) {
return this.min(1, errorUtil.errToObj(message));
}
trim() {
return new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "trim" }],
});
}
toLowerCase() {
return new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "toLowerCase" }],
});
}
toUpperCase() {
return new ZodString({
...this._def,
checks: [...this._def.checks, { kind: "toUpperCase" }],
});
}
get isDatetime() {
return !!this._def.checks.find((ch) => ch.kind === "datetime");
}
get isDate() {
return !!this._def.checks.find((ch) => ch.kind === "date");
}
get isTime() {
return !!this._def.checks.find((ch) => ch.kind === "time");
}
get isDuration() {
return !!this._def.checks.find((ch) => ch.kind === "duration");
}
get isEmail() {
return !!this._def.checks.find((ch) => ch.kind === "email");
}
get isURL() {
return !!this._def.checks.find((ch) => ch.kind === "url");
}
get isEmoji() {
return !!this._def.checks.find((ch) => ch.kind === "emoji");
}
get isUUID() {
return !!this._def.checks.find((ch) => ch.kind === "uuid");
}
get isNANOID() {
return !!this._def.checks.find((ch) => ch.kind === "nanoid");
}
get isCUID() {
return !!this._def.checks.find((ch) => ch.kind === "cuid");
}
get isCUID2() {
return !!this._def.checks.find((ch) => ch.kind === "cuid2");
}
get isULID() {
return !!this._def.checks.find((ch) => ch.kind === "ulid");
}
get isIP() {
return !!this._def.checks.find((ch) => ch.kind === "ip");
}
get isCIDR() {
return !!this._def.checks.find((ch) => ch.kind === "cidr");
}
get isBase64() {
return !!this._def.checks.find((ch) => ch.kind === "base64");
}
get isBase64url() {
// base64url encoding is a modification of base64 that can safely be used in URLs and filenames
return !!this._def.checks.find((ch) => ch.kind === "base64url");
}
get minLength() {
let min: number | null = null;
for (const ch of this._def.checks) {
if (ch.kind === "min") {
if (min === null || ch.value > min) min = ch.value;
}
}
return min;
}
get maxLength() {
let max: number | null = null;
for (const ch of this._def.checks) {
if (ch.kind === "max") {
if (max === null || ch.value < max) max = ch.value;
}
}
return max;
}
static create = (params?: RawCreateParams & { coerce?: true }): ZodString => {
return new ZodString({
checks: [],
typeName: ZodFirstPartyTypeKind.ZodString,
coerce: params?.coerce ?? false,
...processCreateParams(params),
});
};
}
/////////////////////////////////////////
/////////////////////////////////////////
////////// //////////
////////// ZodNumber //////////
////////// //////////
/////////////////////////////////////////
/////////////////////////////////////////
export type ZodNumberCheck =
| { kind: "min"; value: number; inclusive: boolean; message?: string | undefined }
| { kind: "max"; value: number; inclusive: boolean; message?: string | undefined }
| { kind: "int"; message?: string | undefined }
| { kind: "multipleOf"; value: number; message?: string | undefined }
| { kind: "finite"; message?: string | undefined };
// https://stackoverflow.com/questions/3966484/why-does-modulus-operator-return-fractional-number-in-javascript/31711034#31711034
function floatSafeRemainder(val: number, step: number) {
const valDecCount = (val.toString().split(".")[1] || "").length;
const stepDecCount = (step.toString().split(".")[1] || "").length;
const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount;
const valInt = Number.parseInt(val.toFixed(decCount).replace(".", ""));
const stepInt = Number.parseInt(step.toFixed(decCount).replace(".", ""));
return (valInt % stepInt) / 10 ** decCount;
}
export interface ZodNumberDef extends ZodTypeDef {
checks: ZodNumberCheck[];
typeName: ZodFirstPartyTypeKind.ZodNumber;
coerce: boolean;
}
export class ZodNumber extends ZodType<number, ZodNumberDef, number> {
_parse(input: ParseInput): ParseReturnType<number> {
if (this._def.coerce) {
input.data = Number(input.data);
}
const parsedType = this._getType(input);
if (parsedType !== ZodParsedType.number) {
const ctx = this._getOrReturnCtx(input);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_type,
expected: ZodParsedType.number,
received: ctx.parsedType,
});
return INVALID;
}
let ctx: undefined | ParseContext = undefined;
const status = new ParseStatus();
for (const check of this._def.checks) {
if (check.kind === "int") {
if (!util.isInteger(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.invalid_type,
expected: "integer",
received: "float",
message: check.message,
});
status.dirty();
}
} else if (check.kind === "min") {
const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value;
if (tooSmall) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_small,
minimum: check.value,
type: "number",
inclusive: check.inclusive,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "max") {
const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value;
if (tooBig) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.too_big,
maximum: check.value,
type: "number",
inclusive: check.inclusive,
exact: false,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "multipleOf") {
if (floatSafeRemainder(input.data, check.value) !== 0) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.not_multiple_of,
multipleOf: check.value,
message: check.message,
});
status.dirty();
}
} else if (check.kind === "finite") {
if (!Number.isFinite(input.data)) {
ctx = this._getOrReturnCtx(input, ctx);
addIssueToContext(ctx, {
code: ZodIssueCode.not_finite,
message: check.message,
});
status.dirty();
}
} else {
util.assertNever(check);
}
}
return { status: status.value, value: input.data };
}
static create = (params?: RawCreateParams & { coerce?: boolean }): ZodNumber => {
return new ZodNumber({
checks: [],
typeName: ZodFirstPartyTypeKind.ZodNumber,
coerce: params?.coerce ||