Skip to content
This repository was archived by the owner on May 1, 2025. It is now read-only.

Commit d384146

Browse files
committed
Merge pull request #3 from Turistforeningen/feat/in-operator
Add supoort for $in and $nin operators
2 parents 631fdda + d2a22a4 commit d384146

File tree

3 files changed

+107
-21
lines changed

3 files changed

+107
-21
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ useful when building an API and accepting various user specificed queries.
1515
* Blacklisted query parameters
1616
* Whitelisted query parameters
1717
* Basic operators
18-
* `$ne`
18+
* `$eq`
1919
* `$gt`
2020
* `$lt`
21-
* `$regex`
21+
* `$ne`
22+
* `$in`
23+
* `$nin`
2224
* `$exists`
25+
* `$regex`
2326

2427
| operation | query string | query object |
2528
|-----------|---------------|--------------|
@@ -32,6 +35,8 @@ useful when building an API and accepting various user specificed queries.
3235
| starts with | `?foo=^bar` | `{ foo: { $regex: "^foo", $options: "i" }}` |
3336
| ends with | `?foo=$bar` | `{ foo: { $regex: "foo$", $options: "i" }}` |
3437
| contains | `?foo=~bar` | `{ foo: { $regex: "foo", $options: "i" }}` |
38+
| in array | `?foo[]=bar&foo[]=baz` | `{ foo: { $in: ['bar', 'baz'] }}` |
39+
| not in array | `?foo[]=!bar&foo[]=!baz` | `{ foo: { $nin: ['bar', 'baz'] }}` |
3540

3641
* Geospatial operators
3742
* `$geoWithin` (polygon)

index.js

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
module.exports = function MongoQS(opts) {
55
opts = opts || {};
66

7-
this.ops = opts.ops || ['!', '^', '$', '~', '>', '<'];
7+
this.ops = opts.ops || ['!', '^', '$', '~', '>', '<', '$in'];
88
this.alias = opts.alias || {};
99
this.blacklist = opts.blacklist || {};
1010
this.whitelist = opts.whitelist || {};
1111
this.custom = opts.custom || {};
1212

13+
this.keyRegex = opts.keyRegex || /^[a-zæøå0-9-_.]+$/i;
14+
this.arrRegex = opts.arrRegex || /^[a-zæøå0-9-_.]+\[\]$/i;
15+
1316
for (var param in this.custom) {
1417
switch (param) {
1518
case 'bbox':
@@ -104,24 +107,55 @@ module.exports.prototype.parse = function(query) {
104107
for (var key in query) {
105108
val = query[key];
106109

110+
// whitelist
107111
if (Object.keys(this.whitelist).length && !this.whitelist[key]) {
108112
continue;
109113
}
110114

115+
// blacklist
111116
if (this.blacklist[key]) {
112117
continue;
113118
}
114119

115-
if (typeof val !== 'string') {
120+
// alias
121+
if (this.alias[key]) {
122+
key = this.alias[key];
123+
}
124+
125+
// string key
126+
if (typeof val === 'string' && !this.keyRegex.test(key)) {
116127
continue;
117128
}
118129

119-
if (!/^[a-zæøå0-9-_.]+$/i.test(key)) {
130+
// array key
131+
if (val instanceof Array && this.arrRegex.test(key)) {
132+
if (this.ops.indexOf('$in') >= 0) {
133+
key = key.substr(0, key.length-2);
134+
135+
// $in query
136+
if (val[0][0] !== '!') {
137+
res[key] = {$in: val.filter(function(element) {
138+
return element[0] !== '!';
139+
}).map(function(element) {
140+
return isNaN(element) ? element : parseFloat(element, 10);
141+
})};
142+
143+
// $nin query
144+
} else {
145+
res[key] = {$nin: val.filter(function(element) {
146+
return element[0] === '!';
147+
}).map(function(element) {
148+
element = element.substr(1);
149+
return isNaN(element) ? element : parseFloat(element, 10);
150+
})};
151+
}
152+
}
153+
120154
continue;
121155
}
122156

123-
if (this.alias[key]) {
124-
key = this.alias[key];
157+
if (typeof val !== 'string') {
158+
continue;
125159
}
126160

127161
if (typeof this.custom[key] === 'function') {

test.js

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -287,22 +287,69 @@ describe('parse()', function() {
287287
});
288288
});
289289

290-
it('returns multiple querys', function() {
291-
query = qs.parse({
292-
foo: '',
293-
bar: '!foo',
294-
'%foo': 'bar',
295-
'bix.bax': 'that',
296-
'foo-bar': 'bar-foo'
290+
describe('$in / $nin operator', function() {
291+
it('returns in array query', function() {
292+
var string = 'foo[]=10&foo[]=10.011&foo[]=bar';
293+
var params = require('querystring').parse(string);
294+
295+
assert.deepEqual(qs.parse(params), {
296+
foo: {
297+
$in: [10, 10.011, 'bar']
298+
}
299+
});
297300
});
298301

299-
assert.deepEqual(query, {
300-
foo: {
301-
$exists: true
302-
},
303-
bar: {
304-
$ne: 'foo'
305-
},
302+
it('returns in array without any not in array query', function() {
303+
var string = 'foo[]=10&foo[]=!10.011&foo[]=!bar&foo[]=baz';
304+
var params = require('querystring').parse(string);
305+
306+
assert.deepEqual(qs.parse(params), {
307+
foo: {
308+
$in: [10, 'baz']
309+
}
310+
});
311+
});
312+
313+
it('returns not in array query', function() {
314+
var string = 'foo[]=!10&foo[]=!10.011&foo[]=!bar';
315+
var params = require('querystring').parse(string);
316+
317+
assert.deepEqual(qs.parse(params), {
318+
foo: {
319+
$nin: [10, 10.011, 'bar']
320+
}
321+
});
322+
});
323+
324+
it('returns not in array without any in array query', function() {
325+
var string = 'foo[]=!10&foo[]=10.011&foo[]=bar&foo[]=!baz';
326+
var params = require('querystring').parse(string);
327+
328+
assert.deepEqual(qs.parse(params), {
329+
foo: {
330+
$nin: [10, 'baz']
331+
}
332+
});
333+
});
334+
});
335+
336+
it('returns multiple querys', function() {
337+
var string = [
338+
'foo=',
339+
'bar=!',
340+
'baz=!foo',
341+
'bix=bez',
342+
'%foo=bar',
343+
'bix.bax=that',
344+
'foo-bar=bar-foo'
345+
].join('&&');
346+
var params = require('querystring').parse(string);
347+
348+
assert.deepEqual(qs.parse(params), {
349+
foo: { $exists: true },
350+
bar: { $exists: false },
351+
baz: { $ne: 'foo' },
352+
bix: 'bez',
306353
'bix.bax': 'that',
307354
'foo-bar': 'bar-foo'
308355
});

0 commit comments

Comments
 (0)