From 814cdc38ab5c1ac6ddb5b705b9bf57913c27ec76 Mon Sep 17 00:00:00 2001 From: reherhold Date: Sat, 21 Nov 2015 16:04:00 -0500 Subject: [PATCH] Adding custom user-provided throw functions for "Not Implemented" and "Method Not Allowed" --- README.md | 16 +++ lib/router.js | 18 ++- test/lib/router.js | 279 +++++++++++++++++++++++++++++++++++---------- 3 files changed, 253 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index cbf1d88..610c356 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,8 @@ with `405 Method Not Allowed` and `501 Not Implemented` as appropriate. | --- | --- | --- | | [options] | Object | | | [options.throw] | Boolean | throw error instead of setting status and header | +| [options.notImplemented] | Function | throw throw the returned value in place of the default NotImplemented error | +| [options.methodNotAllowed] | Function | throw the returned value in place of the default MethodNotAllowed error | **Example** ```javascript @@ -236,6 +238,20 @@ var router = router(); app.use(router.routes()); app.use(router.allowedMethods()); + +``` +**Example with [Boom](https://github.com/hapijs/boom)** +```javascript +var app = koa(); +var router = router(); +var Boom = require('boom'); + +app.use(router.routes()); +app.use(router.allowedMethods({ + throw: true, + notImplemented: () => new Boom.notImplemented(), + methodNotAllowed: () => new Boom.methodNotAllowed() +})); ``` #### router.redirect(source, destination, code) ⇒ Router diff --git a/lib/router.js b/lib/router.js index d56a6ac..83f144f 100644 --- a/lib/router.js +++ b/lib/router.js @@ -356,6 +356,8 @@ Router.prototype.routes = Router.prototype.middleware = function () { * * @param {Object=} options * @param {Boolean=} options.throw throw error instead of setting status and header + * @param {Function=} options.notImplemented throw the returned value in place of the default NotImplemented error + * @param {Function=} options.methodNotAllowed throw the returned value in place of the default MethodNotAllowed error * @returns {Function} */ @@ -379,7 +381,13 @@ Router.prototype.allowedMethods = function (options) { if (!~implemented.indexOf(this.method)) { if (options.throw) { - throw new HttpError.NotImplemented(); + var notImplementedThrowable; + if (typeof options.notImplemented === 'function') { + notImplementedThrowable = options.notImplemented(); // set whatever the user returns from their function + } else { + notImplementedThrowable = new HttpError.NotImplemented(); + } + throw notImplementedThrowable; } else { this.status = 501; this.set('Allow', allowedArr); @@ -389,7 +397,13 @@ Router.prototype.allowedMethods = function (options) { this.status = 204; } else if (!allowed[this.method]) { if (options.throw) { - throw new HttpError.MethodNotAllowed(); + var notAllowedThrowable; + if (typeof options.methodNotAllowed === 'function') { + notAllowedThrowable = options.methodNotAllowed(); // set whatever the user returns from their function + } else { + notAllowedThrowable = new HttpError.MethodNotAllowed(); + } + throw notAllowedThrowable; } else { this.status = 405; } diff --git a/test/lib/router.js b/test/lib/router.js index 802cac5..7188fab 100644 --- a/test/lib/router.js +++ b/test/lib/router.js @@ -429,71 +429,234 @@ describe('Router', function() { .end(done); }); - it('responds to OPTIONS requests', function(done) { - var app = koa(); - var router = new Router(); - app.use(router.routes()); - app.use(router.allowedMethods()); - router.get('/users', function *() {}); - router.put('/users', function *() {}); - request(http.createServer(app.callback())) - .options('/users') - .expect(204) - .end(function(err, res) { - if (err) return done(err); - res.header.should.have.property('allow', 'HEAD, GET, PUT'); - done(); + describe('Router#allowedMethods()', function() { + it('responds to OPTIONS requests', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(router.allowedMethods()); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + request(http.createServer(app.callback())) + .options('/users') + .expect(204) + .end(function(err, res) { + if (err) return done(err); + res.header.should.have.property('allow', 'HEAD, GET, PUT'); + done(); + }); }); - }); - it('responds with 405 Method Not Allowed', function(done) { - var app = koa(); - var router = new Router(); - app.use(router.routes()); - app.use(router.allowedMethods()); - router.get('/users', function *() {}); - router.put('/users', function *() {}); - router.post('/events', function *() {}); - request(http.createServer(app.callback())) - .post('/users') - .expect(405) - .end(function(err, res) { - if (err) return done(err); - res.header.should.have.property('allow', 'HEAD, GET, PUT'); - done(); + it('responds with 405 Method Not Allowed', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(router.allowedMethods()); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + router.post('/events', function *() {}); + request(http.createServer(app.callback())) + .post('/users') + .expect(405) + .end(function(err, res) { + if (err) return done(err); + res.header.should.have.property('allow', 'HEAD, GET, PUT'); + done(); + }); }); - }); - it('responds with 501 Not Implemented', function(done) { - var app = koa(); - var router = new Router(); - app.use(router.routes()); - app.use(router.allowedMethods()); - router.get('/users', function *() {}); - router.put('/users', function *() {}); - request(http.createServer(app.callback())) - .search('/users') - .expect(501) - .end(function(err, res) { - if (err) return done(err); - done(); + it('responds with 405 Method Not Allowed using the "throw" option', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(function* (next) { + try { + yield next; + } catch (err) { + // assert that the correct HTTPError was thrown + err.name.should.equal('MethodNotAllowedError'); + err.statusCode.should.equal(405); + + // translate the HTTPError to a normal response + this.body = err.name; + this.status = err.statusCode; + } + }); + app.use(router.allowedMethods({ throw: true })); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + router.post('/events', function *() {}); + request(http.createServer(app.callback())) + .post('/users') + .expect(405) + .end(function(err, res) { + if (err) return done(err); + // the 'Allow' header is not set when throwing + res.header.should.not.have.property('allow'); + done(); + }); }); - }); - it('does not send 405 if route matched but status is 404', function (done) { - var app = koa(); - var router = new Router(); - app.use(router.routes()); - app.use(router.allowedMethods()); - router.get('/users', function *() { - this.status = 404; + it('responds with user-provided throwable using the "throw" and "methodNotAllowed" options', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(function* (next) { + try { + yield next; + } catch (err) { + // assert that the correct HTTPError was thrown + err.message.should.equal('Custom Not Allowed Error'); + err.statusCode.should.equal(405); + + // translate the HTTPError to a normal response + this.body = err.body; + this.status = err.statusCode; + } + }); + app.use(router.allowedMethods({ + throw: true, + methodNotAllowed: function() { + var notAllowedErr = new Error('Custom Not Allowed Error'); + notAllowedErr.type = 'custom'; + notAllowedErr.statusCode = 405; + notAllowedErr.body = { + error: 'Custom Not Allowed Error', + statusCode: 405, + otherStuff: true + }; + return notAllowedErr; + } + })); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + router.post('/events', function *() {}); + request(http.createServer(app.callback())) + .post('/users') + .expect(405) + .end(function(err, res) { + if (err) return done(err); + // the 'Allow' header is not set when throwing + res.header.should.not.have.property('allow'); + res.body.should.eql({ error: 'Custom Not Allowed Error', + statusCode: 405, + otherStuff: true + }); + done(); + }); }); - request(http.createServer(app.callback())) - .get('/users') - .expect(404) - .end(function(err, res) { - if (err) return done(err); - done(); + + it('responds with 501 Not Implemented', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(router.allowedMethods()); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + request(http.createServer(app.callback())) + .search('/users') + .expect(501) + .end(function(err, res) { + if (err) return done(err); + done(); + }); + }); + + it('responds with 501 Not Implemented using the "throw" option', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(function* (next) { + try { + yield next; + } catch (err) { + // assert that the correct HTTPError was thrown + err.name.should.equal('NotImplementedError'); + err.statusCode.should.equal(501); + + // translate the HTTPError to a normal response + this.body = err.name; + this.status = err.statusCode; + } + }); + app.use(router.allowedMethods({ throw: true })); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + request(http.createServer(app.callback())) + .search('/users') + .expect(501) + .end(function(err, res) { + if (err) return done(err); + // the 'Allow' header is not set when throwing + res.header.should.not.have.property('allow'); + done(); + }); + }); + + it('responds with user-provided throwable using the "throw" and "notImplemented" options', function(done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(function* (next) { + try { + yield next; + } catch (err) { + // assert that our custom error was thrown + err.message.should.equal('Custom Not Implemented Error'); + err.type.should.equal('custom'); + err.statusCode.should.equal(501); + + // translate the HTTPError to a normal response + this.body = err.body; + this.status = err.statusCode; + } + }); + app.use(router.allowedMethods({ + throw: true, + notImplemented: function() { + var notImplementedErr = new Error('Custom Not Implemented Error'); + notImplementedErr.type = 'custom'; + notImplementedErr.statusCode = 501; + notImplementedErr.body = { + error: 'Custom Not Implemented Error', + statusCode: 501, + otherStuff: true + }; + return notImplementedErr; + } + })); + router.get('/users', function *() {}); + router.put('/users', function *() {}); + request(http.createServer(app.callback())) + .search('/users') + .expect(501) + .end(function(err, res) { + if (err) return done(err); + // the 'Allow' header is not set when throwing + res.header.should.not.have.property('allow'); + res.body.should.eql({ error: 'Custom Not Implemented Error', + statusCode: 501, + otherStuff: true + }); + done(); + }); + }); + + it('does not send 405 if route matched but status is 404', function (done) { + var app = koa(); + var router = new Router(); + app.use(router.routes()); + app.use(router.allowedMethods()); + router.get('/users', function *() { + this.status = 404; + }); + request(http.createServer(app.callback())) + .get('/users') + .expect(404) + .end(function(err, res) { + if (err) return done(err); + done(); + }); }); });