fix(aot): continue onError after undefined#1901
Conversation
WalkthroughThis PR fixes an AOT-mode bug where early ChangesAOT onError Chain Continuation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/core/handle-error.test.ts (1)
144-206: ⚡ Quick winNice catch, but you only tested half the nullish path, baka~ (  ̄▽ ̄)っ♡
Line 144 validates
undefined, but the code change also treatsnullas “unhandled”. Add a sibling assertion for a firstonErrorreturningnullso this contract doesn’t silently regress later, okay~Suggested test extension
it('continues to next onError when previous hook returns undefined', async () => { for (const aot of [true, false]) { @@ } }) + +it('continues to next onError when previous hook returns null', async () => { + for (const aot of [true, false]) { + const calls: string[] = [] + + const logger = new Elysia({ name: `logger-on-error-null-${aot}` }) + .onError({ as: 'global' }, () => { + calls.push('logger') + return null + }) + + const responseWrapper = new Elysia({ name: `response-wrapper-null-${aot}` }) + .onError({ as: 'global' }, ({ error, set }) => { + calls.push('response-wrapper') + set.status = 200 + return Response.json({ code: 403, msg: error instanceof Error ? error.message : 'Forbidden', data: null }) + }) + + const guard = new Elysia({ name: `guard-null-${aot}` }).onBeforeHandle({ as: 'scoped' }, () => { + throw new Error('denied by nested guard') + }) + + const app = new Elysia({ aot }) + .use(logger) + .use(responseWrapper) + .use(new Elysia({ prefix: '/api' }).group('', (app) => app.use(guard).post('/guarded', () => ({ ok: true })))) + + const response = await app.handle(req('/api/guarded', { method: 'POST' })) + + expect(response.status).toBe(200) + expect(await response.json()).toEqual({ code: 403, msg: 'denied by nested guard', data: null }) + expect(calls).toEqual(['logger', 'response-wrapper']) + } +})🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/core/handle-error.test.ts` around lines 144 - 206, Extend the test "continues to next onError when previous hook returns undefined" to also cover the null return path: add a parallel case where the first onError handler (the logger or the one currently returning undefined) explicitly returns null instead of undefined and assert the same behavior — app.handle(...) yields status 200 with JSON { code: 403, msg: 'denied by nested guard', data: null } and the call order is ['logger','response-wrapper']; update the setup using the same Elysia instances (logger, responseWrapper, guard, app) and duplicate the assertions for the null-returning onError to ensure null is treated as "unhandled" just like undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@test/core/handle-error.test.ts`:
- Around line 144-206: Extend the test "continues to next onError when previous
hook returns undefined" to also cover the null return path: add a parallel case
where the first onError handler (the logger or the one currently returning
undefined) explicitly returns null instead of undefined and assert the same
behavior — app.handle(...) yields status 200 with JSON { code: 403, msg: 'denied
by nested guard', data: null } and the call order is
['logger','response-wrapper']; update the setup using the same Elysia instances
(logger, responseWrapper, guard, app) and duplicate the assertions for the
null-returning onError to ensure null is treated as "unhandled" just like
undefined.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cd816cfe-99c5-4476-8387-384724261ebd
📒 Files selected for processing (2)
src/compose.tstest/core/handle-error.test.ts
Summary
mapResponse/mapEarlyResponsefor anonErrorresult that actually handled the erroronErrorbefore a response-producingonErrorFixes #1900
Reproduction
Minimal reproduction repo:
https://github.com/lxy2500798479/elysia-onerror-chain-repro
In AOT mode, a logging-only global
onErrorfollowed by a response-producing globalonErrorcould skip the second handler when a nested guard threw. If amapResponsehook was present, it could receiveundefinedand turn the error into a success-shaped response.Boundary report
group+use+onBeforeHandlethrow path coveredonErrorreturningundefinedcoveredmapResponseconvertingundefinedcoveredTests
bun test test/core/handle-error.test.tsbun test test/core/handle-error.test.ts test/core/dynamic.test.tsbun run test:typesbun testbun run test:imports