Skip to content

Commit

Permalink
Added res.respondTo(obj)
Browse files Browse the repository at this point in the history
  • Loading branch information
tj committed Feb 19, 2012
1 parent 892b605 commit b565e25
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 6 deletions.
65 changes: 65 additions & 0 deletions lib/response.js
Expand Up @@ -14,6 +14,7 @@ var fs = require('fs')
, path = require('path')
, connect = require('connect')
, utils = connect.utils
, accept = require('./utils').accept
, statusCodes = http.STATUS_CODES
, send = connect.static.send
, mime = require('mime')
Expand Down Expand Up @@ -286,6 +287,70 @@ res.type = function(type){
return this.set('Content-Type', mime.lookup(type));
};

/**
* Respond to the Acceptable formats using an `obj`
* of mime-type callbacks.
*
* This method uses `req.accepted`, an array of
* acceptable types ordered by their quality values.
* When "Accept" is not present the _first_ callback
* is invoked, otherwise the first match is used. When
* no match is performed the server responds with
* 406 "Not Acceptable".
*
* Examples:
*
* res.respondTo({
* 'text/plain': function(){
* res.type('text').send('hey');
* },
*
* 'text/html': function(){
* res.send('<p>hey</p>');
* },
*
* 'appliation/json': function(){
* res.send({ message: 'hey' });
* }
* });
*
* @param {Object} obj
* @return {ServerResponse} for chaining
* @api public
*/

res.respondTo = function(obj){
var keys = Object.keys(obj)
, req = this.req
, next = req.next
, accepted = req.accepted
, type
, key;

out:
for (var i = 0, len = accepted.length; i < len; ++i) {
for (var j = 0, jlen = keys.length; j < jlen; ++j) {
if (accept(keys[j].split('/'), accepted[i])) {
key = keys[j];
break out;
}
}
}

if (!accepted.length) key = keys.shift();

if (key) {
obj[key](req, res, next);
} else {
var err = new Error('Not Acceptable');
err.status = 406;
err.types = keys;
next(err);
}

return this;
};

/**
* Set _Content-Disposition_ header to _attachment_ with optional `filename`.
*
Expand Down
24 changes: 18 additions & 6 deletions lib/utils.js
Expand Up @@ -52,7 +52,7 @@ exports.flatten = function(arr, ret){
* @param {String} type
* @param {String} str
* @return {Boolean}
* @api public
* @api private
*/

exports.accepts = function(type, str){
Expand All @@ -73,14 +73,26 @@ exports.accepts = function(type, str){

for (var i = 0; i < len; ++i) {
obj = accepted[i];
ok = (type[0] == obj.type || '*' == obj.type)
&& (type[1] == obj.subtype || '*' == obj.subtype);
if (ok) return true;
if (exports.accept(type, obj)) return true;
}

return false;
};

/**
* Check if `type` array is acceptable for `other`.
*
* @param {Array} type
* @param {Object} other
* @return {Boolean}
* @api private
*/

exports.accept = function(type, other){
return (type[0] == other.type || '*' == other.type)
&& (type[1] == other.subtype || '*' == other.subtype);
};

/**
* Parse accept `str`, returning
* an array objects containing
Expand All @@ -90,7 +102,7 @@ exports.accepts = function(type, str){
*
* @param {Type} name
* @return {Type}
* @api public
* @api private
*/

exports.parseAccept = function(str){
Expand All @@ -111,7 +123,7 @@ exports.parseAccept = function(str){
*
* @param {Type} name
* @return {Type}
* @api public
* @api private
*/

exports.parseQuality = function(str){
Expand Down
65 changes: 65 additions & 0 deletions test/res.respondTo.js
@@ -0,0 +1,65 @@

var express = require('../')
, request = require('./support/http')
, utils = require('../lib/utils');

var app = express();

app.use(function(req, res, next){
res.respondTo({
'text/plain': function(){
res.type('text').send('hey');
},

'text/html': function(){
res.send('<p>hey</p>');
},

'appliation/json': function(){
res.send({ message: 'hey' });
}
});
});

app.use(function(err, req, res, next){
res.send(406, 'Supports: ' + err.types.join(', '));
})

describe('req', function(){
describe('.respondTo(obj)', function(){
it('should utilize qvalues in negotiation', function(done){
request(app)
.get('/')
.set('Accept', 'text/html; q=.5, appliation/json, */*; q=.1')
.expect('{"message":"hey"}', done);
})

it('should allow wildcard type/subtypes', function(done){
request(app)
.get('/')
.set('Accept', 'text/html; q=.5, appliation/*, */*; q=.1')
.expect('{"message":"hey"}', done);
})

describe('when Accept is not present', function(){
it('should invoke the first callback', function(done){
request(app)
.get('/')
.expect('hey', done);
})
})

describe('when no match is made', function(){
it('should should respond with 406 not acceptable', function(done){
request(app)
.get('/')
.set('Accept', 'foo/bar')
.end(function(res){
res.should.have.status(406);
res.body.should.equal('Supports: text/plain, text/html, appliation/json');
done();
});
})
})
})
})

0 comments on commit b565e25

Please sign in to comment.