Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,16 @@ const coerceTransformDecodeError = (
`try{${fnLiteral}}catch(error){` +
`if(error.constructor.name === 'TransformDecodeError'){` +
`c.set.status=422\n` +
// Fix #1660: When error.error is a ValidationError with wrong 'type' field,
// extract valueError and fix its path using TransformDecodeError.path
`if(error.error?.constructor?.name === 'ValidationError' && error.error.valueError){` +
`const ve=error.error.valueError;` +
`const fe={...ve,path:error.path};` +
`const errs={[Symbol.iterator]:function*(){yield fe},First:()=>fe};` +
`throw new ValidationError('${type}',validator.${type},${value},${allowUnsafeValidationDetails},errs)` +
`}` +
// If error.error exists but is not a ValidationError (e.g., NotFoundError),
// re-throw it directly. Otherwise, create new ValidationError.
`throw error.error ?? new ValidationError('${type}',validator.${type},${value},${allowUnsafeValidationDetails})}` +
`}`

Expand Down
63 changes: 63 additions & 0 deletions test/validator/query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1126,4 +1126,67 @@ describe('Query Validator', () => {
expect(invalid1.status).toBe(422)
expect(invalid2.status).toBe(422)
})

// Issue #1660: Validation error metadata should have correct 'on' and 'property' fields
// for constraint violations (min/max), not just type errors
it('should return correct validation error fields for constraint violations', async () => {
const app = new Elysia().get('/', ({ query }) => query, {
query: t.Object({
limit: t.Number({ minimum: 1, maximum: 100, default: 10 })
})
})

// Test constraint violation (value exceeds maximum)
const constraintViolation = await app.handle(
new Request('http://localhost:3000/?limit=200')
)
expect(constraintViolation.status).toBe(422)

const constraintError = await constraintViolation.json()
expect(constraintError.type).toBe('validation')
expect(constraintError.on).toBe('query')
expect(constraintError.property).toBe('/limit')
expect(constraintError.message).toContain('100')

// Test type error (non-numeric string)
const typeError = await app.handle(
new Request('http://localhost:3000/?limit=invalid')
)
expect(typeError.status).toBe(422)

const typeErrorBody = await typeError.json()
expect(typeErrorBody.type).toBe('validation')
expect(typeErrorBody.on).toBe('query')
expect(typeErrorBody.property).toBe('/limit')

// Test valid value
const validResponse = await app.handle(
new Request('http://localhost:3000/?limit=50')
)
expect(validResponse.status).toBe(200)
expect(await validResponse.json()).toEqual({ limit: 50 })
})

it('should return correct validation error fields for nested constraint violations', async () => {
const app = new Elysia().get('/', ({ query }) => query, {
query: t.Object({
config: t.Object({
maxItems: t.Number({ minimum: 1, maximum: 1000 })
})
})
})

const filter = JSON.stringify({ maxItems: 9999 })
const response = await app.handle(
new Request(`http://localhost:3000/?config=${filter}`)
)

expect(response.status).toBe(422)
const error = await response.json()
expect(error.type).toBe('validation')
expect(error.on).toBe('query')
// Path captures the query parameter where the constraint violation occurred
expect(error.property).toBe('/config')
expect(error.message).toContain('1000')
})
})
Loading