Skip to content

Commit 0b58e52

Browse files
committed
🔧 chore: preserve toResponse() status before mapping, let toResponse() decide the final status and resolve Async toResponse() still collapsing to 500
1 parent 0f690ba commit 0b58e52

File tree

4 files changed

+116
-5
lines changed

4 files changed

+116
-5
lines changed

src/adapter/bun/handler.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,19 @@ export const errorToResponse = (error: Error, set?: Context['set']) => {
530530
// @ts-expect-error
531531
if (typeof error?.toResponse === 'function') {
532532
// @ts-expect-error
533-
return mapResponse(error.toResponse(), set ?? { headers: {}, status: 200, redirect: '' })
533+
const raw = error.toResponse()
534+
const targetSet =
535+
set ?? ({ headers: {}, status: 200, redirect: '' } as Context['set'])
536+
const apply = (resolved: unknown) => {
537+
if (resolved instanceof Response) targetSet.status = resolved.status
538+
return mapResponse(resolved, targetSet)
539+
}
540+
541+
// @ts-expect-error
542+
return typeof raw?.then === 'function'
543+
? // @ts-expect-error
544+
raw.then(apply)
545+
: apply(raw)
534546
}
535547

536548
return new Response(

src/adapter/web-standard/handler.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,19 @@ export const errorToResponse = (error: Error, set?: Context['set']) => {
563563
// @ts-expect-error
564564
if (typeof error?.toResponse === 'function') {
565565
// @ts-expect-error
566-
return mapResponse(error.toResponse(), set ?? { headers: {}, status: 200, redirect: '' })
566+
const raw = error.toResponse()
567+
const targetSet =
568+
set ?? ({ headers: {}, status: 200, redirect: '' } as Context['set'])
569+
const apply = (resolved: unknown) => {
570+
if (resolved instanceof Response) targetSet.status = resolved.status
571+
return mapResponse(resolved, targetSet)
572+
}
573+
574+
// @ts-expect-error
575+
return typeof raw?.then === 'function'
576+
? // @ts-expect-error
577+
raw.then(apply)
578+
: apply(raw)
567579
}
568580

569581
return new Response(

src/compose.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2603,10 +2603,14 @@ export const composeErrorHandler = (app: AnyElysia) => {
26032603
// BUT exclude ValidationError and TransformDecodeError to allow proper validation error handling
26042604
fnLiteral +=
26052605
`if(typeof error?.toResponse==='function'&&error.constructor.name!=="ValidationError"&&error.constructor.name!=="TransformDecodeError"){` +
2606-
`const errorResponse=error.toResponse()\n` +
2607-
`if(errorResponse instanceof Response)set.status=errorResponse.status\n` +
2606+
`const raw=error.toResponse()\n` +
2607+
`const apply=(resolved)=>{` +
2608+
`if(resolved instanceof Response)set.status=resolved.status\n` +
26082609
afterResponse() +
2609-
`return context.response=context.responseValue=mapResponse(${saveResponse}errorResponse,set${adapter.mapResponseContext})\n` +
2610+
`return context.response=context.responseValue=mapResponse(${saveResponse}resolved,set${adapter.mapResponseContext})\n` +
2611+
`}\n` +
2612+
`if(typeof raw?.then==='function')return raw.then(apply)\n` +
2613+
`return apply(raw)\n` +
26102614
`}\n`
26112615

26122616
if (app.event.error)

test/core/handle-error.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,4 +432,87 @@ describe('Handle Error', () => {
432432
expect(res.status).toBe(418)
433433
expect(res.headers.get('X-Custom-Header')).toBe('custom-value')
434434
})
435+
436+
it('handle async toResponse() when thrown', async () => {
437+
class AsyncError extends Error {
438+
async toResponse() {
439+
// Simulate async operation
440+
await new Promise(resolve => setTimeout(resolve, 10))
441+
return Response.json({ error: 'async error' }, { status: 418 })
442+
}
443+
}
444+
445+
const app = new Elysia().get('/', () => {
446+
throw new AsyncError()
447+
})
448+
449+
const res = await app.handle(req('/'))
450+
451+
expect(await res.json()).toEqual({ error: 'async error' })
452+
expect(res.status).toBe(418)
453+
})
454+
455+
it('handle async toResponse() when returned', async () => {
456+
class AsyncError extends Error {
457+
async toResponse() {
458+
// Simulate async operation
459+
await new Promise(resolve => setTimeout(resolve, 10))
460+
return Response.json({ error: 'async error' }, { status: 418 })
461+
}
462+
}
463+
464+
const app = new Elysia().get('/', () => {
465+
return new AsyncError()
466+
})
467+
468+
const res = await app.handle(req('/'))
469+
470+
expect(await res.json()).toEqual({ error: 'async error' })
471+
expect(res.status).toBe(418)
472+
})
473+
474+
it('handle async toResponse() with custom headers', async () => {
475+
class AsyncErrorWithHeaders extends Error {
476+
async toResponse() {
477+
await new Promise(resolve => setTimeout(resolve, 10))
478+
return Response.json(
479+
{ error: 'async with headers' },
480+
{
481+
status: 419,
482+
headers: {
483+
'X-Async-Header': 'async-value'
484+
}
485+
}
486+
)
487+
}
488+
}
489+
490+
const app = new Elysia().get('/', () => {
491+
throw new AsyncErrorWithHeaders()
492+
})
493+
494+
const res = await app.handle(req('/'))
495+
496+
expect(await res.json()).toEqual({ error: 'async with headers' })
497+
expect(res.status).toBe(419)
498+
expect(res.headers.get('X-Async-Header')).toBe('async-value')
499+
})
500+
501+
it('handle non-Error with async toResponse()', async () => {
502+
class AsyncNonError {
503+
async toResponse() {
504+
await new Promise(resolve => setTimeout(resolve, 10))
505+
return Response.json({ error: 'non-error async' }, { status: 418 })
506+
}
507+
}
508+
509+
const app = new Elysia().get('/', () => {
510+
throw new AsyncNonError()
511+
})
512+
513+
const res = await app.handle(req('/'))
514+
515+
expect(await res.json()).toEqual({ error: 'non-error async' })
516+
expect(res.status).toBe(418)
517+
})
435518
})

0 commit comments

Comments
 (0)