zod-validation-error
Version:
Wrap zod validation errors in user-friendly readable messages
1 lines • 17.6 kB
Source Map (JSON)
{"version":3,"sources":["../lib/v3/isZodErrorLike.ts","../lib/v3/ValidationError.ts","../lib/v3/isValidationError.ts","../lib/v3/isValidationErrorLike.ts","../lib/v3/fromZodIssue.ts","../lib/v3/MessageBuilder.ts","../lib/utils/NonEmptyArray.ts","../lib/utils/stringify.ts","../lib/utils/joinPath.ts","../lib/v3/config.ts","../lib/v3/errorMap.ts","../lib/v3/fromZodError.ts","../lib/v3/toValidationError.ts","../lib/v3/fromError.ts"],"sourcesContent":["import type * as zod from 'zod/v3';\n\nexport function isZodErrorLike(err: unknown): err is zod.ZodError {\n return (\n err instanceof Error &&\n err.name === 'ZodError' &&\n 'issues' in err &&\n Array.isArray(err.issues)\n );\n}\n","import { isZodErrorLike } from './isZodErrorLike.ts';\nimport type * as zod from 'zod/v3';\n\n// make zod-validation-error compatible with\n// earlier to es2022 typescript configurations\n// @see https://github.com/causaly/zod-validation-error/issues/226\nexport interface ErrorOptions {\n cause?: unknown;\n}\n\nexport class ValidationError extends Error {\n name: 'ZodValidationError';\n details: Array<zod.ZodIssue>;\n\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'ZodValidationError';\n this.details = getIssuesFromErrorOptions(options);\n }\n\n toString(): string {\n return this.message;\n }\n}\n\nfunction getIssuesFromErrorOptions(\n options?: ErrorOptions\n): Array<zod.ZodIssue> {\n if (options) {\n const cause = options.cause;\n\n if (isZodErrorLike(cause)) {\n return cause.issues;\n }\n }\n\n return [];\n}\n","import { ValidationError } from './ValidationError.ts';\n\nexport function isValidationError(err: unknown): err is ValidationError {\n return err instanceof ValidationError;\n}\n","import type { ValidationError } from './ValidationError.ts';\n\nexport function isValidationErrorLike(err: unknown): err is ValidationError {\n return err instanceof Error && err.name === 'ZodValidationError';\n}\n","import * as zod from 'zod/v3';\n\nimport {\n type MessageBuilder,\n type CreateMessageBuilderProps,\n type ZodIssue,\n createMessageBuilder,\n} from './MessageBuilder.ts';\nimport { ValidationError } from './ValidationError.ts';\n\nexport type FromZodIssueOptions =\n | {\n messageBuilder: MessageBuilder;\n }\n // maintain backwards compatibility\n | Omit<CreateMessageBuilderProps, 'maxIssuesInMessage'>;\n\nexport function fromZodIssue(\n issue: ZodIssue,\n options: FromZodIssueOptions = {}\n): ValidationError {\n const messageBuilder = createMessageBuilderFromOptions(options);\n const message = messageBuilder([issue]);\n\n return new ValidationError(message, { cause: new zod.ZodError([issue]) });\n}\n\nfunction createMessageBuilderFromOptions(\n options: FromZodIssueOptions\n): MessageBuilder {\n if ('messageBuilder' in options) {\n return options.messageBuilder;\n }\n\n return createMessageBuilder(options);\n}\n","import * as zod from 'zod/v3';\nimport { type NonEmptyArray, isNonEmptyArray } from '../utils/NonEmptyArray.ts';\nimport { joinPath } from '../utils/joinPath.ts';\nimport {\n ISSUE_SEPARATOR,\n MAX_ISSUES_IN_MESSAGE,\n PREFIX,\n PREFIX_SEPARATOR,\n UNION_SEPARATOR,\n} from './config.ts';\n\nexport type ZodIssue = zod.ZodIssue;\n\nexport type MessageBuilder = (issues: NonEmptyArray<ZodIssue>) => string;\n\nexport type CreateMessageBuilderProps = {\n issueSeparator?: string;\n unionSeparator?: string;\n prefix?: string | null;\n prefixSeparator?: string;\n includePath?: boolean;\n maxIssuesInMessage?: number;\n};\n\nexport function createMessageBuilder(\n props: CreateMessageBuilderProps = {}\n): MessageBuilder {\n const {\n issueSeparator = ISSUE_SEPARATOR,\n unionSeparator = UNION_SEPARATOR,\n prefixSeparator = PREFIX_SEPARATOR,\n prefix = PREFIX,\n includePath = true,\n maxIssuesInMessage = MAX_ISSUES_IN_MESSAGE,\n } = props;\n return (issues) => {\n const message = issues\n // limit max number of issues printed in the reason section\n .slice(0, maxIssuesInMessage)\n // format error message\n .map((issue) =>\n getMessageFromZodIssue({\n issue,\n issueSeparator,\n unionSeparator,\n includePath,\n })\n )\n // concat as string\n .join(issueSeparator);\n\n return prefixMessage(message, prefix, prefixSeparator);\n };\n}\n\nfunction getMessageFromZodIssue(props: {\n issue: ZodIssue;\n issueSeparator: string;\n unionSeparator: string;\n includePath: boolean;\n}): string {\n const { issue, issueSeparator, unionSeparator, includePath } = props;\n\n if (issue.code === zod.ZodIssueCode.invalid_union) {\n return issue.unionErrors\n .reduce<string[]>((acc, zodError) => {\n const newIssues = zodError.issues\n .map((issue) =>\n getMessageFromZodIssue({\n issue,\n issueSeparator,\n unionSeparator,\n includePath,\n })\n )\n .join(issueSeparator);\n\n if (!acc.includes(newIssues)) {\n acc.push(newIssues);\n }\n\n return acc;\n }, [])\n .join(unionSeparator);\n }\n\n if (issue.code === zod.ZodIssueCode.invalid_arguments) {\n return [\n issue.message,\n ...issue.argumentsError.issues.map((issue) =>\n getMessageFromZodIssue({\n issue,\n issueSeparator,\n unionSeparator,\n includePath,\n })\n ),\n ].join(issueSeparator);\n }\n\n if (issue.code === zod.ZodIssueCode.invalid_return_type) {\n return [\n issue.message,\n ...issue.returnTypeError.issues.map((issue) =>\n getMessageFromZodIssue({\n issue,\n issueSeparator,\n unionSeparator,\n includePath,\n })\n ),\n ].join(issueSeparator);\n }\n\n if (includePath && isNonEmptyArray(issue.path)) {\n // handle array indices\n if (issue.path.length === 1) {\n const identifier = issue.path[0];\n\n if (typeof identifier === 'number') {\n return `${issue.message} at index ${identifier}`;\n }\n }\n\n return `${issue.message} at \"${joinPath(issue.path)}\"`;\n }\n\n return issue.message;\n}\n\nfunction prefixMessage(\n message: string,\n prefix: string | null,\n prefixSeparator: string\n): string {\n if (prefix !== null) {\n if (message.length > 0) {\n return [prefix, message].join(prefixSeparator);\n }\n\n return prefix;\n }\n\n if (message.length > 0) {\n return message;\n }\n\n // if both reason and prefix are empty, return default prefix\n // to avoid having an empty error message\n return PREFIX;\n}\n","export type NonEmptyArray<T> = [T, ...T[]];\n\nexport function isNonEmptyArray<T>(value: T[]): value is NonEmptyArray<T> {\n return value.length !== 0;\n}\n","import type { util } from 'zod/v4/core';\n\nexport function stringifySymbol(symbol: symbol): string {\n return symbol.description ?? '';\n}\n\nexport type StringifyValueOptions = {\n wrapStringValueInQuote?: boolean;\n localization?: boolean | Intl.LocalesArgument;\n};\n\nexport function stringify(\n value: util.Primitive | Date,\n options: StringifyValueOptions = {}\n): string {\n switch (typeof value) {\n case 'symbol':\n return stringifySymbol(value);\n case 'bigint':\n case 'number': {\n switch (options.localization) {\n case true:\n return value.toLocaleString();\n case false:\n return value.toString();\n default:\n return value.toLocaleString(options.localization);\n }\n }\n case 'string': {\n if (options.wrapStringValueInQuote) {\n return `\"${value}\"`;\n }\n return value;\n }\n default: {\n if (value instanceof Date) {\n switch (options.localization) {\n case true:\n return value.toLocaleString();\n case false:\n return value.toISOString();\n default:\n return value.toLocaleString(options.localization);\n }\n }\n return String(value);\n }\n }\n}\n","import { stringifySymbol } from './stringify.ts';\nimport type { NonEmptyArray } from './NonEmptyArray.ts';\n\n/**\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers\n */\nconst identifierRegex = /[$_\\p{ID_Start}][$\\u200c\\u200d\\p{ID_Continue}]*/u;\n\nexport function joinPath(path: NonEmptyArray<PropertyKey>): string {\n if (path.length === 1) {\n let propertyKey = path[0];\n\n if (typeof propertyKey === 'symbol') {\n propertyKey = stringifySymbol(propertyKey);\n }\n\n return propertyKey.toString() || '\"\"';\n }\n\n return path.reduce<string>((acc, propertyKey) => {\n // handle numeric indices\n if (typeof propertyKey === 'number') {\n return acc + '[' + propertyKey.toString() + ']';\n }\n\n // handle symbols\n if (typeof propertyKey === 'symbol') {\n propertyKey = stringifySymbol(propertyKey);\n }\n\n // handle quoted values\n if (propertyKey.includes('\"')) {\n return acc + '[\"' + escapeQuotes(propertyKey) + '\"]';\n }\n\n // handle special characters\n if (!identifierRegex.test(propertyKey)) {\n return acc + '[\"' + propertyKey + '\"]';\n }\n\n // handle normal values\n const separator = acc.length === 0 ? '' : '.';\n return acc + separator + propertyKey;\n }, '');\n}\n\nfunction escapeQuotes(str: string): string {\n return str.replace(/\"/g, '\\\\\"');\n}\n","export const ISSUE_SEPARATOR = '; ';\nexport const MAX_ISSUES_IN_MESSAGE = 99; // I've got 99 problems but the b$tch ain't one\nexport const PREFIX = 'Validation error';\nexport const PREFIX_SEPARATOR = ': ';\nexport const UNION_SEPARATOR = ', or ';\n","import { fromZodIssue } from './fromZodIssue.ts';\nimport type * as zod from 'zod/v3';\n\nexport const errorMap: zod.ZodErrorMap = (issue, ctx) => {\n const error = fromZodIssue({\n ...issue,\n // fallback to the default error message\n // when issue does not have a message\n message: issue.message ?? ctx.defaultError,\n });\n\n return {\n message: error.message,\n };\n};\n","import { isNonEmptyArray } from '../utils/NonEmptyArray.ts';\nimport { fromError } from './fromError.ts';\nimport { isZodErrorLike } from './isZodErrorLike.ts';\nimport {\n createMessageBuilder,\n type CreateMessageBuilderProps,\n type MessageBuilder,\n} from './MessageBuilder.ts';\nimport { ValidationError } from './ValidationError.ts';\nimport type * as zod from 'zod/v3';\n\nexport type ZodError = zod.ZodError;\n\nexport type FromZodErrorOptions =\n | {\n messageBuilder: MessageBuilder;\n }\n // maintain backwards compatibility\n | CreateMessageBuilderProps;\n\nexport function fromZodError(\n zodError: ZodError,\n options: FromZodErrorOptions = {}\n): ValidationError {\n // perform runtime check to ensure the input is a ZodError\n // why? because people have been historically using this function incorrectly\n if (!isZodErrorLike(zodError)) {\n throw new TypeError(\n `Invalid zodError param; expected instance of ZodError. Did you mean to use the \"${fromError.name}\" method instead?`\n );\n }\n\n return fromZodErrorWithoutRuntimeCheck(zodError, options);\n}\n\nexport function fromZodErrorWithoutRuntimeCheck(\n zodError: ZodError,\n options: FromZodErrorOptions = {}\n): ValidationError {\n const zodIssues = zodError.errors;\n\n let message: string;\n if (isNonEmptyArray(zodIssues)) {\n const messageBuilder = createMessageBuilderFromOptions(options);\n message = messageBuilder(zodIssues);\n } else {\n message = zodError.message;\n }\n\n return new ValidationError(message, { cause: zodError });\n}\n\nfunction createMessageBuilderFromOptions(\n options: FromZodErrorOptions\n): MessageBuilder {\n if ('messageBuilder' in options) {\n return options.messageBuilder;\n }\n\n return createMessageBuilder(options);\n}\n","import { ValidationError } from './ValidationError.ts';\nimport { isZodErrorLike } from './isZodErrorLike.ts';\nimport {\n fromZodErrorWithoutRuntimeCheck,\n type FromZodErrorOptions,\n} from './fromZodError.ts';\n\nexport const toValidationError =\n (options: FromZodErrorOptions = {}) =>\n (err: unknown): ValidationError => {\n if (isZodErrorLike(err)) {\n return fromZodErrorWithoutRuntimeCheck(err, options);\n }\n\n if (err instanceof Error) {\n return new ValidationError(err.message, { cause: err });\n }\n\n return new ValidationError('Unknown error');\n };\n","import { toValidationError } from './toValidationError.ts';\nimport type { FromZodErrorOptions } from './fromZodError.ts';\nimport type { ValidationError } from './ValidationError.ts';\n\n/**\n * This function is a non-curried version of `toValidationError`\n */\nexport function fromError(\n err: unknown,\n options: FromZodErrorOptions = {}\n): ValidationError {\n return toValidationError(options)(err);\n}\n"],"mappings":"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","names":["zod","issue","createMessageBuilderFromOptions"]}