zod-compiler
Version:
Compile Zod schemas to fast parsers or TypeScript types
541 lines (532 loc) • 20.9 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
// Mirrors ZodParsedType from src/helpers/util.ts
var ZcParsedType = /*#__PURE__*/ function(ZcParsedType) {
ZcParsedType["string"] = "string";
ZcParsedType["nan"] = "nan";
ZcParsedType["number"] = "number";
ZcParsedType["integer"] = "integer";
ZcParsedType["float"] = "float";
ZcParsedType["boolean"] = "boolean";
ZcParsedType["date"] = "date";
ZcParsedType["bigint"] = "bigint";
ZcParsedType["symbol"] = "symbol";
ZcParsedType["function"] = "function";
ZcParsedType["undefined"] = "undefined";
ZcParsedType["null"] = "null";
ZcParsedType["array"] = "array";
ZcParsedType["object"] = "object";
ZcParsedType["unknown"] = "unknown";
ZcParsedType["promise"] = "promise";
ZcParsedType["void"] = "void";
ZcParsedType["never"] = "never";
ZcParsedType["map"] = "map";
ZcParsedType["set"] = "set";
return ZcParsedType;
}({});
// Mirrors getParsedType from src/helpers/util.ts
function typeOf(data) {
switch(typeof data){
case 'undefined':
return "undefined";
case 'string':
return "string";
case 'number':
return isNaN(data) ? "nan" : "number";
case 'boolean':
return "boolean";
case 'function':
return "function";
case 'bigint':
return "bigint";
case 'symbol':
return "symbol";
case 'object':
if (Array.isArray(data)) {
return "array";
}
if (data === null) {
return "null";
}
if (typeof data.then === 'function' && typeof data.catch === 'function') {
return "promise";
}
if (data instanceof Map) {
return "map";
}
if (data instanceof Set) {
return "set";
}
if (data instanceof Date) {
return "date";
}
return "object";
default:
return "unknown";
}
}
// Mirrors mergeValues from src/types.ts
function mergeValues(a, b) {
if (a === b) {
return {
valid: true,
data: a
};
}
const aType = typeOf(a);
const bType = typeOf(b);
if (aType === "object" && bType === "object") {
const bKeys = Object.keys(b);
const sharedKeys = Object.keys(a).filter((key)=>bKeys.indexOf(key) !== -1);
const newObj = {
...a,
...b
};
for (const key of sharedKeys){
const sharedValue = mergeValues(a[key], b[key]);
if (!sharedValue.valid) {
return {
valid: false
};
}
newObj[key] = sharedValue.data;
}
return {
valid: true,
data: newObj
};
} else if (aType === "array" && bType === "array") {
if (a.length !== b.length) {
return {
valid: false
};
}
const newArray = [];
for(let i = 0; i < a.length; i++){
const sharedValue = mergeValues(a[i], b[i]);
if (!sharedValue.valid) {
return {
valid: false
};
}
newArray.push(sharedValue.data);
}
return {
valid: true,
data: newArray
};
} else if (aType === "date" && bType == "date" && +a === +b) {
return {
valid: true,
data: a
};
} else {
return {
valid: false
};
}
}
function stringify(obj) {
return JSON.stringify(obj, (_, value)=>{
if (typeof value === 'bigint') {
return value.toString();
}
return value;
}, 2 /* ugggghhhhhhhh */ );
}
function joinValues(array, separator = ' | ') {
return array.map((val)=>typeof val === 'string' ? `'${val}'` : val).join(separator);
}
function assertNever(_x) {
throw new Error();
}
function isValidJWT(jwt, alg) {
try {
const [header] = jwt.split('.');
// Convert base64url to base64
const base64 = header.replace(/-/g, '+').replace(/_/g, '/').padEnd(header.length + (4 - header.length % 4) % 4, '=');
const decoded = JSON.parse(atob(base64));
if (typeof decoded !== 'object' || decoded === null) {
return false;
}
if (!decoded.typ || !decoded.alg) {
return false;
}
if (alg && decoded.alg !== alg) {
return false;
}
return true;
} catch {
return false;
}
}
function floatSafeRemainder(val, step) {
const valDecCount = (val.toString().split('.')[1] || '').length;
const stepDecCount = (step.toString().split('.')[1] || '').length;
const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount;
const valInt = parseInt(val.toFixed(decCount).replace('.', ''));
const stepInt = parseInt(step.toFixed(decCount).replace('.', ''));
return valInt % stepInt / Math.pow(10, decCount);
}
const helpers = {
typeOf,
mergeValues,
isValidJWT,
floatSafeRemainder
};
var ZcIssueCode = /*#__PURE__*/ function(ZcIssueCode) {
ZcIssueCode["invalid_type"] = "invalid_type";
ZcIssueCode["invalid_literal"] = "invalid_literal";
ZcIssueCode["custom"] = "custom";
ZcIssueCode["invalid_union"] = "invalid_union";
ZcIssueCode["invalid_union_discriminator"] = "invalid_union_discriminator";
ZcIssueCode["invalid_enum_value"] = "invalid_enum_value";
ZcIssueCode["unrecognized_keys"] = "unrecognized_keys";
ZcIssueCode["invalid_arguments"] = "invalid_arguments";
ZcIssueCode["invalid_return_type"] = "invalid_return_type";
ZcIssueCode["invalid_date"] = "invalid_date";
ZcIssueCode["invalid_string"] = "invalid_string";
ZcIssueCode["too_small"] = "too_small";
ZcIssueCode["too_big"] = "too_big";
ZcIssueCode["invalid_intersection_types"] = "invalid_intersection_types";
ZcIssueCode["not_multiple_of"] = "not_multiple_of";
ZcIssueCode["not_finite"] = "not_finite";
return ZcIssueCode;
}({});
class ZcError extends Error {
get errors() {
return this.issues;
}
constructor(issues){
super(), this.issues = issues;
// TODO: why is this required?
const actualProto = new.target.prototype;
Object.setPrototypeOf(this, actualProto);
this.name = 'ZcError';
}
format(_mapper) {
const mapper = _mapper ?? ((issue)=>issue.message);
const fieldErrors = {
_errors: []
};
const processError = (error)=>{
for (const issue of error.issues){
if (issue.code === "invalid_union") {
issue.unionErrors.map(processError);
} else if (issue.code === "invalid_return_type") {
processError(issue.returnTypeError);
} else if (issue.code === "invalid_arguments") {
processError(issue.argumentsError);
} else if (issue.path.length === 0) {
fieldErrors._errors.push(mapper(issue));
} else {
let curr = fieldErrors;
let i = 0;
while(i < issue.path.length){
const el = issue.path[i];
curr[el] ||= {
_errors: []
};
const terminal = i === issue.path.length - 1;
if (terminal) {
curr[el]._errors.push(mapper(issue));
}
curr = curr[el];
i++;
}
}
}
};
processError(this);
return fieldErrors;
}
static create(issues) {
return new ZcError(issues);
}
toString() {
return this.message;
}
get message() {
return stringify(this.issues);
}
get isEmpty() {
return this.issues.length === 0;
}
addIssue(sub) {
// why not push...?
this.issues = [
...this.issues,
sub
];
}
addIssues(subs = []) {
this.issues = [
...this.issues,
...subs
];
}
flatten(mapper = (issue)=>issue.message) {
const fieldErrors = {};
const formErrors = [];
for (const sub of this.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
};
}
get formErrors() {
return this.flatten(); //.formErrors?;
}
}
const errorMap = (issue, _ctx)=>{
let message;
switch(issue.code){
case ZcIssueCode.invalid_type:
if (issue.received === ZcParsedType.undefined) {
message = 'Required';
} else {
message = `Expected ${issue.expected}, received ${issue.received}`;
}
break;
case ZcIssueCode.invalid_literal:
message = `Invalid literal value, expected ${stringify(issue.expected)}`;
break;
case ZcIssueCode.unrecognized_keys:
message = `Unrecognized key(s) in object: ${joinValues(issue.keys, ', ')}`;
break;
case ZcIssueCode.invalid_union:
message = `Invalid input`;
break;
case ZcIssueCode.invalid_union_discriminator:
message = `Invalid discriminator value. Expected ${joinValues(issue.options)}`;
break;
case ZcIssueCode.invalid_enum_value:
message = `Invalid enum value. Expected ${joinValues(issue.options)}, received '${issue.received}'`;
break;
case ZcIssueCode.invalid_arguments:
message = `Invalid function arguments`;
break;
case ZcIssueCode.invalid_return_type:
message = `Invalid function return type`;
break;
case ZcIssueCode.invalid_date:
message = `Invalid date`;
break;
case ZcIssueCode.invalid_string:
if (typeof issue.validation === 'object') {
if ('includes' in issue.validation) {
message = `Invalid input: must include "${issue.validation.includes}"`;
if (typeof issue.validation.position === 'number') {
message += ` at one or more positions greater than or equal to ${issue.validation.position}`;
}
} else if ('startsWith' in issue.validation) {
message = `Invalid input: must start with "${issue.validation.startsWith}"`;
} else if ('endsWith' in issue.validation) {
message = `Invalid input: must end with "${issue.validation.endsWith}"`;
} else {
assertNever(issue.validation);
}
} else if (issue.validation !== 'regex') {
message = `Invalid ${issue.validation}`;
} else {
message = 'Invalid';
}
break;
case ZcIssueCode.too_small:
if (issue.type === 'array') {
message = `Array must contain ${issue.exact ? 'exactly' : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;
} else if (issue.type === 'string') {
message = `String must contain ${issue.exact ? 'exactly' : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
} else if (issue.type === 'number') {
message = `Number must be ${issue.exact ? `exactly equal to` : issue.inclusive ? `greater than or equal to` : `greater than`} ${issue.minimum}`;
} else if (issue.type === 'date') {
message = `Date must be ${issue.exact ? `exactly equal to` : issue.inclusive ? `greater than or equal to` : `greater than`} ${new Date(Number(issue.minimum))}`;
} else {
message = 'Invalid input';
}
break;
case ZcIssueCode.too_big:
if (issue.type === 'array') {
message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;
} else if (issue.type === 'string') {
message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;
} else if (issue.type === 'number') {
message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
} else if (issue.type === 'bigint') {
message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;
} else if (issue.type === 'date') {
message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`;
} else {
message = 'Invalid input';
}
break;
case ZcIssueCode.custom:
message = `Invalid input`;
break;
case ZcIssueCode.invalid_intersection_types:
message = `Intersection results could not be merged`;
break;
case ZcIssueCode.not_multiple_of:
message = `Number must be a multiple of ${issue.multipleOf}`;
break;
case ZcIssueCode.not_finite:
message = 'Number must be finite';
break;
default:
message = _ctx.defaultError;
assertNever();
}
return {
message
};
};
let overrideErrorMap = errorMap;
function setErrorMap(map) {
overrideErrorMap = map;
}
function getErrorMap() {
return overrideErrorMap;
}
const CUID = /^c[^\s-]{8,}$/i;
const CUID2 = /^[0-9a-z]+$/;
const ULID = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
const UUID = /^[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 NANOID = /^[a-z0-9_-]{21}$/i;
const JWT = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/;
const DURATION = /^[-+]?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)?)??$/;
const EMAIL = /^(?!\.)(?!.*\.\.)([A-Z0-9_'+\-\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
const EMOJI = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;
const IPV4 = /^(?:(?: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 IPV4_CIDR = /^(?:(?: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 IPV6 = /^(([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 IPV6_CIDR = /^(([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])$/;
const BASE64 = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;
const BASE64URL = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;
const DATE = /^((\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])))$/;
var regex = {
__proto__: null,
BASE64: BASE64,
BASE64URL: BASE64URL,
CUID: CUID,
CUID2: CUID2,
DATE: DATE,
DURATION: DURATION,
EMAIL: EMAIL,
EMOJI: EMOJI,
IPV4: IPV4,
IPV4_CIDR: IPV4_CIDR,
IPV6: IPV6,
IPV6_CIDR: IPV6_CIDR,
JWT: JWT,
NANOID: NANOID,
ULID: ULID,
UUID: UUID
};
var ParseStatus = /*#__PURE__*/ function(ParseStatus) {
ParseStatus[ParseStatus["VALID"] = 0] = "VALID";
/** At least one issue has been identified, but parsing can still continue to identify more. */ ParseStatus[ParseStatus["DIRTY"] = 1] = "DIRTY";
/** A fatal issue has been encountered and parsing cannot continue. */ ParseStatus[ParseStatus["INVALID"] = 2] = "INVALID";
return ParseStatus;
}({});
const createContext = (dependencies, parseParams)=>{
const overrideMap = getErrorMap();
return {
output: null,
ZcError,
helpers,
regex,
dependencies,
basePath: parseParams?.path ?? [],
errorMaps: [
parseParams?.errorMap,
overrideMap,
overrideMap === errorMap ? undefined : errorMap
].filter((x)=>!!x),
issues: [],
reportIssue (issue, input = null) {
const fullPath = [
...this.basePath,
...issue.path || []
];
const fullIssue = {
...issue,
path: fullPath
};
if (fullIssue.message !== undefined) {
this.issues.push(fullIssue);
return;
}
let errorMessage = '';
const maps = this.errorMaps.filter((m)=>!!m).slice().reverse();
for (const map of maps){
errorMessage = map(fullIssue, {
data: input,
defaultError: errorMessage
}).message;
}
this.issues.push({
...issue,
message: errorMessage
});
}
};
};
function standalone(parser, dependencies = []) {
const typedParser = parser;
return {
parse (data, params) {
const ctx = createContext(dependencies, params);
const status = typedParser(data, ctx);
if (status === 0) {
return ctx.output;
}
throw new ZcError(ctx.issues);
},
safeParse (data, params) {
const ctx = createContext(dependencies, params);
const status = typedParser(data, ctx);
if (status === 0) {
return {
success: true,
data: ctx.output
};
} else {
return {
success: false,
error: new ZcError(ctx.issues)
};
}
},
'~standard': Object.freeze({
version: 1,
vendor: 'zod-compiler',
validate (value) {
const ctx = createContext(dependencies, undefined);
const status = typedParser(value, ctx);
if (status === 0) {
return {
value: ctx.output
};
} else {
return {
issues: ctx.issues
};
}
}
})
};
}
exports.ParseStatus = ParseStatus;
exports.ZcError = ZcError;
exports.ZcIssueCode = ZcIssueCode;
exports.createContext = createContext;
exports.default = standalone;
exports.defaultErrorMap = errorMap;
exports.getErrorMap = getErrorMap;
exports.setErrorMap = setErrorMap;