Skip to content

Commit

Permalink
Adding custom user-provided throw functions for "Not Implemented" and…
Browse files Browse the repository at this point in the history
… "Method Not Allowed"
  • Loading branch information
RobertHerhold committed Nov 21, 2015
1 parent b1abf24 commit 814cdc3
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 60 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
| --- | --- | --- |
| [options] | <code>Object</code> | |
| [options.throw] | <code>Boolean</code> | throw error instead of setting status and header |
| [options.notImplemented] | <code>Function</code> | throw throw the returned value in place of the default NotImplemented error |
| [options.methodNotAllowed] | <code>Function</code> | throw the returned value in place of the default MethodNotAllowed error |

**Example**
```javascript
Expand All @@ -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()
}));
```
<a name="module_koa-router--Router+redirect"></a>
#### router.redirect(source, destination, code) ⇒ <code>Router</code>
Expand Down
18 changes: 16 additions & 2 deletions lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
*/

Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
279 changes: 221 additions & 58 deletions test/lib/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});

Expand Down

0 comments on commit 814cdc3

Please sign in to comment.