Skip to content

Commit eacf012

Browse files
mag123cJaeHo Jang
andauthored
fix: improve validation error messages (#46)
Co-authored-by: JaeHo Jang <[email protected]>
1 parent 309c377 commit eacf012

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

.changeset/hungry-dryers-slide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"ventyd": patch
3+
---
4+
5+
Add more descriptive validation error messages

src/standard.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export function standard<
9494
);
9595
}
9696

97-
return standardValidate(eventSchema, input) as Extract<
97+
return standardValidate(eventSchema, input, eventName) as Extract<
9898
StandardSchemaV1.InferOutput<$$EventDefinition[K]>,
9999
{ eventName: K }
100100
>;
@@ -111,6 +111,7 @@ export function standard<
111111
*
112112
* @param schema - The Standard Schema to validate against
113113
* @param input - The input value to validate
114+
* @param eventName - Optional event name for better error messages
114115
* @returns The validated and typed output
115116
* @throws {Error} If validation fails or if the schema returns a Promise
116117
*
@@ -119,15 +120,52 @@ export function standard<
119120
function standardValidate<T extends StandardSchemaV1>(
120121
schema: T,
121122
input: unknown,
123+
eventName?: string,
122124
): StandardSchemaV1.InferOutput<T> {
123125
const result = schema["~standard"].validate(input);
124126

125127
if (result instanceof Promise) {
126128
throw new Error("Promise validation result is not supported");
127129
}
128130
if (result.issues) {
129-
throw new Error("Validation failed");
131+
const formattedError = formatStandardSchemaIssues(result.issues, eventName);
132+
throw new Error(formattedError);
130133
}
131134

132135
return result.value as StandardSchemaV1.InferOutput<T>;
133136
}
137+
138+
/**
139+
* Formats Standard Schema validation issues into a readable error message.
140+
*
141+
* @param issues - Array of validation issues from Standard Schema
142+
* @param eventName - Optional event name to include in the error message
143+
* @returns Formatted error message string
144+
*
145+
* @internal
146+
*/
147+
function formatStandardSchemaIssues(
148+
issues: readonly StandardSchemaV1.Issue[],
149+
eventName?: string,
150+
): string {
151+
const header = eventName
152+
? `Validation failed: "${eventName}"`
153+
: "Validation failed";
154+
155+
const errors = issues
156+
.map((issue) => {
157+
const pathStr =
158+
issue.path && issue.path.length > 0
159+
? issue.path
160+
.map((segment) =>
161+
typeof segment === "object" ? segment.key : segment,
162+
)
163+
.join(".")
164+
: "(root)";
165+
166+
return ` - ${pathStr}: ${issue.message}`;
167+
})
168+
.join("\n");
169+
170+
return errors ? `${header}\n${errors}` : header;
171+
}

test/standard.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,44 @@ describe("Standard Schema Provider", () => {
303303
});
304304
});
305305

306+
describe("Error message formatting", () => {
307+
test("should include event name in validation error", () => {
308+
const schema = defineSchema("user", {
309+
schema: standard({
310+
event: {
311+
"user:created": v.object({
312+
eventId: v.string(),
313+
eventName: v.literal("user:created"),
314+
eventCreatedAt: v.string(),
315+
entityName: v.string(),
316+
entityId: v.string(),
317+
body: v.object({
318+
email: v.pipe(v.string(), v.email()),
319+
}),
320+
}),
321+
},
322+
state: v.object({
323+
email: v.string(),
324+
}),
325+
}),
326+
initialEventName: "user:created",
327+
});
328+
329+
expect(() => {
330+
schema.parseEventByName("user:created", {
331+
eventId: "evt-123",
332+
eventName: "user:created",
333+
eventCreatedAt: new Date().toISOString(),
334+
entityName: "user",
335+
entityId: "usr-123",
336+
body: {
337+
email: "not-an-email",
338+
},
339+
});
340+
}).toThrow('Validation failed: "user:created"');
341+
});
342+
});
343+
306344
describe("Type inference", () => {
307345
test("should infer correct event types", () => {
308346
const schema = defineSchema("product", {

0 commit comments

Comments
 (0)