@ptolemy2002/zod-utils 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ Various utilities for working with Zod schemas.
7
7
  - [clone](docs/util/clone.md) - Utility for cloning Zod schemas without affecting the original
8
8
  - [function](docs/util/function.md) - Schema factory for validating callable functions with input/output schemas
9
9
  - [interpret](docs/util/interpret.md) - Utilities for formatting Zod errors as strings
10
+ - [issuePathStartsWith](docs/util/issuePathStartsWith.md) - Utility for checking whether a Zod issue path starts with a given prefix
10
11
  - [prefixIssuePath](docs/util/prefixIssuePath.md) - Utility for prepending a path prefix to a Zod issue
11
12
  - [typeGuards](docs/util/typeGuards.md) - Type guards for Zod-related values
12
13
  - [validate](docs/util/validate.md) - Schema-wrapping factories for creating validators
@@ -1,9 +1,23 @@
1
- import z, { ZodArray, ZodUnknown } from "zod";
1
+ import z, { ZodArray, ZodType, ZodUnknown } from "zod";
2
2
  import { $ZodFunctionArgs, $ZodFunctionOut } from "zod/v4/core";
3
+ export type TrialErrorMode = "allow" | "forbid" | "require" | ((e: unknown) => boolean) | {
4
+ require: (e: unknown) => boolean;
5
+ };
6
+ export type FunctionTrial<Input extends unknown[]> = {
7
+ id?: string;
8
+ input: Input;
9
+ outputSchema?: ZodType;
10
+ error?: TrialErrorMode;
11
+ errorStringify?: (e: unknown) => string;
12
+ };
3
13
  export type ZodFunctionParseOptions<In extends $ZodFunctionArgs, Out extends $ZodFunctionOut> = {
4
14
  input?: In;
5
15
  output?: Out;
6
16
  inputPath?: PropertyKey | PropertyKey[];
7
17
  outputPath?: PropertyKey | PropertyKey[];
8
18
  };
9
- export declare function zodFunctionSchema<In extends $ZodFunctionArgs = ZodArray<ZodUnknown>, Out extends $ZodFunctionOut = ZodUnknown>(options?: ZodFunctionParseOptions<In, Out>): z.ZodPipe<z.ZodAny, z.ZodTransform<(...args: z.core.output<In>) => z.core.output<Out>, any>>;
19
+ export type ZodFunctionSchemaOptions<In extends $ZodFunctionArgs, Out extends $ZodFunctionOut> = {
20
+ trials?: FunctionTrial<z.infer<In>>[];
21
+ } & ZodFunctionParseOptions<In, Out>;
22
+ export declare function zodValidatedFunction<In extends $ZodFunctionArgs = ZodArray<ZodUnknown>, Out extends $ZodFunctionOut = ZodUnknown>(func: unknown, { inputPath, outputPath, input, output }?: ZodFunctionParseOptions<In, Out>): (...args: z.infer<In>) => z.core.output<Out>;
23
+ export declare function zodFunctionSchema<In extends $ZodFunctionArgs = ZodArray<ZodUnknown>, Out extends $ZodFunctionOut = ZodUnknown>({ trials, inputPath, outputPath, ...options }?: ZodFunctionSchemaOptions<In, Out>): z.ZodPipe<z.ZodAny, z.ZodTransform<(...args: z.core.output<In>) => z.core.output<Out>, any>>;
package/dist/function.js CHANGED
@@ -36,12 +36,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.zodValidatedFunction = zodValidatedFunction;
39
40
  exports.zodFunctionSchema = zodFunctionSchema;
40
41
  const zod_1 = __importStar(require("zod"));
41
42
  const typeGuards_1 = require("./typeGuards");
42
43
  const prefixIssuePath_1 = require("./prefixIssuePath");
44
+ const issuePathStartsWith_1 = require("./issuePathStartsWith");
43
45
  const is_callable_1 = __importDefault(require("is-callable"));
