Add sanitized Zod wire error summaries

This commit is contained in:
Timothy J. Aveni
2026-06-26 08:17:54 -07:00
parent 990c18f00b
commit 242aa28d5e
10 changed files with 86 additions and 2 deletions
+1
View File
@@ -1,6 +1,7 @@
import z, { type ZodTypeAny, type output as ZodOutput } from "zod";
import { type RuntimeErrorRequestParams } from "./rpc.js";
export * from "./rpc.js";
export * from "./validation.js";
export type PayloadMode = "snapshot" | "delta";
export type SchemaAnnotation = {
type: string;
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@ import WebSocket from "ws";
import z, {} from "zod";
import { BootRequestParamsSchema, CallRequestParamsSchema, ContextCloseRequestParamsSchema, ContextOpenRequestParamsSchema, EventSubscribeRequestParamsSchema, EventUnsubscribeRequestParamsSchema, ServerMessageSchema, TargetContextOpenRequestParamsSchema, ValueGetRequestParamsSchema, ValueUnwatchRequestParamsSchema, ValueWatchRequestParamsSchema, parseJson, sendClientMessage, } from "./rpc.js";
export * from "./rpc.js";
export * from "./validation.js";
export const QUIXOS_MEDIA_JSON_SCHEMA_KEY = "x-quixos-media";
const readSchemaMeta = (schema) => {
try {
+1 -1
View File
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
import type { ZodError } from "zod";
export type WireValidationErrorSummary = {
publicMessage: string;
debugMessage: string;
fields: string[];
};
export declare const summarizeZodErrorForWire: (error: ZodError, { label, maxIssues, }?: {
label?: string;
maxIssues?: number;
}) => WireValidationErrorSummary;
//# sourceMappingURL=validation.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAY,MAAM,KAAK,CAAC;AAc9C,MAAM,MAAM,0BAA0B,GAAG;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,OAAO,QAAQ,EACf,wBAGG;IACD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACf,KACL,0BAcF,CAAC"}
+24
View File
@@ -0,0 +1,24 @@
const DEFAULT_MAX_ISSUES = 6;
const issuePath = (issue) => {
const path = issue.path
.map((segment) => String(segment))
.filter((segment) => segment.length > 0)
.join(".");
return path.length > 0 ? path : "<root>";
};
const unique = (values) => [...new Set(values)];
export const summarizeZodErrorForWire = (error, { label = "message", maxIssues = DEFAULT_MAX_ISSUES, } = {}) => {
const issues = error.issues.slice(0, maxIssues);
const fields = unique(issues.map(issuePath));
const fieldSummary = fields.length > 0 ? fields.join(", ") : "<unknown>";
const suffix = error.issues.length > maxIssues ? ", ..." : "";
const debugIssues = issues
.map((issue) => `${issuePath(issue)}:${issue.code}`)
.join(", ");
return {
publicMessage: `Invalid ${label}: invalid or missing field(s): ${fieldSummary}${suffix}`,
debugMessage: `Invalid ${label}: ${debugIssues}${suffix}`,
fields,
};
};
//# sourceMappingURL=validation.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/validation.ts"],"names":[],"mappings":"AAEA,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,MAAM,SAAS,GAAG,CAAC,KAAe,EAAE,EAAE;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI;SACpB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;SACjC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;SACvC,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3C,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,MAAgB,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAQ1D,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,KAAe,EACf,EACE,KAAK,GAAG,SAAS,EACjB,SAAS,GAAG,kBAAkB,MAI5B,EAAE,EACsB,EAAE;IAC9B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7C,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IACzE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,MAAM,WAAW,GAAG,MAAM;SACvB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;SACnD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,OAAO;QACL,aAAa,EAAE,WAAW,KAAK,kCAAkC,YAAY,GAAG,MAAM,EAAE;QACxF,YAAY,EAAE,WAAW,KAAK,KAAK,WAAW,GAAG,MAAM,EAAE;QACzD,MAAM;KACP,CAAC;AACJ,CAAC,CAAC"}
+1
View File
@@ -23,6 +23,7 @@ import {
} from "./rpc.js";
export * from "./rpc.js";
export * from "./validation.js";
export type PayloadMode = "snapshot" | "delta";
+44
View File
@@ -0,0 +1,44 @@
import type { ZodError, ZodIssue } from "zod";
const DEFAULT_MAX_ISSUES = 6;
const issuePath = (issue: ZodIssue) => {
const path = issue.path
.map((segment) => String(segment))
.filter((segment) => segment.length > 0)
.join(".");
return path.length > 0 ? path : "<root>";
};
const unique = (values: string[]) => [...new Set(values)];
export type WireValidationErrorSummary = {
publicMessage: string;
debugMessage: string;
fields: string[];
};
export const summarizeZodErrorForWire = (
error: ZodError,
{
label = "message",
maxIssues = DEFAULT_MAX_ISSUES,
}: {
label?: string;
maxIssues?: number;
} = {},
): WireValidationErrorSummary => {
const issues = error.issues.slice(0, maxIssues);
const fields = unique(issues.map(issuePath));
const fieldSummary = fields.length > 0 ? fields.join(", ") : "<unknown>";
const suffix = error.issues.length > maxIssues ? ", ..." : "";
const debugIssues = issues
.map((issue) => `${issuePath(issue)}:${issue.code}`)
.join(", ");
return {
publicMessage: `Invalid ${label}: invalid or missing field(s): ${fieldSummary}${suffix}`,
debugMessage: `Invalid ${label}: ${debugIssues}${suffix}`,
fields,
};
};