Skip to content

Commit 7210860

Browse files
committed
wip
1 parent 94c63cc commit 7210860

File tree

3 files changed

+125
-20
lines changed

3 files changed

+125
-20
lines changed

src/compose.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AnyElysia } from '.'
1+
import type { AnyElysia, Context } from '.'
22

33
import { Value } from '@sinclair/typebox/value'
44
import { TypeBoxError, type TAnySchema, type TSchema } from '@sinclair/typebox'
@@ -1515,7 +1515,7 @@ export const composeHandler = ({
15151515
mapResponseReporter.resolve()
15161516

15171517
fnLiteral += encodeCookie
1518-
fnLiteral += `return mapEarlyResponse(${saveResponse}be,c.set${
1518+
fnLiteral += `return mapEarlyResponse(handleErrors,${saveResponse}be,c.set${
15191519
mapResponseContext
15201520
})}\n`
15211521
}
@@ -1780,7 +1780,7 @@ export const composeHandler = ({
17801780
`c.code=error.code??error[ERROR_CODE]??"UNKNOWN"}` +
17811781
`let er\n`
17821782

1783-
for (let i = 0; i < hooks.error.length; i++) {
1783+
for (let i = 0; i < hooks.error.length; i++) { //
17841784
const endUnit = errorReporter.resolveChild(hooks.error[i].fn.name)
17851785

17861786
if (isAsync(hooks.error[i]))
@@ -1815,7 +1815,7 @@ export const composeHandler = ({
18151815

18161816
mapResponseReporter.resolve()
18171817

1818-
fnLiteral += `er=mapEarlyResponse(er,set${mapResponseContext})\n`
1818+
fnLiteral += `er=mapEarlyResponse(handleErrors,er,set${mapResponseContext})\n`
18191819
fnLiteral += `if(er){`
18201820

18211821
if (hasTrace) {
@@ -1919,6 +1919,8 @@ export const composeHandler = ({
19191919

19201920
init += fnLiteral + '}'
19211921

1922+
console.log(init)
1923+
19221924
try {
19231925
if (asManifest) return Function('hooks', init) as any
19241926

@@ -1934,7 +1936,16 @@ export const composeHandler = ({
19341936
utils: {
19351937
mapResponse: adapterHandler.mapResponse,
19361938
mapCompactResponse: adapterHandler.mapCompactResponse,
1937-
mapEarlyResponse: adapterHandler.mapEarlyResponse,
1939+
mapEarlyResponse: (handleErrors: any, response: unknown, set: Context['set'], ...params: unknown[]) => {
1940+
if (response instanceof ElysiaCustomStatusResponse) {
1941+
for (let i = 0; i < handleErrors.length; i++) {
1942+
const errorResponse = handleErrors[i]({ error: response })
1943+
if (errorResponse) return new Response(errorResponse)
1944+
}
1945+
}
1946+
1947+
return adapterHandler.mapEarlyResponse(response, set, ...params)
1948+
},
19381949
parseQuery,
19391950
parseQueryFromURL,
19401951
isNotEmpty
@@ -2137,7 +2148,7 @@ export const composeGeneralHandler = (
21372148

21382149
if (withReturn) {
21392150
fnLiteral +=
2140-
`re=mapEarlyResponse(` +
2151+
`re=mapEarlyResponse(handleErrors,` +
21412152
`${maybeAsync ? 'await ' : ''}onRequest[${i}](c),` +
21422153
`c.set)\n`
21432154

src/dynamic-handle.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { AnyElysia } from '.'
33
import {
44
ElysiaCustomStatusResponse,
55
ElysiaErrors,
6+
error,
67
NotFoundError,
78
ValidationError
89
} from './error'
@@ -14,7 +15,7 @@ import { parseQuery } from './fast-querystring'
1415
import { redirect, signCookie, StatusMap } from './utils'
1516
import { parseCookie } from './cookies'
1617

17-
import type { Handler, LifeCycleStore, SchemaValidator } from './types'
18+
import type { Handler, HookContainer, LifeCycleStore, SchemaValidator } from './types'
1819
import { TransformDecodeError } from '@sinclair/typebox/value'
1920

2021
// JIT Handler
@@ -51,12 +52,25 @@ export const createDynamicHandler = (app: AnyElysia) => {
5152
request,
5253
path,
5354
qi,
54-
redirect
55+
redirect,
56+
error,
5557
}
5658
) as unknown as Context & {
5759
response: unknown
5860
}
5961

62+
const getLocalError = (response: unknown, hookError: HookContainer[]): Result | undefined => {
63+
if (response instanceof ElysiaCustomStatusResponse) {
64+
for (let i = 0; i < hookError.length; i++) {
65+
const errorResponse = hookError[i].fn({ ...context, error: response })
66+
if (errorResponse) return errorResponse
67+
}
68+
69+
const result = mapEarlyResponse(response, context.set)
70+
if (result) return result as Result
71+
}
72+
}
73+
6074
try {
6175
for (let i = 0; i < app.event.request.length; i++) {
6276
const onRequest = app.event.request[i].fn
@@ -295,11 +309,10 @@ export const createDynamicHandler = (app: AnyElysia) => {
295309
const hook = hooks.beforeHandle[i]
296310
let response = hook.fn(context)
297311

298-
if (hook.subType === 'resolve') {
299-
if (response instanceof ElysiaCustomStatusResponse) {
300-
const result = mapEarlyResponse(response, context.set)
301-
if (result) return (context.response = result) as Response
302-
}
312+
const localError = getLocalError(response, hooks.error)
313+
if (localError) {
314+
response = localError
315+
} else if (hook.subType === 'resolve') {
303316
if (response instanceof Promise)
304317
Object.assign(context, await response)
305318
else Object.assign(context, response)
@@ -337,6 +350,9 @@ export const createDynamicHandler = (app: AnyElysia) => {
337350
let response = typeof handle === 'function' ? handle(context) : handle
338351
if (response instanceof Promise) response = await response
339352

353+
const localError = getLocalError(response, hooks.error)
354+
if (localError) response = localError
355+
340356
if (!hooks.afterHandle.length) {
341357
const status =
342358
response instanceof ElysiaCustomStatusResponse
@@ -374,23 +390,21 @@ export const createDynamicHandler = (app: AnyElysia) => {
374390
if (newResponse instanceof Promise)
375391
newResponse = await newResponse
376392

377-
const result = mapEarlyResponse(newResponse, context.set)
378-
if (result !== undefined) {
393+
const localError = getLocalError(newResponse, hooks.error)
394+
if (localError !== undefined) {
379395
const responseValidator =
380396
// @ts-expect-error
381397
validator?.response?.[result.status]
382398

383-
if (responseValidator?.Check(result) === false)
399+
if (responseValidator?.Check(localError) === false)
384400
throw new ValidationError(
385401
'response',
386402
responseValidator,
387-
result
403+
localError
388404
)
389405
else if (responseValidator?.Decode)
390406
response = responseValidator.Decode(response)
391-
392-
// @ts-expect-error
393-
return (context.response = result)
407+
else response = localError
394408
}
395409
}
396410
}

test/lifecycle/error.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,84 @@ describe('error', () => {
272272
somePretty: 'json'
273273
})
274274
})
275+
276+
describe('handle scoped error', async () => {
277+
await Bun.sleep(200)
278+
279+
describe('when calling "error" function from "beforeHandle"', () => {
280+
const route = new Elysia()
281+
.get("/", () => "it won't run", {
282+
beforeHandle({ error }) {
283+
return error(401)
284+
},
285+
error({ error }) {
286+
return `scoped error: ${(error as any).code}`
287+
},
288+
}).onError(() => {
289+
return "global error handler won't be used"
290+
})
291+
292+
it('when aot is on', async () => {
293+
const api = new Elysia().use(route)
294+
295+
const result = await api.handle(new Request('http://localhost/'))
296+
297+
expect(await result.text()).toBe('scoped error: 401')
298+
expect(result.status).toBe(401)
299+
})
300+
301+
it.only('when aot is off', async () => {
302+
const api = new Elysia({ aot: false }).use(route)
303+
304+
const result = await api.handle(new Request('http://localhost/'))
305+
306+
expect(await result.text()).toBe('scoped error: 401')
307+
expect(result.status).toBe(401)
308+
})
309+
})
310+
311+
describe('when calling "error" function from "afterHandle"', () => {
312+
const route = new Elysia()
313+
.get("/", () => "it runs", {
314+
afterHandle({ error }) {
315+
return error(401)
316+
},
317+
error({ error }) {
318+
return `scoped error: ${(error as any).code}`
319+
},
320+
}).onError(() => {
321+
return "global error handler won't be used"
322+
})
323+
324+
it('when aot is off', async () => {
325+
const api = new Elysia({ aot: false }).use(route)
326+
327+
const result = await api.handle(new Request('http://localhost/')).then((r) => r.text())
328+
329+
expect(result).toBe('scoped error: 401')
330+
})
331+
})
332+
333+
describe('when calling "error" function from the path handler', () => {
334+
const route = new Elysia()
335+
.get("/", ({ error }) => {
336+
return error(401)
337+
}, {
338+
error({ error }) {
339+
// @ts-expect-error
340+
return `scoped error: ${error.code}`
341+
},
342+
}).onError(() => {
343+
return "global error handler won't be used"
344+
})
345+
346+
it('when aot is off', async () => {
347+
const api = new Elysia({ aot: false }).use(route)
348+
349+
const result = await api.handle(new Request('http://localhost/')).then((r) => r.text())
350+
351+
expect(result).toBe('scoped error: 401')
352+
})
353+
})
354+
})
275355
})

0 commit comments

Comments
 (0)