├── accessible └── rules.js ├── .npmignore ├── .editorconfig ├── .travis.yml ├── test ├── .eslintrc ├── util │ └── testRules.js └── miscellaneousRules.test.js ├── lib ├── match.js └── rules.js ├── .gitignore ├── package.json ├── appveyor.yml ├── index.js ├── README.md ├── .eslintrc └── .jshintrc /accessible/rules.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../lib/rules'); 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | ./.gitignore 3 | ./.jshintrc 4 | ./.editorconfig 5 | ./.travis.yml 6 | ./appveyor.yml 7 | ./example 8 | ./examples 9 | ./test 10 | ./tests 11 | ./.github 12 | 13 | node_modules 14 | npm-debug.log 15 | .node_history 16 | *.swo 17 | *.swp 18 | *.swn 19 | *.swm 20 | *.seed 21 | *.log 22 | *.out 23 | *.pid 24 | lib-cov 25 | .DS_STORE 26 | *# 27 | *\# 28 | .\#* 29 | *~ 30 | .idea 31 | .netbeans 32 | nbproject 33 | .tmp 34 | dump.rdb 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # ╔═╗╔╦╗╦╔╦╗╔═╗╦═╗┌─┐┌─┐┌┐┌┌─┐┬┌─┐ 2 | # ║╣ ║║║ ║ ║ ║╠╦╝│ │ ││││├┤ ││ ┬ 3 | # o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└ ┴└─┘ 4 | # 5 | # This file (`.editorconfig`) exists to help maintain consistent formatting 6 | # throughout this package, the Sails framework, and the Node-Machine project. 7 | # 8 | # To review what each of these options mean, see: 9 | # http://editorconfig.org/ 10 | root = true 11 | 12 | [*] 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # ╔╦╗╦═╗╔═╗╦ ╦╦╔═╗ ┬ ┬┌┬┐┬ # 3 | # ║ ╠╦╝╠═╣╚╗╔╝║╚═╗ └┬┘││││ # 4 | # o ╩ ╩╚═╩ ╩ ╚╝ ╩╚═╝o ┴ ┴ ┴┴─┘ # 5 | # # 6 | # This file configures Travis CI. # 7 | # (i.e. how we run the tests... mainly) # 8 | # # 9 | # https://docs.travis-ci.com/user/customizing-the-build # 10 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 11 | 12 | language: node_js 13 | 14 | node_js: 15 | - "10" 16 | - "12" 17 | - "14" 18 | - "16" 19 | 20 | branches: 21 | only: 22 | - master 23 | 24 | notifications: 25 | email: 26 | - ci@sailsjs.com 27 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ╔═╗╔═╗╦ ╦╔╗╔╔╦╗┬─┐┌─┐ ┌─┐┬ ┬┌─┐┬─┐┬─┐┬┌┬┐┌─┐ 3 | // ║╣ ╚═╗║ ║║║║ ║ ├┬┘│ │ │└┐┌┘├┤ ├┬┘├┬┘│ ││├┤ 4 | // o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘ └─┘ └┘ └─┘┴└─┴└─┴─┴┘└─┘ 5 | // ┌─ ┌─┐┌─┐┬─┐ ┌─┐┬ ┬┌┬┐┌─┐┌┬┐┌─┐┌┬┐┌─┐┌┬┐ ┌┬┐┌─┐┌─┐┌┬┐┌─┐ ─┐ 6 | // │ ├┤ │ │├┬┘ ├─┤│ │ │ │ ││││├─┤ │ ├┤ ││ │ ├┤ └─┐ │ └─┐ │ 7 | // └─ └ └─┘┴└─ ┴ ┴└─┘ ┴ └─┘┴ ┴┴ ┴ ┴ └─┘─┴┘ ┴ └─┘└─┘ ┴ └─┘ ─┘ 8 | // > An .eslintrc configuration override for use with the tests in this directory. 9 | // 10 | // (See .eslintrc in the root directory of this package for more info.) 11 | 12 | "extends": [ 13 | "../.eslintrc" 14 | ], 15 | 16 | "env": { 17 | "mocha": true 18 | }, 19 | 20 | "globals": { 21 | // "assert": true, 22 | // "util": true 23 | // …plus any other convenience globals exposed in test suites 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /lib/match.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var _ = require('@sailshq/lodash'); 6 | var rules = require('./rules'); 7 | 8 | 9 | /** 10 | * Match a miscellaneous rule 11 | * Returns an empty list on success, 12 | * or a list of errors if things go wrong 13 | */ 14 | 15 | module.exports = function matchRule (data, ruleName, args) { 16 | var self = this; 17 | 18 | // if args is an array we need to make it a nested array 19 | if (Array.isArray(args) && ruleName !== 'len') { 20 | args = [args]; 21 | } 22 | 23 | // Ensure args is a list, then prepend it with data 24 | if (!_.isArray(args)) { 25 | args = [args]; 26 | } 27 | 28 | // push data on to front 29 | args.unshift(data); 30 | 31 | // Lookup rule and determine outcome 32 | var rule = rules[ruleName]; 33 | if (!rule) { 34 | throw new Error('Unknown rule: ' + ruleName); 35 | } 36 | 37 | var errorMessage = rule.apply(self, args); 38 | if (errorMessage) { return [{rule: ruleName, message: errorMessage}]; } 39 | return []; 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /test/util/testRules.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var _ = require('@sailshq/lodash'); 3 | var anchor = require('../../index.js'); 4 | 5 | // Test a rule given a deliberate example and nonexample 6 | // Test WITH and WITHOUT callback 7 | module.exports = function testRules (rules, example, nonexample) { 8 | 9 | // Throw an error if there's any trouble 10 | // (not a good production usage pattern-- just here for testing) 11 | 12 | var exampleOutcome = anchor(example, rules); 13 | if (exampleOutcome.length > 0) { 14 | throw new Error('Valid input marked with error: '+util.inspect(exampleOutcome,{depth:null})+ '\nExample: '+util.inspect(example,{depth:null})+'\nDetails: '+util.inspect(exampleOutcome)); 15 | } 16 | 17 | var nonexampleOutcome = anchor(nonexample, rules); 18 | if (!_.isArray(nonexampleOutcome) || nonexampleOutcome.length === 0) { 19 | throw new Error('Invalid input (' + nonexample + ') allowed through. '+util.inspect(rules,{depth:null})+ '\nNon-example: '+util.inspect(nonexample,{depth:null})+'\nDetails: '+util.inspect(nonexampleOutcome)); 20 | } 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ┌─┐┬┌┬┐╦╔═╗╔╗╔╔═╗╦═╗╔═╗ 2 | # │ ┬│ │ ║║ ╦║║║║ ║╠╦╝║╣ 3 | # o└─┘┴ ┴ ╩╚═╝╝╚╝╚═╝╩╚═╚═╝ 4 | # 5 | # This file (`.gitignore`) exists to signify to `git` that certain files 6 | # and/or directories should be ignored for the purposes of version control. 7 | # 8 | # This is primarily useful for excluding temporary files of all sorts; stuff 9 | # generated by IDEs, build scripts, automated tests, package managers, or even 10 | # end-users (e.g. file uploads). `.gitignore` files like this also do a nice job 11 | # at keeping sensitive credentials and personal data out of version control systems. 12 | # 13 | 14 | ############################ 15 | # sails / node.js / npm 16 | ############################ 17 | node_modules 18 | .tmp 19 | npm-debug.log 20 | package-lock.json 21 | .waterline 22 | .node_history 23 | 24 | ############################ 25 | # editor & OS files 26 | ############################ 27 | *.swo 28 | *.swp 29 | *.swn 30 | *.swm 31 | *.seed 32 | *.log 33 | *.out 34 | *.pid 35 | lib-cov 36 | .DS_STORE 37 | *# 38 | *\# 39 | .\#* 40 | *~ 41 | .idea 42 | .netbeans 43 | nbproject 44 | 45 | ############################ 46 | # misc 47 | ############################ 48 | dump.rdb 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "anchor", 3 | "version": "1.4.2", 4 | "description": "High-level validation library for Node.js (used in Waterline)", 5 | "homepage": "https://sailsjs.com", 6 | "keywords": [ 7 | "validation", 8 | "recursive-validator", 9 | "nested", 10 | "validator", 11 | "strict-types", 12 | "web-framework", 13 | "type-coercion", 14 | "waterline", 15 | "validate-parameters", 16 | "sails.js", 17 | "sails" 18 | ], 19 | "main": "index.js", 20 | "directories": { 21 | "test": "test" 22 | }, 23 | "scripts": { 24 | "test": "npm run lint && npm run custom-tests", 25 | "custom-tests": "node ./node_modules/mocha/bin/mocha -R dot test", 26 | "lint": "node ./node_modules/eslint/bin/eslint . --max-warnings=0 --ignore-pattern 'test/' && echo '✔ Your code looks good.'" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/sailsjs/anchor.git" 31 | }, 32 | "author": "Mike McNeil", 33 | "license": "MIT", 34 | "readmeFilename": "README.md", 35 | "dependencies": { 36 | "@sailshq/lodash": "^3.10.2", 37 | "validator": "13.15.23" 38 | }, 39 | "devDependencies": { 40 | "eslint": "4.11.0", 41 | "mocha": "3.0.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # # # # # # # # # # # # # # # # # # # # # # # # # # 2 | # ╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╦╔═╗╦═╗ ┬ ┬┌┬┐┬ # 3 | # ╠═╣╠═╝╠═╝╚╗╔╝║╣ ╚╦╝║ ║╠╦╝ └┬┘││││ # 4 | # ╩ ╩╩ ╩ ╚╝ ╚═╝ ╩ ╚═╝╩╚═o ┴ ┴ ┴┴─┘ # 5 | # # 6 | # This file configures Appveyor CI. # 7 | # (i.e. how we run the tests on Windows) # 8 | # # 9 | # https://www.appveyor.com/docs/lang/nodejs-iojs/ # 10 | # # # # # # # # # # # # # # # # # # # # # # # # # # 11 | 12 | 13 | # Test against these versions of Node.js. 14 | environment: 15 | matrix: 16 | - nodejs_version: "4" 17 | - nodejs_version: "6" 18 | - nodejs_version: "8" 19 | 20 | # Install scripts. (runs after repo cloning) 21 | install: 22 | # Get the latest stable version of Node.js 23 | # (Not sure what this is for, it's just in Appveyor's example.) 24 | - ps: Install-Product node $env:nodejs_version 25 | # Install declared dependencies 26 | - npm install 27 | 28 | 29 | # Post-install test scripts. 30 | test_script: 31 | # Output Node and NPM version info. 32 | # (Presumably just in case Appveyor decides to try any funny business? 33 | # But seriously, always good to audit this kind of stuff for debugging.) 34 | - node --version 35 | - npm --version 36 | # Run the actual tests. 37 | - npm run custom-tests 38 | 39 | 40 | # Don't actually build. 41 | # (Not sure what this is for, it's just in Appveyor's example. 42 | # I'm not sure what we're not building... but I'm OK with not 43 | # building it. I guess.) 44 | build: off 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var _ = require('@sailshq/lodash'); 6 | var match = require('./lib/match'); 7 | 8 | 9 | /** 10 | * Public access 11 | */ 12 | 13 | module.exports = function (entity, ruleset) { 14 | 15 | var errors = []; 16 | 17 | // If ruleset doesn't contain any explicit rule keys, 18 | // assume that this is a type 19 | 20 | // Look for explicit rules 21 | for (var rule in ruleset) { 22 | 23 | 24 | // TODO: In next major version, remove this: It should definitely not be here. 25 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 26 | // Normalize the value if it looks like a boolean 27 | if(ruleset[rule] === 'true') { 28 | ruleset[rule] = true; 29 | } 30 | 31 | if(ruleset[rule] === 'false') { 32 | ruleset[rule] = false; 33 | } 34 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 35 | 36 | // If the value is false, then we shouldn't even run the validation 37 | if(ruleset[rule] === false) { 38 | break; 39 | } 40 | 41 | // If the rule value is a boolean we don't need to pass the value along. 42 | // Otherwise we can pass it along so it's options are available in 43 | // the validation. 44 | var ruleVal = _.isBoolean(ruleset[rule]) ? undefined : ruleset[rule]; 45 | errors = errors.concat(match(entity, rule, ruleVal)); 46 | 47 | } 48 | 49 | // If errors exist, return the list of them 50 | if (errors.length) { 51 | return errors; 52 | } 53 | 54 | // No errors, so return false 55 | else { 56 | return []; 57 | } 58 | 59 | }; 60 | 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # anchor 2 | 3 | Anchor is a JavaScript library that lets you enforce [high-level validation rules](https://sailsjs.com/documentation/concepts/models-and-orm/validations). It is used in [Waterline and Sails](https://sailsjs.com/features) to complement the type safety imposed by [rttc](https://npmjs.com/package/rttc). 4 | 5 | (Built on top of the great work with https://www.npmjs.com/package/validator) 6 | 7 | ## Usage 8 | 9 | 10 | #### Documentation 11 | 12 | The up-to-date documentation for high-level anchor validation rules is maintained on the [Sails framework website](http://sailsjs.com). 13 | 14 | You can find a detailed reference of all validation rules under [Concepts > Models & ORM > Validations](http://sailsjs.com/documentation/concepts/models-and-orm/validations). 15 | 16 | > For more details on standalone usage, see the source code in this repo. 17 | 18 | 37 | 38 | ## Help 39 | 40 | Check out the recommended [community support options](http://sailsjs.com/support) for tutorials and other resources. If you have a specific question, or just need to clarify how something works, [ask for help](https://gitter.im/balderdashy/sails) or reach out to the core team [directly](http://sailsjs.com/flagship). 41 | 42 | You can keep up to date with security patches, the Sails/Waterline release schedule, new database adapters, and events in your area by following us [@sailsjs](https://twitter.com/sailsjs) and [@waterlineorm](https://twitter.com/waterlineorm) on Twitter. 43 | 44 | ## Bugs   [![NPM version](https://badge.fury.io/js/anchor.svg)](http://npmjs.com/package/anchor) 45 | To report a bug, [click here](http://sailsjs.com/bugs). 46 | 47 | ## Contribute   [![Build Status](https://travis-ci.org/sailshq/anchor.png?branch=master)](https://travis-ci.org/sailshq/anchor) 48 | Please observe the guidelines and conventions laid out in our [contribution guide](http://sailsjs.com/documentation/contributing) when opening issues or submitting pull requests. 49 | 50 | #### Tests 51 | All tests are written with [mocha](https://mochajs.org/) and should be run with [npm](https://www.npmjs.com/): 52 | 53 | ``` bash 54 | $ npm test 55 | ``` 56 | 57 | 58 | ## License 59 | 60 | Copyright 2012-present, [Mike McNeil](https://twitter.com/mikermcneil) 61 | 62 | This core package, like the rest of the [Sails framework](http://sailsjs.com), is free and open-source under the [MIT License](http://sailsjs.com/license). 63 | 64 | [![image_squidhome@2x.png](http://sailsjs.com/images/bkgd_squiddy.png)](http://sailsjs.com/about) 65 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ╔═╗╔═╗╦ ╦╔╗╔╔╦╗┬─┐┌─┐ 3 | // ║╣ ╚═╗║ ║║║║ ║ ├┬┘│ 4 | // o╚═╝╚═╝╩═╝╩╝╚╝ ╩ ┴└─└─┘ 5 | // A set of basic conventions (similar to .jshintrc) for use within any 6 | // arbitrary JavaScript / Node.js package -- inside or outside Sails.js. 7 | // For the master copy of this file, see the `.eslintrc` template file in 8 | // the `sails-generate` package (https://www.npmjs.com/package/sails-generate.) 9 | // Designed for ESLint v4. 10 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 11 | // For more information about any of the rules below, check out the relevant 12 | // reference page on eslint.org. For example, to get details on "no-sequences", 13 | // you would visit `http://eslint.org/docs/rules/no-sequences`. If you're unsure 14 | // or could use some advice, come by https://sailsjs.com/support. 15 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 16 | 17 | "env": { 18 | "node": true 19 | }, 20 | 21 | "parserOptions": { 22 | "ecmaVersion": 5 23 | // ^^This can be changed to `8` if this package doesn't need to support <= Node v6. 24 | }, 25 | 26 | "globals": { 27 | "Promise": true 28 | // ^^Available since Node v4 29 | }, 30 | 31 | "rules": { 32 | "callback-return": ["error", ["done", "proceed", "next", "onwards", "callback", "cb"]], 33 | "camelcase": ["warn", {"properties": "always"}], 34 | "comma-style": ["warn", "last"], 35 | "curly": ["error"], 36 | "eqeqeq": ["error", "always"], 37 | "eol-last": ["warn"], 38 | "handle-callback-err": ["error"], 39 | "indent": ["warn", 2, { 40 | "SwitchCase": 1, 41 | "MemberExpression": "off", 42 | "FunctionDeclaration": {"body":1, "parameters": "off"}, 43 | "FunctionExpression": {"body":1, "parameters": "off"}, 44 | "CallExpression": {"arguments":"off"}, 45 | "ArrayExpression": 1, 46 | "ObjectExpression": 1, 47 | "ignoredNodes": ["ConditionalExpression"] 48 | }], 49 | "linebreak-style": ["error", "unix"], 50 | "no-dupe-keys": ["error"], 51 | "no-duplicate-case": ["error"], 52 | "no-extra-semi": ["warn"], 53 | "no-labels": ["error"], 54 | "no-mixed-spaces-and-tabs": ["error", "smart-tabs"], 55 | "no-redeclare": ["warn"], 56 | "no-return-assign": ["error", "always"], 57 | "no-sequences": ["error"], 58 | "no-trailing-spaces": ["warn"], 59 | "no-undef": ["error"], 60 | "no-unexpected-multiline": ["warn"], 61 | "no-unreachable": ["warn"], 62 | "no-unused-vars": ["warn", {"caughtErrors":"all", "caughtErrorsIgnorePattern": "^unused($|[A-Z].*$)"}], 63 | "no-use-before-define": ["error", {"functions":false}], 64 | "one-var": ["warn", "never"], 65 | "quotes": ["warn", "single", {"avoidEscape":false, "allowTemplateLiterals":true}], 66 | "semi": ["error", "always"], 67 | "semi-spacing": ["warn", {"before":false, "after":true}], 68 | "semi-style": ["warn", "last"] 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ┬┌─┐╦ ╦╦╔╗╔╔╦╗┬─┐┌─┐ 3 | // │└─┐╠═╣║║║║ ║ ├┬┘│ 4 | // o└┘└─┘╩ ╩╩╝╚╝ ╩ ┴└─└─┘ 5 | // 6 | // This file (`.jshintrc`) exists to help with consistency of code 7 | // throughout this package, and throughout Sails and the Node-Machine project. 8 | // 9 | // To review what each of these options mean, see: 10 | // http://jshint.com/docs/options 11 | // 12 | // (or: https://github.com/jshint/jshint/blob/master/examples/.jshintrc) 13 | 14 | 15 | 16 | ////////////////////////////////////////////////////////////////////// 17 | // NOT SUPPORTED IN SOME JSHINT VERSIONS SO LEAVING COMMENTED OUT: 18 | ////////////////////////////////////////////////////////////////////// 19 | // Prevent overwriting prototypes of native classes like `Array`. 20 | // (doing this is _never_ ok in any of our packages that are intended 21 | // to be used as dependencies of other developers' modules and apps) 22 | // "freeze": true, 23 | ////////////////////////////////////////////////////////////////////// 24 | 25 | 26 | ////////////////////////////////////////////////////////////////////// 27 | // EVERYTHING ELSE: 28 | ////////////////////////////////////////////////////////////////////// 29 | 30 | // Allow the use of ES6 features. 31 | // (re ES7, see https://github.com/jshint/jshint/issues/2297) 32 | "esversion": 6, 33 | 34 | // Allow the use of `eval` and `new Function()` 35 | // (we sometimes actually need to use these things) 36 | "evil": true, 37 | 38 | // Tolerate funny-looking dashes in RegExp literals. 39 | // (see https://github.com/jshint/jshint/issues/159#issue-903547) 40 | "regexdash": true, 41 | 42 | // The potential runtime "Environments" (as defined by jshint) 43 | // that the _style_ of code written in this package should be 44 | // compatible with (not the code itself, of course). 45 | "browser": true, 46 | "node": true, 47 | "wsh": true, 48 | 49 | // Tolerate the use `[]` notation when dot notation would be possible. 50 | // (this is sometimes preferable for readability) 51 | "sub": true, 52 | 53 | // Do NOT suppress warnings about mixed tabs and spaces 54 | // (two spaces always, please; see `.editorconfig`) 55 | "smarttabs": false, 56 | 57 | // Suppress warnings about trailing whitespace 58 | // (this is already enforced by the .editorconfig, so no need to warn as well) 59 | "trailing": false, 60 | 61 | // Suppress warnings about the use of expressions where fn calls or assignments 62 | // are expected, and about using assignments where conditionals are expected. 63 | // (while generally a good idea, without this setting, JSHint needlessly lights up warnings 64 | // in existing, working code that really shouldn't be tampered with. Pandora's box and all.) 65 | "expr": true, 66 | "boss": true, 67 | 68 | // Do NOT suppress warnings about using functions inside loops 69 | // (in the general case, we should be using iteratee functions with `_.each()` 70 | // or `Array.prototype.forEach()` instead of `for` or `while` statements 71 | // anyway. This warning serves as a helpful reminder.) 72 | "loopfunc": false, 73 | 74 | // Suppress warnings about "weird constructions" 75 | // i.e. allow code like: 76 | // ``` 77 | // (new (function OneTimeUsePrototype () { } )) 78 | // ``` 79 | // 80 | // (sometimes order of operations in JavaScript can be scary. There is 81 | // nothing wrong with using an extra set of parantheses when the mood 82 | // strikes or you get "that special feeling".) 83 | "supernew": true, 84 | 85 | // Do NOT allow backwards, node-dependency-style commas. 86 | // (while this code style choice was used by the project in the past, 87 | // we have since standardized these practices to make code easier to 88 | // read, albeit a bit less exciting) 89 | "laxcomma": false, 90 | 91 | // Do NOT allow avant garde use of commas in conditional statements. 92 | // (this prevents accidentally writing code like: 93 | // ``` 94 | // if (!_.contains(['+ci', '-ci', '∆ci', '+ce', '-ce', '∆ce']), change.verb) {...} 95 | // ``` 96 | // See the problem in that code? Neither did we-- that's the problem!) 97 | "nocomma": true, 98 | 99 | // Strictly enforce the consistent use of single quotes. 100 | // (this is a convention that was established primarily to make it easier 101 | // to grep [or FIND+REPLACE in Sublime] particular string literals in 102 | // JavaScript [.js] files. Note that JSON [.json] files are, of course, 103 | // still written exclusively using double quotes around key names and 104 | // around string literals.) 105 | "quotmark": "single", 106 | 107 | // Do NOT suppress warnings about the use of `==null` comparisons. 108 | // (please be explicit-- use Lodash or `require('util')` and call 109 | // either `.isNull()` or `.isUndefined()`) 110 | "eqnull": false, 111 | 112 | // Strictly enforce the use of curly braces with `if`, `else`, and `switch` 113 | // as well as, much less commonly, `for` and `while` statements. 114 | // (this is just so that all of our code is consistent, and to avoid bugs) 115 | "curly": true, 116 | 117 | // Strictly enforce the use of `===` and `!==`. 118 | // (this is always a good idea. Check out "Truth, Equality, and JavaScript" 119 | // by Angus Croll [the author of "If Hemmingway Wrote JavaScript"] for more 120 | // explanation as to why.) 121 | "eqeqeq": true, 122 | 123 | // Allow initializing variables to `undefined`. 124 | // For more information, see: 125 | // • https://jslinterrors.com/it-is-not-necessary-to-initialize-a-to-undefined 126 | // • https://github.com/jshint/jshint/issues/1484 127 | // 128 | // (it is often very helpful to explicitly clarify the initial value of 129 | // a local variable-- especially for folks new to more advanced JavaScript 130 | // and who might not recognize the subtle, yet critically important differences between our seemingly 131 | // between `null` and `undefined`, and the impact on `typeof` checks) 132 | "-W080": true 133 | 134 | } 135 | -------------------------------------------------------------------------------- /test/miscellaneousRules.test.js: -------------------------------------------------------------------------------- 1 | var testRules = require('./util/testRules.js'); 2 | 3 | 4 | 5 | describe('miscellaneous rules', function() { 6 | 7 | describe('isInteger', function() { 8 | it ('should fail for strings', function() { 9 | return testRules({ 10 | isInteger: true 11 | }, 2, '2'); 12 | }); 13 | it ('should fail for floats', function() { 14 | return testRules({ 15 | isInteger: true 16 | }, 2, 2.5); 17 | }); 18 | it ('should not allow nulls', function() { 19 | return testRules({ 20 | isInteger: true 21 | }, 2, null); 22 | }); 23 | }); 24 | 25 | describe('notEmptyString', function() { 26 | it('should no allow nulls', function() { 27 | return testRules({ 28 | isNotEmptyString: true 29 | }, 'sfsa', null); 30 | }); 31 | }); 32 | 33 | describe('max/min', function() { 34 | it(' should support "max" rule ', function() { 35 | return testRules({ 36 | max: 3 37 | }, 2, 5); 38 | }); 39 | it(' should support "min" rule ', function() { 40 | return testRules({ 41 | min: 3 42 | }, 5, 2); 43 | }); 44 | describe(' should support both "max" and "min" rules at the same time ', function() { 45 | it('with a val < min', function() { 46 | return testRules({ 47 | min: 3, 48 | max: 5 49 | }, 4, 2); 50 | }); 51 | it('with a val > max', function() { 52 | return testRules({ 53 | min: 3, 54 | max: 5 55 | }, 4, 6); 56 | }); 57 | }); 58 | it('should not allow `null` values', function() { 59 | return testRules({ 60 | min: 3, 61 | max: 5 62 | }, 4, null); 63 | }); 64 | }); 65 | 66 | describe('maxLength/minLength', function() { 67 | it(' should support "maxLength" rule ', function() { 68 | return testRules({ 69 | maxLength: 3 70 | }, 'abc', 'abcde'); 71 | }); 72 | it(' should support "minLength" rule ', function() { 73 | return testRules({ 74 | minLength: 3 75 | }, 'abc', 'ab'); 76 | }); 77 | describe(' should support both "maxLength" and "minLength" rules at the same time ', function() { 78 | it('with a val < minLength', function() { 79 | return testRules({ 80 | minLength: 3, 81 | maxLength: 5 82 | }, 'abcd', 'ab'); 83 | }); 84 | it('with a val > maxLength', function() { 85 | return testRules({ 86 | minLength: 3, 87 | maxLength: 5 88 | }, 'abcd', 'abcdef'); 89 | }); 90 | }); 91 | it('should not allow null values', function() { 92 | return testRules({ 93 | minLength: 3, 94 | maxLength: 5 95 | }, 'abcd', null); 96 | }); 97 | it('should allow empty string values', function() { 98 | return testRules({ 99 | minLength: 3, 100 | maxLength: 5 101 | }, '', 'abcdef'); 102 | }); 103 | 104 | }); 105 | 106 | describe('isURL', function() { 107 | 108 | it('should support "isURL" rule with no options', function() { 109 | return testRules({ 110 | isURL: true 111 | }, 'http://sailsjs.org', 'sailsjs'); 112 | }); 113 | 114 | it('should support "isURL" rule with options', function() { 115 | return testRules({ 116 | isURL: { 117 | require_protocol: true//eslint-disable-line camelcase 118 | } 119 | }, 'http://sailsjs.org', 'www.sailsjs.org'); 120 | }); 121 | it('should validate localhost as a url with the "isURL" rule', function() { 122 | return testRules({ 123 | isURL: true 124 | }, 'localhost', 'localhosts'); 125 | }); 126 | it('should validate localhost as a url with the "isURL" rule with options', function() { 127 | return testRules({ 128 | isURL: { 129 | require_protocol: true//eslint-disable-line camelcase 130 | } 131 | }, 'http://localhost:1337', 'localhost:1337'); 132 | }); 133 | }); 134 | 135 | describe('isBefore/isAfter date', function() { 136 | it(' should support "isBefore" rule when validating Date instances (Date) vs. Date', function() { 137 | return testRules({ 138 | isBefore: new Date() 139 | }, new Date(Date.now() - 100000), new Date(Date.now() + 1000000)); 140 | }); 141 | it(' should support "isBefore" rule when validating Date instances (Date) vs. number', function() { 142 | return testRules({ 143 | isBefore: Date.now() 144 | }, new Date(Date.now() - 100000), new Date(Date.now() + 1000000)); 145 | }); 146 | it(' should support "isBefore" rule when validating js timestamps (number) vs. number', function() { 147 | return testRules({ 148 | isBefore: Date.now() 149 | }, (new Date(Date.now() - 100000)).getTime(), (new Date(Date.now() + 1000000)).getTime()); 150 | }); 151 | it(' should support "isBefore" rule when validating js timestamps (number) vs. Date', function() { 152 | return testRules({ 153 | isBefore: new Date() 154 | }, (new Date(Date.now() - 100000)).getTime(), (new Date(Date.now() + 1000000)).getTime()); 155 | }); 156 | it(' should support "isBefore" rule when validating JSON timestamps (string) vs. string', function() { 157 | return testRules({ 158 | isBefore: (new Date()).toJSON() 159 | }, (new Date(Date.now() - 100000)).toJSON(), (new Date(Date.now() + 1000000)).toJSON()); 160 | }); 161 | it(' should support "isBefore" rule when validating JSON timestamps (string) vs. number', function() { 162 | return testRules({ 163 | isBefore: Date.now() 164 | }, (new Date(Date.now() - 100000)).toJSON(), (new Date(Date.now() + 1000000)).toJSON()); 165 | }); 166 | 167 | it(' should support "isAfter" rule when validating Date instances (Date) vs. Date', function() { 168 | return testRules({ 169 | isAfter: new Date() 170 | }, new Date(Date.now() + 100000), new Date(Date.now() - 1000000)); 171 | }); 172 | it(' should support "isAfter" rule when validating js timestamps (number) vs. number', function() { 173 | return testRules({ 174 | isAfter: new Date() 175 | }, (new Date(Date.now() + 100000)).getTime(), (new Date(Date.now() - 1000000)).getTime()); 176 | }); 177 | it(' should support "isAfter" rule when validating JSON timestamps (string) vs. string', function() { 178 | return testRules({ 179 | isAfter: (new Date()).toJSON() 180 | }, (new Date(Date.now() + 100000)).toJSON(), (new Date(Date.now() - 1000000)).toJSON()); 181 | }); 182 | }); 183 | 184 | describe('custom rule', function() { 185 | it ('should support a custom rule as a function', function() { 186 | return testRules({ 187 | custom: function(x) { 188 | return x.indexOf('foo') === 0; 189 | } 190 | }, 'foobar', 'notfoobar'); 191 | }); 192 | }); 193 | 194 | 195 | describe('isNotIn rule', function() { 196 | it ('should support isNotIn', function() { 197 | return testRules({ 198 | isNotIn: ['foo','bar','baz'] 199 | }, 'bloop', 'foo'); 200 | }); 201 | }); 202 | 203 | 204 | describe('regex rule', function() { 205 | it ('should support regex', function() { 206 | return testRules({ 207 | regex: /^\w\d{2}!$/ 208 | }, 'a12!', 'a1goop!'); 209 | }); 210 | }); 211 | 212 | 213 | }); 214 | -------------------------------------------------------------------------------- /lib/rules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | 5 | var util = require('util'); 6 | var _ = require('@sailshq/lodash'); 7 | var validator = require('validator'); 8 | 9 | /** 10 | * Type rules 11 | */ 12 | 13 | var rules = { 14 | 15 | // ┬┌─┐┌┐┌┌─┐┬─┐┌─┐ ┌┐┌┬ ┬┬ ┬ 16 | // ││ ┬││││ │├┬┘├┤ ││││ ││ │ 17 | // ┴└─┘┘└┘└─┘┴└─└─┘ ┘└┘└─┘┴─┘┴─┘ 18 | 'isBoolean': { 19 | fn: function(x) { 20 | return typeof x === 'boolean'; 21 | }, 22 | defaultErrorMessage: function(x) { return 'Value ('+util.inspect(x)+') was not a boolean.'; }, 23 | expectedTypes: ['json', 'ref'] 24 | }, 25 | 'isNotEmptyString': { 26 | fn: function(x) { 27 | return x !== ''; 28 | }, 29 | defaultErrorMessage: function(x) { return 'Value ('+util.inspect(x)+') was an empty string.'; }, 30 | expectedTypes: ['json', 'ref', 'string'] 31 | }, 32 | 'isInteger': { 33 | fn: function(x) { 34 | return typeof x === 'number' && (parseInt(x) === x); 35 | }, 36 | defaultErrorMessage: function(x) { return 'Value ('+util.inspect(x)+') was not an integer.'; }, 37 | expectedTypes: ['json', 'ref', 'number'] 38 | }, 39 | 'isNumber': { 40 | fn: function(x) { 41 | return typeof x === 'number'; 42 | }, 43 | defaultErrorMessage: function(x) { return 'Value ('+util.inspect(x)+') was not a number.'; }, 44 | expectedTypes: ['json', 'ref'] 45 | }, 46 | 'isString': { 47 | fn: function(x) { 48 | return typeof x === 'string'; 49 | }, 50 | defaultErrorMessage: function(x) { return 'Value ('+util.inspect(x)+') was not a string.'; }, 51 | expectedTypes: ['json', 'ref'] 52 | }, 53 | 'max': { 54 | fn: function(x, maximum) { 55 | if (typeof x !== 'number') { throw new Error ('Value was not a number.'); } 56 | return x <= maximum; 57 | }, 58 | defaultErrorMessage: function(x, maximum) { return 'Value ('+util.inspect(x)+') was greater than the configured maximum (' + maximum + ')'; }, 59 | expectedTypes: ['json', 'ref', 'number'], 60 | checkConfig: function(constraint) { 61 | if (typeof constraint !== 'number') { 62 | return 'Maximum must be specified as a number; instead got `' + util.inspect(constraint) + '`.'; 63 | } 64 | return false; 65 | } 66 | }, 67 | 'min': { 68 | fn: function(x, minimum) { 69 | if (typeof x !== 'number') { throw new Error ('Value was not a number.'); } 70 | return x >= minimum; 71 | }, 72 | defaultErrorMessage: function(x, minimum) { return 'Value ('+util.inspect(x)+') was less than the configured minimum (' + minimum + ')'; }, 73 | expectedTypes: ['json', 'ref', 'number'], 74 | checkConfig: function(constraint) { 75 | if (typeof constraint !== 'number') { 76 | return 'Minimum must be specified as a number; instead got `' + util.inspect(constraint) + '`.'; 77 | } 78 | return false; 79 | } 80 | }, 81 | 82 | 83 | // ┬┌─┐┌┐┌┌─┐┬─┐┌─┐ ┌┐┌┬ ┬┬ ┬ ┌─┐┌┐┌┌┬┐ ┌─┐┌┬┐┌─┐┌┬┐┬ ┬ ┌─┐┌┬┐┬─┐┬┌┐┌┌─┐ 84 | // ││ ┬││││ │├┬┘├┤ ││││ ││ │ ├─┤│││ ││ ├┤ │││├─┘ │ └┬┘ └─┐ │ ├┬┘│││││ ┬ 85 | // ┴└─┘┘└┘└─┘┴└─└─┘ ┘└┘└─┘┴─┘┴─┘ ┴ ┴┘└┘─┴┘ └─┘┴ ┴┴ ┴ ┴ └─┘ ┴ ┴└─┴┘└┘└─┘ 86 | 'isAfter': { 87 | fn: function(x, constraint) { 88 | 89 | var normalizedX; 90 | if (_.isNumber(x)) { 91 | normalizedX = new Date(x).getTime(); 92 | } else if (_.isDate(x)) { 93 | normalizedX = x.getTime(); 94 | } else { 95 | normalizedX = Date.parse(x); 96 | } 97 | 98 | var normalizedConstraint; 99 | if (_.isNumber(constraint)) { 100 | normalizedConstraint = new Date(constraint).getTime(); 101 | } else if (_.isDate(constraint)) { 102 | normalizedConstraint = constraint.getTime(); 103 | } else { 104 | normalizedConstraint = Date.parse(constraint); 105 | } 106 | 107 | return normalizedX > normalizedConstraint; 108 | }, 109 | expectedTypes: ['json', 'ref', 'string', 'number'], 110 | defaultErrorMessage: function(x, constraint) { return 'Value ('+util.inspect(x)+') was before the configured time (' + constraint + ')'; }, 111 | ignoreEmptyString: true, 112 | checkConfig: function(constraint) { 113 | var isValidConstraint = (_.isNumber(constraint) || _.isDate(constraint) || (_.isString(constraint) && _.isNull(validator.toDate(constraint)))); 114 | if (!isValidConstraint) { 115 | return 'Validation rule must be specified as a JS timestamp (number of ms since epoch), a natively-parseable date string, or a JavaScript Date instance; instead got `' + util.inspect(constraint) + '`.'; 116 | } else { 117 | return false; 118 | } 119 | } 120 | 121 | }, 122 | 'isBefore': { 123 | fn: function(x, constraint) { 124 | 125 | var normalizedX; 126 | if (_.isNumber(x)) { 127 | normalizedX = new Date(x).getTime(); 128 | } else if (_.isDate(x)) { 129 | normalizedX = x.getTime(); 130 | } else { 131 | normalizedX = Date.parse(x); 132 | } 133 | 134 | var normalizedConstraint; 135 | if (_.isNumber(constraint)) { 136 | normalizedConstraint = new Date(constraint).getTime(); 137 | } else if (_.isDate(constraint)) { 138 | normalizedConstraint = constraint.getTime(); 139 | } else { 140 | normalizedConstraint = Date.parse(constraint); 141 | } 142 | 143 | return normalizedX < normalizedConstraint; 144 | }, 145 | expectedTypes: ['json', 'ref', 'string', 'number'], 146 | defaultErrorMessage: function(x, constraint) { return 'Value ('+util.inspect(x)+') was after the configured time (' + constraint + ')'; }, 147 | ignoreEmptyString: true, 148 | checkConfig: function(constraint) { 149 | var isValidConstraint = (_.isNumber(constraint) || _.isDate(constraint) || (_.isString(constraint) && _.isNull(validator.toDate(constraint)))); 150 | if (!isValidConstraint) { 151 | return 'Validation rule must be specified as a JS timestamp (number of ms since epoch), a natively-parseable date string, or a JavaScript Date instance; instead got `' + util.inspect(constraint) + '`.'; 152 | } else { 153 | return false; 154 | } 155 | } 156 | }, 157 | 'isCreditCard': { 158 | fn: function(x) { 159 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 160 | return validator.isCreditCard(x); 161 | }, 162 | expectedTypes: ['json', 'ref', 'string'], 163 | defaultErrorMessage: function () { return 'Value was not a valid credit card.'; }, 164 | ignoreEmptyString: true 165 | }, 166 | 'isEmail': { 167 | fn: function(x) { 168 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 169 | return validator.isEmail(x); 170 | }, 171 | expectedTypes: ['json', 'ref', 'string'], 172 | defaultErrorMessage: function (x) { return 'Value ('+util.inspect(x)+') was not a valid email address.'; }, 173 | ignoreEmptyString: true 174 | }, 175 | 'isHexColor': { 176 | fn: function(x) { 177 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 178 | return validator.isHexColor(x); 179 | }, 180 | expectedTypes: ['json', 'ref', 'string'], 181 | defaultErrorMessage: function (x) { return 'Value ('+util.inspect(x)+') was not a valid hex color.'; }, 182 | ignoreEmptyString: true 183 | }, 184 | 'isIn': { 185 | fn: function(x, constraint) { 186 | return _.contains(constraint, x); 187 | }, 188 | expectedTypes: ['json', 'ref', 'string', 'number'], 189 | defaultErrorMessage: function(x, whitelist) { return 'Value ('+util.inspect(x)+') was not in the configured whitelist (' + whitelist.join(', ') + ')'; }, 190 | ignoreEmptyString: true, 191 | checkConfig: function(constraint) { 192 | if (!_.isArray(constraint)) { 193 | return 'Allowable values must be specified as an array; instead got `' + util.inspect(constraint) + '`.'; 194 | } 195 | return false; 196 | } 197 | 198 | }, 199 | 'isIP': { 200 | fn: function(x) { 201 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 202 | return validator.isIP(x); 203 | }, 204 | expectedTypes: ['json', 'ref', 'string'], 205 | defaultErrorMessage: function (x) { return 'Value ('+util.inspect(x)+') was not a valid IP address.'; }, 206 | ignoreEmptyString: true 207 | }, 208 | 'isNotIn': { 209 | fn: function(x, constraint) { 210 | return !_.contains(constraint, x); 211 | }, 212 | expectedTypes: ['json', 'ref', 'string', 'number'], 213 | defaultErrorMessage: function(x, blacklist) { return 'Value ('+util.inspect(x)+') was in the configured blacklist (' + blacklist.join(', ') + ')'; }, 214 | ignoreEmptyString: true, 215 | checkConfig: function(constraint) { 216 | if (!_.isArray(constraint)) { 217 | return 'Blacklisted values must be specified as an array; instead got `' + util.inspect(constraint) + '`.'; 218 | } 219 | return false; 220 | } 221 | }, 222 | 'isURL': { 223 | fn: function(x, opt) { 224 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 225 | var defaultOptions = {}; 226 | // As of Validator 8.0.0, isURL requires `require_tld` to be set to false to validate localhost as a URL. 227 | if(x.match(/^(https?:\/\/)?localhost(\:|\/|$)/)) { 228 | // If the value we're checking is localhost, we'll add require_tld: false to the options we pass into validator 229 | defaultOptions = { 230 | require_tld: false,//eslint-disable-line camelcase 231 | }; 232 | } 233 | var options = _.extend(defaultOptions, opt); // Note: If the provided options include `require_tld: true`, that value will override the value set in defaultOptions. 234 | return validator.isURL(x, options); 235 | }, 236 | expectedTypes: ['json', 'ref', 'string'], 237 | defaultErrorMessage: function (x) { return 'Value ('+util.inspect(x)+') was not a valid URL.'; }, 238 | ignoreEmptyString: true 239 | }, 240 | 'isUUID': { 241 | fn: function(x) { 242 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 243 | return validator.isUUID(x); 244 | }, 245 | expectedTypes: ['json', 'ref', 'string'], 246 | defaultErrorMessage: function (x) { return 'Value ('+util.inspect(x)+') was not a valid UUID.'; }, 247 | ignoreEmptyString: true 248 | }, 249 | 250 | 'minLength': { 251 | fn: function(x, minLength) { 252 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 253 | return x.length >= minLength; 254 | }, 255 | expectedTypes: ['json', 'ref', 'string'], 256 | defaultErrorMessage: function(x, minLength) { return 'Value ('+util.inspect(x)+') was shorter than the configured minimum length (' + minLength + ')'; }, 257 | ignoreEmptyString: true, 258 | checkConfig: function(constraint) { 259 | if (typeof constraint !== 'number' && parseInt(constraint) !== constraint) { 260 | return 'Minimum length must be specified as an integer; instead got `' + util.inspect(constraint) + '`.'; 261 | } 262 | return false; 263 | } 264 | }, 265 | 'maxLength': { 266 | fn: function(x, maxLength) { 267 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 268 | return x.length <= maxLength; 269 | }, 270 | expectedTypes: ['json', 'ref', 'string'], 271 | defaultErrorMessage: function(x, maxLength) { return 'Value was '+(maxLength-x.length)+' character'+((maxLength-x.length !== 1) ? 's' : '')+' longer than the configured maximum length (' + maxLength + ')'; }, 272 | ignoreEmptyString: true, 273 | checkConfig: function(constraint) { 274 | if (typeof constraint !== 'number' && parseInt(constraint) !== constraint) { 275 | return 'Maximum length must be specified as an integer; instead got `' + util.inspect(constraint) + '`.'; 276 | } 277 | return false; 278 | } 279 | }, 280 | 281 | 'regex': { 282 | fn: function(x, regex) { 283 | if (typeof x !== 'string') { throw new Error ('Value was not a string.'); } 284 | return validator.matches(x, regex); 285 | }, 286 | defaultErrorMessage: function(x, regex) { return 'Value ('+util.inspect(x)+') did not match the configured regular expression (' + regex + ')'; }, 287 | expectedTypes: ['json', 'ref', 'string'], 288 | ignoreEmptyString: true, 289 | checkConfig: function(constraint) { 290 | if (!_.isRegExp(constraint)) { 291 | return 'Expected a regular expression as the constraint; instead got `' + util.inspect(constraint) + '`.'; 292 | } 293 | return false; 294 | } 295 | 296 | }, 297 | 298 | // ┌─┐┬ ┬┌─┐┌┬┐┌─┐┌┬┐ 299 | // │ │ │└─┐ │ │ ││││ 300 | // └─┘└─┘└─┘ ┴ └─┘┴ ┴ 301 | // Custom rule function. 302 | 'custom': { 303 | fn: function(x, customFn) { 304 | return customFn(x); 305 | }, 306 | expectedTypes: ['json', 'ref', 'string', 'number', 'boolean'], 307 | defaultErrorMessage: function (x) { return 'Value ('+util.inspect(x)+') failed custom validation.'; }, 308 | checkConfig: function(constraint) { 309 | if (!_.isFunction(constraint)) { 310 | return 'Expected a function as the constraint; instead got `' + util.inspect(constraint) + '`. Please return `true` to indicate success, or otherwise return `false` or throw to indicate failure'; 311 | } 312 | if (constraint.constructor.name === 'AsyncFunction') { 313 | return 'Custom validation function cannot be an `async function` -- please use synchronous logic and return `true` to indicate success, or otherwise return `false` or throw to indicate failure.'; 314 | } 315 | return false; 316 | } 317 | 318 | } 319 | 320 | }; 321 | 322 | // Wrap a rule in a function that handles nulls and empty strings as requested, 323 | // and adds an `expectedTypes` array that users of the rule can check to see 324 | // if their value is of a type that the rule is designed to handle. Note that 325 | // this list of types is not necessarily validated in the rule itself; that is, 326 | // just because it lists "json, ref, string" doesn't necessarily mean that it 327 | // will automatically kick out numbers (it might stringify them). It's up to 328 | // you to decide whether to run the validation based on its `expectedTypes`. 329 | module.exports = _.reduce(rules, function createRule(memo, rule, ruleName) { 330 | 331 | // Wrap the original rule in a function that kicks out null and empty string if necessary. 332 | var wrappedRule = function(x) { 333 | 334 | // Never allow null or undefined. 335 | if (_.isNull(x) || _.isUndefined(x)) { 336 | return 'Got invalid value `' + x + '`!'; 337 | } 338 | 339 | // Allow empty strings if we're explicitly ignoring them. 340 | if (x === '' && rule.ignoreEmptyString) { 341 | return false; 342 | } 343 | 344 | var passed; 345 | // Run the original rule function. 346 | try { 347 | passed = rule.fn.apply(rule, arguments); 348 | } catch (e) { 349 | // console.error('ERROR:',e); 350 | if (_.isError(e)) { 351 | return e.message; 352 | } else { 353 | return String(e); 354 | } 355 | } 356 | 357 | if (passed) { return false; } 358 | return _.isFunction(rule.defaultErrorMessage) ? rule.defaultErrorMessage.apply(rule, arguments) : rule.defaultErrorMessage; 359 | 360 | };//ƒ 361 | 362 | // If the rule doesn't declare its own config-checker, assume that the constraint is supposed to be `true`. 363 | // This is the case for most of the `is` rules like `isBoolean`, `isCreditCard`, `isEmail`, etc. 364 | if (_.isUndefined(rule.checkConfig)) { 365 | wrappedRule.checkConfig = function (constraint) { 366 | if (constraint !== true) { 367 | return 'This validation only accepts `true` as a constraint. Instead, saw `' + constraint + '`.'; 368 | } 369 | return false; 370 | }; 371 | } else { 372 | wrappedRule.checkConfig = rule.checkConfig; 373 | } 374 | 375 | // Set the `expectedTypes` property of the wrapped function. 376 | wrappedRule.expectedTypes = rule.expectedTypes; 377 | 378 | // Return the wrapped function. 379 | memo[ruleName] = wrappedRule; 380 | 381 | return memo; 382 | 383 | }, {}); 384 | --------------------------------------------------------------------------------