Skip to content

Commit fe8f06f

Browse files
committed
🔧 fix: use instanceof checks and wrap toResponse() in try-catch
1 parent 95f825c commit fe8f06f

File tree

3 files changed

+56
-10
lines changed

3 files changed

+56
-10
lines changed

src/compose.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AnyElysia } from './index'
22

3-
import { Value } from '@sinclair/typebox/value'
3+
import { Value, TransformDecodeError } from '@sinclair/typebox/value'
44
import {
55
Kind,
66
OptionalKind,
@@ -2520,6 +2520,8 @@ export const composeErrorHandler = (app: AnyElysia) => {
25202520
`mapResponse,` +
25212521
`ERROR_CODE,` +
25222522
`ElysiaCustomStatusResponse,` +
2523+
`ValidationError,` +
2524+
`TransformDecodeError,` +
25232525
allocateIf(`onError,`, app.event.error) +
25242526
allocateIf(`afterResponse,`, app.event.afterResponse) +
25252527
allocateIf(`trace,`, app.event.trace) +
@@ -2596,11 +2598,14 @@ export const composeErrorHandler = (app: AnyElysia) => {
25962598
hasTrace || !!hooks.afterResponse?.length ? 'context.response = ' : ''
25972599

25982600
fnLiteral +=
2599-
`if(typeof error?.toResponse==='function'&&error.constructor.name!=="ValidationError"&&error.constructor.name!=="TransformDecodeError"){` +
2601+
`if(typeof error?.toResponse==='function'&&!(error instanceof ValidationError)&&!(error instanceof TransformDecodeError)){` +
2602+
`try{` +
26002603
`let raw=error.toResponse()\n` +
26012604
`if(typeof raw?.then==='function')raw=await raw\n` +
26022605
`if(raw instanceof Response)set.status=raw.status\n` +
26032606
`context.response=context.responseValue=raw\n` +
2607+
`}catch(toResponseError){\n` +
2608+
`}\n` +
26042609
`}\n`
26052610

26062611
if (app.event.error)
@@ -2657,7 +2662,7 @@ export const composeErrorHandler = (app: AnyElysia) => {
26572662
}
26582663

26592664
fnLiteral +=
2660-
`if(error.constructor.name==="ValidationError"||error.constructor.name==="TransformDecodeError"){\n` +
2665+
`if(error instanceof ValidationError||error instanceof TransformDecodeError){\n` +
26612666
`if(error.error)error=error.error\n` +
26622667
`set.status=error.status??422\n` +
26632668
afterResponse() +
@@ -2714,6 +2719,8 @@ export const composeErrorHandler = (app: AnyElysia) => {
27142719
mapResponse: app['~adapter'].handler.mapResponse,
27152720
ERROR_CODE,
27162721
ElysiaCustomStatusResponse,
2722+
ValidationError,
2723+
TransformDecodeError,
27172724
onError: app.event.error?.map(mapFn),
27182725
afterResponse: app.event.afterResponse?.map(mapFn),
27192726
trace: app.event.trace?.map(mapFn),

src/dynamic-handle.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -686,13 +686,18 @@ export const createDynamicErrorHandler = (app: AnyElysia) => {
686686

687687
// @ts-expect-error
688688
if (typeof error?.toResponse === 'function' &&
689-
error.constructor.name !== 'ValidationError' &&
690-
error.constructor.name !== 'TransformDecodeError') {
691-
// @ts-expect-error
692-
let raw = error.toResponse()
693-
if (typeof raw?.then === 'function') raw = await raw
694-
if (raw instanceof Response) context.set.status = raw.status
695-
context.response = raw
689+
!(error instanceof ValidationError) &&
690+
!(error instanceof TransformDecodeError)) {
691+
try {
692+
// @ts-expect-error
693+
let raw = error.toResponse()
694+
if (typeof raw?.then === 'function') raw = await raw
695+
if (raw instanceof Response) context.set.status = raw.status
696+
context.response = raw
697+
} catch (toResponseError) {
698+
// If toResponse() throws, fall through to normal error handling
699+
// Don't set context.response so onError hooks will run
700+
}
696701
}
697702

698703
if (!context.response && app.event.error)

test/core/handle-error.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,4 +515,38 @@ describe('Handle Error', () => {
515515
expect(await res.json()).toEqual({ error: 'non-error async' })
516516
expect(res.status).toBe(418)
517517
})
518+
519+
it('handle toResponse() that throws an error', async () => {
520+
class BrokenError extends Error {
521+
toResponse() {
522+
throw new Error('toResponse failed')
523+
}
524+
}
525+
526+
const app = new Elysia().get('/', () => {
527+
throw new BrokenError('original error')
528+
})
529+
530+
const res = await app.handle(req('/'))
531+
532+
expect(res.status).toBe(500)
533+
expect(await res.text()).toBe('original error')
534+
})
535+
536+
it('handle async toResponse() that throws an error', async () => {
537+
class BrokenAsyncError extends Error {
538+
async toResponse() {
539+
throw new Error('async toResponse failed')
540+
}
541+
}
542+
543+
const app = new Elysia().get('/', () => {
544+
throw new BrokenAsyncError('original error')
545+
})
546+
547+
const res = await app.handle(req('/'))
548+
549+
expect(res.status).toBe(500)
550+
expect(await res.text()).toBe('original error')
551+
})
518552
})

0 commit comments

Comments
 (0)