[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"es2015\"\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": \"standard\",\n  \"env\": {\n    \"node\": true,\n    \"es6\": true\n  },\n  \"rules\": {\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nlogs\n*.log\nnode_modules\ndist\ntmp\ncoverage\n.nyc_output\nnpm-debug.log\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nservices: mongodb\nnode_js:\n  - v6\nafter_script:\n  - 'npm run coveralls'\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "<!-- START doctoc generated TOC please keep comment here to allow auto update -->\n<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->\n# Changelog\n\n- [v1.0.0](#v100)\n\n<!-- END doctoc generated TOC please keep comment here to allow auto update -->\n\n### v1.0.0\n\n * menquery initial commit.\n"
  },
  {
    "path": "LICENSE",
    "content": "This software is released under the MIT license:\n\nCopyright (c) 2016 Diego Haz hazdiego@gmail.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.))\n"
  },
  {
    "path": "README.md",
    "content": "# Querymen\n\n[![JS Standard Style][standard-image]][standard-url]\n[![NPM version][npm-image]][npm-url]\n[![Build Status][travis-image]][travis-url]\n[![Coveralls Status][coveralls-image]][coveralls-url]\n[![Dependency Status][depstat-image]][depstat-url]\n[![Downloads][download-badge]][npm-url]\n\n> Querystring parser middleware for MongoDB, Express and Nodejs\n\n## Install\n\n```sh\nnpm install --save querymen\n```\n\n## Examples\n\n### Pagination\nQuerymen has a default schema to handle pagination. This is the most simple and common usage.\n```js\nimport { middleware as query } from 'querymen';\n\napp.get('/posts', query(), ({ querymen: { query, select, cursor } }, res) => {\n\n  Post.find(query, select, cursor).then(posts => {\n    // posts are proper paginated here\n  });\n});\n```\nUser requests `/posts?page=2&limit=20&sort=-createdAt` querymen will be:\n```js\nquerymen = {\n  query: {},\n  select: {},\n  cursor: {\n    limit: 20, \n    skip: 20, \n    sort: { createdAt: -1 }\n  }\n}\n```\nUser requests `/posts?q=term&fields=title,desc` querymen will be:\n> When user requests `/posts?q=term`, querymen parses it to `{keywords: /term/i}`. It was designed to work with [mongoose-keywords](https://github.com/diegohaz/mongoose-keywords) plugin, which adds a `keywords` field to schemas (check that out).\n\n```js\nquerymen = {\n  query: {\n    keywords: /term/i\n  },\n  select: {\n    title: 1,\n    desc: 1\n  },\n  cursor: {\n    // defaults\n    limit: 30, \n    skip: 0, \n    sort: { createdAt: -1 }\n  }\n}\n```\nUser requests `/posts?fields=-title&sort=name,-createdAt` querymen will be:\n```js\nquerymen = {\n  query: {},\n  select: {\n    title: 0\n  },\n  cursor: {\n    limit: 30, \n    skip: 0, \n    sort: {\n      name: 1,\n      createdAt: -1\n    }\n  }\n}\n```\n\n### Custom schema\nYou can define a custom schema, which will be merged into querymen default schema (explained above).\n```js\nimport { middleware as query } from 'querymen';\n\napp.get('/posts', query({\n  after: {\n    type: Date,\n    paths: ['createdAt'],\n    operator: '$gte'\n  }\n}), ({ querymen }, res) => {\n  Post.find(querymen.query).then(posts => {\n    // ...\n  });\n});\n```\nUser requests `/posts?after=2016-04-23` querymen will be:\n```js\nquerymen = {\n  query: {\n    createdAt: { $gte: 1461369600000 }\n  },\n  select: {},\n  cursor: {\n    // defaults\n    limit: 30, \n    skip: 0, \n    sort: { createdAt: -1 }\n  }\n}\n```\n\n### Reusable schemas\nYou can create reusable schemas as well. Just instantiate a `Schema` object.\n```js\nimport { middleware as query, Schema } from 'querymen';\n\nconst schema = new Schema({\n  tags: {\n    type: [String],\n  }\n});\n\n// user requests /posts?tags=world,travel\n// querymen.query is { tags: { $in: ['world', 'travel'] }}\napp.get('/posts', query(schema));\napp.get('/articles', query(schema));\n```\n\n### Advanced schema\n```js\nimport { middleware as query, Schema } from 'querymen';\n\nconst schema = new Schema({\n  active: Boolean, // shorthand to { type: Boolean }\n  sort: '-createdAt', // shorthand to { type: String, default: '-createdAt' }\n  term: {\n    type: RegExp,\n    paths: ['title', 'description'],\n    bindTo: 'search' // default was 'query'\n  },\n  with_picture: {\n    type: Boolean,\n    paths: ['picture'],\n    operator: '$exists'\n  }\n}, {\n  page: false, // disable default parameter `page`\n  limit: 'max_items' // change name of default parameter `limit` to `max_items`\n});\n\napp.get('/posts', query(schema), ({ querymen }, res) => {\n  // user requests /posts?term=awesome&with_picture=true&active=true&max_items=100\n  // querymen.query is { picture: { $exists: true }, active: true }\n  // querymen.cursor is { limit: 100, sort: { createdAt: -1 } }\n  // querymen.search is { $or: [{ title: /awesome/i }, { description: /awesome/i }]}\n});\n```\n\n### Dynamic advanced schema\n```js\nimport { middleware as query, Schema } from 'querymen';\nconst schema = new Schema();\n\nschema.formatter('scream', (scream, value, param) => {\n  if (scream) {\n    value = value.toUpperCase() + '!!!!!!!';\n  }\n  return value;\n});\n\nschema.param('text', null, { type: String }); // { type: String }\nschema.param('text').option('scream', true); // { type: String, scream: true }\nschema.param('text').value('help');\nconsole.log(schema.param('text').value()); // HELP!!!!!!!\n\nschema.validator('isPlural', (isPlural, value, param) => {\n  return {\n    valid: !isPlural || value.substr(-1) === 's',\n    message: param.name + ' must be in plural form.'\n  };\n});\n\nschema.param('text').option('isPlural', true); // { type: String, scream: true, isPlural: true }\nconsole.log(schema.validate()); // false\nschema.param('text', 'helps');\nconsole.log(schema.validate()); // true\nconsole.log(schema.param('text').value()); // HELPS!!!!!!!\n\nschema.parser('elemMatch', (elemMatch, value, path, operator) => {\n  if (elemMatch) {\n    value = { [path]: { $elemMatch: {[elemMatch]: {[operator]: value } }}};\n  }\n  return value;\n});\n\nschema.param('text', 'ivegotcontrols');\nconsole.log(schema.param('text').parse()); // { text: 'IVEGOTCONTROLS!!!!!!!' }\n\nschema.param('text').option('elemMatch', 'prop');\nconsole.log(schema.param('text').parse()); // { text: { $elemMatch: { prop: { $eq: 'IVEGOTCONTROLS!!!!!!!'} }}}\n```\n\n### Geo queries\nQuerymen also support geo queries, but it's disabled by default. To enable geo queries you just need to set `near` option to true in schema options.\n```js\nimport { middleware as query } from 'querymen';\n\napp.get('/places', query({}, { near: true }), (req, res) => {\n  \n});\n```\nIts `paths` option is set to `['location']` by default, but you can change this as well:\n```js\nimport { middleware as query } from 'querymen';\n\napp.get('/places', \n  query({\n    near: { paths: ['loc'] }\n  }, {\n    near: true\n  }), \n  (req, res) => {\n  \n  });\n```\nUser requests `/places?near=-22.332113,-44.312311` (latitude, longitude), req.querymen.query will be:\n```js\nreq.querymen.query = {\n  loc: {\n    $near: {\n      $geometry: {\n        type: 'Point',\n        coordinates: [-44.312311, -22.332113]\n      }\n    }\n  }\n}\n```\nUser requests `/places?near=-22.332113,-44.312311&min_distance=200&max_distance=2000` (min_distance and max_distance in meters), req.querymen.query will be:\n```js\nreq.querymen.query = {\n  loc: {\n    $near: {\n      $geometry: {\n        type: 'Point',\n        coordinates: [-44.312311, -22.332113]\n      },\n      $minDistace: 200,\n      $maxDistance: 2000\n    }\n  }\n}\n```\nYou can also use legacy geo queries as well. Just set `geojson` option in param:\n```js\nimport { middleware as query } from 'querymen';\n\napp.get('/places', \n  query({\n    near: {\n      paths: ['loc'],\n      geojson: false\n    }\n  }, {\n    near: true\n  }), \n  (req, res) => {\n  \n  });\n```\nUser requests `/places?near=-22.332113,-44.312311&min_distance=200&max_distance=2000`, req.querymen.query will be:\n```js\nreq.querymen.query = {\n  loc: {\n    $near: [-44.312311, -22.332113],\n    // convert meters to radians automatically\n    $minDistace: 0.000031,\n    $maxDistance: 0.00031\n  }\n}\n```\n\n### Error handling\n```js\n// user requests /posts?category=world\nimport { middleware as query, querymen, Schema } from 'querymen';\n\nconst schema = new Schema({\n  category: {\n    type: String,\n    enum: ['culture', 'general', 'travel']\n  }\n});\n\napp.get('/posts', query(schema));\n\n// create your own handler\napp.use((err, req, res, next) => {\n  res.status(400).json(err);\n});\n\n// or use querymen error handler\napp.use(querymen.errorHandler());\n```\nResponse body will look like:\n```json\n{\n  \"valid\": false,\n  \"name\": \"enum\",\n  \"enum\": [\"culture\", \"general\", \"travel\"],\n  \"value\": \"world\",\n  \"message\": \"category must be one of: culture, general, travel\"\n}\n```\n\n## Contributing\n\nThis package was created with [generator-rise](https://github.com/bucaran/generator-rise). Please refer to there to understand the codestyle and workflow. Issues and PRs are welcome! \n\n## License\n\nMIT © [Diego Haz](http://github.com/diegohaz)\n\n[standard-url]: http://standardjs.com\n[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg\n\n[npm-url]: https://npmjs.org/package/querymen\n[npm-image]: https://img.shields.io/npm/v/querymen.svg?style=flat-square\n\n[travis-url]: https://travis-ci.org/diegohaz/querymen\n[travis-image]: https://img.shields.io/travis/diegohaz/querymen.svg?style=flat-square\n\n[coveralls-url]: https://coveralls.io/r/diegohaz/querymen\n[coveralls-image]: https://img.shields.io/coveralls/diegohaz/querymen.svg?style=flat-square\n\n[depstat-url]: https://david-dm.org/diegohaz/querymen\n[depstat-image]: https://david-dm.org/diegohaz/querymen.svg?style=flat-square\n\n[download-badge]: http://img.shields.io/npm/dm/querymen.svg?style=flat-square\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"querymen\",\n  \"version\": \"2.1.4\",\n  \"description\": \"Querystring parser middleware for MongoDB, Express and Nodejs\",\n  \"main\": \"dist/index.js\",\n  \"files\": [\n    \"bin/\",\n    \"dist/\"\n  ],\n  \"scripts\": {\n    \"clean\": \"rimraf dist\",\n    \"lint\": \"eslint src test\",\n    \"check\": \"npm run lint -s && dependency-check package.json --entry src\",\n    \"watch\": \"watch 'npm run build' src test\",\n    \"test\": \"babel-node test/index.js | tspec\",\n    \"prebuild\": \"npm run check -s && npm run clean -s\",\n    \"build\": \"babel --optional runtime src -d dist\",\n    \"postbuild\": \"npm run test -s\",\n    \"coverage\": \"nyc --reporter=lcov --reporter=text npm run test\",\n    \"coveralls\": \"npm run coverage -s && coveralls < coverage/lcov.info\",\n    \"postcoveralls\": \"rimraf ./coverage\",\n    \"prepublish\": \"npm run build -s\",\n    \"deploy\": \"git pull --rebase origin master && git push origin master\",\n    \"patch\": \"npm version patch && npm publish\",\n    \"minor\": \"npm version minor && npm publish\",\n    \"major\": \"npm version major && npm publish\",\n    \"postpublish\": \"git push origin master --follow-tags\",\n    \"toc\": \"doctoc --github --title \\\"# Changelog\\\" CHANGELOG.md\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/diegohaz/querymen.git\"\n  },\n  \"keywords\": [\n    \"mongo\",\n    \"express\",\n    \"node\",\n    \"query\",\n    \"querystring\",\n    \"param\",\n    \"mongodb\",\n    \"expressjs\",\n    \"nodejs\"\n  ],\n  \"author\": \"Diego Haz <hazdiego@gmail.com>\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/diegohaz/querymen/issues\"\n  },\n  \"homepage\": \"https://github.com/diegohaz/querymen#readme\",\n  \"devDependencies\": {\n    \"babel-cli\": \"^6.7.5\",\n    \"babel-core\": \"^6.7.6\",\n    \"babel-eslint\": \"^7.1.1\",\n    \"babel-preset-es2015\": \"^6.6.0\",\n    \"blue-tape\": \"^1.0.0\",\n    \"coveralls\": \"^2.11.12\",\n    \"dependency-check\": \"^2.6.0\",\n    \"doctoc\": \"^1.2.0\",\n    \"eslint\": \"^3.14.0\",\n    \"eslint-config-standard\": \"^6.2.1\",\n    \"eslint-plugin-promise\": \"^3.4.0\",\n    \"eslint-plugin-standard\": \"^2.0.1\",\n    \"express\": \"^4.13.4\",\n    \"mongoose\": \"^4.7.8\",\n    \"nyc\": \"^11.0.1\",\n    \"rimraf\": \"^2.5.4\",\n    \"supertest\": \"^3.0.0\",\n    \"tap-spec\": \"^4.1.1\",\n    \"tape\": \"^4.6.0\",\n    \"watch\": \"^1.0.1\"\n  },\n  \"dependencies\": {\n    \"lodash\": \"^4.11.1\",\n    \"rich-param\": \"^1.0.1\"\n  }\n}\n"
  },
  {
    "path": "src/index.js",
    "content": "/** @module querymen */\nimport _ from 'lodash'\nimport Param from './querymen-param'\nimport Schema from './querymen-schema'\n\nexport { Param, Schema }\n\nexport let handlers = {\n  parsers: {},\n  formatters: {},\n  validators: {}\n}\n\n/**\n * Get or set a handler.\n * @memberof querymen\n * @param {string} type - Handler type.\n * @param {string} name - Handler name.\n * @param {Function} [fn] - Set the handler method.\n */\nexport function handler (type, name, fn) {\n  if (\n    type === 'constructor' ||\n    type === '__proto__' ||\n    name === 'constructor' ||\n    name === '__proto__'\n  ) {\n    return\n  }\n  if (arguments.length > 2) {\n    handlers[type][name] = fn\n  }\n\n  return handlers[type][name]\n}\n\n/**\n * Get or set a parser.\n * @memberof querymen\n * @param {string} name - Parser name.\n * @param {parserFn} [fn] - Set the parser method.\n * @return {parserFn} The parser method.\n */\nexport function parser (name, fn) {\n  return handler('parsers', ...arguments)\n}\n\n/**\n * Get or set a formatter.\n * @memberof querymen\n * @param {string} name - Formatter name.\n * @param {formatterFn} [fn] - Set the formatter method.\n * @return {formatterFn} The formatter method.\n */\nexport function formatter (name, fn) {\n  return handler('formatters', ...arguments)\n}\n\n/**\n * Get or set a validator.\n * @memberof querymen\n * @param {string} name - Validator name.\n * @param {validatorFn} [fn] - Set the validator method.\n * @return {validatorFn} The validator method.\n */\nexport function validator (name, fn) {\n  return handler('validators', ...arguments)\n}\n\n/**\n * Create a middleware.\n * @memberof querymen\n * @param {QuerymenSchema|Object} [schema] - Schema object.\n * @param {Object} [options] - Options to be passed to schema.\n * @return {Function} The middleware.\n */\nexport function middleware (schema, options) {\n  return function (req, res, next) {\n    let _schema\n    // If option near is enable with make a simple clone\n    // In otherwise we make a _.cloneDeep\n    if (schema && schema.options && schema.options.near) {\n      _schema = schema instanceof Schema\n        ? _.clone(schema)\n        : new Schema(schema, options)\n    } else {\n      _schema = schema instanceof Schema\n        ? _.cloneDeep(schema)\n        : new Schema(schema, options)\n    }\n\n    _schema.validate(req.query, (err) => {\n      if (err) {\n        req.querymen = { error: err }\n        res.status(400)\n        return next(err.message)\n      }\n\n      req.querymen = _schema.parse()\n      req.querymen.schema = _schema\n      next()\n    })\n  }\n}\n\n/**\n * Error handler middleware.\n * @memberof querymen\n * @return {Function} The middleware.\n */\nexport function errorHandler () {\n  return function (err, req, res, next) {\n    if (req.querymen && req.querymen.error) {\n      res.status(400).json(req.querymen.error)\n    } else {\n      next(err)\n    }\n  }\n}\n\nexport default { Schema, Param, handlers, handler, parser, formatter, validator, middleware, errorHandler }\n"
  },
  {
    "path": "src/querymen-param.js",
    "content": "import _ from 'lodash'\nimport Param from 'rich-param'\n\n/** QuerymenParam class. */\nexport default class QuerymenParam extends Param {\n\n  /**\n   * Create a param.\n   * @param {string} name - Param name.\n   * @param {*} [value] - The value of the param.\n   * @param {Object} [options] - Options of the param.\n   * @param {Object} [schema] - Schema of the param.\n   */\n  constructor (name, value, options = {}) {\n    super(name, value, options)\n    this.handlers.parsers = {}\n    this.options = _.assign({\n      paths: [name],\n      operator: '$eq',\n      parse: (value, path, operator, param) => {\n        if (operator === '$eq') {\n          return {[path]: value}\n        } else if (_.isRegExp(value)) {\n          return operator === '$ne' ? {[path]: {$not: value}} : {[path]: value}\n        } else {\n          return {[path]: {[operator]: value}}\n        }\n      }\n    }, this.options)\n  }\n\n  /**\n   * Get or set a parser.\n   * @param {string} name - Parser name.\n   * @param {parserFn} [fn] - Set the parser method.\n   * @return {parserFn} The parser method.\n   */\n  parser (name, fn) {\n    return this.handler('parsers', ...arguments)\n  }\n\n  /**\n   * Parse the param.\n   * @param {*} [value] - Set the param value.\n   * @param {string|string[]} path - Set the param path/paths.\n   * @return {Object} The parsed value.\n   */\n  parse (value = this._value, path = this.options.paths) {\n    let operator = this.options.operator\n    let query = {}\n\n    if (_.isNil(value)) {\n      return query\n    } else if (value !== this._value) {\n      value = this.value(value)\n    }\n\n    if (_.isArray(path)) {\n      let paths = path\n      if (paths.length > 1) {\n        query.$or = paths.map((path) => this.parse(value, path))\n        return query\n      }\n      path = paths[0]\n    }\n\n    if (_.isArray(value)) {\n      operator = operator === '$ne' ? '$nin' : '$in'\n    }\n\n    _.forIn(this.options, (optionValue, option) => {\n      let parser\n      if (option === 'parse' && _.isFunction(optionValue)) {\n        parser = optionValue\n      } else if (_.isFunction(this.handlers.parsers[option])) {\n        parser = this.handlers.parsers[option].bind(this, optionValue)\n      } else {\n        return\n      }\n      query = parser(value, path, operator, this)\n    })\n\n    return query\n  }\n}\n"
  },
  {
    "path": "src/querymen-schema.js",
    "content": "import _ from 'lodash'\nimport querymen from './'\nimport QuerymenParam from './querymen-param'\n\n/**\n * QuerymenSchema class.\n */\nexport default class QuerymenSchema {\n\n  /**\n   * Create a schema.\n   * @param {Object} [params] - Params object.\n   * @param {Object} [options] - Options object.\n   */\n  constructor (params = {}, options = {}) {\n    this.params = {}\n    this.options = _.assign({\n      near: false\n    }, options)\n    this.handlers = {\n      parsers: {},\n      formatters: {},\n      validators: {}\n    }\n    this._params = {\n      q: {\n        type: RegExp,\n        normalize: true,\n        paths: ['keywords']\n      },\n      fields: {\n        type: [String],\n        bindTo: 'select',\n        parse: (value) => {\n          let fields = _.isArray(value) ? value : [value]\n          let query = {}\n          fields.forEach((field) => {\n            if (_.isNil(field) || _.isEmpty(field)) return\n            field = field.replace(/^([-+]?)id/, '$1_id')\n            if (field.charAt(0) === '-') {\n              query[field.slice(1)] = 0\n            } else if (field.charAt(0) === '+') {\n              query[field.slice(1)] = 1\n            } else {\n              query[field] = 1\n            }\n          })\n          return query\n        }\n      },\n      near: {\n        type: [Number],\n        maxlength: 2,\n        minlength: 2,\n        max: 180,\n        min: -180,\n        paths: ['location'],\n        max_distance: true,\n        min_distance: true,\n        geojson: true,\n        format: (value, param) => {\n          if (param.option('min_distance') && !this.param('min_distance')) {\n            this.param('min_distance', null, {type: Number, min: 0, parse: () => false})\n          }\n          if (param.option('max_distance') && !this.param('max_distance')) {\n            this.param('max_distance', null, {type: Number, parse: () => false})\n          }\n          return value\n        },\n        parse: (value, path, operator, param) => {\n          let query = {[path]: {$near: {}}}\n          let minDistance = this.param('min_distance')\n          let maxDistance = this.param('max_distance')\n          if (param.option('geojson')) {\n            query[path].$near = {$geometry: {type: 'Point', coordinates: [value[1], value[0]]}}\n            if (minDistance && minDistance.value()) {\n              query[path].$near.$minDistance = minDistance.value()\n            }\n            if (maxDistance && maxDistance.value()) {\n              query[path].$near.$maxDistance = maxDistance.value()\n            }\n          } else {\n            query[path].$near = [value[1], value[0]]\n            if (minDistance && minDistance.value()) {\n              query[path].$minDistance = minDistance.value() / 6371000\n            }\n            if (maxDistance && maxDistance.value()) {\n              query[path].$maxDistance = maxDistance.value() / 6371000\n            }\n          }\n          this.option('sort', false)\n          return query\n        }\n      },\n      page: {\n        type: Number,\n        default: 1,\n        max: 30,\n        min: 1,\n        bindTo: 'cursor',\n        parse: (value, path, operator, param) => {\n          return {skip: this.param('limit').value() * (value - 1)}\n        }\n      },\n      limit: {\n        type: Number,\n        default: 30,\n        max: 100,\n        min: 1,\n        bindTo: 'cursor',\n        parse: (value) => ({limit: value})\n      },\n      sort: {\n        type: [String],\n        default: '-createdAt',\n        bindTo: 'cursor',\n        parse: (value) => {\n          let fields = _.isArray(value) ? value : [value]\n          let sort = {}\n          fields.forEach((field) => {\n            if (field.charAt(0) === '-') {\n              sort[field.slice(1)] = -1\n            } else if (field.charAt(0) === '+') {\n              sort[field.slice(1)] = 1\n            } else {\n              sort[field] = 1\n            }\n          })\n          return {sort: sort}\n        }\n      }\n    }\n\n    let keys = _.union(_.keys(this._params), _.keys(params))\n\n    keys.forEach((key) => this.add(key, undefined, params[key]))\n\n    _.forIn(querymen.handlers, (typedHandler, type) => {\n      _.forIn(typedHandler, (handler, name) => {\n        this.handler(type, name, handler)\n      })\n    })\n  }\n\n  /**\n   * Get or set an option.\n   * @param {string} name - Option name.\n   * @param {*} [value] - Set the value of the option.\n   * @return {*} Value of the option.\n   */\n  option (name, value) {\n    if (arguments.length > 1) {\n      this.options[name] = value\n    }\n\n    return this.options[name]\n  }\n\n  /**\n   * Get or set a handler.\n   * @param {string} type - Handler type.\n   * @param {string} name - Handler name.\n   * @param {Function} [fn] - Set the handler method.\n   */\n  handler (type, name, fn) {\n    if (arguments.length > 2) {\n      this.handlers[type][name] = fn\n      this._refreshHandlersInParams({[type]: {[name]: fn}})\n    }\n\n    return this.handlers[type][name]\n  }\n\n  /**\n   * Get or set a parser.\n   * @param {string} name - Parser name.\n   * @param {parserFn} [fn] - Set the parser method.\n   * @return {parserFn} The parser method.\n   */\n  parser (name, fn) {\n    return this.handler('parsers', ...arguments)\n  }\n\n  /**\n   * Get or set a formatter.\n   * @param {string} name - Formatter name.\n   * @param {formatterFn} [fn] - Set the formatter method.\n   * @return {formatterFn} The formatter method.\n   */\n  formatter (name, fn) {\n    return this.handler('formatters', ...arguments)\n  }\n\n  /**\n   * Get or set a validator.\n   * @param {string} name - Validator name.\n   * @param {validatorFn} [fn] - Set the validator method.\n   * @return {validatorFn} The validator method.\n   */\n  validator (name, fn) {\n    return this.handler('validators', ...arguments)\n  }\n\n  /**\n   * Get a param\n   * @param {string} name - Param name.\n   * @return {QuerymenParam|undefined} The param or undefined if it doesn't exist.\n   */\n  get (name) {\n    name = this._getSchemaParamName(name)\n\n    return this.params[name]\n  }\n\n  /**\n   * Set param value.\n   * @param {string} name - Param name.\n   * @param {*} value - Param value.\n   * @param {Object} [options] - Param options.\n   * @return {QuerymenParam|undefined} The param or undefined if it doesn't exist.\n   */\n  set (name, value, options) {\n    name = this._getSchemaParamName(name)\n\n    if (this.params[name]) {\n      let param = this.params[name]\n\n      param.value(value)\n\n      _.forIn(options, (optionValue, option) => {\n        param.option(option, optionValue)\n      })\n\n      return param\n    } else {\n      return\n    }\n  }\n\n  /**\n   * Add param.\n   * @param {string} name - Param name.\n   * @param {*} [value] - Param value.\n   * @param {Object} [options] - Param options.\n   * @return {QuerymenParam|boolean} The param or false if param is set to false in schema options.\n   */\n  add (name, value, options) {\n    if (name instanceof QuerymenParam) {\n      options = name.options\n      value = name.value()\n      name = name.name\n    }\n\n    name = this._getSchemaParamName(name)\n\n    if (this.options[name] === false) {\n      return false\n    }\n\n    options = this._parseParamOptions(options)\n\n    options = _.assign({bindTo: 'query'}, this._params[name], options)\n    this.params[name] = new QuerymenParam(name, value, options, this)\n\n    this._refreshHandlersInParams(undefined, {[name]: this.params[name]})\n\n    return this.params[name]\n  }\n\n  /**\n   * Get, set or add param.\n   * @param {string} name - Param name.\n   * @param {*} [value] - Param value.\n   * @param {Object} [options] - Param options.\n   * @return {QuerymenParam|undefined} The param or undefined if it doesn't exist.\n   */\n  param (name, value, options) {\n    if (arguments.length === 1) {\n      return this.get(name)\n    }\n\n    return this.set(name, value, options) || this.add(name, value, options)\n  }\n\n  /**\n   * Parse values of the schema params.\n   * @param {Object} [values] - Object with {param: value} pairs to parse.\n   * @return {Object} Parsed object.\n   */\n  parse (values = {}) {\n    let query = {}\n\n    _.forIn(this.params, (param) => {\n      let value = values[this._getQueryParamName(param.name)]\n\n      if (!_.isNil(value)) {\n        param.value(value)\n      }\n    })\n\n    _.forIn(this.params, (param) => {\n      if (this.options[this._getSchemaParamName(param.name)] === false) return\n      let bind = param.options.bindTo\n\n      query[bind] = _.merge(query[bind], param.parse())\n    })\n\n    return query\n  }\n\n  /**\n   * Validate values of the schema params.\n   * @param {Object} [values] - Object with {param: value} pairs to validate.\n   * @param {Function} [next] - Callback to be called with error\n   * @return {boolean} Result of the validation.\n   */\n  validate (values = {}, next = (error) => !error) {\n    let error\n\n    if (_.isFunction(values)) {\n      next = values\n      values = {}\n    }\n\n    _.forIn(this.params, (param) => {\n      let value = values[this._getQueryParamName(param.name)]\n\n      if (!_.isNil(value)) {\n        param.value(value)\n      }\n    })\n\n    for (let i in this.params) {\n      if (error) break\n      let param = this.params[i]\n      param.validate((err) => { error = err })\n    }\n\n    return next(error)\n  }\n\n  _refreshHandlersInParams (handlers = this.handlers, params = this.params) {\n    _.forIn(handlers, (typedHandler, type) => {\n      _.forIn(typedHandler, (handler, name) => {\n        _.forIn(params, (param) => {\n          param.handler(type, name, handler)\n        })\n      })\n    })\n  }\n\n  _getSchemaParamName (paramName) {\n    return _.findKey(this.options, (option) => option === paramName) || paramName\n  }\n\n  _getQueryParamName (paramName) {\n    return _.isString(this.options[paramName]) ? this.options[paramName] : paramName\n  }\n\n  _parseParamOptions (options) {\n    if (_.isArray(options) && options.length) {\n      let innerOption = this._parseParamOptions(options[0])\n      options = {}\n      if (innerOption.type) {\n        options.type = [innerOption.type]\n      }\n      if (innerOption.default) {\n        options.default = innerOption.default\n      }\n    } else if (_.isString(options)) {\n      options = {default: options}\n    } else if (_.isNumber(options)) {\n      options = {type: Number, default: options}\n    } else if (_.isBoolean(options)) {\n      options = {type: Boolean, default: options}\n    } else if (_.isDate(options)) {\n      options = {type: Date, default: options}\n    } else if (_.isRegExp(options)) {\n      options = {type: RegExp, default: options}\n    } else if (_.isFunction(options)) {\n      options = {type: options}\n    }\n\n    return options || {}\n  }\n\n}\n"
  },
  {
    "path": "test/index.js",
    "content": "import request from 'supertest'\nimport express from 'express'\nimport mongoose from 'mongoose'\nimport test from 'tape'\nimport querymen from '../src'\nimport './querymen-param'\nimport './querymen-schema'\n\nmongoose.connect('mongodb://localhost/querymen-test')\n\nconst entitySchema = mongoose.Schema({})\nconst testSchema = mongoose.Schema({\n  title: String,\n  entity: {\n    type: mongoose.Schema.ObjectId,\n    ref: 'Entity'\n  },\n  createdAt: {\n    type: Date,\n    default: Date.now\n  },\n  location: {\n    type: [Number],\n    index: '2d'\n  }\n})\n\nconst Entity = mongoose.model('Entity', entitySchema)\nconst Test = mongoose.model('Test', testSchema)\n\nconst route = (...args) => {\n  const app = express()\n  app.get('/tests', querymen.middleware(...args), (req, res) => {\n    Test.find(req.querymen.query, req.querymen.select, req.querymen.cursor).then((items) => {\n      res.status(200).json(items)\n    }).catch((err) => {\n      res.status(500).send(err)\n    })\n  })\n\n  app.use(querymen.errorHandler())\n  return app\n}\n\ntest('Prototype pollution', (t) => {\n  const { toString } = {}\n\n  querymen.handler('__proto__', 'toString', 'JHU')\n  t.ok({}.toString === toString, 'should not be vulnerable to prototype pollution')\n\n  querymen.handler('formatters', '__proto__', { toString: 'JHU' })\n  t.ok({}.toString === toString, 'should not be vulnerable to prototype pollution')\n\n  querymen.handler('validators', '__proto__', { toString: 'JHU' })\n  t.ok({}.toString === toString, 'should not be vulnerable to prototype pollution')\n\n  t.end()\n})\n\ntest('Querymen handler', (t) => {\n  t.notOk(querymen.parser('testParser'), 'should not get nonexistent parser')\n  t.notOk(querymen.formatter('testFormatter'), 'should not get nonexistent formatter')\n  t.notOk(querymen.validator('testValidator'), 'should not get nonexistent validator')\n\n  querymen.parser('testParser', () => 'test')\n  querymen.formatter('testFormatter', () => 'test')\n  querymen.validator('testValidator', () => ({valid: false}))\n\n  t.ok(querymen.parser('testParser'), 'should get parser')\n  t.ok(querymen.formatter('testFormatter'), 'should get formatter')\n  t.ok(querymen.validator('testValidator'), 'should get validator')\n\n  let schema = new querymen.Schema({test: String})\n\n  t.ok(schema.parser('testParser'), 'should get parser in schema')\n  t.ok(schema.formatter('testFormatter'), 'should get formatter in schema')\n  t.ok(schema.validator('testValidator'), 'should get validator in schema')\n\n  t.ok(schema.param('test').parser('testParser'), 'should get parser in param')\n  t.ok(schema.param('test').formatter('testFormatter'), 'should get formatter in param')\n  t.ok(schema.param('test').validator('testValidator'), 'should get validator in param')\n\n  t.end()\n})\n\ntest('Querymen middleware', (t) => {\n  t.plan(12)\n\n  Test.remove({}).then(() => {\n    return Entity.create({})\n  }).then((entity) => {\n    return Test.create(\n      {title: 'Test', entity, createdAt: new Date('2016-04-25T10:05'), location: [-44.1, -22.0]},\n      {title: 'Example', createdAt: new Date('2016-04-24T10:05'), location: [-44.3, -22.0]},\n      {title: 'Spaced test', createdAt: new Date('2016-04-23T10:05'), location: [-44.2, -22.0]},\n      {title: 'nice!', createdAt: new Date('2016-04-22T10:05'), location: [-44.4, -22.0]}\n    )\n  }).then((test1) => {\n    let app = route({\n      entity: testSchema.tree.entity\n    })\n\n    request(app)\n      .get('/tests')\n      .query({entity: test1.entity.id})\n      .expect(200)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body.length, 1, 'should respond to id filter')\n      })\n\n    request(app)\n      .get('/tests')\n      .query({page: 50})\n      .expect(400)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body.param, 'page', 'should respond with error object')\n      })\n\n    request(app)\n      .get('/tests')\n      .expect(200)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body.length, 4, 'should respond with 4 items')\n      })\n\n    request(app)\n      .get('/tests')\n      .query({fields: 'title,-id'})\n      .expect(200)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body.length, 4, 'should respond with 4 items')\n        t.true(res.body[0].title, 'should have title property')\n        t.false(res.body[0]._id, 'should not have _id property')\n        t.false(res.body[0].createdAt, 'should not have createdAt property')\n      })\n\n    request(route(new querymen.Schema({\n      before: {\n        type: Date,\n        operator: '$lte',\n        paths: ['createdAt']\n      },\n      after: {\n        type: Date,\n        operator: '$gte',\n        paths: ['createdAt']\n      }\n    })))\n      .get('/tests')\n      .query({before: '2016-04-23T10:00', after: '2016-04-22T10:00'})\n      .expect(200)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body.length, 1, 'should respond with 1 item')\n        t.equal(res.body[0].title, 'nice!', 'should respond with item after and before date')\n      })\n\n    request(route(new querymen.Schema({\n      after: {\n        type: Date,\n        operator: '$gte',\n        paths: ['createdAt']\n      }\n    })))\n      .get('/tests')\n      .query({after: '2016-04-25T10:00'})\n      .expect(200)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body.length, 1, 'should respond with 1 item')\n        t.equal(res.body[0].title, 'Test', 'should respond with item after date')\n      })\n\n    request(route(new querymen.Schema({\n      near: {\n        geojson: false\n      }\n    }, {near: true})))\n      .get('/tests')\n      .query({near: '-22.0,-44.0'})\n      .expect(200)\n      .end((err, res) => {\n        if (err) throw err\n        t.equal(res.body[1].title, 'Spaced test', 'should respond with item near')\n      })\n  })\n})\n\ntest.onFinish(() => {\n  mongoose.disconnect()\n})\n"
  },
  {
    "path": "test/querymen-param.js",
    "content": "import test from 'tape'\nimport _ from 'lodash'\nimport {Param} from '../src/'\n\nlet param = (value, options) => new Param('test', value, options)\nlet date = new Date('2016-04-24')\n\ntest('QuerymenParam constructor type', (t) => {\n  let type = (value) => param(value).option('type')\n  t.same(type('test'), String, 'should set type string automatically')\n  t.same(type(['test']), String, 'should set type string automatically by array')\n  t.same(type(123455), Number, 'should set type number automatically')\n  t.same(type([123455]), Number, 'should set type number automatically by array')\n  t.same(type(false), Boolean, 'should set type boolean automatically')\n  t.same(type([false]), Boolean, 'should set type boolean automatically by array')\n  t.same(type(new Date()), Date, 'should set type date automatically')\n  t.same(type([new Date()]), Date, 'should set type date automatically by array')\n  t.same(type(/test/i), RegExp, 'should set type regexp automatically')\n  t.same(type([/test/i]), RegExp, 'should set type regexp automatically by array')\n  t.end()\n})\n\ntest('QuerymenParam constructor options', (t) => {\n  let value = (...args) => param(...args).value()\n  t.equal(value('foo', {default: 'test'}), 'foo', 'should not set default value string')\n  t.equal(value(null, {default: 'test'}), 'test', 'should set default value string')\n  t.equal(value(20, {default: 30}), 20, 'should not set default value number')\n  t.equal(value(null, {default: () => 30}), 30, 'should set default value number by function')\n  t.equal(value(null, {type: String, default: 30}), '30', 'should set default value with different type')\n  t.equal(value('Bé_ free!', {normalize: false}), 'Bé_ free!', 'should not normalize value')\n  t.equal(value('Bé_ free!', {normalize: true}), 'be free', 'should normalize value')\n  t.equal(value('lower', {uppercase: false}), 'lower', 'should not uppercase value')\n  t.equal(value('lower', {uppercase: true}), 'LOWER', 'should uppercase value')\n  t.equal(value('UPPER', {lowercase: false}), 'UPPER', 'should not lowercase value')\n  t.equal(value('UPPER', {lowercase: true}), 'upper', 'should lowercase value')\n  t.equal(value('  trim   ', {trim: false}), '  trim   ', 'should not trim value')\n  t.equal(value('  trim   ', {trim: true}), 'trim', 'should trim value')\n  t.equal(value('123', {format: (value) => value + '!'}), '123!', 'should format value')\n  t.end()\n})\n\ntest('QuerymenParam constructor options with multiple value', (t) => {\n  let value = (value, options) => param(value, _.assign(options, {multiple: true})).value()\n  t.same(value('foo,bar', {default: 'test'}), ['foo', 'bar'], 'should not set default value string')\n  t.same(value('Bé_!, Bê!', {normalize: false}), ['Bé_!', 'Bê!'], 'should not normalize value')\n  t.same(value('Bé_!, Bê!', {normalize: true}), ['be', 'be'], 'should normalize value')\n  t.same(value('low,wol', {uppercase: false}), ['low', 'wol'], 'should not uppercase value')\n  t.same(value('low,wol', {uppercase: true}), ['LOW', 'WOL'], 'should uppercase value')\n  t.same(value('UP,PU', {lowercase: false}), ['UP', 'PU'], 'should not lowercase value')\n  t.same(value('UP,PU', {lowercase: true}), ['up', 'pu'], 'should lowercase value')\n  t.same(value(' tr ,  rt', {trim: false}), [' tr ', '  rt'], 'should not trim value')\n  t.same(value(' tr ,  rt', {trim: true}), ['tr', 'rt'], 'should trim value')\n  t.same(value('123,321', {format: (value) => value + '!'}), ['123!', '321!'], 'should format value')\n  t.end()\n})\n\ntest('QuerymenParam value', (t) => {\n  let value = (val, type) => param(val, {type: type}).value()\n  let dateValue = (val) => value(val, Date)\n  t.equal(value(23, String), '23', 'should convert 23 to \"23\"')\n  t.equal(value('23', Number), 23, 'should convert \"23\" to 23')\n  t.equal(value('1', Boolean), true, 'should convert \"1\" to true')\n  t.equal(value('0', Boolean), false, 'should convert \"0\" to false')\n  t.equal(value(1, Boolean), true, 'should convert 1 to true')\n  t.equal(value(0, Boolean), false, 'should convert 0 to false')\n  t.equal(value('true', Boolean), true, 'should convert \"true\" to true')\n  t.equal(value('false', Boolean), false, 'should convert \"false\" to false')\n  t.same(value('test', RegExp), /test/i, 'should convert \"test\" to /test/i')\n  t.same(value(123, RegExp), /123/i, 'should convert 123 to /123/i')\n  t.same(dateValue('2016-04-24'), date, 'should convert sameo string to date')\n  t.same(dateValue('1461456000000'), date, 'should convert timestamp string to date')\n  t.same(dateValue(1461456000000), date, 'should convert number to date')\n  t.end()\n})\n\ntest('QuerymenParam value with multiple value', (t) => {\n  let value = (val, type) => param(val, {type: type, multiple: true}).value()\n  let dateValue = (val) => value(val, Date)\n  t.same(value('23,24', String), ['23', '24'], 'should convert to string')\n  t.same(value('23,24', Number), [23, 24], 'should convert to number')\n  t.same(value('1,0,true,false', Boolean), [true, false, true, false], 'should convert to boolean')\n  t.same(value('foo,bar', RegExp), [/foo/i, /bar/i], 'should convert to regexp')\n  t.same(dateValue('2016-04-24,1461456000000'), [date, date], 'should convert to date')\n  value = (val, type) => param(val, {type: [type]}).value()\n  t.same(value('23,24', String), ['23', '24'], 'should convert to string with array type')\n  t.same(value('23,24', Number), [23, 24], 'should convert to number with array type')\n  t.same(value('1,0,true,false', Boolean), [true, false, true, false], 'should convert to boolean with array type')\n  t.same(value('foo,bar', RegExp), [/foo/i, /bar/i], 'should convert to regexp with array type')\n  t.same(dateValue('2016-04-24,1461456000000'), [date, date], 'should convert to date with array type')\n  t.end()\n})\n\ntest('QuerymenParam validate', (t) => {\n  let validate = (...args) => param(...args).validate()\n  let minDate = new Date('2016-01-01')\n  let maxDate = new Date('2016-04-25')\n  t.true(validate(), 'should validate no options with success')\n  t.true(validate(null, {required: false}), 'should validate non required with success')\n  t.false(validate(null, {required: true}), 'should validate required with error')\n  t.true(validate('test', {required: true}), 'should validate required with success')\n  t.true(validate(null, {min: 4}), 'should validate null value min with success')\n  t.false(validate(3, {min: 4}), 'should validate min with error')\n  t.true(validate(4, {min: 4}), 'should validate min with success')\n  t.false(validate(new Date('2015'), {min: minDate}), 'should validate min date with error')\n  t.true(validate(minDate, {min: minDate}), 'should validate min date with success')\n  t.true(validate(null, {max: 4}), 'should validate null value max with success')\n  t.false(validate(5, {max: 4}), 'should validate max with error')\n  t.true(validate(4, {max: 4}), 'should validate max with success')\n  t.false(validate(new Date('2017'), {max: maxDate}), 'should validate max date with error')\n  t.true(validate(maxDate, {max: maxDate}), 'should validate max date with success')\n  t.true(validate(null, {minlength: 2}), 'should validate null value minlength with success')\n  t.false(validate('a', {minlength: 2}), 'should validate minlength with error')\n  t.true(validate('ab', {minlength: 2}), 'should validate minlength with success')\n  t.true(validate(null, {maxlength: 2}), 'should validate null value maxlength with success')\n  t.false(validate('abc', {maxlength: 2}), 'should validate maxlength with error')\n  t.true(validate('ab', {maxlength: 2}), 'should validate maxlength with success')\n  t.true(validate(null, {enum: ['ab']}), 'should validate null value enum with success')\n  t.false(validate('a', {enum: ['ab']}), 'should validate enum with error')\n  t.true(validate('ab', {enum: ['ab']}), 'should validate enum with success')\n  t.true(validate(null, {match: /^\\D+$/i}), 'should validate null value match with success')\n  t.false(validate('3', {match: /^\\D+$/i}), 'should validate match with error')\n  t.true(validate('ab', {match: /^\\D+$/i}), 'should validate match with success')\n  t.false(validate(null, {validate: (value) => ({valid: false})}), 'should validate option with error')\n  t.true(validate(null, {validate: (value) => ({valid: true})}), 'should validate option with success')\n  t.false(param().validate((err) => err), 'should validate with callback')\n  t.end()\n})\n\ntest('QuerymenParam validate with multiple value', (t) => {\n  let validate = (value, options) => param(value, _.assign(options, {multiple: true})).validate()\n  t.false(validate('test,', {required: true}), 'should validate required with error')\n  t.true(validate('test', {required: true}), 'should validate required with success')\n  t.false(validate('3,4', {min: 4}), 'should validate min with error')\n  t.true(validate('4,5', {min: 4}), 'should validate min with success')\n  t.false(validate('4,5', {max: 4}), 'should validate max with error')\n  t.true(validate('3,4', {max: 4}), 'should validate max with success')\n  t.false(validate('ab', {minlength: 2}), 'should validate minlength with error')\n  t.true(validate('a,ab', {minlength: 2}), 'should validate minlength with success')\n  t.false(validate('ab', {maxlength: 0}), 'should validate maxlength with error')\n  t.true(validate('ab,abc', {maxlength: 2}), 'should validate maxlength with success')\n  t.true(validate('ab', {maxlength: 2}), 'should validate maxlength with success')\n  t.false(validate('ab,a', {enum: ['ab']}), 'should validate enum with error')\n  t.true(validate('ab,ab', {enum: ['ab']}), 'should validate enum with success')\n  t.false(validate('ab,3', {match: /^\\D+$/i}), 'should validate match with error')\n  t.true(validate('ab,a', {match: /^\\D+$/i}), 'should validate match with success')\n  t.end()\n})\n\ntest('QuerymenParam parse', (t) => {\n  let parse = (...args) => param(...args).parse()\n  t.same(parse(), {}, 'should parse nothing')\n  t.same(parse(123), {test: 123}, 'should parse $eq as default')\n  t.same(parse('123,456', {multiple: true}), {test: {$in: ['123', '456']}}, 'should parse $in')\n  t.same(parse('123', {operator: '$ne'}), {test: {$ne: '123'}}, 'should parse $ne')\n  t.same(parse('123,456', {operator: '$ne', multiple: true}), {test: {$nin: ['123', '456']}}, 'should parse $nin')\n  t.same(parse(123, {operator: '$gt'}), {test: {$gt: 123}}, 'should parse $gt')\n  t.same(parse(123, {operator: '$gte'}), {test: {$gte: 123}}, 'should parse $gte')\n  t.same(parse(123, {operator: '$lt'}), {test: {$lt: 123}}, 'should parse $lt')\n  t.same(parse(123, {operator: '$lte'}), {test: {$lte: 123}}, 'should parse $lte')\n  t.same(parse(/test/i, {operator: '$ne'}), {test: {$not: /test/i}}, 'should parse $not')\n  t.same(parse(123, {parse: (value, path, operator) => ({[path]: operator})}), {test: '$eq'}, 'should parse option')\n  t.end()\n})\n\ntest('QuerymenParam parse $or', (t) => {\n  let or = (value, options) => param(value, _.assign({paths: ['p1', 'p2']}, options)).parse()\n  let eqMultiple = {$or: [{p1: {$in: ['1', '2']}}, {p2: {$in: ['1', '2']}}]}\n  let neMultiple = {$or: [{p1: {$nin: ['1', '2']}}, {p2: {$nin: ['1', '2']}}]}\n  t.same(or(123), {$or: [{p1: 123}, {p2: 123}]}, 'should parse $eq')\n  t.same(or('1,2', {multiple: true}), eqMultiple, 'should parse $in')\n  t.same(or(123, {operator: '$ne'}), {$or: [{p1: {$ne: 123}}, {p2: {$ne: 123}}]}, 'should parse $ne')\n  t.same(or('1,2', {operator: '$ne', multiple: true}), neMultiple, 'should parse $nin')\n  t.same(or(123, {operator: '$gt'}), {$or: [{p1: {$gt: 123}}, {p2: {$gt: 123}}]}, 'should parse $gt')\n  t.same(or(123, {operator: '$gte'}), {$or: [{p1: {$gte: 123}}, {p2: {$gte: 123}}]}, 'should parse $gte')\n  t.same(or(123, {operator: '$lt'}), {$or: [{p1: {$lt: 123}}, {p2: {$lt: 123}}]}, 'should parse $lt')\n  t.same(or(123, {operator: '$lte'}), {$or: [{p1: {$lte: 123}}, {p2: {$lte: 123}}]}, 'should parse $lte')\n  t.same(or(123, {parse: (value, path, operator) => ({[path]: operator})}), {$or: [{p1: '$eq'}, {p2: '$eq'}]}, 'should parse option')\n  t.end()\n})\n\ntest('QuerymenParam option', (t) => {\n  let optionParam = param()\n  t.true(param().option('paths'), 'should get an option')\n  t.false(param().option('test'), 'should not get a nonexistent option')\n  t.true(optionParam.option('test', true), 'should set a custom option')\n  t.true(optionParam.option('test'), 'should get a custom option')\n  t.end()\n})\n\ntest('QuerymenParam formatter', (t) => {\n  let fParam = param()\n  fParam.formatter('capitalize', (capitalize, value, param) => {\n    t.equal(param.name, 'test', 'should pass param object to formatter method')\n    return capitalize ? _.capitalize(value) : value\n  })\n  t.true(param().formatter('default'), 'should get formatter')\n  t.false(param().formatter('capitalize'), 'should not get nonexistent formatter')\n  t.true(fParam.formatter('capitalize'), 'should get custom formatter')\n  t.equal(fParam.value('TEST'), 'TEST', 'should not apply custom formatter if option was not set')\n  t.true(fParam.option('capitalize', true), 'should set formatter option to true')\n  t.equal(fParam.value('TEST'), 'Test', 'should apply custom formatter')\n  t.false(fParam.option('capitalize', false), 'should set formatter option to false')\n  t.equal(fParam.value('TEST'), 'TEST', 'should not apply custom formatter if option is false')\n  t.end()\n})\n\ntest('QuerymenParam parser', (t) => {\n  let pParam = param(null, {multiple: true})\n  pParam.parser('elemMatch', (elemMatch, value, path, operator, param) => {\n    t.true(path, 'should pass path to parser')\n    t.true(operator, 'should pass operator to parser')\n    return elemMatch\n      ? {[path]: {$elemMatch: {[elemMatch]: {[operator]: value}}}}\n      : {[path]: operator !== '$eq' ? {[operator]: value} : value}\n  })\n  t.false(param().parser('elemMatch'), 'should not get nonexistent parser')\n  t.true(pParam.parser('elemMatch'), 'should get custom parser')\n  t.same(pParam.parse('test'), {test: 'test'}, 'should not apply custom parser if option was not set')\n  t.true(pParam.option('elemMatch', 'path'), 'should set parser option to true')\n  t.same(pParam.parse('test'), {test: {$elemMatch: {path: {$eq: 'test'}}}}, 'should apply custom parser')\n  t.same(pParam.parse('test,foo'), {test: {$elemMatch: {path: {$in: ['test', 'foo']}}}}, 'should apply custom parser to multiple values')\n  t.false(pParam.option('elemMatch', false), 'should set parser option to false')\n  t.same(pParam.parse('test'), {test: 'test'}, 'should not apply custom parser if option is false')\n  t.end()\n})\n\ntest('QuerymenParam validator', (t) => {\n  let vParam = param(null, {multiple: true})\n  vParam.validator('isPlural', (isPlural, value, param) => {\n    t.equal(param.name, 'test', 'should pass param object to validator method')\n    return {valid: !isPlural || value.toLowerCase().substr(-1) === 's'}\n  })\n  t.true(param().validator('required'), 'should get validator')\n  t.false(param().validator('isPlural'), 'should not get nonexistent validator')\n  t.true(vParam.validator('isPlural'), 'should get custom validator')\n  t.true(vParam.validate('test'), 'should not apply custom validator if option was not set')\n  t.true(vParam.option('isPlural', true), 'should set validator option to true')\n  t.false(vParam.validate('test'), 'should apply custom validator and validate false')\n  t.false(vParam.validate('tests,FOO'), 'should apply custom validator to multiple values and validate false')\n  t.true(vParam.validate('tests'), 'should apply custom validator and validate true')\n  t.true(vParam.validate('tests,FOOS'), 'should apply custom validator to multiple values and validate true')\n  t.false(vParam.option('isPlural', false), 'should set validator option to false')\n  t.true(vParam.validate('test'), 'should not apply custom validator if option is false')\n  t.end()\n})\n"
  },
  {
    "path": "test/querymen-schema.js",
    "content": "import test from 'tape'\nimport {Schema, Param} from '../src/'\n\nlet schema = (params, options) => new Schema(params, options)\n\ntest('QuerymenSchema add', (t) => {\n  let add = (...args) => schema().add('test', ...args).value()\n  t.true(schema().add(new Param('test')), 'should add a param with instance')\n  t.true(schema().add('test'), 'should add a param')\n  t.equal(add('123'), '123', 'should add a param with value')\n  t.true(schema().add('test', null, {test: true}).option('test'), 'should add a param with option')\n  t.equal(add(null, '123'), '123', 'should add a param with default option string')\n  t.equal(add(null, 123), 123, 'should add a param with default option number')\n  t.equal(add(null, true), true, 'should add a param with default option boolean')\n  t.same(add(null, new Date('2016')), new Date('2016'), 'should add a param with default option date')\n  t.same(add(null, /123/i), /123/i, 'should add a param with default option regexp')\n  t.equal(add(123, String), '123', 'should add a param with type option string')\n  t.equal(add('123', Number), 123, 'should add a param with type option number')\n  t.equal(add('123', Boolean), true, 'should add a param with type option boolean')\n  t.same(add('2016', Date), new Date('2016'), 'should add a param with type option date')\n  t.same(add('123', RegExp), /123/i, 'should add a param with type option regexp')\n\n  t.same(add(null, ['123']), '123', 'should add a param with default option string array')\n  t.same(add(null, [123]), 123, 'should add a param with default option number array')\n  t.same(add(null, [true]), true, 'should add a param with default option boolean array')\n  t.same(add(null, [new Date('2016')]), new Date('2016'), 'should add a param with default option date array')\n  t.same(add(null, [/123/i]), /123/i, 'should add a param with default option regexp array')\n  t.same(add(123, [String]), '123', 'should add a param with type option string array')\n  t.same(add('123,456', [Number]), [123, 456], 'should add a param with type option number array')\n  t.same(add('123,0', [Boolean]), [true, false], 'should add a param with type option boolean array')\n  t.same(add('2016,2017', [Date]), [new Date('2016'), new Date('2017')], 'should add a param with type option date array')\n  t.same(add('123,456', [RegExp]), [/123/i, /456/i], 'should add a param with type option regexp array')\n  t.end()\n})\n\ntest('QuerymenSchema get', (t) => {\n  let mySchema = schema()\n  mySchema.add('test')\n  t.false(schema().get('test'), 'should not get a nonexistent param')\n  t.true(mySchema.get('test'), 'should get a param')\n  t.end()\n})\n\ntest('QuerymenSchema set', (t) => {\n  let mySchema = schema()\n  mySchema.add('test')\n  t.false(schema().set('test', '123'), 'should not set a nonexistent param')\n  t.true(mySchema.set('test', '123'), 'should set a param')\n  t.true(mySchema.set('test', '123', {test: true}).option('test'), 'should set param option')\n  t.end()\n})\n\ntest('QuerymenSchema option', (t) => {\n  let mySchema = schema()\n  t.equal(mySchema.option('test', false), false, 'should set option')\n  t.equal(mySchema.option('test'), false, 'should get option')\n  t.false(mySchema.add('test'), 'should not add disallowed param')\n  t.end()\n})\n\ntest('QuerymenSchema param', (t) => {\n  let mySchema = schema()\n  t.false(mySchema.param('test'), 'should not get a nonexistent param')\n  t.true(mySchema.param('test', null), 'should add a param')\n  t.true(mySchema.param('test'), 'should get a param')\n  t.true(mySchema.param('test', '123'), 'should set a param')\n  t.end()\n})\n\ntest('QuerymenSchema formatter', (t) => {\n  let mySchema = schema({test: '123'})\n  let formatter = mySchema.formatter('scream', (scream, value) => {\n    return scream ? value.toUpperCase() : value\n  })\n  t.true(formatter, 'should create a formatter')\n  t.false(schema().formatter('scream'), 'should not get a nonexistent formatter')\n  t.true(mySchema.formatter('scream'), 'should get a formatter')\n  t.true(mySchema.param('test').formatter('scream'), 'should get param formatter')\n  t.equal(mySchema.param('test').value(), '123', 'should not format value')\n  t.true(mySchema.param('test').option('scream', true), 'should set param option')\n  t.equal(mySchema.param('test').value('help'), 'HELP', 'should format value')\n  t.true(mySchema.param('f', null).formatter('scream'), 'should get lazy param formatter')\n  t.end()\n})\n\ntest('QuerymenSchema validator', (t) => {\n  let mySchema = schema({test: 'help'})\n  let validator = mySchema.validator('isPlural', (isPlural, value, param) => ({\n    valid: !isPlural || value.toLowerCase().substr(-1) === 's',\n    message: param.name + ' must be in plural form.'\n  }))\n  t.true(validator, 'should create a validator')\n  t.false(schema().validator('isPlural'), 'should not get a nonexistent validator')\n  t.true(mySchema.validator('isPlural'), 'should get a validator')\n  t.true(mySchema.param('test').validator('isPlural'), 'should get param validator')\n  t.true(mySchema.validate(), 'should not apply validator')\n  t.true(mySchema.param('test').option('isPlural', true), 'should set param option')\n  t.false(mySchema.validate(), 'should apply validator and validate false')\n  t.true(mySchema.validate({test: 'helps'}), 'should apply validator and validate true')\n  t.true(mySchema.param('v', null).validator('isPlural'), 'should get lazy param validator')\n  mySchema.validate((err) => t.false(err, 'should call validation success'))\n  mySchema.validate({test: 'help'}, (err) => t.false(err.valid, 'should call validation error'))\n  t.end()\n})\n\ntest('QuerymenSchema parser', (t) => {\n  let mySchema = schema({test: 'help'})\n  let parser = mySchema.parser('elemMatch', (elemMatch, value, path) => {\n    return elemMatch ? {[path]: {$elemMatch: {[elemMatch]: value}}} : value\n  })\n  t.true(parser, 'should create a parser')\n  t.false(schema().parser('elemMatch'), 'should not get a nonexistent parser')\n  t.true(mySchema.parser('elemMatch'), 'should get a parser')\n  t.true(mySchema.param('test').parser('elemMatch'), 'should get param parser')\n  t.same(mySchema.parse().query, {test: 'help'}, 'should not apply parser')\n  t.true(mySchema.param('test').option('elemMatch', 'prop'), 'should set param option')\n  t.same(mySchema.parse().query, {test: {$elemMatch: {prop: 'help'}}}, 'should apply parser')\n  t.true(mySchema.param('p', null).parser('elemMatch'), 'should get lazy param parser')\n  t.end()\n})\n\ntest('QuerymenSchema name', (t) => {\n  let mySchema = schema({}, {page: 'p'})\n  let name = (type, name) => mySchema[`_get${type}ParamName`](name)\n  t.equal(name('Schema', 'p'), 'page', 'should get schema param name by query param name')\n  t.equal(name('Schema', 'page'), 'page', 'should get schema param name by itself')\n  t.equal(name('Query', 'page'), 'p', 'should get query param name by schema param name')\n  t.equal(name('Query', 'p'), 'p', 'should get query param name by itself')\n  mySchema = schema({test: String}, {test: 't'})\n  t.equal(name('Schema', 't'), 'test', 'should get custom schema param name by query param name')\n  t.equal(name('Schema', 'test'), 'test', 'should get custom schema param name by itself')\n  t.equal(name('Query', 'test'), 't', 'should get custom query param name by schema param name')\n  t.equal(name('Query', 't'), 't', 'should get custom query param name by itself')\n  t.end()\n})\n\ntest('QuerymenSchema default parse', (t) => {\n  let parse = (...args) => schema().parse(...args)\n  t.same(parse({q: 'testing'}).query.keywords, /testing/i, 'should parse q')\n  t.same(parse().select, {}, 'should not parse undefined select')\n  t.same(parse({fields: ''}).select, {}, 'should not parse empty select')\n  t.same(parse({fields: 'a'}).select, {a: 1}, 'should parse one select')\n  t.same(parse({fields: '-id'}).select, {_id: 0}, 'should parse fields=id to fields=_id')\n  t.same(parse({fields: 'id'}).select, {_id: 1}, 'should parse fields=id to fields=_id')\n  t.same(parse({fields: '-a,b,+c'}).select, {a: 0, b: 1, c: 1}, 'should parse multiple select')\n  t.same(parse({page: 2, limit: 10}).cursor.skip, 10, 'should parse page')\n  t.same(parse({limit: 10}).cursor.limit, 10, 'should parse limit')\n  t.same(parse({sort: ''}).cursor.sort, {createdAt: -1}, 'should not parse empty sort')\n  t.same(parse({sort: '-a'}).cursor.sort, {a: -1}, 'should parse sort')\n  t.same(parse({sort: '-a,b,+c'}).cursor.sort, {a: -1, b: 1, c: 1}, 'should parse multiple sort')\n  t.same(schema({distance: 0}).parse({distance: 23}).query.distance, 23, 'should parse any')\n\n  let near = (params, values) => schema(params, {near: true}).parse(values).query\n\n  t.false(parse({near: '-22.84377,-44.3213214'}).query.location, 'should not enable near param by default')\n\n  t.same(\n    near({}, {near: '-22.84377,-44.3213214'}).location,\n    {$near: {$geometry: {type: 'Point', coordinates: [-44.3213214, -22.84377]}}},\n    'should parse near')\n\n  t.same(\n    near({}, {near: '-22.84377,-44.3213214', min_distance: 56}).location,\n    {$near: {$geometry: {type: 'Point', coordinates: [-44.3213214, -22.84377]}, $minDistance: 56}},\n    'should parse near min_distance')\n\n  t.same(\n    near({}, {near: '-22.84377,-44.3213214', max_distance: 56}).location,\n    {$near: {$geometry: {type: 'Point', coordinates: [-44.3213214, -22.84377]}, $maxDistance: 56}},\n    'should parse near max_distance')\n\n  t.same(\n    near({near: {min_distance: false}}, {near: '-22.84377,-44.3213214', min_distance: 56}).location,\n    {$near: {$geometry: {type: 'Point', coordinates: [-44.3213214, -22.84377]}}},\n    'should not parse near min_distance')\n\n  t.same(\n    near({near: {max_distance: false}}, {near: '-22.84377,-44.3213214', max_distance: 56}).location,\n    {$near: {$geometry: {type: 'Point', coordinates: [-44.3213214, -22.84377]}}},\n    'should not parse near max_distance')\n\n  t.false(near({}, {min_distance: 56}).min_distance, 'should not parse min_distance without near')\n  t.false(near({}, {max_distance: 56}).max_distance, 'should not parse max_distance without near')\n  t.false(near({}, {near: '-22.84377,-44.3213214', min_distance: 56}).min_distance, 'should not parse min_distance')\n  t.false(near({}, {near: '-22.84377,-44.3213214', max_distance: 56}).max_distance, 'should not parse max_distance')\n\n  t.same(\n    near({near: {geojson: false}}, {near: '-22.84377,-44.3213214'}).location,\n    {$near: [-44.3213214, -22.84377]},\n    'should parse near no geojson')\n\n  t.same(\n    near({near: {geojson: false}}, {near: '-22.84377,-44.3213214', min_distance: 56}).location,\n    {$near: [-44.3213214, -22.84377], $minDistance: 56 / 6371000},\n    'should parse near min_distance no geojson')\n\n  t.same(\n    near({near: {geojson: false}}, {near: '-22.84377,-44.3213214', max_distance: 56}).location,\n    {$near: [-44.3213214, -22.84377], $maxDistance: 56 / 6371000},\n    'should parse near max_distance no geojson')\n\n  t.end()\n})\n"
  }
]