Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding custom user-provided throw functions for "Not Implemented" and "Method Not Allowed" #206

Merged
merged 1 commit into from
Nov 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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