44
- function zodFunctionParse(func, { inputPath = "args", outputPath = "return", input = zod_1.default.array(zod_1.default.unknown()), output = zod_1.default.unknown() }) {
46
+ const interpret_1 = require("./interpret");
47
+ function zodValidatedFunction(func, { inputPath = "args", outputPath = "return", input = zod_1.default.array(zod_1.default.unknown()), output = zod_1.default.unknown() } = {}) {
45
48
  if (!(0, is_callable_1.default)(func)) {
46
49
  throw new zod_1.ZodError([{
47
50
  message: `Expected a callable function, but received ${typeof func}`,
@@ -51,32 +54,114 @@ function zodFunctionParse(func, { inputPath = "args", outputPath = "return", inp
51
54
  }]);
52
55
  }
53
56
  const functionFactory = zod_1.default.function({ input, output });
54
- const wrappedFunction = (setReachedCaller, ...args) => {
57
+ const wrappedFunction = (setReachedCaller, setFinishedCall, ...args) => {
55
58
  setReachedCaller(true);
56
- return func(...args);
59
+ const result = func(...args);
60
+ setFinishedCall(true);
61
+ return result;
57
62
  };
58
63
  return (...args) => {
59
64
  // This is how we will differentiate between argument and
60
65
  // parameter validation errors
61
66
  let reachedCaller = false;
67
+ let finishedCall = false;
62
68
  try {
63
- const implementedFunction = functionFactory.implement(((...args) => wrappedFunction((v) => (reachedCaller = v), ...args)));
69
+ const implementedFunction = functionFactory.implement(((...args) => wrappedFunction((v) => (reachedCaller = v), (v) => (finishedCall = v), ...args)));
64
70
  return implementedFunction(...args);
65
71
  }
66
72
  catch (e) {
67
73
  if ((0, typeGuards_1.isZodError)(e)) {
68
- e = new zod_1.ZodError(e.issues.map(i => (0, prefixIssuePath_1.prefixZodIssuePath)(i, reachedCaller ? outputPath : inputPath)));
74
+ if (!reachedCaller || finishedCall)
75
+ e = new zod_1.ZodError(e.issues.map(i => (0, prefixIssuePath_1.prefixZodIssuePath)(i, reachedCaller ? outputPath : inputPath)));
69
76
  }
70
77
  throw e;
71
78
  }
79
+ finally {
80
+ // Reset reachedCaller and finishedCall for the next call
81
+ reachedCaller = false;
82
+ finishedCall = false;
83
+ }
72
84
  };
73
85
  }
74
- function zodFunctionSchema(options = {}) {
86
+ function zodFunctionSchema({ trials = [], inputPath = "args", outputPath = "return", ...options } = {}) {
75
87
  // Transform the input value using zodFunctionParse
76
88
  // so that this schema can be used to validate functions
77
89
  return zod_1.default.any().transform((v, ctx) => {
78
90
  try {
79
- return zodFunctionParse(v, options);
91
+ const result = zodValidatedFunction(v, {
92
+ inputPath, outputPath,
93
+ ...options
94
+ });
95
+ // That zodValidatedFunction call guarantees that this is a function.
96
+ // We will not call it if there are no trials,
97
+ // as functions with side effects would be problematic to test.
98
+ if (trials.length > 0) {
99
+ trials.forEach((trial, i) => {
100
+ var _a;
101
+ const id = (_a = trial.id) !== null && _a !== void 0 ? _a : `trial_${i}`;
102
+ const { outputSchema = zod_1.default.unknown(), error, errorStringify = (e) => {
103
+ if ((0, typeGuards_1.isZodError)(e))
104
+ return (0, interpret_1.interpretZodError)(e, { multiline: false });
105
+ if (e instanceof Error)
106
+ return e.message;
107
+ return String(e);
108
+ } } = trial;
109
+ const requiresError = error === "require" || (typeof error === "object" && error !== null);
110
+ const isExpectedError = (err) => {
111
+ if (error === "allow" || error === "require")
112
+ return true;
113
+ if (error === undefined || error === "forbid")
114
+ return false;
115
+ if (typeof error === "function")
116
+ return error(err);
117
+ return error.require(err);
118
+ };
119
+ try {
120
+ const output = result(...trial.input);
121
+ if (requiresError) {
122
+ ctx.addIssue({
123
+ code: "custom",
124
+ message: "Unexpected Success",
125
+ path: [id],
126
+ });
127
+ return;
128
+ }
129
+ const { success: outputSuccess, error: outputError } = outputSchema.safeParse(output);
130
+ if (!outputSuccess) {
131
+ // Indicate the trial where the error occurred to each issue.
132
+ // The fact that the error is in the output is already indicated,
133
+ // courtesy of the wrapper zodValidatedFunction provides.
134
+ outputError.issues.forEach(issue => ctx.addIssue({
135
+ ...issue,
136
+ path: [id, ...issue.path]
137
+ }));
138
+ }
139
+ }
140
+ catch (err) {
141
+ if ((0, typeGuards_1.isZodError)(err)) {
142
+ if (err.issues.every(issue => (0, issuePathStartsWith_1.issuePathStartsWith)(issue.path, inputPath) ||
143
+ (0, issuePathStartsWith_1.issuePathStartsWith)(issue.path, outputPath))) {
144
+ // Indicate the trial where the error occurred to each issue.
145
+ // The fact that the error is in either the input or output is
146
+ // already indicated, courtesy of the wrapper zodValidatedFunction provides.
147
+ err.issues.forEach(issue => ctx.addIssue({
148
+ ...issue,
149
+ path: [id, ...issue.path]
150
+ }));
151
+ return;
152
+ }
153
+ }
154
+ if (!isExpectedError(err)) {
155
+ ctx.addIssue({
156
+ code: "custom",
157
+ message: `Unexpected Error: ${errorStringify(err)}`,
158
+ path: [id],
159
+ });
160
+ }
161
+ }
162
+ });
163
+ }
164
+ return result;
80
165
  }
81
166
  catch (e) {
82
167
  if ((0, typeGuards_1.isZodError)(e)) {
package/dist/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export * from './interpret';
5
5
  export * from './validate';
6
6
  export * from './clone';
7
7
  export * from './prefixIssuePath';
8
+ export * from './issuePathStartsWith';
package/dist/index.js CHANGED
@@ -21,3 +21,4 @@ __exportStar(require("./interpret"), exports);
21
21
  __exportStar(require("./validate"), exports);
22
22
  __exportStar(require("./clone"), exports);
23
23
  __exportStar(require("./prefixIssuePath"), exports);
24
+ __exportStar(require("./issuePathStartsWith"), exports);
@@ -1,3 +1,7 @@
1
- import { ZodError } from 'zod';
2
- import { $ZodError } from 'zod/v4/core';
3
- export declare function interpretZodError(err: ZodError | $ZodError, prefix?: PropertyKey | PropertyKey[]): string;
1
+ import { InterpretableZodError, InterpretableZodIssue } from './types';
2
+ export type InterpretZodErrorOptions = {
3
+ prefix?: PropertyKey | PropertyKey[];
4
+ multiline?: boolean;
5
+ };
6
+ export declare function interpretZodError(err: InterpretableZodError, options?: PropertyKey | PropertyKey[] | InterpretZodErrorOptions): string;
7
+ export declare function interpretZodIssue(issue: InterpretableZodIssue, options?: PropertyKey | PropertyKey[] | InterpretZodErrorOptions): string;
package/dist/interpret.js CHANGED
@@ -1,42 +1,25 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
35
5
  Object.defineProperty(exports, "__esModule", { value: true });
36
6
  exports.interpretZodError = interpretZodError;
37
- const zod_1 = __importStar(require("zod"));
7
+ exports.interpretZodIssue = interpretZodIssue;
8
+ const zod_1 = __importDefault(require("zod"));
38
9
  const prefixIssuePath_1 = require("./prefixIssuePath");
39
- function interpretZodError(err, prefix = "") {
40
- const modifiedErr = new zod_1.ZodError(err.issues.map(issue => (0, prefixIssuePath_1.prefixZodIssuePath)(issue, prefix)));
41
- return zod_1.default.prettifyError(modifiedErr);
10
+ function interpretZodError(err, options = "") {
11
+ if (typeof options !== "object" || Array.isArray(options))
12
+ options = { prefix: options };
13
+ const { multiline = true, prefix = "" } = options;
14
+ const modifiedErr = {
15
+ issues: err.issues.map(issue => (0, prefixIssuePath_1.prefixZodIssuePath)(issue, prefix))
16
+ };
17
+ let result = zod_1.default.prettifyError(modifiedErr);
18
+ if (!multiline) {
19
+ result = result.replace(/\n\s*→/g, " →");
20
+ }
21
+ return result;
22
+ }
23
+ function interpretZodIssue(issue, options = "") {
24
+ return interpretZodError({ issues: [issue] }, options);
42
25
  }
@@ -0,0 +1 @@
1
+ export declare function issuePathStartsWith(issuePath: PropertyKey[], prefix: PropertyKey | PropertyKey[]): boolean;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.issuePathStartsWith = issuePathStartsWith;
4
+ function issuePathStartsWith(issuePath, prefix) {
5
+ if (!Array.isArray(prefix))
6
+ prefix = [prefix];
7
+ if (issuePath.length < prefix.length)
8
+ return false;
9
+ return prefix.every((segment, i) => issuePath[i] === segment);
10
+ }
@@ -1,2 +1,2 @@
1
- import z from "zod";
2
- export declare function prefixZodIssuePath(issue: z.core.$ZodIssue, prefix: PropertyKey | PropertyKey[]): z.core.$ZodIssue;
1
+ import { InterpretableZodIssue } from "./types";
2
+ export declare function prefixZodIssuePath<I extends InterpretableZodIssue>(issue: I, prefix: PropertyKey | PropertyKey[]): I;
@@ -2,9 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.prefixZodIssuePath = prefixZodIssuePath;
4
4
  function prefixZodIssuePath(issue, prefix) {
5
+ var _a;
5
6
  if (!Array.isArray(prefix))
6
7
  prefix = [prefix];
7
- const newPath = [...prefix, ...issue.path]
8
+ const newPath = [...prefix, ...((_a = issue.path) !== null && _a !== void 0 ? _a : [])]
8
9
  .filter(p => typeof p !== "string" || p.length > 0);
9
10
  if (newPath.length === 0)
10
11
  newPath.push("(root)");
package/dist/types.d.ts CHANGED
@@ -18,3 +18,10 @@ export type ZodValidateWithErrorsOptions = {
18
18
  prefix?: string | string[];
19
19
  };
20
20
  export type MaybeZodOptional<ZT extends ZodType> = ZT | ZodOptional<ZT>;
21
+ export type InterpretableZodIssue = Readonly<{
22
+ message: string;
23
+ path?: PropertyKey[];
24
+ }>;
25
+ export type InterpretableZodError = Readonly<{
26
+ issues: InterpretableZodIssue[];
27
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ptolemy2002/zod-utils",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "private": false,
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",