diff --git a/src/error.ts b/src/error.ts index 6ec92c08..1478e5e1 100644 --- a/src/error.ts +++ b/src/error.ts @@ -431,7 +431,33 @@ export class ValidationError extends Error { Object.setPrototypeOf(this, ValidationError.prototype) } - get all() { + get all(): MapValueError[] { + // Handle standard schema validators (Zod, Valibot, etc.) + if ( + // @ts-ignore + this.validator?.provider === 'standard' || + '~standard' in this.validator || + // @ts-ignore + ('schema' in this.validator && this.validator.schema && '~standard' in this.validator.schema) + ) { + const standard = // @ts-ignore + ('~standard' in this.validator + ? this.validator + : // @ts-ignore + this.validator.schema)['~standard'] + + const issues = standard.validate(this.value).issues + + // Map standard schema issues to the expected format + return issues?.map((issue: any) => ({ + summary: issue.message, + path: issue.path?.join('.') || 'root', + message: issue.message, + value: this.value + })) || [] + } + + // Handle TypeBox validators return 'Errors' in this.validator ? [...this.validator.Errors(this.value)].map(mapValueError) : // @ts-ignore diff --git a/test/lifecycle/error.test.ts b/test/lifecycle/error.test.ts index d0b8c8e1..743708f4 100644 --- a/test/lifecycle/error.test.ts +++ b/test/lifecycle/error.test.ts @@ -9,6 +9,7 @@ import { } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' +import * as z from 'zod' describe('error', () => { it('use custom 404', async () => { @@ -450,4 +451,81 @@ describe('error', () => { expect(value.type).toBe('validation') expect(value.message).not.toStartWith('{') }) + + it('ValidationError.all works with Zod validators', async () => { + const app = new Elysia() + .onError(({ error, code }) => { + if (error instanceof ValidationError) { + const errors = error.all + + return { + message: 'Validation failed', + errors: errors + } + } + }) + .post('/login', ({ body }) => body, { + body: z.object({ + username: z.string(), + password: z.string() + }) + }) + + const res = await app.handle(post('/login', {})) + const data = (await res.json()) as any + + expect(data).toHaveProperty('message', 'Validation failed') + expect(data).toHaveProperty('errors') + expect(data.errors).toBeArray() + expect(data.errors.length).toBeGreaterThan(0) + expect(res.status).toBe(422) + }) + + it('ValidationError.all provides error details with Zod validators', async () => { + const app = new Elysia() + .onError(({ error, code }) => { + expect(code).toBe('VALIDATION') + if (error instanceof ValidationError) { + const errors = error.all + + return { + message: 'Validation failed', + errors: errors.map((e: any) => ({ + path: e.path, + message: e.message, + summary: e.summary + })) + } + } + }) + .post('/user', ({ body }) => body, { + body: z.object({ + name: z.string().min(3), + email: z.string(), + age: z.number().min(18) + }) + }) + + const res = await app.handle( + post('/user', { + name: 'ab', + email: 'invalid', + age: 10 + }) + ) + const data = (await res.json()) as any + + expect(data).toHaveProperty('message', 'Validation failed') + expect(data).toHaveProperty('errors') + expect(data.errors).toBeArray() + expect(data.errors.length).toBeGreaterThan(0) + + for (const error of data.errors) { + expect(error).toHaveProperty('path') + expect(error).toHaveProperty('message') + expect(error).toHaveProperty('summary') + } + + expect(res.status).toBe(422) + }) })