diff --git a/index.js b/index.js index 8d4f6a2..9010108 100644 --- a/index.js +++ b/index.js @@ -67,14 +67,14 @@ Negotiator.prototype.languages = function languages(available) { return preferredLanguages(this.request.headers['accept-language'], available); }; -Negotiator.prototype.mediaType = function mediaType(available) { - var set = this.mediaTypes(available); +Negotiator.prototype.mediaType = function mediaType(available, options) { + var set = this.mediaTypes(available, options); return set && set[0]; }; -Negotiator.prototype.mediaTypes = function mediaTypes(available) { +Negotiator.prototype.mediaTypes = function mediaTypes(available, options) { var preferredMediaTypes = loadModule('mediaType').preferredMediaTypes; - return preferredMediaTypes(this.request.headers.accept, available); + return preferredMediaTypes(this.request.headers.accept, available, options); }; // Backwards compatibility diff --git a/lib/mediaType.js b/lib/mediaType.js index 9e890ae..007eec9 100644 --- a/lib/mediaType.js +++ b/lib/mediaType.js @@ -159,16 +159,17 @@ function specify(type, spec, index) { * @public */ -function preferredMediaTypes(accept, provided) { +function preferredMediaTypes(accept, provided, options) { // RFC 2616 sec 14.2: no header = */* var accepts = parseAccept(accept === undefined ? '*/*' : accept || ''); + var detailed = options && options.detailed; if (!provided) { // sorted list of all types return accepts .filter(isQuality) .sort(compareSpecs) - .map(getFullType); + .map(detailed ? getDetailedSpec : getFullType); } var priorities = provided.map(function getPriority(type, index) { @@ -176,9 +177,11 @@ function preferredMediaTypes(accept, provided) { }); // sorted list of accepted types - return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) { + var acceptables = priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) { return provided[priorities.indexOf(priority)]; }); + + return detailed ? acceptables.map(parseMediaType).map(getDetailedSpec) : acceptables; } /** @@ -199,6 +202,19 @@ function getFullType(spec) { return spec.type + '/' + spec.subtype; } +/** + * Get detailed description object for this spec. + * @private + */ + +function getDetailedSpec(spec) { + return { + type: spec.type + '/' + spec.subtype, + parameters: spec.params, + q: spec.q + }; +}; + /** * Check if a spec has any quality. * @private diff --git a/test/mediaType.js b/test/mediaType.js index 8365be7..20bf406 100644 --- a/test/mediaType.js +++ b/test/mediaType.js @@ -52,6 +52,26 @@ describe('negotiator.mediaType()', function () { }) }) +describe('negotiator.mediaType(undefined, {detailed: true})', function () { + whenAccept('text/*', function () { + it('should return a detailed spec object with text/*', function () { + assert.deepEqual( + this.negotiator.mediaType(undefined, {detailed: true}), + {"type": "text/*", parameters: {}, "q": 1} + ) + }) + }) + + whenAccept('text/plain;charset=utf-8;q=0.8, application/json;q=0.5, text/html;q=0.7, */*;q=0.1', function () { + it('should return a detailed object for text/plain', function () { + assert.deepEqual( + this.negotiator.mediaType(undefined, {detailed: true}), + {"type": "text/plain", parameters: {"charset": "utf-8"}, "q": .8} + ) + }) + }) +}) + describe('negotiator.mediaType(array)', function () { whenAccept(undefined, function () { it('should return first item in list', function () { @@ -126,6 +146,31 @@ describe('negotiator.mediaType(array)', function () { }) }) +describe('negotiator.mediaType(array, {detailed: true})', function() { + whenAccept('text/*', function () { + it('should return a detailed spec object with text/html', function () { + assert.deepEqual( + this.negotiator.mediaType(["text/html"], {detailed: true}), + {"type": "text/html", parameters: {}, "q": 1} + ) + }) + }) + + whenAccept('text/html;LEVEL=1, application/json;q=0.5', function () { + it('should return parameters, but require an exact match', function () { + assert.deepEqual( + this.negotiator.mediaType(["text/html"], {detailed: true}), + undefined + ) + + assert.deepEqual( + this.negotiator.mediaType(["text/html; level=1"], {detailed: true}), + {"type": "text/html", "parameters": {"level": "1"}, "q": 1} + ) + }) + }) +}) + describe('negotiator.mediaTypes()', function () { whenAccept(undefined, function () { it('should return */*', function () { @@ -194,6 +239,29 @@ describe('negotiator.mediaTypes()', function () { }) }) +describe('negotiator.mediaTypes(undefined, {detailed: true})', function() { + whenAccept('text/*', function () { + it('should return a detailed spec object with text/*', function () { + assert.deepEqual( + this.negotiator.mediaTypes(undefined, {detailed: true}), + [{"type": "text/*", parameters: {}, "q": 1}] + ) + }) + }) + + 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} + ] + ) + }) + }) +}) + describe('negotiator.mediaTypes(array)', function () { whenAccept(undefined, function () { it('should return return original list', mediaTypesNegotiated( @@ -458,6 +526,31 @@ describe('negotiator.mediaTypes(array)', function () { }) }) +describe('negotiator.mediaTypes(array, {detailed: true})', function() { + whenAccept('text/*', function () { + it('should return a detailed spect object with text/html', function () { + assert.deepEqual( + this.negotiator.mediaTypes(["text/html"], {detailed: true}), + [{"type": "text/html", parameters: {}, "q": 1}] + ) + }) + }) + + whenAccept('text/html;LEVEL=1, application/json;q=0.5', function () { + it('should return parameters, but require an exact match', 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}] + ) + }) + }) +}) + function createRequest(headers) { var request = { headers: {}