Skip to content

Commit ec7b797

Browse files
committed
fix: run middleware upstream flow when route middleware or handler throws an error
1 parent 36f6960 commit ec7b797

File tree

6 files changed

+229
-29
lines changed

6 files changed

+229
-29
lines changed

src/router/executor.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@
1010
import type { ContainerResolver } from '@adonisjs/fold'
1111
import type { StoreRouteNode } from '../types/route.js'
1212
import type { HttpContext } from '../http_context/main.js'
13+
import type { ServerErrorHandler } from '../types/server.js'
1314
import { useReturnValue } from './factories/use_return_value.js'
1415

1516
/**
1617
* Executor to execute the route middleware pipeline the route
1718
* handler
1819
*/
19-
export function execute(route: StoreRouteNode, resolver: ContainerResolver<any>, ctx: HttpContext) {
20+
export function execute(
21+
route: StoreRouteNode,
22+
resolver: ContainerResolver<any>,
23+
ctx: HttpContext,
24+
errorResponder: ServerErrorHandler['handle']
25+
) {
2026
return route.middleware
2127
.runner()
28+
.errorHandler((error) => errorResponder(error, ctx))
2229
.finalHandler(async () => {
2330
if (typeof route.handler === 'function') {
2431
return Promise.resolve(route.handler(ctx)).then(useReturnValue(ctx))

src/server/factories/final_handler.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ import type { ContainerResolver } from '@adonisjs/fold'
1212
import * as errors from '../../exceptions.js'
1313
import type { Router } from '../../router/main.js'
1414
import type { HttpContext } from '../../http_context/main.js'
15+
import type { ServerErrorHandler } from '../../types/server.js'
1516

1617
/**
1718
* The final handler is executed after the server middleware stack.
1819
* It looks for a matching route and executes the route middleware
1920
* stack.
2021
*/
21-
export function finalHandler(router: Router, resolver: ContainerResolver<any>, ctx: HttpContext) {
22+
export function finalHandler(
23+
router: Router,
24+
resolver: ContainerResolver<any>,
25+
ctx: HttpContext,
26+
errorResponder: ServerErrorHandler['handle']
27+
) {
2228
return function () {
2329
const url = ctx.request.url()
2430
const method = ctx.request.method()
@@ -30,7 +36,7 @@ export function finalHandler(router: Router, resolver: ContainerResolver<any>, c
3036
ctx.subdomains = route.subdomains
3137
ctx.route = route.route
3238
ctx.routeKey = route.routeKey
33-
return route.route.execute(route.route, resolver, ctx)
39+
return route.route.execute(route.route, resolver, ctx, errorResponder)
3440
}
3541

3642
return Promise.reject(new errors.E_ROUTE_NOT_FOUND([method, url]))

src/server/main.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ export class Server {
115115
*/
116116
#middleware: ParsedGlobalMiddleware[] = []
117117

118+
/**
119+
* The request error response is attached to the middleware
120+
* pipeline to intercept errors and invoke the user
121+
* registered error handler.
122+
*
123+
* We share this with the route middleware pipeline as well,
124+
* so that it does not throw any exceptions
125+
*/
126+
#requestErrorResponder: ServerErrorHandler['handle'] = (error, ctx) => {
127+
this.#resolvedErrorHandler.report(error, ctx)
128+
return this.#resolvedErrorHandler.handle(error, ctx)
129+
}
130+
118131
/**
119132
* Know if async local storage is enabled or not.
120133
*/
@@ -168,11 +181,8 @@ export class Server {
168181
*/
169182
#handleRequest(ctx: HttpContext, resolver: ContainerResolver<any>) {
170183
return this.#serverMiddlewareStack!.runner()
171-
.errorHandler((error) => {
172-
this.#resolvedErrorHandler.report(error, ctx)
173-
return this.#resolvedErrorHandler.handle(error, ctx)
174-
})
175-
.finalHandler(finalHandler(this.#router!, resolver, ctx))
184+
.errorHandler((error) => this.#requestErrorResponder(error, ctx))
185+
.finalHandler(finalHandler(this.#router!, resolver, ctx, this.#requestErrorResponder))
176186
.run(middlewareHandler(resolver, ctx))
177187
.catch((error) => {
178188
ctx.logger.fatal({ err: error }, 'Exception raised by error handler')

src/types/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { ContainerResolver } from '@adonisjs/fold'
1313
import type { Constructor, LazyImport } from './base.js'
1414
import type { HttpContext } from '../http_context/main.js'
1515
import type { MiddlewareFn, ParsedGlobalMiddleware } from './middleware.js'
16+
import { ServerErrorHandler } from './server.js'
1617

1718
/**
1819
* Returns a union of methods from a controller that accepts
@@ -70,7 +71,12 @@ export type StoreRouteNode = {
7071
* The execute function to execute the route middleware
7172
* and the handler
7273
*/
73-
execute: (route: StoreRouteNode, resolver: ContainerResolver<any>, ctx: HttpContext) => any
74+
execute: (
75+
route: StoreRouteNode,
76+
resolver: ContainerResolver<any>,
77+
ctx: HttpContext,
78+
errorResponder: ServerErrorHandler['handle']
79+
) => any
7480

7581
/**
7682
* A unique name for the route

tests/router/execute.spec.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ test.group('Route | execute', () => {
4242
})
4343

4444
const routeJSON = route.toJSON()
45-
await routeJSON.execute(routeJSON, app.container.createResolver(), context)
45+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, () => {})
4646
assert.deepEqual(stack, ['handler'])
4747
})
4848

@@ -75,7 +75,7 @@ test.group('Route | execute', () => {
7575
},
7676
}
7777

78-
await routeJSON.execute(routeJSON, resolver, context)
78+
await routeJSON.execute(routeJSON, resolver, context, () => {})
7979
assert.deepEqual(stack, ['controller'])
8080
})
8181

@@ -111,7 +111,7 @@ test.group('Route | execute', () => {
111111
})
112112

113113
const routeJSON = route.toJSON()
114-
await routeJSON.execute(routeJSON, resolver, context)
114+
await routeJSON.execute(routeJSON, resolver, context, () => {})
115115

116116
const route1 = new Route(app, [], {
117117
pattern: '/',
@@ -121,7 +121,7 @@ test.group('Route | execute', () => {
121121
})
122122

123123
const route1JSON = route1.toJSON()
124-
await route1JSON.execute(route1JSON, resolver, context)
124+
await route1JSON.execute(route1JSON, resolver, context, () => {})
125125

126126
assert.deepEqual(stack, ['invoked', 'invoked handle'])
127127
})
@@ -152,7 +152,7 @@ test.group('Route | execute', () => {
152152
})
153153

154154
const routeJSON = route.toJSON()
155-
await routeJSON.execute(routeJSON, resolver, context)
155+
await routeJSON.execute(routeJSON, resolver, context, () => {})
156156

157157
const route1 = new Route(app, [], {
158158
pattern: '/',
@@ -162,7 +162,7 @@ test.group('Route | execute', () => {
162162
})
163163

164164
const route1JSON = route1.toJSON()
165-
await route1JSON.execute(route1JSON, resolver, context)
165+
await route1JSON.execute(route1JSON, resolver, context, () => {})
166166

167167
assert.deepEqual(stack, ['invoked', 'invoked handle'])
168168
})
@@ -200,7 +200,7 @@ test.group('Route | execute', () => {
200200
})
201201

202202
const routeJSON = route.toJSON()
203-
await routeJSON.execute(routeJSON, app.container.createResolver(), context)
203+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, () => {})
204204
assert.deepEqual(stack, ['middleware 1', 'middleware 2', 'handler'])
205205
})
206206

@@ -238,7 +238,7 @@ test.group('Route | execute', () => {
238238
})
239239

240240
const routeJSON = route.toJSON()
241-
await routeJSON.execute(routeJSON, app.container.createResolver(), context)
241+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, () => {})
242242
assert.deepEqual(stack, ['middleware 1', 'middleware 2'])
243243
})
244244

@@ -303,7 +303,7 @@ test.group('Route | execute', () => {
303303
})
304304

305305
const routeJSON = route.toJSON()
306-
await routeJSON.execute(routeJSON, app.container.createResolver(), context)
306+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, () => {})
307307
assert.deepEqual(stack, ['bodyparser', 'log', 'middleware 1', 'middleware 2', 'handler'])
308308
})
309309

@@ -369,7 +369,7 @@ test.group('Route | execute', () => {
369369
})
370370

371371
const routeJSON = route.toJSON()
372-
await routeJSON.execute(routeJSON, app.container.createResolver(), context)
372+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, () => {})
373373
assert.deepEqual(stack, ['bodyparser', 'log'])
374374
})
375375

@@ -430,10 +430,9 @@ test.group('Route | execute', () => {
430430
})
431431

432432
const routeJSON = route.toJSON()
433-
await assert.rejects(
434-
() => routeJSON.execute(routeJSON, app.container.createResolver(), context),
435-
'Log middleware failed'
436-
)
433+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, (error) => {
434+
assert.equal(error.message, 'Log middleware failed')
435+
})
437436

438437
assert.deepEqual(stack, ['bodyparser', 'log'])
439438
})
@@ -494,10 +493,9 @@ test.group('Route | execute', () => {
494493
})
495494

496495
const routeJSON = route.toJSON()
497-
await assert.rejects(
498-
() => routeJSON.execute(routeJSON, app.container.createResolver(), context),
499-
'route handler failed'
500-
)
496+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, (error) => {
497+
assert.equal(error.message, 'route handler failed')
498+
})
501499

502500
assert.deepEqual(stack, ['bodyparser', 'log', 'middleware 1', 'middleware 2'])
503501
})
@@ -546,11 +544,11 @@ test.group('Route | execute', () => {
546544
route1.middleware(namedMiddleware.acl({ role: 'admin' }))
547545

548546
const routeJSON = route.toJSON()
549-
await routeJSON.execute(routeJSON, app.container.createResolver(), context)
547+
await routeJSON.execute(routeJSON, app.container.createResolver(), context, () => {})
550548
assert.deepEqual(stack, [undefined, 'handler'])
551549

552550
const route1JSON = route1.toJSON()
553-
await route1JSON.execute(route1JSON, app.container.createResolver(), context)
551+
await route1JSON.execute(route1JSON, app.container.createResolver(), context, () => {})
554552
assert.deepEqual(stack, [undefined, 'handler', { role: 'admin' }, 'handler'])
555553
})
556554
})

0 commit comments

Comments
 (0)