diff --git a/flaska.mjs b/flaska.mjs index a0bd2d1..11725d1 100644 --- a/flaska.mjs +++ b/flaska.mjs @@ -341,11 +341,6 @@ export class Flaska { this._onreserror = handler } - onerror(handler) { - assertIsHandler(handler, 'onerror()') - this._onerror = handler - } - before(handler) { assertIsHandler(handler, 'before()') this._before.push(handler) @@ -427,6 +422,18 @@ export class Flaska { requestStartInternal(ctx) { let route = this.routers[ctx.method].match(ctx.url) + if (!route) { + let middle = this._on404(ctx) + if (middle && middle.then) { + return middle.then(() => { + this.requestEnd(null, ctx) + }, err => { + this.requestEnd(err, ctx) + }) + } + return this.requestEnd(null, ctx) + } + ctx.params = route.params if (route.middlewares.length) { @@ -467,7 +474,11 @@ export class Flaska { requestEnd(err, ctx) { if (err) { - this._onerror(err, ctx) + try { + this._onerror(err, ctx) + } catch (err) { + this._backuperror(err, ctx) + } } if (ctx.res.writableEnded) { return diff --git a/test.mjs b/test.mjs index 4758d71..80727e3 100644 --- a/test.mjs +++ b/test.mjs @@ -4,18 +4,11 @@ const port = 51024 const flaska = new Flaska({}, ) flaska.get('/', function(ctx) { - return new Promise(function(res, rej) { - ctx.body = { status: true } - setTimeout(res, 1000) - }) + ctx.body = { status: true } }) -flaska.after(function(ctx) { - if (ctx.aborted) { - process.stdout.write("A") - } else { - process.stdout.write(".") - } +flaska.get('/error', function(ctx) { + process.exit(1) }) flaska.listen(port, function() { diff --git a/test/flaska.in.test.mjs b/test/flaska.in.test.mjs index 6d219da..8930ac3 100644 --- a/test/flaska.in.test.mjs +++ b/test/flaska.in.test.mjs @@ -274,6 +274,126 @@ t.describe('#requestStart()', function() { }), createRes()) }) + t.test('calls 404 if route handler is not found', function(cb) { + const on404Error = spy() + + let handler = function() { + throw new Error('should not be called') + } + + let flaska = new Flaska({}, faker) + flaska.on404(on404Error) + flaska.get('/test', function() { throw new Error('should not be called') }) + flaska.compile() + + flaska.requestEnd = function(err, ctx) { + if (err) return cb(err) + + try { + assert.ok(ctx) + assert.strictEqual(on404Error.callCount, 1) + assert.strictEqual(on404Error.firstCall[0], ctx) + cb() + } catch (err) { cb(err) } + } + + flaska.requestStart(createReq({ + url: '/nope', + method: 'GET', + }), createRes()) + }) + + t.test('calls 404 if route handler is not found and supports promise', function(cb) { + let checkCtx = null + const assertError = new Error('should be seen') + const on404Error = function(ctx) { + checkCtx = ctx + return Promise.resolve().then(function() { return Promise.reject(assertError) }) + } + + let flaska = new Flaska({}, faker) + flaska.on404(on404Error) + flaska.get('/test', function() { throw new Error('should not be called') }) + flaska.compile() + + flaska.requestEnd = function(err, ctx) { + if (err && err !== assertError) return cb(err) + + try { + assert.ok(err) + assert.ok(ctx) + assert.strictEqual(ctx, checkCtx) + assert.strictEqual(err, assertError) + cb() + } catch (err) { cb(err) } + } + + flaska.requestStart(createReq({ + url: '/nope', + method: 'GET', + }), createRes()) + }) + + t.test(`should handle unexpected errors in on404 correctly`, function(cb) { + const assertError = new Error('should be seen') + let checkCtx = null + + let flaska = new Flaska({}, faker) + flaska.on404(function(ctx) { + checkCtx = ctx + throw assertError + }) + flaska.get('/test', function() { throw new Error('should not be called') }) + flaska.compile() + + flaska.requestEnd = function(err, ctx) { + if (err && err !== assertError) return cb(err) + + try { + assert.ok(err) + assert.ok(ctx) + assert.strictEqual(ctx, checkCtx) + assert.strictEqual(err, assertError) + cb() + } catch (err) { cb(err) } + } + + flaska.requestStart(createReq({ + url: '/nope', + method: 'GET', + }), createRes()) + }) + + t.test(`should handle unexpected errors in middleware correctly`, function(cb) { + const assertError = new Error('should be seen') + let checkCtx = null + + let flaska = new Flaska({}, faker) + let middles = [function(ctx) { + checkCtx = ctx + throw assertError + }] + flaska.get('/test', middles, function() { throw new Error('should not be called') }) + flaska.compile() + + flaska.requestEnd = function(err, ctx) { + if (err && err !== assertError) return cb(err) + + try { + assert.ok(err) + assert.ok(ctx) + assert.strictEqual(ctx, checkCtx) + assert.strictEqual(err, assertError) + cb() + } catch (err) { cb(err) } + } + + flaska.requestStart(createReq({ + url: '/test', + method: 'GET', + }), createRes()) + }) + t.test('should work with synchronous handler', function(cb) { const assertBody = { a: 1 } let handler = function(ctx) { diff --git a/test/flaska.out.test.mjs b/test/flaska.out.test.mjs index baeb8c7..6d9175e 100644 --- a/test/flaska.out.test.mjs +++ b/test/flaska.out.test.mjs @@ -39,6 +39,28 @@ t.describe('#requestEnd()', function() { flaska.requestEnd(assertError, ctx) }) + + t.test('calls backup onerror correctly if error throws', function(cb) { + const assertErrorNotSeen = new Error('should not be seen') + const assertError = new Error('test') + + let onFinish = function(body) { + try { + assert.strictEqual(ctx.status, 500) + assert.strictEqual(ctx.body.status, 500) + assert.strictEqual(ctx.body.message, 'Internal Server Error') + cb() + } catch (err) { cb(err) } + } + const ctx = createCtx({}, onFinish) + + let flaska = new Flaska({}, fakerHttp, fakeStream) + flaska.onerror(function() { + throw assertError + }) + + flaska.requestEnd(assertErrorNotSeen, ctx) + }) t.test('call res and end correctly when dealing with objects', function(cb) { const assertStatus = 202