UNPKG

zod-compiler

Version:

Compile Zod schemas to fast parsers or TypeScript types

541 lines (532 loc) 20.9 kB
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;