Commit 3e4f68b0 by Jose Carlos López

Initial commit

parents
node_modules
The MIT License (MIT)
Copyright (c) 2015 Phil Batey <pbatey@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# query-to-mongo
Node.js package to convert query parameters into a [mongo](https://www.mongodb.org) query criteria and options
For example, a query such as: `name=john&age>21&fields=name,age&sort=name,-age&offset=10&limit=10` becomes the following hash:
```javascript
{
criteria: {
name: 'john',
age: { $gt: 21 }
},
options: {
fields: { name: true, age: true },
sort: { name: 1, age: -1 },
offset: 10,
limit: 10
}
}
```
The resulting query object can be used as parameters for a mongo collection query:
```javascript
var q2m = require('query-to-mongo')
var mongoskin = require('mongoskin')
var db = mongoskin.db('mongodb://localhost:27027/mydb')
var collection = db.collection('mycollection')
var query = q2m('name=john&age>13&limit=20')
collection.find(query.criteria, query.options).toArray(function(err, results) {
...
})
```
## API
### queryToMongo(query, options)
Convert the query portion of a url to a mongo query.
```javascript
var queryToMongo = require('query-to-mongo')
var query = queryToMongo('name=john&age>21&limit=10')
console.log(query)
```
```javascript
{ criteria: { name: 'john', age: { '$gt': 21 } },
options: { limit: 10 },
links: [Function] }
```
#### options:
* **maxLimit** The maximum limit (default is none)
* **ignore** List of criteria to ignore in addition to those used for query options ("fields", "sort", "offset", "limit")
* **parser** Query parser to use instead of _querystring_. Must implement `parse(string)` and `stringify(obj)`.
#### returns:
* **criteria** Mongo query criteria.
* **options** Mongo query options.
* **links** Function to calculate relative links.
##### links(url, totalCount)
Calculate relative links given the base url and totalCount. Can be used to populate the [express response links](http://expressjs.com/4x/api.html#res.links).
```javascript
var queryToMongo = require('query-to-mongo')
var query = queryToMongo('name=john&age>21&offset=20&limit=10')
console.log(query.links('http://localhost/api/v1/users', 100))
```
```javascript
{ prev: 'http://localhost/api/v1/users?name=john&age%3E21=&offset=10&limit=10',
first: 'http://localhost/api/v1/users?name=john&age%3E21=&offset=0&limit=10',
next: 'http://localhost/api/v1/users?name=john&age%3E21=&offset=30&limit=10',
last: 'http://localhost/api/v1/users?name=john&age%3E21=&offset=90&limit=10' }
```
## Use
The module is intended for use by express routes, and so takes a parsed query as input:
```
var querystring = require('querystring')
var q2m = require('query-to-mongo')
var query = 'name=john&age>21&fields=name,age&sort=name,-age&offset=10&limit=10'
var q = q2m(querystring.parse(query))
```
This makes it easy to use in an express route:
```
router.get('/api/v1/mycollection', function(req, res, next) {
var q = q2m(res.query);
...
}
```
The format for arguments was inspired by item #7 in [this article](http://blog.mwaysolutions.com/2014/06/05/10-best-practices-for-better-restful-api/) about best practices for RESTful APIs.
### Field selection
The _fields_ argument is a comma separated list of field names to include in the results. For example `fields=name,age` results in a _option.fields_ value of `{'name':true,'age':true}`. If no fields are specified then _option.fields_ is null, returning full documents as results.
The _omit_ argument is a comma separated list of field names to exclude in the results. For example `omit=name,age` results in a _option.fields_ value of `{'name':false,'age':false}`. If no fields are specified then _option.fields_ is null, returning full documents as results.
Note that either _fields_ or _omit_ can be used. If both are specified then _omit_ takes precedence and the _fields_ entry is ignored. Mongo will not accept a mix of true and false fields
### Sorting
The _sort_ argument is a comma separated list of fields to sort the results by. For example `sort=name,-age` results in a _option.sort_ value of `{'name':1,'age':-1}`. If no sort is specified then _option.sort_ is null and the results are not sorted.
### Paging
The _offset_ and _limit_ arguments indicate the subset of the full results to return. By default, the full results are returned. If _limit_ is set and the total count is obtained for the query criteria, pagination links can be generated:
```
collection.count(q.query, function(err, count) {
var links = q.links('http://localhost/api/v1/mycollection', count)
}
```
For example, if _offset_ was 20, _limit_ was 10, and _count_ was 95, the following links would be generated:
```
{
'prev': 'http://localhost/api/v1/mycollection?offset=10&limit=10',
'first': `http://localhost/api/v1/mycollection?offset=0&limit=10`,
'next': 'http://localhost/api/v1/mycollection?offset=30&limit=10',
'last': 'http://localhost/api/v1/mycollection?offset=90&limit=10'
}
```
These pagination links can be used to populate the [express response links](http://expressjs.com/4x/api.html#res.links).
### Filtering
Any query parameters other then _fields_, _omit_, _sort_, _offset_, and _limit_ are interpreted as query criteria. For example `name=john&age>21` results in a _criteria_ value of:
```
{
'name': 'john',
'age': { $gt: 21 }
}
```
* Supports standard comparison operations (=, !=, >, <, >=, <=).
* Numeric values, where `Number(value) != NaN`, are compared as numbers (ie., `field=10` yields `{field:10}`).
* Values of _true_ and _false_ are compared as booleans (ie., `{field:true}`)
* Values that are [dates](http://www.w3.org/TR/NOTE-datetime) are compared as dates (except for YYYY which matches the number rule).
* Multiple equals comparisons are merged into a `$in` operator. For example, `id=a&id=b` yields `{id:{$in:['a','b']}}`.
* Multiple not-equals comparisons are merged into a `$nin` operator. For example, `id!=a&id!=b` yields `{id:{$nin:['a','b']}}`.
* Comma separated values in equals or not-equals yeild an `$in` or `$nin` operator. For example, `id=a,b` yields `{id:{$in:['a','b']}}`.
* Regex patterns. For example, `name=/^john/i` yields `{id: /^john/i}`.
* Parameters without a value check that the field is present. For example, `foo&bar=10` yields `{foo: {$exists: true}, bar: 10}`.
* Parameters prefixed with a _not_ (!) and without a value check that the field is not present. For example, `!foo&bar=10` yields `{foo: {$exists: false}, bar: 10}`.
* Supports some of the named comparision operators ($type, $size and $all). For example, `foo:type=string`, yeilds `{ foo: {$type: 'string} }`.
* Support for forced string comparison; value in single or double quotes (`field='10'` or `field="10"`) would force a string compare. Allows for string with embedded comma (`field="a,b"`) and quotes (`field="that's all folks"`).
### A note on embedded documents
Comparisons on embedded documents should use mongo's [dot notation](http://docs.mongodb.org/manual/reference/glossary/#term-dot-notation) instead of express's 'extended' [query parser](https://www.npmjs.com/package/qs) (Use `foo.bar=value` instead of `foo[bar]=value`).
Although exact matches are handled for either method, comparisons (such as `foo[bar]!=value`) are not supported because the 'extended' parser expects an equals sign after the nested object reference; if it's not an equals the remainder is discarded.
## Development
There's a *test* script listed in package.json that will execute the mocha tests:
```
npm install
npm test
```
## Todo
* Geospatial search
* $text searches
* $mod comparision
* Bitwise comparisions
* Escaping or double quoting in forced string comparison, ='That\'s all folks' or ='That''s all folks'
var querystring = require('querystring')
var iso8601 = require('./lib/iso8601-regex')
var isObjectID = require('./lib/objectID').isObjectID;
var toObjectID = require('./lib/objectID').toObjectID;
// Convert comma separated list to a mongo projection.
// for example f('field1,field2,field3') -> {field1:true,field2:true,field3:true}
function fieldsToMongo(fields) {
if (!fields) return null
var hash = {}
fields.split(',').forEach(function(field) {
hash[field.trim()] = true
})
return hash
}
// Convert comma separated list to a mongo projection which specifies fields to omit.
// for example f('field2') -> {field2:false}
function omitFieldsToMongo(omitFields) {
if (!omitFields) return null
var hash = {}
omitFields.split(',').forEach(function(omitField) {
hash[omitField.trim()] = false
})
return hash
}
// Convert comma separated list to mongo sort options.
// for example f('field1,+field2,-field3') -> {field1:1,field2:1,field3:-1}
function sortToMongo(sort) {
if (!sort) return null
var hash = {}, c
sort.split(',').forEach(function(field) {
c = field.charAt(0)
if (c == '-') field = field.substr(1)
hash[field.trim()] = (c == '-') ? -1 : 1
})
return hash
}
// Convert String to Number, Date, or Boolean if possible
function typedValue(value) {
var regex = value.match(/^\/(.*)\/(i?)$/);
var quotedString = value.match(/(["'])(?:\\\1|.)*?\1/);
if (regex) {
return new RegExp(regex[1], regex[2]);
} else if (quotedString) {
return quotedString[0].substr(1, quotedString[0].length - 2);
} else if (value === 'true') {
return true;
} else if (value === 'false') {
return false;
} else if (iso8601.test(value) && value.length !== 4) {
return new Date(value);
} else if (!isNaN(Number(value))) {
return Number(value);
} else if (isObjectID(value)){
return toObjectID(value);
}
return value;
}
// Convert a comma separated string value to an array of values. Commas
// in a quoted string are ignored.
function typedValues(svalue) {
var commaSplit = /("[^"]*")|('[^']*')|([^,]+)/g
var values = []
svalue
.match(commaSplit)
.forEach(function(value) {
values.push(typedValue(value))
})
return values;
}
// Convert a key/value pair split at an equals sign into a mongo comparison.
// Converts value Strings to Numbers or Booleans when possible.
// for example:
// + f('key','value') => {key:'key',value:'value'}
// + f('key>','value') => {key:'key',value:{$gte:'value'}}
// + f('key') => {key:'key',value:{$exists: true}}
// + f('!key') => {key:'key',value:{$exists: false}}
// + f('key:op','value') => {key: 'key', value:{ $op: value}}
function comparisonToMongo(key, value) {
var join = (value == '') ? key : key.concat('=', value)
var parts = join.match(/^(!?[^><!=:]+)(?:([><]=?|!?=|:.+=)(.+))?$/)
var op, hash = {}
if (!parts) return null
key = parts[1]
op = parts[2]
if (!op) {
if (key[0] != '!') value = { '$exists': true }
else {
key = key.substr(1)
value = { '$exists': false }
}
} else if (op == '=' || op == '!=') {
var array = typedValues(parts[3]);
if (array.length > 1) {
value = {}
op = (op == '=') ? '$in' : '$nin'
value[op] = array
} else if (op == '!=') {
value = { '$ne': array[0] }
} else {
value = array[0]
}
} else if (op[0] == ':' && op[op.length - 1] == '=') {
op = '$' + op.substr(1, op.length - 2)
var array = []
parts[3].split(',').forEach(function(value) {
array.push(typedValue(value))
})
value = { }
value[op] = array.length == 1 ? array[0] : array
} else {
value = typedValue(parts[3])
if (op == '>') value = {'$gt': value}
else if (op == '>=') value = {'$gte': value}
else if (op == '<') value = {'$lt': value}
else if (op == '<=') value = { '$lte': value}
}
hash.key = key
hash.value = value
return hash
}
// Checks for keys that are ordinal positions, such as {'0':'one','1':'two','2':'three'}
function hasOrdinalKeys(obj) {
var c = 0
for (var key in obj) {
if (Number(key) !== c++) return false
}
return true
}
// Convert query parameters to a mongo query criteria.
// for example {field1:"red","field2>2":""} becomes {field1:"red",field2:{$gt:2}}
function queryCriteriaToMongo(query, options) {
var hash = {}, p, v, deep
options = options || {}
for (var key in query) {
if (Object.prototype.hasOwnProperty.call(query,key) && (!options.ignore || options.ignore.indexOf(key) == -1)) {
deep = (typeof query[key] === 'object' && !hasOrdinalKeys(query[key]))
if (deep) {
p = {
key: key,
value: queryCriteriaToMongo(query[key])
}
} else {
p = comparisonToMongo(key, query[key])
}
if (p) {
hash[p.key] = p.value
}
}
}
return hash
}
// Convert query parameters to a mongo query options.
// for example {fields:'a,b',offset:8,limit:16} becomes {fields:{a:true,b:true},skip:8,limit:16}
function queryOptionsToMongo(query, options) {
var hash = {},
fields = fieldsToMongo(query.fields),
omitFields = omitFieldsToMongo(query.omit),
sort = sortToMongo(query.sort),
maxLimit = options.maxLimit || 9007199254740992,
limit = options.maxLimit || 0
if (fields) hash.fields = fields
// omit intentionally overwrites fields if both have been specified in the query
// mongo does not accept mixed true/fals field specifiers for projections
if (omitFields) hash.fields = omitFields
if (sort) hash.sort = sort
if (query.offset) hash.skip = Number(query.offset)
if (query.limit) limit = Math.min(Number(query.limit), maxLimit)
if (limit) {
hash.limit = limit
} else if (options.maxLimit) {
hash.limit = maxLimit
}
return hash
}
module.exports = function(query, options) {
query = query || {};
options = options || {}
if (!options.ignore) {
options.ignore = []
} else {
options.ignore = (typeof options.ignore === 'string') ? [options.ignore] : options.ignore
}
options.ignore = options.ignore.concat(['fields', 'omit', 'sort', 'offset', 'limit'])
if (!options.parser) options.parser = querystring
if (typeof query === 'string') query = options.parser.parse(query)
return {
criteria: queryCriteriaToMongo(query, options),
options: queryOptionsToMongo(query, options),
links: function(url, totalCount) {
var offset = this.options.skip || 0
var limit = Math.min(this.options.limit || 0, totalCount)
var links = {}
var last = {}
if (!limit) return null
options = options || {}
if (offset > 0) {
query.offset = Math.max(offset - limit, 0)
links['prev'] = url + '?' + options.parser.stringify(query)
query.offset = 0
links['first'] = url + '?' + options.parser.stringify(query)
}
if (offset + limit < totalCount) {
last.pages = Math.ceil(totalCount / limit)
last.offset = (last.pages - 1) * limit
query.offset = Math.min(offset + limit, last.offset)
links['next'] = url + '?' + options.parser.stringify(query)
query.offset = last.offset
links['last'] = url + '?' + options.parser.stringify(query)
}
return links
}
}
}
module.exports = /^\d{4}(-(0[1-9]|1[0-2])(-(0[1-9]|[12][0-9]|3[01]))?)?(T([01][0-9]|2[0-3]):[0-5]\d(:[0-5]\d(\.\d+)?)?(Z|[+-]\d{2}:\d{2}))?$/
var ObjectID = require('bson').ObjectID;
module.exports = {
isObjectID: function (value) { return ObjectID.isValid(value); },
toObjectID: function (value) { return ObjectID(value); }
};
{
"name": "query-to-mongo",
"version": "0.5.1",
"description": "Convert query parameters into mongo query criteria and options.",
"main": "index.js",
"repository": {
"type": "git",
"url": "http://github.com/pbatey/query-to-mongo"
},
"author": "Phil Batey <pbatey@gmail.com>",
"license": "MIT License",
"devDependencies": {
"chai": "^2.1.0",
"mocha": "^2.1.0",
"qs": "^6.0.0"
},
"scripts": {
"test": "node_modules/mocha/bin/mocha test"
},
"dependencies": {
"bson": "^0.4.23"
}
}
var assert = require("chai").assert
var iso8601 = require('../lib/iso8601-regex')
describe("iso8601-regex", function () {
it("should match YYYY", function () {
assert.ok(iso8601.test("2000"))
})
it("should match YYYY-MM", function () {
assert.ok(iso8601.test("2000-04"))
})
it("should match YYYY-MM-DD", function () {
assert.ok(iso8601.test("2000-04-01"))
})
it("should match YYYY-MM-DDThh:mmZ", function () {
assert.ok(iso8601.test("2000-04-01T12:00Z"), 'Z')
assert.ok(iso8601.test("2000-04-01T12:00-08:00"), '-08:00')
assert.ok(iso8601.test("2000-04-01T12:00+01:00"), '+01:00')
})
it("should match YYYY-MM-DDThh:mm:ssZ", function () {
assert.ok(iso8601.test("2000-04-01T12:00:30Z"), 'Z')
assert.ok(iso8601.test("2000-04-01T12:00:30-08:00"), '-08:00')
assert.ok(iso8601.test("2000-04-01T12:00:30+01:00"), '+01:00')
})
it("should match YYYY-MM-DDThh:mm:ss.sZ", function () {
assert.ok(iso8601.test("2000-04-01T12:00:30.250Z"), 'Z')
assert.ok(iso8601.test("2000-04-01T12:00:30.250-08:00"), '-08:00')
assert.ok(iso8601.test("2000-04-01T12:00:30.250+01:00"), '+01:00')
})
it("should not match time without timezone", function () {
assert.notOk(iso8601.test("2000-04-01T12:00"), 'hh:mm')
assert.notOk(iso8601.test("2000-04-01T12:00:00"), 'hh:mm:ss')
assert.notOk(iso8601.test("2000-04-01T12:00:00.000"), 'hh:mm:ss.s')
})
it("should not match out of range month", function () {
assert.notOk(iso8601.test("2000-00"), '00')
assert.notOk(iso8601.test("2000-13"), '13')
})
it("should not match out of range day", function () {
assert.notOk(iso8601.test("2000-04-00"), '00')
assert.notOk(iso8601.test("2000-04-32"), '32')
})
it("should not match out of range hour", function () {
assert.notOk(iso8601.test("2000-04-01T24:00Z"))
})
it("should not match out of range minute", function () {
assert.notOk(iso8601.test("2000-04-01T12:60Z"))
})
it("should not match out of range second", function () {
assert.notOk(iso8601.test("2000-04-01T12:00:60Z"))
})
it("should not match time without timezone", function () {
assert.notOk(iso8601.test("2000-04-01T12:00"), 'hh:mm')
assert.notOk(iso8601.test("2000-04-01T12:00:00"), 'hh:mm:ss')
assert.notOk(iso8601.test("2000-04-01T12:00:00.000"), 'hh:mm:ss.s')
})
})
var assert = require("chai").assert
var q2m = require("../index")
describe("query-to-mongo(query).links =>", function () {
describe("#links", function () {
var links = q2m("offset=20&limit=10").links('http://localhost', 95)
it("should create first link", function () {
assert.equal(links.first, "http://localhost?offset=0&limit=10")
})
it("should create prev link", function () {
assert.equal(links.prev, "http://localhost?offset=10&limit=10")
})
it("should create next link", function () {
assert.equal(links.next, "http://localhost?offset=30&limit=10")
})
it("should create last link", function () {
assert.equal(links.last, "http://localhost?offset=90&limit=10")
})
describe("with no pages", function () {
var links = q2m("offset=0&limit=100").links('http://localhost', 95)
it("should not create links", function () {
assert.notOk(links.first)
assert.notOk(links.last)
assert.notOk(links.next)
assert.notOk(links.prev)
})
})
describe("when on first page", function () {
var links = q2m("offset=0&limit=10").links('http://localhost', 95)
it("should not create prev link", function () {
assert.notOk(links.prev)
})
it("should not create first link", function () {
assert.notOk(links.first)
})
it("should create next link", function () {
assert.equal(links.next, "http://localhost?offset=10&limit=10")
})
it("should create last link", function () {
assert.equal(links.last, "http://localhost?offset=90&limit=10")
})
})
describe("when on last page", function () {
var links = q2m("offset=90&limit=10").links('http://localhost', 95)
it("should not create next link", function () {
assert.notOk(links.next)
})
it("should not create last link", function () {
assert.notOk(links.last)
})
it("should create prev link", function () {
assert.equal(links.prev, "http://localhost?offset=80&limit=10")
})
it("should not create first link", function () {
assert.equal(links.first, "http://localhost?offset=0&limit=10")
})
})
})
})
var assert = require("chai").assert
var q2m = require("../index")
var qs = require("qs")
describe("query-to-mongo(query,{paser: qs}) =>", function () {
describe(".criteria", function () {
it("should create criteria", function () {
var results = q2m("foo[bar]=value", {parser: qs})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {foo: {bar: "value"}})
})
it("should create numeric criteria", function () {
var results = q2m("foo[i]=10&foo[f]=1.2&foo[z]=0", {parser: qs})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {foo:{"i": 10, "f": 1.2, "z": 0}})
})
it("should create boolean criteria", function () {
var results = q2m("foo[t]=true&foo[f]=false", {parser: qs})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {foo:{t: true, f: false}})
})
it("should create regex criteria", function () {
var results = q2m("foo[r]=/regex/&foo[ri]=/regexi/i", {parser: qs})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {foo:{r: /regex/, ri: /regexi/i}})
})
// can't create comparisons for embedded documents
it("shouldn't ignore deep criteria", function () {
var results = q2m("field=value&foo[envelope]=true", {ignore: ['envelope'], parser: qs})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: "value", foo: {envelope: true}})
})
it("should create string criteria when forced with a quote", function () {
var results = q2m("a='10'&b=\'11\'&c='a,b'&d=10,11&z=\"that's all folks\"", {parser: qs})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {a: "10", b: "11", c: "a,b", d: {$in: [10, 11]}, z: "that's all folks"})
})
})
describe(".options", function () {
it("should create paging options", function () {
var results = q2m("offset=8&limit=16", {parser: qs})
assert.ok(results.options)
assert.deepEqual(results.options, {skip: 8, limit: 16})
})
it("should create field option", function () {
var results = q2m("fields=a,b,c", {parser: qs})
assert.ok(results.options)
assert.deepEqual(results.options, {fields: {a:true, b:true, c:true}})
})
it("should create sort option", function () {
var results = q2m("sort=a,+b,-c", {parser: qs})
assert.ok(results.options)
assert.deepEqual(results.options, {sort: {a:1, b:1, c:-1}})
})
it("should limit queries", function () {
var results = q2m("limit=100", {maxLimit: 50, parser: qs})
assert.ok(results.options)
assert.deepEqual(results.options, {limit: 50})
})
})
describe("#links", function () {
var links = q2m("foo[bar]=baz&offset=20&limit=10", {maxLimit: 50, parser: qs}).links('http://localhost', 100)
it("should create first link", function () {
assert.equal(links.first, "http://localhost?foo%5Bbar%5D=baz&offset=0&limit=10")
})
it("should create prev link", function () {
assert.equal(links.prev, "http://localhost?foo%5Bbar%5D=baz&offset=10&limit=10")
})
it("should create next link", function () {
assert.equal(links.next, "http://localhost?foo%5Bbar%5D=baz&offset=30&limit=10")
})
it("should create last link", function () {
assert.equal(links.last, "http://localhost?foo%5Bbar%5D=baz&offset=90&limit=10")
})
})
})
var assert = require("chai").assert
var q2m = require("../index")
var ObjectID = require('bson').ObjectID;
describe("query-to-mongo(query) =>", function () {
describe(".criteria", function () {
it("should create criteria", function () {
var results = q2m("field=value")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: "value"})
})
it("should create numeric criteria", function () {
var results = q2m("i=10&f=1.2&z=0")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {"i": 10, "f": 1.2, "z": 0})
})
it("should create boolean criteria", function () {
var results = q2m("t=true&f=false")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {t: true, f: false})
})
it("should create regex criteria", function () {
var results = q2m("r=/regex/&ri=/regexi/i")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {r: /regex/, ri: /regexi/i})
})
it("should create Date criteria from YYYY-MM", function () {
var results = q2m("d=2010-04")
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1))})
})
it("should create Date criteria from YYYY-MM-DD", function () {
var results = q2m("d=2010-04-01"), expect
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1))})
})
it("should create Date criteria from YYYY-MM-DDThh:mmZ", function () {
var results = q2m("d=2010-04-01T12:00Z")
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1, 12, 0))})
})
it("should create Date criteria from YYYY-MM-DDThh:mm:ssZ", function () {
var results = q2m("d=2010-04-01T12:00:30Z")
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1, 12, 0, 30))})
})
it("should create Date criteria from YYYY-MM-DDThh:mm:ss.sZ", function () {
var results = q2m("d=2010-04-01T12:00:30.250Z")
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1, 12, 0, 30, 250))})
})
it("should create Date criteria from YYYY-MM-DDThh:mm:ss.s-hh:mm", function () {
var results = q2m("d=2010-04-01T11:00:30.250-01:00")
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1, 12, 0, 30, 250))})
})
it("should create Date criteria from YYYY-MM-DDThh:mm:ss.s+hh:mm", function () {
var results = q2m(encodeURIComponent("d=2010-04-01T13:00:30.250+01:00"))
assert.ok(results.criteria)
assert.ok(results.criteria.d instanceof Date, 'instanceof Date')
assert.deepEqual(results.criteria, {d: new Date(Date.UTC(2010, 3, 1, 12, 0, 30, 250))})
})
it("should create $gt criteria", function () {
var results = q2m("field>value")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$gt": "value"}})
})
it("should create $lt criteria", function () {
var results = q2m("field<value")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$lt": "value"}})
})
it("should create $gte criteria", function () {
var results = q2m("field>=value")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$gte": "value"}})
})
it("should create $lte criteria", function () {
var results = q2m("field<=value")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$lte": "value"}})
})
it("should create $ne criteria", function () {
var results = q2m("field!=value")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$ne": "value"}})
})
it("should create $in criteria", function () {
var results = q2m("field=a&field=b")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$in": ["a","b"]}})
})
it("should create $nin criteria", function () {
var results = q2m("field!=a&field!=b")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {"$nin": ["a","b"]}})
})
it("should ignore criteria", function () {
var results = q2m("field=value&envelope=true&&offset=0&limit=10&fields=id&sort=name", { ignore: ['envelope']})
assert.ok(results.criteria)
assert.notOk(results.criteria.envelope, "envelope")
assert.notOk(results.criteria.skip, "offset")
assert.notOk(results.criteria.limit, "limit")
assert.notOk(results.criteria.fields, "fields")
assert.notOk(results.criteria.sort, "sort")
assert.deepEqual(results.criteria, {field: "value"})
})
it("should create $exists true criteria", function () {
var results = q2m("a&b=10&c", { ignore: ['c']})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {a: {"$exists": true}, b: 10})
})
it("should create $exists false criteria", function () {
var results = q2m("!a&b=10&c", { ignore: ['c']})
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {a: {"$exists": false}, b: 10})
})
it("should create $type criteria with BSON type number", function () {
var results = q2m("field:type=2")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {$type: 2} })
})
it("should create $type criteria with BSON type name", function () {
var results = q2m("field:type=string")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {field: {$type: "string"} })
})
it("should create $size criteria", function () {
var results = q2m("array:size=2")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {array: {$size: 2} })
})
it("should create $all criteria", function () {
var results = q2m("array:all=50,60")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {array: {$all: [50, 60]} })
})
it("should create forced string criteria", function () {
var results = q2m("s='a,b'")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {s: "a,b"})
})
it("should create numeric criteria from YYYY exception", function () {
var results = q2m("d=2016")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {d: 2016})
})
it("should create mongo objectID criteria", function () {
var results = q2m("d=574553013b58d8582cdb1d5f")
assert.ok(results.criteria)
assert.deepEqual(results.criteria, {d: ObjectID('574553013b58d8582cdb1d5f')})
})
})
describe(".options", function () {
it("should create paging options", function () {
var results = q2m("offset=8&limit=16")
assert.ok(results.options)
assert.deepEqual(results.options, {skip: 8, limit: 16})
})
it("should create field option", function () {
var results = q2m("fields=a,b,c")
assert.ok(results.options)
assert.deepEqual(results.options, {fields: {a:true, b:true, c:true}})
})
it("should create omit option", function () {
var results = q2m("omit=b")
assert.ok(results.options)
assert.deepEqual(results.options, {fields: {b:false}})
})
it("should create omit option", function () {
var results = q2m("omit=b")
assert.ok(results.options)
assert.deepEqual(results.options, {fields: {b:false}})
})
it("should create sort option", function () {
var results = q2m("sort=a,+b,-c")
assert.ok(results.options)
assert.deepEqual(results.options, {sort: {a:1, b:1, c:-1}})
})
it("should limit queries", function () {
var results = q2m("limit=100", {maxLimit: 50})
assert.ok(results.options)
assert.deepEqual(results.options, {limit: 50})
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment