diff --git a/src/server/factories/route_finder.ts b/src/server/factories/route_finder.ts index a025b10..2c4206d 100644 --- a/src/server/factories/route_finder.ts +++ b/src/server/factories/route_finder.ts @@ -41,19 +41,29 @@ export function routeFinder( * @returns Promise that resolves when route execution completes or rejects with route not found error */ return function () { - const url = ctx.request.url() - const method = ctx.request.method() - const hostname = router.usingDomains ? ctx.request.hostname() : undefined - const route = router.match(url, method, ctx.request.parsedUrl.shouldDecodeParam, hostname) + /** + * Sync errors (for example, from a matcher cast) are returned as a + * rejected promise. The middleware runner does not catch a sync + * error from its final handler, so without global middleware the + * error would skip the app error handler. + */ + try { + const url = ctx.request.url() + const method = ctx.request.method() + const hostname = router.usingDomains ? ctx.request.hostname() : undefined + const route = router.match(url, method, ctx.request.parsedUrl.shouldDecodeParam, hostname) - if (route) { - ctx.params = route.params - ctx.subdomains = route.subdomains - ctx.route = route.route - ctx.routeKey = route.routeKey - return route.route.execute(route.route, resolver, ctx, errorResponder) - } + if (route) { + ctx.params = route.params + ctx.subdomains = route.subdomains + ctx.route = route.route + ctx.routeKey = route.routeKey + return route.route.execute(route.route, resolver, ctx, errorResponder) + } - return Promise.reject(new errors.E_ROUTE_NOT_FOUND([method, url])) + return Promise.reject(new errors.E_ROUTE_NOT_FOUND([method, url])) + } catch (error) { + return Promise.reject(error) + } } } diff --git a/tests/server.spec.ts b/tests/server.spec.ts index 43be740..b5d521b 100644 --- a/tests/server.spec.ts +++ b/tests/server.spec.ts @@ -1080,6 +1080,42 @@ test.group('Server | error handler', () => { assert.equal(text, 'handled by error handler') }) + test('pass route matching errors to the error handler', async ({ assert }) => { + const app = new AppFactory().create(BASE_URL, () => {}) + const server = new ServerFactory().merge({ app }).create() + const httpServer = createServer(server.handle.bind(server)) + await app.init() + + class ErrorHandler { + report() {} + handle(error: any, { response }: HttpContext) { + response.status(422).send(`handled by error handler: ${error.message}`) + } + } + + server.use([]) + + server.errorHandler(async () => { + return { + default: ErrorHandler, + } + }) + + server + .getRouter() + .get('/:id', () => 'ok') + .where('id', { + match: /.*/, + cast: () => { + throw new Error('Invalid id') + }, + }) + await server.boot() + + const { text } = await supertest(httpServer).get('/1').expect(422) + assert.equal(text, 'handled by error handler: Invalid id') + }) + test('report response serialization errors', async ({ assert }) => { const app = new AppFactory().create(BASE_URL, () => {}) const server = new ServerFactory().merge({ app }).create()