From 8717a3ad2bcada0a16bc7dace6260e087b9b5f09 Mon Sep 17 00:00:00 2001 From: Jonathan Ong Date: Thu, 28 Nov 2013 00:13:13 -0800 Subject: [PATCH] req.is(): make better closes #105 and #106 --- docs/api/request.md | 26 +++++++-------- lib/request.js | 70 ++++++++++++++++++++++++---------------- test/request/is.js | 78 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 122 insertions(+), 52 deletions(-) diff --git a/docs/api/request.md b/docs/api/request.md index a36c999..3c33874 100644 --- a/docs/api/request.md +++ b/docs/api/request.md @@ -152,26 +152,24 @@ this.body = yield db.find('something'); ### req.is(type) - Check if the incoming request contains the `Content-Type` - header field, and it contains the give mime `type`. + Check if the incoming request contains the "Content-Type" + header field, and it contains any of the give mime `type`s. + If there is no request body, `null` is returned. + If there is no content type, `false` is returned. + Otherwise, it returns the first `type` that matches. ```js // With Content-Type: text/html; charset=utf-8 -this.is('html'); -this.is('.html'); -this.is('text/html'); -this.is('text/*'); -// => true +this.is('html'); // => 'html' +this.is('text/html'); // => 'text/html' +this.is('text/*', 'text/html'); // => 'text/*' // When Content-Type is application/json -this.is('json'); -this.is('.json'); -this.is('application/json'); -this.is('application/*'); -// => true +this.is('json', 'urlencoded'); // => 'json' +this.is('application/json'); // => 'application/json' +this.is('html', 'application/*'); // => 'application/*' -this.is('html'); -// => false +this.is('html'); // => false ``` ### req.accepts(types) diff --git a/lib/request.js b/lib/request.js index 2e7e8e1..001ed9e 100644 --- a/lib/request.js +++ b/lib/request.js @@ -487,49 +487,63 @@ module.exports = { /** * Check if the incoming request contains the "Content-Type" - * header field, and it contains the give mime `type`. + * header field, and it contains any of the give mime `type`s. + * If there is no request body, `null` is returned. + * If there is no content type, `false` is returned. + * Otherwise, it returns the first `type` that matches. * * Examples: * * // With Content-Type: text/html; charset=utf-8 - * this.is('html'); - * this.is('text/html'); - * this.is('text/*'); - * // => true + * this.is('html'); // => 'html' + * this.is('text/html'); // => 'text/html' + * this.is('text/*', 'text/html'); // => 'text/*' * * // When Content-Type is application/json - * this.is('json'); - * this.is('application/json'); - * this.is('application/*'); - * // => true + * this.is('json', 'urlencoded'); // => 'json' + * this.is('application/json'); // => 'application/json' + * this.is('html', 'application/*'); // => 'application/*' * - * this.is('html'); - * // => false + * this.is('html'); // => false * - * @param {String} type - * @return {Boolean} + * @param {String|Array} types... + * @return {String|false|null} * @api public */ - is: function(type){ + is: function(types){ + // no request body + if (!(this.length || 'transfer-encoding' in this.header)) return null; var ct = this.type; + // request body without a content type? if (!ct) return false; ct = ct.split(';')[0]; - - // extension given - if (!~type.indexOf('/')) type = mime.lookup(type); - - // type or subtype match - if (~type.indexOf('*')) { - type = type.split('/'); - ct = ct.split('/'); - if ('*' == type[0] && type[1] == ct[1]) return true; - if ('*' == type[1] && type[0] == ct[0]) return true; - return false; + if (!types) return ct; + if (!Array.isArray(types)) types = [].slice.call(arguments); + var type, mt, cts; + for (var i = 0; i < types.length; i++) { + type = types[i]; + // exact match + if (type == ct) return type; + // convert to a valid mime type + switch (type) { + case 'urlencoded': + mt = 'application/x-www-form-urlencoded'; + break; + default: + mt = ~type.indexOf('/') ? type : mime.lookup(type); + } + // mime types match + if (mt == ct) return type; + // type or subtype match + if (!~type.indexOf('*')) continue; + mt = mt.split('/'); + cts = cts || ct.split('/'); + if ('*' == mt[0] && mt[1] == cts[1]) return type; + if ('*' == mt[1] && mt[0] == cts[0]) return type; } - - // exact match - return type == ct; + // no matches + return false; }, /** diff --git a/test/request/is.js b/test/request/is.js index c8a2654..398735a 100644 --- a/test/request/is.js +++ b/test/request/is.js @@ -1,38 +1,96 @@ +var should = require('should'); var context = require('../context'); describe('ctx.is(type)', function(){ it('should ignore params', function(){ var ctx = context(); ctx.header['content-type'] = 'text/html; charset=utf-8'; - ctx.is('text/*').should.be.true; + ctx.header['transfer-encoding'] = 'chunked'; + + ctx.is('text/*').should.equal('text/*'); }) - describe('given a mime', function(){ - it('should check the type', function(){ + describe('when no body is given', function(){ + it('should return null', function(){ + var ctx = context(); + + should(ctx.is()).null; + should(ctx.is('image/*')).null; + should(ctx.is('text/*', 'image/*')).null; + }) + }) + + describe('when no content type is given', function(){ + it('should return false', function(){ + var ctx = context(); + ctx.header['transfer-encoding'] = 'chunked'; + + ctx.is().should.be.false; + ctx.is('image/*').should.be.false; + ctx.is('text/*', 'image/*').should.be.false; + }) + }) + + describe('give no types', function(){ + it('should return the mime type', function(){ var ctx = context(); ctx.header['content-type'] = 'image/png'; + ctx.header['transfer-encoding'] = 'chunked'; - ctx.is('image/png').should.be.true; - ctx.is('image/*').should.be.true; - ctx.is('*/png').should.be.true; + ctx.is().should.equal('image/png'); + }) + }) + describe('given one type', function(){ + it('should return the type or false', function(){ + var ctx = context(); + ctx.header['content-type'] = 'image/png'; + ctx.header['transfer-encoding'] = 'chunked'; + + ctx.is('png').should.equal('png'); + ctx.is('.png').should.equal('.png'); + ctx.is('image/png').should.equal('image/png'); + ctx.is('image/*').should.equal('image/*'); + ctx.is('*/png').should.equal('*/png'); + + ctx.is('jpeg').should.be.false; + ctx.is('.jpeg').should.be.false; ctx.is('image/jpeg').should.be.false; ctx.is('text/*').should.be.false; ctx.is('*/jpeg').should.be.false; }) }) - describe('given an extension', function(){ - it('should check the type', function(){ + describe('given multiple types', function(){ + it('should return the first match or false', function(){ var ctx = context(); ctx.header['content-type'] = 'image/png'; + ctx.header['transfer-encoding'] = 'chunked'; - ctx.is('png').should.be.true; - ctx.is('.png').should.be.true; + ctx.is('png').should.equal('png'); + ctx.is('.png').should.equal('.png'); + ctx.is('text/*', 'image/*').should.equal('image/*'); + ctx.is('image/*', 'text/*').should.equal('image/*'); + ctx.is('image/*', 'image/png').should.equal('image/*'); + ctx.is('image/png', 'image/*').should.equal('image/png'); ctx.is('jpeg').should.be.false; ctx.is('.jpeg').should.be.false; + ctx.is('text/*', 'application/*').should.be.false; + ctx.is('text/html', 'text/plain', 'application/json').should.be.false; + }) + }) + + describe('when Content-Type: application/x-www-form-urlencoded', function(){ + it('should match `urlencoded', function(){ + var ctx = context(); + ctx.header['content-type'] = 'application/x-www-form-urlencoded'; + ctx.header['transfer-encoding'] = 'chunked'; + + ctx.is('urlencoded').should.equal('urlencoded'); + ctx.is('json', 'urlencoded').should.equal('urlencoded'); + ctx.is('urlencoded', 'json').should.equal('urlencoded'); }) }) }) \ No newline at end of file