From ee8b3c391f0899d4f5b1b05b3fe9b5a781891a42 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 27 May 2015 22:40:42 -0700 Subject: [PATCH] Media Type: Expose more-complete, unstringified parse results --- index.js | 4 ++-- lib/mediaType.js | 34 +++++++++++++++++++++++++++------- test/mediaType.js | 26 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index edae9cf..f0be2ef 100644 --- a/index.js +++ b/index.js @@ -47,8 +47,8 @@ Negotiator.prototype.mediaType = function mediaType(available) { return set && set[0]; }; -Negotiator.prototype.mediaTypes = function mediaTypes(available) { - return preferredMediaTypes(this.request.headers.accept, available); +Negotiator.prototype.mediaTypes = function mediaTypes(available, options) { + return preferredMediaTypes(this.request.headers.accept, available, options); }; // Backwards compatibility diff --git a/lib/mediaType.js b/lib/mediaType.js index 4170c25..e92481f 100644 --- a/lib/mediaType.js +++ b/lib/mediaType.js @@ -120,25 +120,45 @@ function specify(type, spec, index) { } -function preferredMediaTypes(accept, provided) { +/** + * Returns a list of the acceptable media ranges in preferred order. + * + * @param {string} accept The raw contents of the `Accept` header + * @param {string[]} provided The media ranges the server can produce + * for the resource in question. + * @param {Object} options Configuration options + * @param {boolean} options.detailed If true, this function returns a + * {type, parameters, q} object for each acceptable type, rather than a string. + */ +function preferredMediaTypes(accept, provided, options) { // RFC 2616 sec 14.2: no header = */* var accepts = parseAccept(accept === undefined ? '*/*' : accept || ''); + var detailed = options && options.detailed; + + var specDetails = function(spec) { + return { + type: "" + spec.type + '/' + spec.subtype, + parameters: spec.params, + q: spec.q + }; + }; + // return sorted list of all types if none are provided. if (!provided) { - // sorted list of all types - return accepts.filter(isQuality).sort(compareSpecs).map(function getType(spec) { - return spec.full; - }); + var mapper = detailed ? specDetails : function(spec) { return spec.full; }; + return accepts.filter(isQuality).sort(compareSpecs).map(mapper); } var priorities = provided.map(function getPriority(type, index) { return getMediaTypePriority(type, accepts, index); }); - // sorted list of accepted types - return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) { + // sorted list of acceptable types + var acceptables = priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) { return provided[priorities.indexOf(priority)]; }); + + return detailed ? acceptables.map(parseMediaType).map(specDetails) : acceptables; } function compareSpecs(a, b) { diff --git a/test/mediaType.js b/test/mediaType.js index d64ca4e..f39a8ec 100644 --- a/test/mediaType.js +++ b/test/mediaType.js @@ -194,6 +194,32 @@ describe('negotiator.mediaTypes()', function () { }) }) +describe('negotiator.mediaTypes(_, {detailed: true})', function() { + whenAccept('text/html;LEVEL=1, application/json;q=0.5', function () { + it('should return more-detailed spec objects', function () { + assert.deepEqual( + this.negotiator.mediaTypes(undefined, {detailed: true}), + [ + {"type": "text/html", "parameters": {"level": "1"}, "q": 1}, + {"type": "application/json", "parameters": {}, "q": 0.5}, + ] + ); + }); + + it('should accept a provided list, but still match parameters exactly', function () { + assert.deepEqual( + this.negotiator.mediaTypes(["text/html"], {detailed: true}), + [] + ); + + assert.deepEqual( + this.negotiator.mediaTypes(["text/html; level=1"], {detailed: true}), + [{"type": "text/html", "parameters": {"level": "1"}, "q": 1}] + ); + }); + }); +}); + describe('negotiator.mediaTypes(array)', function () { whenAccept(undefined, function () { it('should return return original list', mediaTypesNegotiated(