Skip to content

Commit 3e69b82

Browse files
authored
Merge pull request #1543 from MarcelOlsen/fix/toResponse-error-handling
🔧 fix: respect toResponse() method on Error classes
2 parents 95277d7 + fe8f06f commit 3e69b82

File tree

5 files changed

+301
-15
lines changed

5 files changed

+301
-15
lines changed

src/adapter/bun/handler.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,8 +526,24 @@ export const mapCompactResponse = (
526526
}
527527
}
528528

529-
export const errorToResponse = (error: Error, set?: Context['set']) =>
530-
new Response(
529+
export const errorToResponse = (error: Error, set?: Context['set']) => {
530+
// @ts-expect-error
531+
if (typeof error?.toResponse === 'function') {
532+
// @ts-expect-error
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+
return typeof raw?.then === 'function'
542+
? raw.then(apply)
543+
: apply(raw)
544+
}
545+
546+
return new Response(
531547
JSON.stringify({
532548
name: error?.name,
533549
message: error?.message,
@@ -539,6 +555,7 @@ export const errorToResponse = (error: Error, set?: Context['set']) =>
539555
headers: set?.headers as any
540556
}
541557
)
558+
}
542559

543560
export const createStaticHandler = (
544561
handle: unknown,

src/adapter/web-standard/handler.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,8 +559,24 @@ export const mapCompactResponse = (
559559
}
560560
}
561561

562-
export const errorToResponse = (error: Error, set?: Context['set']) =>
563-
new Response(
562+
export const errorToResponse = (error: Error, set?: Context['set']) => {
563+
// @ts-expect-error
564+
if (typeof error?.toResponse === 'function') {
565+
// @ts-expect-error
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+
return typeof raw?.then === 'function'
575+
? raw.then(apply)
576+
: apply(raw)
577+
}
578+
579+
return new Response(
564580
JSON.stringify({
565581
name: error?.name,
566582
message: error?.message,
@@ -572,6 +588,7 @@ export const errorToResponse = (error: Error, set?: Context['set']) =>
572588
headers: set?.headers as any
573589
}
574590
)
591+
}
575592

576593
export const createStaticHandler = (
577594
handle: unknown,

src/compose.ts

Lines changed: 21 additions & 10 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,
@@ -2512,6 +2512,8 @@ export const composeErrorHandler = (app: AnyElysia) => {
25122512
`mapResponse,` +
25132513
`ERROR_CODE,` +
25142514
`ElysiaCustomStatusResponse,` +
2515+
`ValidationError,` +
2516+
`TransformDecodeError,` +
25152517
allocateIf(`onError,`, app.event.error) +
25162518
allocateIf(`afterResponse,`, app.event.afterResponse) +
25172519
allocateIf(`trace,`, app.event.trace) +
@@ -2521,11 +2523,8 @@ export const composeErrorHandler = (app: AnyElysia) => {
25212523
adapterVariables +
25222524
`}=inject\n`
25232525

2524-
fnLiteral += `return ${
2525-
app.event.error?.find(isAsync) || app.event.mapResponse?.find(isAsync)
2526-
? 'async '
2527-
: ''
2528-
}function(context,error,skipGlobal){`
2526+
// Always make error handler async since toResponse() may return promises
2527+
fnLiteral += `return async function(context,error,skipGlobal){`
25292528

25302529
fnLiteral += ''
25312530

@@ -2590,6 +2589,17 @@ export const composeErrorHandler = (app: AnyElysia) => {
25902589
const saveResponse =
25912590
hasTrace || !!hooks.afterResponse?.length ? 'context.response = ' : ''
25922591

2592+
fnLiteral +=
2593+
`if(typeof error?.toResponse==='function'&&!(error instanceof ValidationError)&&!(error instanceof TransformDecodeError)){` +
2594+
`try{` +
2595+
`let raw=error.toResponse()\n` +
2596+
`if(typeof raw?.then==='function')raw=await raw\n` +
2597+
`if(raw instanceof Response)set.status=raw.status\n` +
2598+
`context.response=context.responseValue=raw\n` +
2599+
`}catch(toResponseError){\n` +
2600+
`}\n` +
2601+
`}\n`
2602+
25932603
if (app.event.error)
25942604
for (let i = 0; i < app.event.error.length; i++) {
25952605
const handler = app.event.error[i]
@@ -2598,7 +2608,7 @@ export const composeErrorHandler = (app: AnyElysia) => {
25982608
isAsync(handler) ? 'await ' : ''
25992609
}onError[${i}](context)\n`
26002610

2601-
fnLiteral += 'if(skipGlobal!==true){'
2611+
fnLiteral += 'if(skipGlobal!==true&&!context.response){'
26022612

26032613
if (hasReturn(handler)) {
26042614
fnLiteral +=
@@ -2644,17 +2654,16 @@ export const composeErrorHandler = (app: AnyElysia) => {
26442654
}
26452655

26462656
fnLiteral +=
2647-
`if(error.constructor.name==="ValidationError"||error.constructor.name==="TransformDecodeError"){\n` +
2657+
`if(error instanceof ValidationError||error instanceof TransformDecodeError){\n` +
26482658
`if(error.error)error=error.error\n` +
26492659
`set.status=error.status??422\n` +
26502660
afterResponse() +
26512661
adapter.validationError +
26522662
`\n}\n`
26532663

26542664
fnLiteral +=
2655-
`if(error instanceof Error){` +
2665+
`if(!context.response&&error instanceof Error){` +
26562666
afterResponse() +
2657-
`\nif(typeof error.toResponse==='function')return context.response=context.responseValue=error.toResponse()\n` +
26582667
adapter.unknownError +
26592668
`\n}`
26602669

@@ -2702,6 +2711,8 @@ export const composeErrorHandler = (app: AnyElysia) => {
27022711
mapResponse: app['~adapter'].handler.mapResponse,
27032712
ERROR_CODE,
27042713
ElysiaCustomStatusResponse,
2714+
ValidationError,
2715+
TransformDecodeError,
27052716
onError: app.event.error?.map(mapFn),
27062717
afterResponse: app.event.afterResponse?.map(mapFn),
27072718
trace: app.event.trace?.map(mapFn),

src/dynamic-handle.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,23 @@ export const createDynamicErrorHandler = (app: AnyElysia) => {
684684
const errorContext = Object.assign(context, { error, code: error.code })
685685
errorContext.set = context.set
686686

687-
if (app.event.error)
687+
// @ts-expect-error
688+
if (typeof error?.toResponse === 'function' &&
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+
}
701+
}
702+
703+
if (!context.response && app.event.error)
688704
for (let i = 0; i < app.event.error.length; i++) {
689705
const hook = app.event.error[i]
690706
let response = hook.fn(errorContext as any)
@@ -696,6 +712,19 @@ export const createDynamicErrorHandler = (app: AnyElysia) => {
696712
))
697713
}
698714

715+
if (context.response) {
716+
if (app.event.mapResponse)
717+
for (let i = 0; i < app.event.mapResponse.length; i++) {
718+
const hook = app.event.mapResponse[i]
719+
let response = hook.fn(errorContext as any)
720+
if (response instanceof Promise) response = await response
721+
if (response !== undefined && response !== null)
722+
context.response = response
723+
}
724+
725+
return mapResponse(context.response, context.set)
726+
}
727+
699728
return new Response(
700729
typeof error.cause === 'string' ? error.cause : error.message,
701730
{

0 commit comments

Comments
 (0)