Skip to content

Commit

Permalink
Changed query params handling to accept query params matching
Browse files Browse the repository at this point in the history
  • Loading branch information
groyoh committed Sep 2, 2015
1 parent 4b7f7f2 commit bfb2c69
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 59 deletions.
1 change: 1 addition & 0 deletions .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"it": false,
"console": false,
"describe": false,
"context": false,
"before": false,
"after": false,
"expect": false,
Expand Down
5 changes: 2 additions & 3 deletions lib/middleware/route-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,13 @@ module.exports = function(options, cb) {
});

if (handlers) {
var queryParams = Object.keys(req.query);
if (queryParams.length === 0){
var queryParams = req.query;
if (Object.keys(queryParams).length === 0){
handlers.sort(queryComparator.noParamComparator);
} else {
queryComparator.countMatchingQueryParms(handlers, queryParams);
handlers.sort(queryComparator.queryParameterComparator);
}

var requestHandled = false;
handlers.forEach(function (handler) {
if (requestHandled) {
Expand Down
16 changes: 6 additions & 10 deletions lib/parse/url.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
var qs = require('qs');

//https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md#operators
var PARAMETER_OPERATORS_REGEX = /\#|\+|\?|\&/g;
var PARAMETER_OPERATORS_REGEX = /\#|\+|\?/g;
var PATHNAME_REGEX = /\{(.*?)\}/g;
var URL_SPLIT_REGEX = /\{?\?/;

Expand All @@ -9,22 +11,16 @@ exports.parse = function (url) {
var processPathname = function(path){
return path.replace(PATHNAME_REGEX, ':$1');
};

var processQueryParameters = function(queryParams) {
if (!queryParams) {
return [];
return {};
}

return queryParams.split(/\,|\{|\}/g).map(function(i){
return i.replace(PARAMETER_OPERATORS_REGEX, '');
}).filter(function(i){
return i.length > 0;
});
return qs.parse(queryParams.replace(/\{|\}/g,'').replace(/\,/g,'&').replace(PARAMETER_OPERATORS_REGEX, ''));
};

return {
uriTemplate: url,
queryParams: urlArr.length > 1 ? processQueryParameters(urlArr[1]) : [],
queryParams: urlArr.length > 1 ? processQueryParameters(urlArr[1]) : {},
url: processPathname(urlArr[0])
};
};
30 changes: 22 additions & 8 deletions lib/query-comparator.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
var _ = require('lodash');

exports.noParamComparator = function(a, b){
return (a.parsedUrl.queryParams.length - b.parsedUrl.queryParams.length);
return (Object.keys(a.parsedUrl.queryParams).length - Object.keys(b.parsedUrl.queryParams).length);
};

exports.queryParameterComparator = function(a, b){
if (b.matchingQueryParams === a.matchingQueryParams){
return (a.nonMatchingQueryParams - b.nonMatchingQueryParams);
if (b.exactMatchingQueryParams === a.exactMatchingQueryParams){
return (a.nonMatchingQueryParams - b.nonMatchingQueryParams);
}
return (b.exactMatchingQueryParams - a.exactMatchingQueryParams);
}
return (b.matchingQueryParams - a.matchingQueryParams);
};

exports.countMatchingQueryParms = function (handlers, reqQueryParams){
handlers.forEach(function(handler){
handler.matchingQueryParams = 0;
handler.exactMatchingQueryParams = 0;
handler.nonMatchingQueryParams = 0;
handler.parsedUrl.queryParams.forEach(function(templateQueryParam){
if (reqQueryParams.indexOf(templateQueryParam)>-1){
handler.matchingQueryParams +=1;
} else {
var specQueryParams = handler.parsedUrl.queryParams;
for(var param in specQueryParams){
var value = specQueryParams[param];
if (reqQueryParams.hasOwnProperty(param)){
var reqValue = reqQueryParams[param];
if (_.isEqual(value, reqValue)){
handler.matchingQueryParams += 1;
handler.exactMatchingQueryParams += 1;
}else if (value === ''){
handler.matchingQueryParams += 1;
}
}else{
handler.nonMatchingQueryParams +=1;
}
});
}
});
};
};
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"lodash": "^3.8.0",
"optimist": "0.6.1",
"path-to-regexp": "^1.0.3",
"tv4": "^1.1.9"
"tv4": "^1.1.9",
"qs": "^4.0.0"
},
"devDependencies": {
"grunt": "^0.4.5",
Expand Down
29 changes: 29 additions & 0 deletions test/api/query-parameter-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,33 @@ describe('Query Parameters', function(){
});
});

describe('/api/things?param1=12345{&param2}', function(){
it('should respond with response specified in a endpoint with "param1" and "param2" parameters', function(done){
request.get('/api/query?param1=12345&param2=2')
.expect(200)
.expect('Content-type', 'application/json;charset=UTF-8')
.expect({id: 'parameter1_12345_parameter2'})
.end(helper.endCb(done));
});
});

describe('/api/things?param1=12345&param1=6789', function(){
it('should respond with response specified in a endpoint with "param1" parameter as array', function(done){
request.get('/api/query?param1=12345&param1=6789')
.expect(200)
.expect('Content-type', 'application/json;charset=UTF-8')
.expect({id: 'parameter1_12345_6789'})
.end(helper.endCb(done));
});
});

describe('/api/things?param1[key1]=12345&param1[key2]=6789', function(){
it('should respond with response specified in a endpoint with "param1" parameter as object', function(done){
request.get('/api/query?param1[key1]=12345&param1[key2]=6789')
.expect(200)
.expect('Content-type', 'application/json;charset=UTF-8')
.expect({id: 'parameter1_key1_12345_key2_6789'})
.end(helper.endCb(done));
});
});
});
46 changes: 46 additions & 0 deletions test/example/md/query-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,49 @@ See [Blueprint API - URI parameters section](https://github.com/apiaryio/api-blu
{
"id": "parameter2_parameter3"
}

## Things [/api/query?param1=12345{&param2}]

+ Parameters
+ param1 (string, `12345`) ... Parameter for the request
+ param2 (string, `12345`) ... Parameter for the request

### Get with query parameter [GET]

+ Response 200 (application/json;charset=UTF-8)

+ Body

{
"id": "parameter1_12345_parameter2"
}

## Things [/api/query?param1=12345&param1=6789]

+ Parameters
+ param1 (array, [`12345`,`6789`]) ... Parameter for the request

### Get with query parameter [GET]

+ Response 200 (application/json;charset=UTF-8)

+ Body

{
"id": "parameter1_12345_6789"
}

## Things [/api/query?param1[key1]=12345&param1[key2]=6789]

+ Parameters
+ param1 (object, { `key1`: `12345`, `key2`: `6789` }) ... Parameter for the request

### Get with query parameter [GET]

+ Response 200 (application/json;charset=UTF-8)

+ Body

{
"id": "parameter1_key1_12345_key2_6789"
}
129 changes: 99 additions & 30 deletions test/unit/query-comparator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ describe('Query Comparator', function() {
describe('noParamComparator', function() {
it('Should sort by number of query parameters in the specification', function(){
var requests = [
{parsedUrl: {queryParams: [1,2,4]}},
{parsedUrl: {queryParams: [1,4]}},
{parsedUrl: {queryParams: [1]}},
{parsedUrl: {queryParams: []}}
{parsedUrl: {queryParams: {'param1': ''}}},
{parsedUrl: {queryParams: {'param1': '', 'param2': '', 'param3': ''}}},
{parsedUrl: {queryParams: {'param3': '', 'param4': ''}}},
{parsedUrl: {queryParams: {'param2': '', 'param4': '', 'param1': '12345'}}}
];

var expected = [
{parsedUrl: {queryParams: []}},
{parsedUrl: {queryParams: [1]}},
{parsedUrl: {queryParams: [1, 4]}},
{parsedUrl: {queryParams: [1, 2, 4]}}
{parsedUrl: {queryParams: {'param1': ''}}},
{parsedUrl: {queryParams: {'param3': '', 'param4': ''}}},
{parsedUrl: {queryParams: {'param1': '', 'param2': '', 'param3': ''}}},
{parsedUrl: {queryParams: {'param2': '', 'param4': '', 'param1': '12345'}}}
];

requests.sort(queryComparator.noParamComparator);
Expand All @@ -27,17 +27,19 @@ describe('Query Comparator', function() {
describe('queryParameterComparator', function() {
it('Should sort by number of query parameters in the specification', function(){
var requests = [
{matchingQueryParams: 1, nonMatchingQueryParams: 5},
{matchingQueryParams: 2, nonMatchingQueryParams: 1},
{matchingQueryParams: 2, nonMatchingQueryParams: 2},
{matchingQueryParams: 4, nonMatchingQueryParams: 1}
{matchingQueryParams: 1, exactMatchingQueryParams: 0, nonMatchingQueryParams: 5},
{matchingQueryParams: 2, exactMatchingQueryParams: 0, nonMatchingQueryParams: 2},
{matchingQueryParams: 2, exactMatchingQueryParams: 0, nonMatchingQueryParams: 1},
{matchingQueryParams: 2, exactMatchingQueryParams: 1, nonMatchingQueryParams: 2},
{matchingQueryParams: 4, exactMatchingQueryParams: 0, nonMatchingQueryParams: 1}
];

var expected = [
{matchingQueryParams: 4, nonMatchingQueryParams: 1},
{matchingQueryParams: 2, nonMatchingQueryParams: 1},
{matchingQueryParams: 2, nonMatchingQueryParams: 2},
{matchingQueryParams: 1, nonMatchingQueryParams: 5}
{matchingQueryParams: 4, exactMatchingQueryParams: 0, nonMatchingQueryParams: 1},
{matchingQueryParams: 2, exactMatchingQueryParams: 1, nonMatchingQueryParams: 2},
{matchingQueryParams: 2, exactMatchingQueryParams: 0, nonMatchingQueryParams: 1},
{matchingQueryParams: 2, exactMatchingQueryParams: 0, nonMatchingQueryParams: 2},
{matchingQueryParams: 1, exactMatchingQueryParams: 0, nonMatchingQueryParams: 5}
];

requests.sort(queryComparator.queryParameterComparator);
Expand All @@ -46,23 +48,90 @@ describe('Query Comparator', function() {
});

describe('countMatchingQueryParms', function() {
it('Should count the number of matching and non matching query parameters against handlers query parameters', function(){
var requestParams = {
'param1': '12345',
'param2': '6789',
'param5': ['12345','6789'],
'param6': {'key1': '12345', 'key2': '6789'}
};
var handlers = null;

var handlers = [
{parsedUrl: {queryParams: ['param1']}},
{parsedUrl: {queryParams: ['param1', 'param2', 'param3']}},
{parsedUrl: {queryParams: ['param3', 'param4']}}
];
beforeEach(function(){
queryComparator.countMatchingQueryParms(handlers, requestParams);
});

var requestParams = ['param1', 'param2'];
context('when no value is given', function() {
var handler = {parsedUrl: {queryParams: {'param1': ''}}};

queryComparator.countMatchingQueryParms(handlers, requestParams);
assert.equal(handlers[0].matchingQueryParams, 1);
assert.equal(handlers[0].nonMatchingQueryParams, 0);
assert.equal(handlers[1].matchingQueryParams, 2);
assert.equal(handlers[1].nonMatchingQueryParams, 1);
assert.equal(handlers[2].matchingQueryParams, 0);
assert.equal(handlers[2].nonMatchingQueryParams, 2);
before(function(){
handlers = [handler];
});

it('Should count the number of matching and non matching query parameters against handlers query parameters', function(){
assert.equal(handler.matchingQueryParams, 1);
assert.equal(handler.exactMatchingQueryParams, 0);
assert.equal(handler.nonMatchingQueryParams, 0);
});
});

context('when multiples params are given', function() {
before(function(){
handlers = [
{parsedUrl: {queryParams: {'param1': '', 'param2': '', 'param3': ''}}},
{parsedUrl: {queryParams: {'param3': '', 'param4': ''}}}
];
});

it('Should count the number of matching and non matching query parameters against handlers query parameters', function(){
assert.equal(handlers[0].matchingQueryParams, 2);
assert.equal(handlers[0].exactMatchingQueryParams, 0);
assert.equal(handlers[0].nonMatchingQueryParams, 1);
assert.equal(handlers[1].matchingQueryParams, 0);
assert.equal(handlers[1].exactMatchingQueryParams, 0);
assert.equal(handlers[1].nonMatchingQueryParams, 2);
});
});

context('when param is a string', function() {
var handler = {parsedUrl: {queryParams: {'param1': '12345', 'param2': '', 'param4': ''}}};

before(function(){
handlers = [handler];
});

it('Should count the number of matching and non matching query parameters against handlers query parameters', function(){
assert.equal(handler.matchingQueryParams, 2);
assert.equal(handler.exactMatchingQueryParams, 1);
assert.equal(handler.nonMatchingQueryParams, 1);
});
});

context('when param is an array', function() {
var handler = {parsedUrl: {queryParams: {'param5': ['12345','6789'], 'param4': ''}}};

before(function(){
handlers = [handler];
});

it('Should count the number of matching and non matching query parameters against handlers query parameters', function(){
assert.equal(handler.matchingQueryParams, 1);
assert.equal(handler.exactMatchingQueryParams, 1);
assert.equal(handler.nonMatchingQueryParams, 1);
});
});

context('when param is an object', function() {
var handler = {parsedUrl: {queryParams: {'param6': {'key1': '12345', 'key2': '6789'}, 'param4': ''}}};

before(function(){
handlers = [handler];
});

it('Should count the number of matching and non matching query parameters against handlers query parameters', function(){
assert.equal(handler.matchingQueryParams, 1);
assert.equal(handler.exactMatchingQueryParams, 1);
assert.equal(handler.nonMatchingQueryParams, 1);
});
});
});

Expand Down
Loading

0 comments on commit bfb2c69

Please sign in to comment.