├── .github └── workflows │ ├── codeql-analysis.yml │ ├── nodejs.yml │ └── release.yml ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── History.md ├── LICENSE.txt ├── README.md ├── benchmark.js ├── example.js ├── index.js ├── package.json └── test └── index.test.js /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '35 4 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | pull_request: 8 | branches: [ master ] 9 | 10 | workflow_dispatch: {} 11 | 12 | jobs: 13 | Job: 14 | name: Node.js 15 | uses: artusjs/github-actions/.github/workflows/node-test.yml@v1 16 | with: 17 | os: 'ubuntu-latest' 18 | version: '10, 12, 14, 16, 18' 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | workflow_dispatch: {} 8 | 9 | jobs: 10 | release: 11 | name: Node.js 12 | uses: artusjs/github-actions/.github/workflows/node-release.yml@v1 13 | secrets: 14 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 15 | GIT_TOKEN: ${{ secrets.GIT_TOKEN }} 16 | with: 17 | checkTest: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.csv 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.es5.js 9 | 10 | pids 11 | logs 12 | results 13 | 14 | node_modules 15 | npm-debug.log 16 | 17 | coverage/ 18 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Ordered by date of first contribution. 2 | 3 | dead-horse (https://github.com/dead-horse) 4 | fengmk2 (https://github.com/fengmk2) 5 | Jason Lee (https://github.com/huacnlee) 6 | 闲耘™ (https://github.com/hotoo) 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.7.0](https://github.com/node-modules/parameter/compare/v3.6.0...v3.7.0) (2023-03-14) 4 | 5 | 6 | ### Features 7 | 8 | * custom error message ([#68](https://github.com/node-modules/parameter/issues/68)) ([818cd23](https://github.com/node-modules/parameter/commit/818cd23b2c6dd0470939b03218e8746feed4f467)), closes [#31](https://github.com/node-modules/parameter/issues/31) [#15](https://github.com/node-modules/parameter/issues/15) 9 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 3.6.0 / 2019-05-22 3 | ================== 4 | 5 | **features** 6 | * [[`156fc36`](http://github.com/node-modules/parameter/commit/156fc3609710369b1a28f8618c4abf18111ac948)] - feat: support convertType function added by addRule (hdumok <>) 7 | 8 | 3.5.0 / 2018-12-11 9 | ================== 10 | 11 | **features** 12 | * [[`ee3521c`](http://github.com/node-modules/parameter/commit/ee3521c74e73630b5b72014d7aeba7ed87eb865e)] - feat: support override options.widelyUndefined (#59) (Yiyu He <>) 13 | 14 | 3.4.0 / 2018-10-30 15 | ================== 16 | 17 | **features** 18 | * [[`abcd739`](http://github.com/node-modules/parameter/commit/abcd739cd24133205e1f0c34e2d2a4adbf7be921)] - feat: support es5 with babel (#56) (Angela <>) 19 | 20 | 3.3.1 / 2018-10-09 21 | ================== 22 | 23 | **fixes** 24 | * [[`f5e33e3`](http://github.com/node-modules/parameter/commit/f5e33e379d0c70616faed6d010cf75b577818bb5)] - fix: allow default to be `null` or `false` (#55) (Cong Liu <>) 25 | 26 | 3.3.0 / 2018-09-19 27 | ================== 28 | 29 | **features** 30 | * [[`4d95c6a`](http://github.com/node-modules/parameter/commit/4d95c6a52416c8b8cd4f71b647ec4c05e0bab8ac)] - feat: support `trim: true` on string value (#54) (fengmk2 <>) 31 | 32 | 3.2.0 / 2018-09-11 33 | ================== 34 | 35 | **features** 36 | * [[`9d35b7f`](http://github.com/node-modules/parameter/commit/9d35b7fda2b21bf59d4ba036f7b75874d82ca9ad)] - feat: support options.widelyUndefined and rule.default (#53) (Yiyu He <>) 37 | 38 | 3.1.0 / 2018-08-30 39 | ================== 40 | 41 | **features** 42 | * [[`487ae22`](http://github.com/node-modules/parameter/commit/487ae229df85b235761bfbb402826baaf61f29bb)] - feat: addRule support set default convertType (#51) (fengmk2 <>) 43 | 44 | 3.0.0 / 2018-08-30 45 | ================== 46 | 47 | **others** 48 | * [[`f1dda26`](http://github.com/node-modules/parameter/commit/f1dda26ac3697c63a7e344228b7aed7bb55b3931)] - refactor: make rules more practical (#50) (Yiyu He <>) 49 | 50 | 2.4.0 / 2018-08-03 51 | ================== 52 | 53 | **features** 54 | * [[`e12fac4`](http://github.com/node-modules/parameter/commit/e12fac4d1681e51b02193defd5acb9e4864138dd)] - feat: add global addRule method (#46) (fengmk2 <>) 55 | 56 | 2.3.0 / 2018-06-19 57 | ================== 58 | 59 | **features** 60 | * [[`a5c1721`](http://github.com/node-modules/parameter/commit/a5c172179071aaaafb6fac604bc6bf4ebb0c8d98)] - feat: Add option.validateRoot to enable validate the root object (#41) (paranoidjk <>) 61 | 62 | 2.2.3 / 2018-05-16 63 | ================== 64 | 65 | 2.2.2 / 2018-05-04 66 | ================== 67 | 68 | **fixes** 69 | * [[`8317071`](http://github.com/node-modules/parameter/commit/83170710bbc88b1d7267cba6b9eae764a1e5fd36)] - fix: email regx to case insensitive (#36) (mansonchor.github.com <>), 70 | 71 | 2.2.1 / 2017-11-23 72 | ================== 73 | 74 | **fixes** 75 | * [[`3693861`](http://github.com/node-modules/parameter/commit/3693861897e2d207edf791f56db51820f76588b2)] - fix: check number exclude NaN (#30) (Gao Peng <>) 76 | 77 | **others** 78 | * [[`67d8cf6`](http://github.com/node-modules/parameter/commit/67d8cf6269abe4fdf3480b6c25cd08b67868ed11)] - chore(docs): add semantic-release process description (fengmk2 <>) 79 | 80 | 2.2.0 / 2017-06-13 81 | ================== 82 | 83 | * feat(array): support max and min in array check (#22) 84 | * chore: use semantic-release workflow (#23) 85 | 86 | 2.1.0 / 2017-06-02 87 | ================== 88 | 89 | * feat: allowEmpty can be use with max and min 90 | * feat: allowEmpty can be use with format 91 | 92 | 2.0.0 / 2015-09-25 93 | ================== 94 | 95 | * test: use codecov.io 96 | * Refactor with Class, use ES6 class style. 97 | 98 | 1.2.0 / 2015-09-21 99 | ================== 100 | 101 | * feat: remove field name from error message. #12 102 | 103 | 1.1.1 / 2015-09-15 104 | ================== 105 | 106 | * fix: it should not translate the `field` key. 107 | 108 | 1.1.0 / 2015-09-15 109 | ================== 110 | 111 | * feat: I18n translate possible by override the `validate.translate` method. 112 | 113 | 1.0.2 / 2015-01-12 114 | ================== 115 | 116 | * feat: add url 117 | * fix: remove david badge 118 | 119 | 1.0.1 / 2015-01-09 120 | ================== 121 | 122 | * fix(readme): fix exmaple arguments 123 | 124 | 1.0.0 / 2015-01-09 125 | ================== 126 | 127 | * fix: ensure error message correct when add custom rules 128 | * feat: add password 129 | * feat: add email type 130 | * feat(addRule): support add custom rules 131 | * doc: fix readme and add example 132 | * fix(array): array itemType 133 | * feat(string): detault allowEmpty to false 134 | * fix:(test) fix test cases 135 | * refactor: rewrite 136 | 137 | 0.0.7 / 2013-08-09 138 | ================== 139 | 140 | * support Enum check fixed #1 141 | 142 | 0.0.6 / 2013-08-07 143 | ================== 144 | 145 | * Custom error message 146 | * remove 0.11 from travis 147 | 148 | 0.0.5 / 2013-08-06 149 | ================== 150 | 151 | * support RegExp rule 152 | 153 | 0.0.4 / 2013-07-01 154 | ================== 155 | 156 | * support isArray and isObject 157 | 158 | 0.0.3 / 2013-06-26 159 | ================== 160 | 161 | * fixed required=false bug 162 | * run again to vaild cache functions 163 | 164 | 0.0.2 / 2013-06-26 165 | ================== 166 | 167 | * improve performance 168 | 169 | 0.0.1 / 2013-06-26 170 | ================== 171 | 172 | * add test cases 173 | * first commit 174 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This software is licensed under the MIT License. 2 | 3 | Copyright(c) 2013 - present node-modules and other contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | parameter 2 | ======= 3 | 4 | [![NPM version][npm-image]][npm-url] 5 | [![Node.js CI](https://github.com/node-modules/parameter/actions/workflows/nodejs.yml/badge.svg)](https://github.com/node-modules/parameter/actions/workflows/nodejs.yml) 6 | [![Test coverage][codecov-image]][codecov-url] 7 | [![npm download][download-image]][download-url] 8 | 9 | [npm-image]: https://img.shields.io/npm/v/parameter.svg?style=flat-square 10 | [npm-url]: https://npmjs.org/package/parameter 11 | [codecov-image]: https://codecov.io/github/node-modules/parameter/coverage.svg?branch=master 12 | [codecov-url]: https://codecov.io/github/node-modules/parameter?branch=master 13 | [download-image]: https://img.shields.io/npm/dm/parameter.svg?style=flat-square 14 | [download-url]: https://npmjs.org/package/parameter 15 | 16 | A parameter verify tools. 17 | 18 | ## Install 19 | 20 | ```bash 21 | $ npm install parameter --save 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### API 27 | 28 | `Parameter` Class 29 | 30 | - `constructor([options])` - new Class `Parameter` instance 31 | - `options.translate` - translate function 32 | - `options.validateRoot` - config whether to validate the passed in value must be a object, default to `false`. 33 | - `options.convert` - convert primitive params to specific type, default to `false`. 34 | - `options.widelyUndefined` - convert empty string(`''`), NaN, Null to undefined, this option can make `rule.required` more powerful, default to `false`.__This may change the original input params__. 35 | - `validate(rule, value)` - validate the `value` conforms to `rule`. return an array of errors if break rule. 36 | - `addRule(type, check)` - add custom rules. 37 | - `type` - rule type, required and must be string type. 38 | - `check` - check handler. can be a `function` or a `RegExp`. 39 | 40 | __Note: when `options.convert` enabled, all built-in rules check for primitive input param and convert it to rule's default `convertType`(which defined below), you can also enable this feature for specific rule by `convertType` options in each rule definition.__ 41 | 42 | ### Example 43 | 44 | ```js 45 | var Parameter = require('parameter'); 46 | 47 | var parameter = new Parameter({ 48 | translate: function() { 49 | var args = Array.prototype.slice.call(arguments); 50 | // Assume there have I18n.t method for convert language. 51 | return I18n.t.apply(I18n, args); 52 | }, 53 | validateRoot: true, // restrict the being validate value must be a object 54 | }); 55 | 56 | var data = { 57 | name: 'foo', 58 | age: 24, 59 | gender: 'male' 60 | }; 61 | 62 | var rule = { 63 | name: 'string', 64 | age: 'int', 65 | gender: ['male', 'female', 'unknown'] 66 | }; 67 | 68 | var errors = parameter.validate(rule, data); 69 | ``` 70 | 71 | #### [complex example](example.js) 72 | 73 | ### Rule 74 | 75 | #### common rule 76 | 77 | - `required` - if `required` is set to false, this property can be null or undefined. default to `true`. 78 | - `type` - The type of property, every type has it's own rule for the validate. 79 | - `convertType` - Make parameter convert the input param to the specific type, support `int`, `number`, `string` and `boolean`, also support a function to customize your own convert method. 80 | - `default` - The default value of property, once the property is allowed non-required and missed, parameter will use this as the default value. __This may change the original input params__. 81 | - `widelyUndefined` - override `options.widelyUndefined` 82 | 83 | __Note: you can combile require and type end with a notation `?` like: `int?` or `string?` to specific both type and non-required.__ 84 | 85 | #### int 86 | 87 | If type is `int`, there has tow addition rules: 88 | 89 | - `max` - The maximum of the value, `value` must <= `max`. 90 | - `min` - The minimum of the value, `value` must >= `min`. 91 | 92 | Default `convertType` is `int`. 93 | 94 | __Note: default `convertType` will only work when `options.convert` set to true in parameter's constructor.__ 95 | 96 | #### integer 97 | 98 | Alias to `int`. 99 | 100 | #### number 101 | 102 | If type is `number`, there has tow addition rules: 103 | 104 | - `max` - The maximum of the value, `value` must <= `max`. 105 | - `min` - The minimum of the value, `value` must >= `min`. 106 | 107 | Default `convertType` is `number`. 108 | 109 | #### date 110 | 111 | The `date` type want to match `YYYY-MM-DD` type date string. 112 | 113 | Default `convertType` is `string`. 114 | 115 | #### dateTime 116 | 117 | The `dateTime` type want to match `YYYY-MM-DD HH:mm:ss` type date string. 118 | 119 | Default `convertType` is `string`. 120 | 121 | #### datetime 122 | 123 | Alias to `dateTime`. 124 | 125 | #### id 126 | 127 | The `id` type want to match `/^\d+$/` type date string. 128 | 129 | Default `convertType` is `string`. 130 | 131 | #### boolean 132 | 133 | Match `boolean` type value. 134 | 135 | Default `convertType` is `boolean`. 136 | 137 | #### bool 138 | 139 | Alias to `boolean` 140 | 141 | #### string 142 | 143 | If type is `string`, there has four addition rules: 144 | 145 | - `allowEmpty`(alias to `empty`) - allow empty string, default to false. If `rule.required` set to false, `allowEmpty` will be set to `true` by default. 146 | - `format` - A `RegExp` to check string's format. 147 | - `max` - The maximum length of the string. 148 | - `min` - The minimum length of the string. 149 | - `trim` - Trim the string before check, default is `false`. 150 | 151 | Default `convertType` is `string`. 152 | 153 | #### email 154 | 155 | The `email` type want to match [RFC 5322](http://tools.ietf.org/html/rfc5322#section-3.4) email address. 156 | 157 | - `allowEmpty` - allow empty string, default is false. 158 | 159 | Default `convertType` is `string`. 160 | 161 | #### password 162 | 163 | The `password` type want to match `/^$/` type string. 164 | 165 | - `compare` - Compare field to check equal, default null, not check. 166 | - `max` - The maximum length of the password. 167 | - `min` - The minimum length of the password, default is 6. 168 | 169 | Default `convertType` is `string`. 170 | 171 | #### url 172 | 173 | The `url` type want to match [web url](https://gist.github.com/dperini/729294). 174 | 175 | Default `convertType` is `string`. 176 | 177 | #### enum 178 | 179 | If type is `enum`, it requires an addition rule: 180 | 181 | - `values` - An array of data, `value` must be one on them. ___this rule is required.___ 182 | 183 | #### object 184 | 185 | If type is `object`, there has one addition rule: 186 | 187 | - `rule` - An object that validate the properties ot the object. 188 | 189 | #### array 190 | 191 | If type is `array`, there has four addition rule: 192 | 193 | - `itemType` - The type of every item in this array. 194 | - `rule` - An object that validate the items of the array. Only work with `itemType`. 195 | ```js 196 | /** 197 | * check array 198 | * { 199 | * type: 'array', 200 | * itemType: 'string' 201 | * rule: {type: 'string', allowEmpty: true} 202 | * } 203 | * 204 | * { 205 | * type: 'array'. 206 | * itemType: 'object', 207 | * rule: { 208 | * name: 'string' 209 | * } 210 | * } 211 | */ 212 | ``` 213 | - `max` - The maximum length of the array. 214 | - `min` - The minimum lenght of the array. 215 | 216 | #### abbr 217 | 218 | - `'int'` => `{type: 'int', required: true}` 219 | - `'int?'` => `{type: 'int', required: false }` 220 | - `'integer'` => `{type: 'integer', required: true}` 221 | - `'number'` => `{type: 'number', required: true}` 222 | - `'date'` => `{type: 'date', required: true}` 223 | - `'dateTime'` => `{type: 'dateTime', required: true}` 224 | - `'id'` => `{type: 'id', required: true}` 225 | - `'boolean'` => `{type: 'boolean', required: true}` 226 | - `'bool'` => `{type: 'bool', required: true}` 227 | - `'string'` => `{type: 'string', required: true, allowEmpty: false}` 228 | - `'string?'` => `{type: 'string', required: false, allowEmpty: true}` 229 | - `'email'` => `{type: 'email', required: true, allowEmpty: false, format: EMAIL_RE}` 230 | - `'password'` => `{type: 'password', required: true, allowEmpty: false, format: PASSWORD_RE, min: 6}` 231 | - `'object'` => `{type: 'object', required: true}` 232 | - `'array'` => `{type: 'array', required: true}` 233 | - `[1, 2]` => `{type: 'enum', values: [1, 2]}` 234 | - `/\d+/` => `{type: 'string', required: true, allowEmpty: false, format: /\d+/}` 235 | 236 | ### `errors` examples 237 | 238 | #### `code: missing_field` 239 | 240 | ```js 241 | { 242 | code: 'missing_field', 243 | field: 'name', 244 | message: 'required' 245 | } 246 | ``` 247 | 248 | #### `code: invalid` 249 | 250 | ```js 251 | { 252 | code: 'invalid', 253 | field: 'age', 254 | message: 'should be an integer' 255 | } 256 | ``` 257 | 258 | ## License 259 | 260 | [MIT](LICENSE.txt) 261 | 262 | 263 | 264 | ## Contributors 265 | 266 | |[
fengmk2](https://github.com/fengmk2)
|[
dead-horse](https://github.com/dead-horse)
|[
huacnlee](https://github.com/huacnlee)
|[
hotoo](https://github.com/hotoo)
|[
sang4lv](https://github.com/sang4lv)
|[
ghostoy](https://github.com/ghostoy)
| 267 | | :---: | :---: | :---: | :---: | :---: | :---: | 268 | [
beliefgp](https://github.com/beliefgp)
|[
taylorharwin](https://github.com/taylorharwin)
|[
tomowang](https://github.com/tomowang)
|[
hdumok](https://github.com/hdumok)
|[
paranoidjk](https://github.com/paranoidjk)
|[
zcxsythenew](https://github.com/zcxsythenew)
269 | 270 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Apr 05 2022 10:44:22 GMT+0800`. 271 | 272 | 273 | -------------------------------------------------------------------------------- /benchmark.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Benchmark = require('benchmark'); 4 | var benchmarks = require('beautify-benchmark'); 5 | var Parameter = require('./'); 6 | 7 | var suite = new Benchmark.Suite(); 8 | 9 | var data = { 10 | id: '043624', 11 | nick: '苏千', 12 | date: '2013-06-25', 13 | time: '2013-06-25 01:12:22', 14 | age: 29, 15 | sid1: 123, 16 | sid2: '123', 17 | sid3: 'foo', 18 | unit: 'y', 19 | email: 'fengmk2@gmail.com', 20 | password: '!@#123', 21 | 're-password': '!@#123', 22 | url: 'https://github.com/node-modules/parameter/compare/url?expand=1' 23 | }; 24 | 25 | var rules = [ 26 | { id: 'id' }, 27 | { id: { type: 'id' } }, 28 | 29 | { date: 'date' }, 30 | { date: { type: 'date' } }, 31 | 32 | { time: 'datetime' }, 33 | { time: { type: 'datetime' } }, 34 | 35 | { age: 'number' }, 36 | { age: { type: 'number' } }, 37 | 38 | { nick: 'string'}, 39 | { nick: { type: 'string' } }, 40 | { not_exists: 'string', required: false }, 41 | { sid1: /^\d+$/}, 42 | { sid2: { type: 'string', format: /^\d+$/ } }, 43 | // { sid3: { type: 'string', format: /^\d+$/, message: 'should be digital' } }, 44 | 45 | { unit: ['y', 'm', 'd', 'w'] }, 46 | { unit: { type: 'enum', values: ['y', 'm', 'd', 'w'] } }, 47 | { unit: ['yy', 'mm', 'dd', 'ww'] }, 48 | 49 | { email: 'email' }, 50 | 51 | { password: { type: 'password', compare: 're-password' } }, 52 | 53 | { url: 'url'}, 54 | ]; 55 | 56 | function json(obj) { 57 | return JSON.stringify(obj); 58 | } 59 | 60 | var globalParameter = new Parameter(); 61 | 62 | rules.forEach(function (rule) { 63 | suite.add('globalParameter: verify ' + json(rule), function () { 64 | globalParameter.validate(rule, data); 65 | }); 66 | 67 | suite.add('localParameter: verify ' + json(rule), function () { 68 | var p = new Parameter(); 69 | p.validate(rule, data); 70 | }); 71 | }); 72 | 73 | suite.on('cycle', function(event) { 74 | benchmarks.add(event.target); 75 | }) 76 | .on('start', function(event) { 77 | console.log('\n node version: %s, date: %s\n Starting...', process.version, Date()); 78 | }) 79 | .on('complete', function done() { 80 | benchmarks.log(); 81 | }) 82 | .run({ 'async': false }); 83 | 84 | // node version: v2.5.0, date: Fri Sep 25 2015 23:29:32 GMT+0800 (CST) 85 | // Starting... 86 | // 38 tests completed. 87 | // 88 | // globalParameter: verify {"id":"id"} x 1,755,826 ops/sec ±1.14% (92 runs sampled) 89 | // localParameter: verify {"id":"id"} x 1,700,584 ops/sec ±1.11% (91 runs sampled) 90 | // globalParameter: verify {"id":{"type":"id"}} x 1,664,283 ops/sec ±1.26% (90 runs sampled) 91 | // localParameter: verify {"id":{"type":"id"}} x 1,654,914 ops/sec ±1.35% (91 runs sampled) 92 | // globalParameter: verify {"date":"date"} x 1,547,474 ops/sec ±1.22% (92 runs sampled) 93 | // localParameter: verify {"date":"date"} x 1,543,998 ops/sec ±1.00% (92 runs sampled) 94 | // globalParameter: verify {"date":{"type":"date"}} x 1,528,876 ops/sec ±1.17% (91 runs sampled) 95 | // localParameter: verify {"date":{"type":"date"}} x 1,531,417 ops/sec ±1.16% (93 runs sampled) 96 | // globalParameter: verify {"time":"datetime"} x 1,436,431 ops/sec ±1.16% (91 runs sampled) 97 | // localParameter: verify {"time":"datetime"} x 1,435,370 ops/sec ±1.14% (91 runs sampled) 98 | // globalParameter: verify {"time":{"type":"datetime"}} x 1,412,463 ops/sec ±1.30% (92 runs sampled) 99 | // localParameter: verify {"time":{"type":"datetime"}} x 1,399,845 ops/sec ±1.74% (91 runs sampled) 100 | // globalParameter: verify {"age":"number"} x 2,708,155 ops/sec ±1.27% (91 runs sampled) 101 | // localParameter: verify {"age":"number"} x 2,587,876 ops/sec ±1.33% (92 runs sampled) 102 | // globalParameter: verify {"age":{"type":"number"}} x 2,607,889 ops/sec ±1.10% (93 runs sampled) 103 | // localParameter: verify {"age":{"type":"number"}} x 2,538,770 ops/sec ±1.31% (91 runs sampled) 104 | // globalParameter: verify {"nick":"string"} x 2,307,909 ops/sec ±1.13% (94 runs sampled) 105 | // localParameter: verify {"nick":"string"} x 2,206,572 ops/sec ±1.27% (92 runs sampled) 106 | // globalParameter: verify {"nick":{"type":"string"}} x 2,296,899 ops/sec ±0.98% (94 runs sampled) 107 | // localParameter: verify {"nick":{"type":"string"}} x 2,170,179 ops/sec ±1.52% (92 runs sampled) 108 | // globalParameter: verify {"not_exists":"string","required":false} x 160,931 ops/sec ±1.15% (91 runs sampled) 109 | // localParameter: verify {"not_exists":"string","required":false} x 158,010 ops/sec ±1.63% (93 runs sampled) 110 | // globalParameter: verify {"sid1":{}} x 266,353 ops/sec ±3.10% (87 runs sampled) 111 | // localParameter: verify {"sid1":{}} x 278,138 ops/sec ±1.61% (90 runs sampled) 112 | // globalParameter: verify {"sid2":{"type":"string","format":{}}} x 1,711,115 ops/sec ±1.15% (90 runs sampled) 113 | // localParameter: verify {"sid2":{"type":"string","format":{}}} x 1,712,745 ops/sec ±1.07% (95 runs sampled) 114 | // globalParameter: verify {"unit":["y","m","d","w"]} x 2,763,750 ops/sec ±1.26% (90 runs sampled) 115 | // localParameter: verify {"unit":["y","m","d","w"]} x 2,703,971 ops/sec ±1.17% (93 runs sampled) 116 | // globalParameter: verify {"unit":{"type":"enum","values":["y","m","d","w"]}} x 3,930,004 ops/sec ±1.43% (91 runs sampled) 117 | // localParameter: verify {"unit":{"type":"enum","values":["y","m","d","w"]}} x 3,839,268 ops/sec ±1.40% (92 runs sampled) 118 | // globalParameter: verify {"unit":["yy","mm","dd","ww"]} x 186,797 ops/sec ±1.75% (92 runs sampled) 119 | // localParameter: verify {"unit":["yy","mm","dd","ww"]} x 188,961 ops/sec ±1.30% (94 runs sampled) 120 | // globalParameter: verify {"email":"email"} x 370,479 ops/sec ±1.92% (89 runs sampled) 121 | // localParameter: verify {"email":"email"} x 376,473 ops/sec ±1.19% (90 runs sampled) 122 | // globalParameter: verify {"password":{"type":"password","compare":"re-password"}} x 1,252,958 ops/sec ±1.12% (91 runs sampled) 123 | // localParameter: verify {"password":{"type":"password","compare":"re-password"}} x 1,232,913 ops/sec ±1.47% (90 runs sampled) 124 | // globalParameter: verify {"url":"url"} x 269,519 ops/sec ±1.15% (93 runs sampled) 125 | // localParameter: verify {"url":"url"} x 262,017 ops/sec ±1.23% (91 runs sampled) 126 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var Parameter = require('./'); 2 | 3 | var rule = { 4 | name: 'string', 5 | age: {type: 'int', max: 200, message: { max: '年龄不能大于 200 岁' }}, 6 | gender: ['male', 'female'], 7 | working: 'boolean', 8 | salary: {type: 'number', min: 0}, 9 | birthday: 'date', 10 | now: 'dateTime', 11 | id: 'id', 12 | childrens: { 13 | type: 'array', 14 | itemType: 'object', 15 | required: false, 16 | rule: { 17 | name: 'string', 18 | age: { type: 'int', message: { required: '子女年龄不能为空' }}, 19 | gender: ['male', 'female'], 20 | birthday: {type: 'date', required: false} 21 | } 22 | }, 23 | mate: { 24 | type: 'object', 25 | required: false, 26 | rule: { 27 | name: 'string', 28 | age: 'int', 29 | gender: ['male', 'female'], 30 | birthday: {type: 'date', required: false} 31 | } 32 | } 33 | }; 34 | 35 | var valid = { 36 | name: 'foo', 37 | gender: 'male', 38 | age: 30, 39 | working: true, 40 | salary: 10000.1, 41 | birthday: '1990-01-01', 42 | now: '2015-01-07 00:00:00', 43 | id: '052111', 44 | childrens: [{ 45 | name: 'bar1', 46 | age: 1, 47 | gender: 'female', 48 | birthday: '2014-01-01' 49 | }, { 50 | name: 'bar2', 51 | age: 2, 52 | gender: 'male', 53 | birthday: '2013-01-01' 54 | }], 55 | mate: { 56 | name: 'hee', 57 | age: 29, 58 | gender: 'female' 59 | } 60 | }; 61 | 62 | var invalid = { 63 | name: 1, 64 | gender: 'male1', 65 | age: 300, 66 | working: 'true', 67 | salary: '10000.1', 68 | birthday: '1990-01-011', 69 | id: '052111x', 70 | childrens: [{ 71 | name: 'bar1', 72 | gender: 'female', 73 | birthday: '2014-01-01' 74 | }, { 75 | name: 'bar2', 76 | age: 2, 77 | birthday: '2013-01-01' 78 | }], 79 | mate: { 80 | age: 29, 81 | gender: 'female' 82 | } 83 | }; 84 | 85 | var p = new Parameter(); 86 | 87 | console.log('valid object result: ', p.validate(rule, valid)); 88 | console.log('invalid object result: ', p.validate(rule, invalid)); 89 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | 5 | /** 6 | * Regexps 7 | */ 8 | 9 | var DATE_TYPE_RE = /^\d{4}\-\d{2}\-\d{2}$/; 10 | var DATETIME_TYPE_RE = /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/; 11 | var ID_RE = /^\d+$/; 12 | 13 | // http://www.regular-expressions.info/email.html 14 | var EMAIL_RE = /^[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+(?:\.[a-z0-9\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+)*@(?:[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9\-]*[a-z0-9])?$/i; 15 | 16 | var PASSWORD_RE = /^[\w\`\~\!\@\#\$\%\^\&\*\(\)\-\_\=\+\[\]\{\}\|\;\:\'\"\,\<\.\>\/\?]+$/; 17 | 18 | // https://gist.github.com/dperini/729294 19 | var URL_RE = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/\S*)?$/i; 20 | 21 | /** 22 | * Parameter class 23 | * @class Parameter 24 | */ 25 | class Parameter { 26 | constructor(opts) { 27 | opts = opts || {}; 28 | 29 | if (typeof opts.translate === 'function') { 30 | this.translate = opts.translate; 31 | } 32 | 33 | if (opts.validateRoot) this.validateRoot = true; 34 | if (opts.convert) this.convert = true; 35 | if (opts.widelyUndefined) this.widelyUndefined = true; 36 | } 37 | 38 | t() { 39 | var args = Array.prototype.slice.call(arguments); 40 | if (typeof this.translate === 'function') { 41 | return this.translate.apply(this, args); 42 | } else { 43 | return util.format.apply(util, args); 44 | } 45 | } 46 | 47 | message(rule, key, defaultMessage) { 48 | return rule.message && rule.message[key] || defaultMessage; 49 | } 50 | 51 | /** 52 | * validate 53 | * 54 | * @param {Object} rules 55 | * @return {Object} obj 56 | * @api public 57 | */ 58 | validate(rules, obj) { 59 | if (typeof rules !== 'object') { 60 | throw new TypeError('need object type rule'); 61 | } 62 | 63 | if (this.validateRoot && (typeof obj !== 'object' || !obj)) { 64 | return [{ 65 | message: this.t('the validated value should be a object'), 66 | code: this.t('invalid'), 67 | field: undefined, 68 | }]; 69 | } 70 | 71 | var self = this; 72 | 73 | var errors = []; 74 | 75 | for (var key in rules) { 76 | var rule = formatRule(rules[key]); 77 | var value = obj[key]; 78 | 79 | if (typeof value === 'string' && rule.trim === true) { 80 | value = obj[key] = value.trim(); 81 | } 82 | 83 | // treat null / '' / NaN as undefined 84 | let widelyUndefined = this.widelyUndefined; 85 | if ('widelyUndefined' in rule) widelyUndefined = rule.widelyUndefined; 86 | if (widelyUndefined && 87 | (value === '' || value === null || Number.isNaN(value))) { 88 | value = obj[key] = undefined; 89 | } 90 | 91 | var has = value !== null && value !== undefined; 92 | 93 | if (!has) { 94 | if (rule.required !== false) { 95 | errors.push({ 96 | message: this.message(rule, 'required', this.t('required')), 97 | field: key, 98 | code: this.t('missing_field') 99 | }); 100 | } 101 | // support default value 102 | if ('default' in rule) { 103 | obj[key] = rule.default; 104 | } 105 | continue; 106 | } 107 | 108 | var checker = TYPE_MAP[rule.type]; 109 | if (!checker) { 110 | throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + 111 | ', but the following type was passed: ' + rule.type); 112 | } 113 | 114 | convert(rule, obj, key, this.convert); 115 | var msg = checker.call(self, rule, obj[key], obj); 116 | if (typeof msg === 'string') { 117 | errors.push({ 118 | message: msg, 119 | code: this.t('invalid'), 120 | field: key 121 | }); 122 | } 123 | 124 | if (Array.isArray(msg)) { 125 | msg.forEach(function (e) { 126 | var dot = rule.type === 'object' ? '.' : ''; 127 | e.field = key + dot + e.field; 128 | errors.push(e); 129 | }); 130 | } 131 | } 132 | 133 | if (errors.length) { 134 | return errors; 135 | } 136 | } 137 | }; 138 | 139 | /** 140 | * Module exports 141 | * @type {Function} 142 | */ 143 | module.exports = Parameter; 144 | 145 | /** 146 | * add custom rule to global rules list. 147 | * 148 | * @param {String} type 149 | * @param {Function | RegExp} check 150 | * @param {Boolean} [override] - override exists rule or not, default is true 151 | * @param {String|Function} [convertType] 152 | * @api public 153 | */ 154 | Parameter.prototype.addRule = Parameter.addRule = function addRule(type, check, override, convertType) { 155 | if (!type) { 156 | throw new TypeError('`type` required'); 157 | } 158 | 159 | // addRule(type, check, convertType) 160 | if (typeof override === 'string' || typeof override === 'function') { 161 | convertType = override; 162 | override = true; 163 | } 164 | 165 | if (typeof override !== 'boolean') { 166 | override = true; 167 | } 168 | 169 | if (!override && TYPE_MAP[type]) { 170 | throw new TypeError('rule `' + type + '` exists'); 171 | } 172 | 173 | if (convertType) { 174 | if (typeof convertType !== 'string' && typeof convertType !== 'function') { 175 | throw new TypeError('convertType should be string or function'); 176 | } 177 | Parameter.CONVERT_MAP[type] = convertType; 178 | } 179 | 180 | 181 | if (typeof check === 'function') { 182 | TYPE_MAP[type] = check; 183 | return; 184 | } 185 | 186 | if (check instanceof RegExp) { 187 | TYPE_MAP[type] = function (rule, value) { 188 | return checkString.call(this, {format: check}, value); 189 | }; 190 | return; 191 | } 192 | 193 | throw new TypeError('check must be function or regexp'); 194 | }; 195 | 196 | /** 197 | * Simple type map 198 | * @type {Object} 199 | */ 200 | var TYPE_MAP = Parameter.TYPE_MAP = { 201 | number: checkNumber, 202 | int: checkInt, 203 | integer: checkInt, 204 | string: checkString, 205 | id: checkId, 206 | date: checkDate, 207 | dateTime: checkDateTime, 208 | datetime: checkDateTime, 209 | boolean: checkBoolean, 210 | bool: checkBoolean, 211 | array: checkArray, 212 | object: checkObject, 213 | enum: checkEnum, 214 | email: checkEmail, 215 | password: checkPassword, 216 | url: checkUrl, 217 | }; 218 | 219 | var CONVERT_MAP = Parameter.CONVERT_MAP = { 220 | number: 'number', 221 | int: 'int', 222 | integer: 'int', 223 | string: 'string', 224 | id: 'string', 225 | date: 'string', 226 | dateTime: 'string', 227 | datetime: 'string', 228 | boolean: 'bool', 229 | bool: 'bool', 230 | email: 'string', 231 | password: 'string', 232 | url: 'string', 233 | }; 234 | 235 | /** 236 | * format a rule 237 | * - resolve abbr 238 | * - resolve `?` 239 | * - resolve default convertType 240 | * 241 | * @param {Mixed} rule 242 | * @return {Object} 243 | * @api private 244 | */ 245 | 246 | function formatRule(rule) { 247 | rule = rule || {}; 248 | if (typeof rule === 'string') { 249 | rule = { type: rule }; 250 | } else if (Array.isArray(rule)) { 251 | rule = { type: 'enum', values: rule }; 252 | } else if (rule instanceof RegExp) { 253 | rule = { type: 'string', format: rule }; 254 | } 255 | 256 | if (rule.type && rule.type[rule.type.length - 1] === '?') { 257 | rule.type = rule.type.slice(0, -1); 258 | rule.required = false; 259 | } 260 | 261 | rule.message = rule.message || {} 262 | if (typeof rule.message === 'string') { 263 | rule.message = { 264 | [rule.type]: rule.message, 265 | } 266 | } 267 | 268 | return rule; 269 | } 270 | 271 | /** 272 | * convert param to specific type 273 | * @param {Object} rule 274 | * @param {Object} obj 275 | * @param {String} key 276 | * @param {Boolean} defaultConvert 277 | */ 278 | function convert(rule, obj, key, defaultConvert) { 279 | var convertType; 280 | if (defaultConvert) convertType = CONVERT_MAP[rule.type]; 281 | if (rule.convertType) convertType = rule.convertType; 282 | if (!convertType) return; 283 | 284 | const value = obj[key]; 285 | // convert type only work for primitive data 286 | if (typeof value === 'object') return; 287 | 288 | // convertType support function 289 | if (typeof convertType === 'function') { 290 | obj[key] = convertType(value, obj); 291 | return; 292 | } 293 | 294 | switch (convertType) { 295 | case 'int': 296 | obj[key] = parseInt(value, 10); 297 | break; 298 | case 'string': 299 | obj[key] = String(value); 300 | break; 301 | case 'number': 302 | obj[key] = Number(obj[key]); 303 | break; 304 | case 'bool': 305 | case 'boolean': 306 | obj[key] = !!value; 307 | break; 308 | default: 309 | // support convertType function added by addRule 310 | if (typeof CONVERT_MAP[convertType] === 'function' ) { 311 | obj[key] = CONVERT_MAP[rule.type](obj[key]); 312 | } 313 | } 314 | } 315 | 316 | /** 317 | * check interger 318 | * { 319 | * max: 100, 320 | * min: 0 321 | * } 322 | * 323 | * @param {Object} rule 324 | * @param {Mixed} value 325 | * @return {Boolean} 326 | * @api private 327 | */ 328 | 329 | function checkInt(rule, value) { 330 | if (typeof value !== 'number' || value % 1 !== 0) { 331 | return this.message(rule, 'int', this.t('should be an integer')); 332 | } 333 | 334 | if (rule.hasOwnProperty('max') && value > rule.max) { 335 | return this.message(rule, 'max', this.t('should smaller than %s', rule.max)); 336 | } 337 | 338 | if (rule.hasOwnProperty('min') && value < rule.min) { 339 | return this.message(rule, 'min', this.t('should bigger than %s', rule.min)); 340 | } 341 | } 342 | 343 | /** 344 | * check number 345 | * { 346 | * max: 100, 347 | * min: 0 348 | * } 349 | * 350 | * @param {Object} rule 351 | * @param {Mixed} value 352 | * @return {Boolean} 353 | * @api private 354 | */ 355 | 356 | function checkNumber(rule, value) { 357 | if (typeof value !== 'number' || isNaN(value)) { 358 | return this.message(rule, 'number', this.t('should be a number')); 359 | } 360 | if (rule.hasOwnProperty('max') && value > rule.max) { 361 | return this.message(rule, 'max', this.t('should smaller than %s', rule.max)); 362 | } 363 | if (rule.hasOwnProperty('min') && value < rule.min) { 364 | return this.message(rule, 'min', this.t('should bigger than %s', rule.min)); 365 | } 366 | } 367 | 368 | /** 369 | * check string 370 | * { 371 | * allowEmpty: true, // resolve default convertType to false, alias to empty) 372 | * format: /^\d+$/, 373 | * max: 100, 374 | * min: 0, 375 | * trim: false, 376 | * } 377 | * 378 | * @param {Object} rule 379 | * @param {Mixed} value 380 | * @return {Boolean} 381 | * @api private 382 | */ 383 | 384 | function checkString(rule, value) { 385 | if (typeof value !== 'string') { 386 | return this.message(rule, rule.type || 'string', this.t('should be a string')); 387 | } 388 | 389 | // if required === false, set allowEmpty to true by default 390 | if (!rule.hasOwnProperty('allowEmpty') && rule.required === false) { 391 | rule.allowEmpty = true; 392 | } 393 | 394 | var allowEmpty = rule.hasOwnProperty('allowEmpty') 395 | ? rule.allowEmpty 396 | : rule.empty; 397 | 398 | if (!value) { 399 | if (allowEmpty) return; 400 | return this.message(rule, 'allowEmpty', '') || this.message(rule, 'empty', '') || this.t('should not be empty'); 401 | } 402 | 403 | if (rule.hasOwnProperty('max') && value.length > rule.max) { 404 | return this.message(rule, 'max', this.t('length should smaller than %s', rule.max)); 405 | } 406 | if (rule.hasOwnProperty('min') && value.length < rule.min) { 407 | return this.message(rule, 'min', this.t('length should bigger than %s', rule.min)); 408 | } 409 | 410 | if (rule.format && !rule.format.test(value)) { 411 | return this.message(rule, 'format', this.t('should match %s', rule.format)); 412 | } 413 | } 414 | 415 | /** 416 | * check id format 417 | * format: /^\d+/ 418 | * 419 | * @param {Object} rule 420 | * @param {Mixed} value 421 | * @return {Boolean} 422 | * @api private 423 | */ 424 | 425 | function checkId(rule, value) { 426 | const errorMessage = checkString.call(this, { format: ID_RE, allowEmpty: rule.allowEmpty }, value); 427 | if (errorMessage) { 428 | return this.message(rule, 'id', errorMessage); 429 | } 430 | } 431 | 432 | /** 433 | * check date format 434 | * format: YYYY-MM-DD 435 | * 436 | * @param {Object} rule 437 | * @param {Mixed} value 438 | * @return {Boolean} 439 | * @api private 440 | */ 441 | 442 | function checkDate(rule, value) { 443 | const errorMessage = checkString.call(this, { format: DATE_TYPE_RE, allowEmpty: rule.allowEmpty }, value); 444 | if (errorMessage) { 445 | return this.message(rule, 'date', errorMessage); 446 | } 447 | } 448 | 449 | /** 450 | * check date time format 451 | * format: YYYY-MM-DD HH:mm:ss 452 | * 453 | * @param {Object} rule 454 | * @param {Mixed} value 455 | * @return {Boolean} 456 | * @api private 457 | */ 458 | 459 | function checkDateTime(rule, value) { 460 | const errorMessage = checkString.call(this, { format: DATETIME_TYPE_RE, allowEmpty: rule.allowEmpty }, value); 461 | if (errorMessage) { 462 | return this.message(rule, 'dateTime', errorMessage); 463 | } 464 | } 465 | 466 | /** 467 | * check boolean 468 | * 469 | * @param {Object} rule 470 | * @param {Mixed} value 471 | * @return {Boolean} 472 | * @api private 473 | */ 474 | 475 | function checkBoolean(rule, value) { 476 | if (typeof value !== 'boolean') { 477 | return this.message(rule, 'bool', '') || this.message(rule, 'boolean', '') || this.t('should be a boolean'); 478 | } 479 | } 480 | 481 | /** 482 | * check enum 483 | * { 484 | * values: [0, 1, 2] 485 | * } 486 | * 487 | * @param {Object} rule 488 | * @param {Mixed} value 489 | * @return {Boolean} 490 | * @api private 491 | */ 492 | 493 | function checkEnum(rule, value) { 494 | if (!Array.isArray(rule.values)) { 495 | throw new TypeError('check enum need array type values'); 496 | } 497 | if (rule.values.indexOf(value) === -1) { 498 | return this.message(rule, 'enum', this.t('should be one of %s', rule.values.join(', '))); 499 | } 500 | } 501 | 502 | /** 503 | * check email 504 | * 505 | * @param {Object} rule 506 | * @param {Mixed} value 507 | * @return {Boolean} 508 | * @api private 509 | */ 510 | 511 | function checkEmail(rule, value) { 512 | const errorMessage = checkString.call(this, { 513 | format: EMAIL_RE, 514 | allowEmpty: rule.allowEmpty, 515 | }, value); 516 | if (errorMessage) { 517 | return this.message(rule, 'email', this.t('should be an email')); 518 | } 519 | } 520 | 521 | /** 522 | * check password 523 | * @param {Object} rule 524 | * @param {Object} value 525 | * @param {Object} obj 526 | * @return {Boolean} 527 | * 528 | * @api private 529 | */ 530 | 531 | function checkPassword(rule, value, obj) { 532 | if (!rule.min) { 533 | rule.min = 6; 534 | } 535 | rule.format = PASSWORD_RE; 536 | var error = checkString.call(this, rule, value); 537 | if (error) { 538 | return this.message(rule, 'password', error); 539 | } 540 | if (rule.compare && obj[rule.compare] !== value) { 541 | return this.message(rule, 'compare', this.t('should equal to %s', rule.compare)); 542 | } 543 | } 544 | 545 | /** 546 | * check url 547 | * 548 | * @param {Object} rule 549 | * @param {Object} value 550 | * @return {Boolean} 551 | * @api private 552 | */ 553 | 554 | function checkUrl(rule, value) { 555 | const error = checkString.call(this, { 556 | format: URL_RE, 557 | allowEmpty: rule.allowEmpty 558 | }, value); 559 | if (error) { 560 | return this.message(rule, 'url', this.t('should be a url')); 561 | } 562 | } 563 | 564 | /** 565 | * check object 566 | * { 567 | * rule: {} 568 | * } 569 | * 570 | * @param {Object} rule 571 | * @param {Mixed} value 572 | * @return {Boolean} 573 | * @api private 574 | */ 575 | 576 | function checkObject(rule, value) { 577 | if (typeof value !== 'object') { 578 | return this.message(rule, 'object', this.t('should be an object')); 579 | } 580 | 581 | if (rule.rule) { 582 | return this.validate(rule.rule, value); 583 | } 584 | } 585 | 586 | /** 587 | * check array 588 | * { 589 | * type: 'array', 590 | * itemType: 'string' 591 | * rule: {type: 'string', allowEmpty: true} 592 | * } 593 | * 594 | * { 595 | * type: 'array'. 596 | * itemType: 'object', 597 | * rule: { 598 | * name: 'string' 599 | * } 600 | * } 601 | * 602 | * @param {Object} rule 603 | * @param {Mixed} value 604 | * @return {Boolean} 605 | * @api private 606 | */ 607 | 608 | function checkArray(rule, value) { 609 | if (!Array.isArray(value)) { 610 | return this.message(rule, 'array', this.t('should be an array')); 611 | } 612 | 613 | if (rule.hasOwnProperty('max') && value.length > rule.max) { 614 | return this.message(rule, 'max', this.t('length should smaller than %s', rule.max)); 615 | } 616 | if (rule.hasOwnProperty('min') && value.length < rule.min) { 617 | return this.message(rule, 'min', this.t('length should bigger than %s', rule.min)); 618 | } 619 | 620 | if (!rule.itemType) { 621 | return; 622 | } 623 | 624 | var self = this; 625 | var checker = TYPE_MAP[rule.itemType]; 626 | if (!checker) { 627 | throw new TypeError('rule type must be one of ' + Object.keys(TYPE_MAP).join(', ') + 628 | ', but the following type was passed: ' + rule.itemType); 629 | } 630 | 631 | var errors = []; 632 | var subRule = rule.itemType === 'object' 633 | ? rule 634 | : rule.rule || formatRule(rule.itemType); 635 | 636 | value.forEach(function (v, i) { 637 | var index = '[' + i + ']'; 638 | var errs = checker.call(self, subRule, v); 639 | 640 | if (typeof errs === 'string') { 641 | errors.push({ 642 | field: index, 643 | message: errs, 644 | code: self.t('invalid') 645 | }); 646 | } 647 | if (Array.isArray(errs)) { 648 | errors = errors.concat(errs.map(function (e) { 649 | e.field = index + '.' + e.field; 650 | e.message = e.message; 651 | return e; 652 | })); 653 | } 654 | }); 655 | 656 | return errors; 657 | } 658 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "parameter", 3 | "version": "3.7.0", 4 | "description": "A parameter verify tools.", 5 | "main": "index.js", 6 | "module": "index.es5.js", 7 | "files": [ 8 | "index.js", 9 | "index.es5.js" 10 | ], 11 | "scripts": { 12 | "lint": "echo 'ignore'", 13 | "test": "egg-bin test", 14 | "cov": "egg-bin cov", 15 | "ci": "npm run cov", 16 | "build:es5": "babel index.js --presets babel-preset-es2015 -o index.es5.js", 17 | "prepublish": "npm run build:es5" 18 | }, 19 | "devDependencies": { 20 | "babel-cli": "^6.26.0", 21 | "babel-preset-es2015": "^6.24.1", 22 | "beautify-benchmark": "0", 23 | "benchmark": "*", 24 | "egg-bin": "^4.19.0", 25 | "should": "*" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/node-modules/parameter.git" 30 | }, 31 | "keywords": [ 32 | "parameter", 33 | "verify", 34 | "univ" 35 | ], 36 | "engines": { 37 | "node": ">= 10.0.0" 38 | }, 39 | "author": "fengmk2 ", 40 | "license": "MIT" 41 | } 42 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var should = require('should'); 5 | var util = require('util'); 6 | var Parameter = require('..'); 7 | var parameter = new Parameter(); 8 | 9 | var parameterWithRootValidate = new Parameter({ 10 | validateRoot: true, 11 | }); 12 | 13 | var parameterWithConvert = new Parameter({ 14 | convert: true, 15 | }); 16 | 17 | var parameterWithWidelyUndefined = new Parameter({ 18 | widelyUndefined: true, 19 | }); 20 | 21 | describe('parameter', () => { 22 | describe('required', () => { 23 | it('should required work fine', () => { 24 | var value = {int: 1}; 25 | var rule = {int: {type: 'int', required: true}}; 26 | parameter.validate(rule, {})[0].should.eql({ 27 | code: 'missing_field', 28 | field: 'int', 29 | message: 'required' 30 | }); 31 | }); 32 | 33 | it('should not required work fine', () => { 34 | var value = {int: 1}; 35 | var rule = {int: {type: 'int', required: false}}; 36 | should.not.exist(parameter.validate(rule, {})); 37 | }); 38 | 39 | it('should not required work fine with null', () => { 40 | var value = { int: 1 }; 41 | var rule = { int: { type: 'int', required: false } }; 42 | should.not.exist(parameter.validate(rule, { int: null })); 43 | }); 44 | 45 | it('should not required work fine with ?', () => { 46 | var rule = { int: 'int?' }; 47 | should.not.exist(parameter.validate(rule, {})); 48 | rule = { int: { type: 'int?' }}; 49 | should.not.exist(parameter.validate(rule, {})); 50 | }); 51 | 52 | it('should not required check ok', () => { 53 | var value = {int: 1.1}; 54 | var rule = {int: {type: 'int', required: false}}; 55 | parameter.validate(rule, value)[0].should.eql({ 56 | code: 'invalid', 57 | field: 'int', 58 | message: 'should be an integer' 59 | }); 60 | }); 61 | }); 62 | 63 | describe('validate', () => { 64 | it('should throw error when received a non object', () => { 65 | var value = null; 66 | var rule = {int: {type: 'int1', required: false}}; 67 | let err; 68 | try { 69 | parameter.validate(rule, undefined) 70 | } catch (e) { 71 | err = e; 72 | } 73 | 74 | assert(err.message === 'Cannot read property \'int\' of undefined' || 75 | err.message === 'Cannot read properties of undefined (reading \'int\')'); 76 | }); 77 | 78 | it('should invalid type throw', () => { 79 | (() => { 80 | var value = {int: 1.1}; 81 | var rule = {int: {type: 'int1', required: false}}; 82 | parameter.validate(rule, value); 83 | }).should.throw('rule type must be one of number, int, integer, string, id, date, dateTime, datetime, boolean, bool, array, object, enum, email, password, url, but the following type was passed: int1'); 84 | }); 85 | 86 | it('should throw without rule', () => { 87 | (() => { 88 | parameter.validate(); 89 | }).should.throw('need object type rule'); 90 | }); 91 | 92 | it('should throw when rule is null', () => { 93 | (() => { 94 | parameter.validate({d: null}, {d: 1}); 95 | }).should.throw('rule type must be one of number, int, integer, string, id, date, dateTime, datetime, boolean, bool, array, object, enum, email, password, url, but the following type was passed: undefined'); 96 | }); 97 | }); 98 | 99 | describe('int', () => { 100 | it('should check ok', () => { 101 | var value = { int: 1 }; 102 | var rule = { int: {type: 'int', max: 100, min: 1 }}; 103 | should.not.exist(parameter.validate(rule, value)); 104 | should.not.exist(parameter.validate({int: 'int'}, value)); 105 | }); 106 | 107 | it('should check number error', () => { 108 | var value = { int: '1' }; 109 | var rule = { int: {type: 'int', max: 100, min: 1 }}; 110 | parameter.validate(rule, value)[0].message.should.equal('should be an integer'); 111 | }); 112 | 113 | it('should check int error', () => { 114 | var value = { int: 1.1 }; 115 | var rule = { int: {type: 'int', max: 100, min: 1 }}; 116 | parameter.validate(rule, value)[0].message.should.equal('should be an integer'); 117 | }); 118 | 119 | it('should check max error', () => { 120 | var value = { int: 101 }; 121 | var rule = { int: {type: 'int', max: 100, min: 1 }}; 122 | parameter.validate(rule, value)[0].message.should.equal('should smaller than 100'); 123 | }); 124 | 125 | it('should check min error', () => { 126 | var value = { int: -1 }; 127 | var rule = { int: {type: 'int', max: 100, min: 0 }}; 128 | parameter.validate(rule, value)[0].message.should.equal('should bigger than 0'); 129 | }); 130 | 131 | it('should check error with custom message', () => { 132 | var value = { int: '1' }; 133 | var rule = { int: {type: 'int', message: 'custom message'}}; 134 | var rule2 = { int: {type: 'int', message: { int: 'custom message'}}}; 135 | parameter.validate(rule, value)[0].message.should.equal('custom message'); 136 | parameter.validate(rule2, value)[0].message.should.equal('custom message'); 137 | }); 138 | 139 | it('should check error with custom min message', () => { 140 | var value = { int: 1 }; 141 | var rule = { int: {type: 'int', min: 2, message: { min: '不能小于2' }}}; 142 | parameter.validate(rule, value)[0].message.should.equal('不能小于2'); 143 | }); 144 | 145 | it('should check error with custom max message', () => { 146 | var value = { int: 10 }; 147 | var rule = { int: {type: 'int', max: 5, message: { max: '不能大于5' }}}; 148 | parameter.validate(rule, value)[0].message.should.equal('不能大于5'); 149 | }); 150 | }); 151 | 152 | describe('number', () => { 153 | it('should check ok', () => { 154 | var value = { number: 1.1 }; 155 | var rule = { number: {type: 'number', max: 100, min: 1 }}; 156 | should.not.exist(parameter.validate(rule, value)); 157 | should.not.exist(parameter.validate({number: 'number'}, value)); 158 | }); 159 | 160 | it('should check number error', () => { 161 | var value = { number: '1' }; 162 | var rule = { number: {type: 'number', max: 100, min: 1 }}; 163 | parameter.validate(rule, value)[0].message.should.equal('should be a number'); 164 | }); 165 | 166 | it('should check NaN error', () => { 167 | var value = { number: NaN }; 168 | var rule = { number: 'number' }; 169 | parameter.validate(rule, value)[0].message.should.equal('should be a number'); 170 | }); 171 | 172 | it('should check max error', () => { 173 | var value = { number: 101 }; 174 | var rule = { number: {type: 'number', max: 100, min: 1 }}; 175 | parameter.validate(rule, value)[0].message.should.equal('should smaller than 100'); 176 | }); 177 | 178 | it('should check min error', () => { 179 | var value = { number: -1 }; 180 | var rule = { number: {type: 'number', max: 100, min: 0 }}; 181 | parameter.validate(rule, value)[0].message.should.equal('should bigger than 0'); 182 | }); 183 | 184 | it('should check error with custom message', () => { 185 | var value = { number: '-1' }; 186 | var rule = { number: {type: 'number', message: 'custom message'}}; 187 | var rule2 = { number: {type: 'number', message: { number: 'custom message'}}}; 188 | parameter.validate(rule, value)[0].message.should.equal('custom message'); 189 | parameter.validate(rule2, value)[0].message.should.equal('custom message'); 190 | }); 191 | 192 | it('should check error with custom min message', () => { 193 | var value = { number: 1 }; 194 | var rule = { number: {type: 'number', min: 2, message: { min: '不能小于2' }}}; 195 | parameter.validate(rule, value)[0].message.should.equal('不能小于2'); 196 | }); 197 | 198 | it('should check error with custom max message', () => { 199 | var value = { number: 10 }; 200 | var rule = { number: {type: 'number', max: 5, message: { max: '不能大于5' }}}; 201 | parameter.validate(rule, value)[0].message.should.equal('不能大于5'); 202 | }); 203 | }); 204 | 205 | describe('string', () => { 206 | it('should check ok', () => { 207 | var value = { string: 'hello' }; 208 | var rule = { string: {type: 'string', max: 100, min: 1, format: /^\D+$/ }}; 209 | should.not.exist(parameter.validate(rule, value)); 210 | should.not.exist(parameter.validate({string: 'string'}, value)); 211 | should.not.exist(parameter.validate({string: {type: 'string', allowEmpty: true}}, {string: ''})); 212 | }); 213 | 214 | it('should check empty error', () => { 215 | var value = { string: '' }; 216 | var rule = { string: 'string'}; 217 | parameter.validate(rule, value)[0].message.should.equal('should not be empty'); 218 | rule = { string: {type: 'string', empty: false }}; 219 | parameter.validate(rule, value)[0].message.should.equal('should not be empty'); 220 | rule = { string: {type: 'string', empty: false, message: { empty: '不能为空' } }}; 221 | parameter.validate(rule, value)[0].message.should.equal('不能为空'); 222 | rule = { string: {type: 'string', empty: false, message: { allowEmpty: '不能为空' } }}; 223 | parameter.validate(rule, value)[0].message.should.equal('不能为空'); 224 | }); 225 | 226 | it('should check with rule.trim', () => { 227 | should.not.exist(parameter.validate({string: {type: 'string', trim: true, allowEmpty: true}}, {string: ' '})); 228 | parameter.validate({string: {type: 'string', trim: true}}, {string: ' '})[0].message.should.equal('should not be empty'); 229 | }); 230 | 231 | it('should check max error', () => { 232 | var value = { string: 'hello' }; 233 | var rule = { string: {type: 'string', max: 4, min: 1 }}; 234 | parameter.validate(rule, value)[0].message.should.equal('length should smaller than 4'); 235 | rule = { string: {type: 'string', max: 4, min: 1, message: { max: '不能多于4个字符'} }}; 236 | parameter.validate(rule, value)[0].message.should.equal('不能多于4个字符'); 237 | }); 238 | 239 | it('should check min error', () => { 240 | var value = { string: 'hello' }; 241 | var rule = { string: {type: 'string', max: 100, min: 10 }}; 242 | parameter.validate(rule, value)[0].message.should.equal('length should bigger than 10'); 243 | rule = { string: {type: 'string', max: 100, min: 10, message: { min: '不能少于10个字符'} }}; 244 | parameter.validate(rule, value)[0].message.should.equal('不能少于10个字符'); 245 | }); 246 | 247 | it('should check format error', () => { 248 | var value = {string: 'hello'}; 249 | var rule = {string: { type: 'string', format: /\d+/ }}; 250 | parameter.validate(rule, value)[0].message.should.equal('should match /\\d+/'); 251 | rule = {string: { type: 'string', format: /\d+/, message: { format: '格式不正确' }}}; 252 | parameter.validate(rule, value)[0].message.should.equal('格式不正确'); 253 | }); 254 | 255 | it('should check allowEmpty with format ok', () => { 256 | var value = {string: ''}; 257 | var rule = {string: { type: 'string', format: /\d+/, allowEmpty: true}}; 258 | should.not.exist(parameter.validate(rule, value)); 259 | }); 260 | 261 | it('should check allowEmpty with min and max ok', () => { 262 | var value = {string: ''}; 263 | var rule = {string: { type: 'string', min: 10, max: 100, allowEmpty: true}}; 264 | should.not.exist(parameter.validate(rule, value)); 265 | }); 266 | 267 | it('should allowEmpty default to true if required is false', () => { 268 | var value = { string: '' }; 269 | var rule = { string: { type: 'string', format: /\d+/, required: false } }; 270 | should.not.exist(parameter.validate(rule, value)); 271 | }); 272 | }); 273 | 274 | describe('id', () => { 275 | it('should check ok', () => { 276 | var value = {id : '0524' }; 277 | var rule = {id: 'id'}; 278 | should.not.exist(parameter.validate(rule, value)); 279 | }); 280 | 281 | it('should check allowEmpty ok', () => { 282 | [ 283 | '231', 284 | '', 285 | ].forEach(function (id) { 286 | should.not.exist(parameter.validate({ name: { type: 'id', allowEmpty: true } }, { name: id })); 287 | }); 288 | }); 289 | 290 | it('should check id not ok', () => { 291 | var value = {id : '0524x' }; 292 | var rule = {id: 'id'}; 293 | parameter.validate(rule, value)[0].message.should.equal('should match /^\\d+$/'); 294 | rule = {id: { type: 'id', message: 'ID 格式不正确' }}; 295 | parameter.validate(rule, value)[0].message.should.equal('ID 格式不正确'); 296 | }); 297 | }); 298 | 299 | 300 | describe('date', () => { 301 | it('should check ok', () => { 302 | var value = {date : '2014-11-11' }; 303 | var rule = {date: 'date'}; 304 | should.not.exist(parameter.validate(rule, value)); 305 | }); 306 | 307 | it('should check date not ok', () => { 308 | var value = {date : '2014-xx-xx' }; 309 | var rule = {date: 'date'}; 310 | parameter.validate(rule, value)[0].message.should.equal('should match /^\\d{4}\\-\\d{2}\\-\\d{2}$/'); 311 | rule = {date: { type: 'date', message: '日期格式不正确'}}; 312 | parameter.validate(rule, value)[0].message.should.equal('日期格式不正确'); 313 | }); 314 | 315 | it('should check allowEmpty ok', () => { 316 | [ 317 | '2014-11-11', 318 | '', 319 | ].forEach(function (date) { 320 | should.not.exist(parameter.validate({ name: { type: 'date', allowEmpty: true } }, { name: date })); 321 | }); 322 | }); 323 | }); 324 | 325 | describe('dateTime', () => { 326 | it('should check ok', () => { 327 | var value = {dateTime : '2014-11-11 00:00:00' }; 328 | var rule = {dateTime: 'dateTime'}; 329 | should.not.exist(parameter.validate(rule, value)); 330 | }); 331 | 332 | it('should check dateTime not ok', () => { 333 | var value = {dateTime : '2014-11-11 00:xx:00' }; 334 | var rule = {dateTime: 'dateTime'}; 335 | parameter.validate(rule, value)[0].message.should.equal('should match /^\\d{4}\\-\\d{2}\\-\\d{2} \\d{2}:\\d{2}:\\d{2}$/'); 336 | rule = {dateTime: { type: 'dateTime', message: "时间格式不正确" }}; 337 | parameter.validate(rule, value)[0].message.should.equal('时间格式不正确'); 338 | }); 339 | 340 | it('should datetime alias to dateTime', () => { 341 | var value = {datetime : '2014-11-11 00:00:00' }; 342 | var rule = {datetime: 'dateTime'}; 343 | should.not.exist(parameter.validate(rule, value)); 344 | }); 345 | 346 | it('should check allowEmpty ok', () => { 347 | [ 348 | '2014-11-11 00:00:00', 349 | '', 350 | ].forEach(function (datetime) { 351 | should.not.exist(parameter.validate({ name: { type: 'dateTime', allowEmpty: true } }, { name: datetime })); 352 | }); 353 | }); 354 | }); 355 | 356 | describe('boolean', () => { 357 | it('should check ok', () => { 358 | var value = {boolean : true }; 359 | var rule = {boolean: 'boolean'}; 360 | should.not.exist(parameter.validate(rule, value)); 361 | rule = {boolean: 'bool'}; 362 | should.not.exist(parameter.validate(rule, value)); 363 | }); 364 | 365 | it('should check boolean not ok', () => { 366 | var value = {boolean : '2014-11-11 00:xx:00' }; 367 | var rule = {boolean: 'boolean'}; 368 | parameter.validate(rule, value)[0].message.should.equal('should be a boolean'); 369 | rule = {boolean: { type: 'boolean', message: '应该是一个布尔值' }}; 370 | parameter.validate(rule, value)[0].message.should.equal('应该是一个布尔值'); 371 | rule = {boolean: { type: 'bool', message: '应该是一个布尔值' }}; 372 | parameter.validate(rule, value)[0].message.should.equal('应该是一个布尔值'); 373 | }); 374 | }); 375 | 376 | describe('enum', () => { 377 | it('should check ok', () => { 378 | var value = {enum : 1 }; 379 | var rule = {enum: [1, 2, 3]}; 380 | should.not.exist(parameter.validate(rule, value)); 381 | rule = {enum: {type: 'enum', values: [1, 2, 3]}}; 382 | should.not.exist(parameter.validate(rule, value)); 383 | }); 384 | 385 | it('should throw when no values', () => { 386 | (() => { 387 | parameter.validate({enum: {type: 'enum'}}, {enum: 1}); 388 | }).should.throw('check enum need array type values'); 389 | }); 390 | 391 | it('should check enum not ok', () => { 392 | var value = {enum : 4 }; 393 | var rule = {enum: [1, 2, 3]}; 394 | parameter.validate(rule, value)[0].message.should.equal('should be one of 1, 2, 3'); 395 | rule = {enum: { type: 'enum', values: [1, 2, 3], message: '不合法的枚举值' }}; 396 | parameter.validate(rule, value)[0].message.should.equal('不合法的枚举值'); 397 | }); 398 | }); 399 | 400 | describe('email', () => { 401 | it('should check ok', () => { 402 | [ 403 | 'fengmk2@gmail.com', 404 | 'dead-horse@qq.com', 405 | 'fengmk2+github@gmail.com', 406 | 'fengmk2@yahoo.com.cn', 407 | 'Fengmk2@126.Com', 408 | ].forEach(function (email) { 409 | should.not.exist(parameter.validate({ name: 'email' }, { name: email })); 410 | should.not.exist(parameter.validate({ name: { type: 'email' } }, { name: email })); 411 | }); 412 | }); 413 | 414 | it('should check allowEmpty ok', () => { 415 | [ 416 | 'fengmk2@gmail.com', 417 | '', 418 | ].forEach(function (email) { 419 | should.not.exist(parameter.validate({ name: { type: 'email', allowEmpty: true } }, { name: email })); 420 | }); 421 | }); 422 | 423 | it('should check fail', () => { 424 | [ 425 | 'fengmk2@中文.域名', 426 | '.fengmk2@gmail.com', 427 | 'dead-horse@qq.', 428 | 'fengmk2+github@gmail', 429 | 'fengmk2@yahoo.com.cn+', 430 | ].forEach(function (email) { 431 | parameter.validate({ name: 'email' }, { name: email }).should.eql([ 432 | { 433 | code: 'invalid', 434 | field: 'name', 435 | message: 'should be an email' 436 | } 437 | ]); 438 | 439 | parameter.validate({ name: { type: 'email', message: '错误 email' } }, { name: email }).should.eql([ 440 | { 441 | code: 'invalid', 442 | field: 'name', 443 | message: '错误 email' 444 | } 445 | ]); 446 | }); 447 | }); 448 | }); 449 | 450 | describe('password', () => { 451 | it('should check ok', () => { 452 | should.not.exist(parameter.validate({ 453 | password: { 454 | type: 'password', 455 | compare: 're-password' 456 | } 457 | }, { 458 | password: '123123~!@', 459 | 're-password': '123123~!@', 460 | })); 461 | 462 | should.not.exist(parameter.validate({ 463 | password: { 464 | type: 'password', 465 | } 466 | }, { 467 | password: '123123', 468 | })); 469 | }); 470 | 471 | it('should check fail', () => { 472 | parameter.validate({ 473 | password: { 474 | type: 'password', 475 | compare: 're-password' 476 | } 477 | }, { 478 | password: '123123', 479 | 're-password': '1231231', 480 | }).should.eql([ 481 | { 482 | code: 'invalid', 483 | field: 'password', 484 | message: 'should equal to re-password' 485 | } 486 | ]); 487 | 488 | parameter.validate({ 489 | password: { 490 | type: 'password', 491 | compare: 're-password', 492 | message: { 493 | compare: '两次输入的密码不一致' 494 | } 495 | } 496 | }, { 497 | password: '123123', 498 | 're-password': '1231231', 499 | }).should.eql([ 500 | { 501 | code: 'invalid', 502 | field: 'password', 503 | message: '两次输入的密码不一致' 504 | } 505 | ]); 506 | 507 | parameter.validate({ 508 | password: { 509 | type: 'password', 510 | compare: 're-password' 511 | } 512 | }, { 513 | password: '12312', 514 | 're-password': '12312', 515 | }).should.eql([ 516 | { 517 | code: 'invalid', 518 | field: 'password', 519 | message: 'length should bigger than 6' 520 | } 521 | ]); 522 | 523 | parameter.validate({ 524 | password: { 525 | type: 'password', 526 | min: 4, 527 | compare: 're-password' 528 | } 529 | }, { 530 | password: '1', 531 | 're-password': '1', 532 | }).should.eql([ 533 | { 534 | code: 'invalid', 535 | field: 'password', 536 | message: 'length should bigger than 4' 537 | } 538 | ]); 539 | }); 540 | }); 541 | 542 | describe('url', () => { 543 | it('should check ok', () => { 544 | [ 545 | 'http://✪df.ws/123', 546 | 'http://userid:password@example.com:8080', 547 | 'http://userid:password@example.com:8080/', 548 | 'http://userid@example.com', 549 | 'http://userid@example.com/', 550 | 'http://userid@example.com:8080', 551 | 'http://userid@example.com:8080/', 552 | 'http://userid:password@example.com', 553 | 'http://userid:password@example.com/', 554 | 'http://142.42.1.1/', 555 | 'http://142.42.1.1:8080/', 556 | 'http://➡.ws/䨹', 557 | 'http://⌘.ws', 558 | 'http://⌘.ws/', 559 | 'http://foo.com/blah_(wikipedia)#cite-1', 560 | 'http://foo.com/blah_(wikipedia)_blah#cite-1', 561 | 'http://foo.com/unicode_(✪)_in_parens', 562 | 'http://foo.com/(something)?after=parens', 563 | 'http://☺.damowmow.com/', 564 | 'http://code.google.com/events/#&product=browser', 565 | 'http://j.mp', 566 | 'ftp://foo.bar/baz', 567 | 'http://foo.bar/?q=Test%20URL-encoded%20stuff', 568 | 'http://مثال.إختبار', 569 | 'http://例子.测试', 570 | ].forEach(function (url) { 571 | assert(parameter.validate({ name: 'url' }, { name: url }) === undefined); 572 | assert(parameter.validate({ name: { type: 'url' } }, { name: url }) === undefined); 573 | }); 574 | }); 575 | 576 | it('should check fail', () => { 577 | [ 578 | 'http://', 579 | 'http://.', 580 | 'http://..', 581 | 'http://../', 582 | 'http://?', 583 | 'http://foo.bar?q=Spaces should be encoded', 584 | '//', 585 | '//a', 586 | '///a', 587 | 'http:// shouldfail.com', 588 | ':// should fail', 589 | 'http://foo.bar/foo(bar)baz quux', 590 | 'ftps://foo.bar/', 591 | 'http://-error-.invalid/', 592 | 'http://-a.b.co', 593 | 'http://a.b-.co', 594 | 'http://0.0.0.0', 595 | 'http://www.foo.bar./', 596 | 'http://.www.foo.bar./', 597 | 'http://10.1.1.1', 598 | 'http://10.1.1.254', 599 | // private & local networks will fail 600 | // https://gist.github.com/dperini/729294#file-regex-weburl-js-L77 601 | // https://github.com/node-modules/parameter/issues/92 602 | 'http://localhost', 603 | 'http://127.0.0.1', 604 | 'http://127.0.0.1:8080', 605 | 'http://127.0.0.1:80', 606 | ].forEach(function (url) { 607 | assert.deepStrictEqual(parameter.validate({ name: 'url' }, { name: url }), [ 608 | { 609 | code: 'invalid', 610 | field: 'name', 611 | message: 'should be a url' 612 | } 613 | ]); 614 | 615 | assert.deepStrictEqual(parameter.validate({ name: { type: 'url', message: '不合法 url' } }, { name: url }), [ 616 | { 617 | code: 'invalid', 618 | field: 'name', 619 | message: '不合法 url' 620 | } 621 | ]); 622 | }); 623 | }); 624 | }); 625 | 626 | describe('object', () => { 627 | it('should check ok', () => { 628 | var value = { 629 | object: { 630 | name: 'string', 631 | age: 20 632 | } 633 | }; 634 | var rule = { 635 | object: { 636 | type: 'object', 637 | rule: { 638 | name: 'string', 639 | age: 'int' 640 | } 641 | } 642 | }; 643 | should.not.exist(parameter.validate(rule, value)); 644 | should.not.exist(parameter.validate({object: 'object'}, value)); 645 | }); 646 | 647 | it('should check object', () => { 648 | var value = {object: 1}; 649 | var rule = {object: 'object'}; 650 | parameter.validate(rule, value)[0].message.should.equal('should be an object'); 651 | }); 652 | 653 | it('should check error', () => { 654 | var value = { 655 | object: { 656 | name: 'string', 657 | age: '20' 658 | } 659 | }; 660 | var rule = { 661 | object: { 662 | type: 'object', 663 | rule: { 664 | name: 'string', 665 | age: 'int' 666 | } 667 | } 668 | }; 669 | var result = parameter.validate(rule, value)[0] 670 | result.message.should.equal('should be an integer'); 671 | result.field.should.equal('object.age'); 672 | }); 673 | }); 674 | 675 | describe('array', () => { 676 | it('should check ok', () => { 677 | var value = { 678 | array: [{ 679 | name: 'string', 680 | age: 20 681 | }, { 682 | name: 'name', 683 | age: 21 684 | }] 685 | }; 686 | var rule = { 687 | array: { 688 | type: 'array', 689 | itemType: 'object', 690 | rule: { 691 | name: 'string', 692 | age: 'int' 693 | } 694 | } 695 | }; 696 | should.not.exist(parameter.validate(rule, value)); 697 | should.not.exist(parameter.validate({array: 'array'}, value)); 698 | }); 699 | 700 | it('should check array', () => { 701 | var value = {array: 1}; 702 | var rule = {array: 'array'}; 703 | parameter.validate(rule, value)[0].message.should.equal('should be an array'); 704 | }); 705 | 706 | it('should invalid itemType throw error', () => { 707 | var rule = {array: {type: 'array', itemType: 'invalid'}}; 708 | (() => { 709 | parameter.validate(rule, {array: []}); 710 | }).should.throw('rule type must be one of number, int, integer, string, id, date, dateTime, datetime, boolean, bool, array, object, enum, email, password, url, but the following type was passed: invalid'); 711 | }); 712 | 713 | it('should check max error', () => { 714 | var value = {array: [0, 1, 2, 3, 4]}; 715 | var rule = {array: {type: 'array', itemType: 'int', max: 4, min: 1}}; 716 | parameter.validate(rule, value)[0].message.should.equal('length should smaller than 4'); 717 | }); 718 | 719 | it('should check min error', () => { 720 | var value = {array: [0, 1, 2, 3, 4]}; 721 | var rule = {array: {type: 'array', itemType: 'int', max: 100, min: 10}}; 722 | parameter.validate(rule, value)[0].message.should.equal('length should bigger than 10'); 723 | }); 724 | 725 | it('should check itemType=object error', () => { 726 | var value = { 727 | array: [{ 728 | name: 22, 729 | age: 20 730 | }, { 731 | name: 'name', 732 | age: '21' 733 | }] 734 | }; 735 | var rule = { 736 | array: { 737 | type: 'array', 738 | itemType: 'object', 739 | rule: { 740 | name: 'string', 741 | age: 'int' 742 | } 743 | } 744 | }; 745 | parameter.validate(rule, value)[0].message.should.equal('should be a string'); 746 | parameter.validate(rule, value)[1].message.should.equal('should be an integer'); 747 | }); 748 | 749 | it('should check itemType=string error', () => { 750 | var value = { 751 | array: ['test', 'foo', 1, ''] 752 | }; 753 | var rule = { 754 | array: { 755 | type: 'array', 756 | itemType: 'string' 757 | } 758 | }; 759 | 760 | var rule2 = { 761 | array: { 762 | type: 'array', 763 | itemType: 'string', 764 | rule: {type: 'string', allowEmpty: true} 765 | } 766 | } 767 | parameter.validate(rule, value)[0].message.should.equal('should be a string'); 768 | parameter.validate(rule, value)[1].message.should.equal('should not be empty'); 769 | parameter.validate(rule2, value)[0].message.should.equal('should be a string'); 770 | parameter.validate(rule2, value).should.have.length(1); 771 | }); 772 | }); 773 | 774 | describe('addRule', () => { 775 | it('should throw without type', () => { 776 | (() => { 777 | parameter.addRule(); 778 | }).should.throw('`type` required'); 779 | (() => { 780 | Parameter.addRule(); 781 | }).should.throw('`type` required'); 782 | }); 783 | 784 | it('should throw error when override exists rule', () => { 785 | (() => { 786 | parameter.addRule('string', function() {}, false); 787 | }).should.throw('rule `string` exists'); 788 | (() => { 789 | Parameter.addRule('string', function() {}, false); 790 | }).should.throw('rule `string` exists'); 791 | }); 792 | 793 | it('should throw without check', () => { 794 | (() => { 795 | parameter.addRule('type'); 796 | }).should.throw('check must be function or regexp'); 797 | }); 798 | 799 | it('should add with function', () => { 800 | parameter.addRule('prefix', function (rule, value) { 801 | if (value.indexOf(rule.prefix) !== 0) { 802 | return 'should start with ' + rule.prefix; 803 | } 804 | }); 805 | 806 | var rule = {key: {type: 'prefix', prefix: 'prefix'}}; 807 | var value = {key: 'not-prefixed'}; 808 | parameter.validate(rule, value)[0].message.should.equal('should start with prefix'); 809 | }); 810 | 811 | it('should add with regexp', () => { 812 | parameter.addRule('prefix', /^prefix/); 813 | var rule = {key: 'prefix'}; 814 | var value = {key: 'not-prefixed'}; 815 | parameter.validate(rule, value)[0].message.should.equal('should match /^prefix/'); 816 | }); 817 | 818 | it('should add with regexp on global', () => { 819 | Parameter.addRule('prefix2', /^prefix/); 820 | var rule = {key: 'prefix2'}; 821 | var value = {key: 'not-prefixed'}; 822 | parameter.validate(rule, value)[0].message.should.equal('should match /^prefix/'); 823 | }); 824 | 825 | it('should add work with required false by ?', () => { 826 | parameter.addRule('prefix', function (rule, value) { 827 | if (value.indexOf(rule.prefix) !== 0) { 828 | return 'should start with ' + rule.prefix; 829 | } 830 | }); 831 | should.not.exist(parameter.validate({ foo: 'prefix?' }, {})); 832 | parameter.validate({ foo: { type: 'prefix', prefix: 'hello' } }, {})[0].message.should.equal('required'); 833 | parameter.validate({ foo: { type: 'prefix', prefix: 'hello' } }, { foo: 'world' })[0].message.should.equal('should start with hello'); 834 | }); 835 | 836 | it('should add rule support function convertType', () => { 837 | parameter.addRule('httpBoolean', Parameter.TYPE_MAP['boolean'], false, (value, obj) => { 838 | if (value === 'false' || value === '0') return false; 839 | return !!value; 840 | }); 841 | 842 | const obj = { 843 | a: 'false', 844 | b: '0', 845 | c: true, 846 | d: false, 847 | e: 1, 848 | f: 0, 849 | g: null, 850 | h: undefined, 851 | i: '', 852 | j: NaN, 853 | k: '00', 854 | l: '\t', 855 | }; 856 | should.not.exist(parameterWithConvert.validate({ 857 | a: 'httpBoolean', 858 | b: 'httpBoolean', 859 | c: 'httpBoolean', 860 | d: 'httpBoolean', 861 | e: 'httpBoolean', 862 | f: 'httpBoolean', 863 | g: 'httpBoolean?', 864 | h: 'httpBoolean?', 865 | i: 'httpBoolean', 866 | j: 'httpBoolean', 867 | k: 'httpBoolean', 868 | l: 'httpBoolean', 869 | }, obj)); 870 | obj.should.eql({ 871 | a: false, 872 | b: false, 873 | c: true, 874 | d: false, 875 | e: true, 876 | f: false, 877 | g: null, 878 | h: undefined, 879 | i: false, 880 | j: false, 881 | k: true, 882 | l: true, 883 | }); 884 | }); 885 | 886 | it('should add rule support function convertType used without convert', () => { 887 | parameter.addRule( 888 | 'time', 889 | (rule, value) => { 890 | if (String(value) === 'Invalid Date') { 891 | return 'must be timestamp or format date'; 892 | } 893 | return; 894 | }, 895 | (value, obj) => { 896 | if (typeof value === 'string' && /^\d{13,}$/.test(value)) { 897 | return new Date(parseInt(value)); 898 | } 899 | 900 | return new Date(value); 901 | } 902 | ); 903 | 904 | const obj1 = { 905 | a: 1558507311641, 906 | b: '1558507311641', 907 | c: "2019-05-22T06:41:51.641Z", 908 | d: "2019-05-22 14:41:51", 909 | e: '2019-05-22', 910 | }; 911 | 912 | should.not.exist(parameter.validate({ 913 | a: { type: 'time', convertType: 'time' }, 914 | b: { type: 'time', convertType: 'time' }, 915 | c: { type: 'time', convertType: 'time' }, 916 | d: { type: 'time', convertType: 'time' }, 917 | e: { type: 'time', convertType: 'time' }, 918 | }, obj1)); 919 | 920 | obj1.should.eql({ 921 | a: new Date(1558507311641), 922 | b: new Date(1558507311641), 923 | c: new Date("2019-05-22T06:41:51.641Z"), 924 | d: new Date("2019-05-22 14:41:51"), 925 | e: new Date("2019-05-22"), 926 | }); 927 | 928 | const obj2 = { 929 | f: 'abc', 930 | g: '155850731164a', 931 | h: '', 932 | }; 933 | parameter.validate({ 934 | f: { type: 'time', convertType: 'time' }, 935 | g: { type: 'time', convertType: 'time' }, 936 | h: { type: 'time', convertType: 'time', required: false }, 937 | }, obj2).should.eql([ 938 | { 939 | message: 'must be timestamp or format date', 940 | code: 'invalid', 941 | field: 'f' 942 | }, 943 | { 944 | message: 'must be timestamp or format date', 945 | code: 'invalid', 946 | field: 'g' 947 | }, 948 | { 949 | message: 'must be timestamp or format date', 950 | code: 'invalid', 951 | field: 'h' 952 | }, 953 | ]); 954 | }); 955 | 956 | it('should add rule support string convertType', () => { 957 | parameter.addRule('httpBoolean2', Parameter.TYPE_MAP['boolean'], false, 'boolean'); 958 | 959 | const obj = { 960 | a: 'false', 961 | b: '0', 962 | c: true, 963 | d: false, 964 | e: 1, 965 | f: 0, 966 | g: null, 967 | h: undefined, 968 | i: '', 969 | j: NaN, 970 | k: '00', 971 | l: '\t', 972 | }; 973 | should.not.exist(parameterWithConvert.validate({ 974 | a: 'httpBoolean2', 975 | b: 'httpBoolean2', 976 | c: 'httpBoolean2', 977 | d: 'httpBoolean2', 978 | e: 'httpBoolean2', 979 | f: 'httpBoolean2', 980 | g: 'httpBoolean2?', 981 | h: 'httpBoolean2?', 982 | i: 'httpBoolean2', 983 | j: 'httpBoolean2', 984 | k: 'httpBoolean2', 985 | l: 'httpBoolean2', 986 | }, obj)); 987 | obj.should.eql({ 988 | a: true, 989 | b: true, 990 | c: true, 991 | d: false, 992 | e: true, 993 | f: false, 994 | g: null, 995 | h: undefined, 996 | i: false, 997 | j: false, 998 | k: true, 999 | l: true, 1000 | }); 1001 | }); 1002 | 1003 | it('should add rule support string convertType ignore override', () => { 1004 | parameter.addRule('httpBoolean3', Parameter.TYPE_MAP['boolean'], 'boolean'); 1005 | 1006 | const obj = { 1007 | a: 'false', 1008 | b: '0', 1009 | c: true, 1010 | d: false, 1011 | e: 1, 1012 | f: 0, 1013 | g: null, 1014 | h: undefined, 1015 | i: '', 1016 | j: NaN, 1017 | k: '00', 1018 | l: '\t', 1019 | }; 1020 | should.not.exist(parameterWithConvert.validate({ 1021 | a: 'httpBoolean3', 1022 | b: 'httpBoolean3', 1023 | c: 'httpBoolean3', 1024 | d: 'httpBoolean3', 1025 | e: 'httpBoolean3', 1026 | f: 'httpBoolean3', 1027 | g: 'httpBoolean3?', 1028 | h: 'httpBoolean3?', 1029 | i: 'httpBoolean3', 1030 | j: 'httpBoolean3', 1031 | k: 'httpBoolean3', 1032 | l: 'httpBoolean3', 1033 | }, obj)); 1034 | obj.should.eql({ 1035 | a: true, 1036 | b: true, 1037 | c: true, 1038 | d: false, 1039 | e: true, 1040 | f: false, 1041 | g: null, 1042 | h: undefined, 1043 | i: false, 1044 | j: false, 1045 | k: true, 1046 | l: true, 1047 | }); 1048 | }); 1049 | }); 1050 | 1051 | describe('custom translate function', function(){ 1052 | it('should work', function() { 1053 | var translate = function() { 1054 | var args = Array.prototype.slice.call(arguments); 1055 | args[0] = args[0] + '-add.'; 1056 | return util.format.apply(util, args); 1057 | }; 1058 | 1059 | var p1 = new Parameter({ translate: translate }); 1060 | 1061 | var rule = { name: 'string' }; 1062 | var error = p1.validate(rule, {})[0]; 1063 | error.message.should.equal('required-add.'); 1064 | error.code.should.equal('missing_field-add.'); 1065 | error.field.should.equal('name'); 1066 | }); 1067 | }); 1068 | }); 1069 | 1070 | 1071 | describe('validate with option.validateRoot', () => { 1072 | it('should not pass when received a invalid value', () => { 1073 | var value = null; 1074 | var rule = { int: { type: 'int1', required: false } }; 1075 | parameterWithRootValidate.validate(rule, value)[0].message.should.equal('the validated value should be a object');; 1076 | }); 1077 | }); 1078 | 1079 | describe('validate with options.convert', function() { 1080 | it('should convert to specific type by default', () => { 1081 | var value = { 1082 | int: '1.1', 1083 | number: '1.23', 1084 | string: 123, 1085 | boolean: 'foo', 1086 | regexp: 567, 1087 | id: 888, 1088 | }; 1089 | parameterWithConvert.validate({ 1090 | int: 'int', 1091 | number: 'number', 1092 | string: 'string', 1093 | boolean: 'boolean', 1094 | regexp: /\d+/, 1095 | id: 'id', 1096 | }, value); 1097 | value.should.eql({ 1098 | int: 1, 1099 | number: 1.23, 1100 | string: '123', 1101 | boolean: true, 1102 | regexp: '567', 1103 | id: '888' 1104 | }); 1105 | }); 1106 | 1107 | it('should convert to boolean', () => { 1108 | var value = { 1109 | a: '0', 1110 | b: '', 1111 | c: 0, 1112 | d: 1, 1113 | e: 'false', 1114 | f: 'true', 1115 | g: true, 1116 | h: false, 1117 | n: null, 1118 | u: undefined, 1119 | i: NaN, 1120 | j: Infinity, 1121 | }; 1122 | parameterWithConvert.validate({ 1123 | a: 'boolean', 1124 | b: 'boolean', 1125 | c: 'boolean', 1126 | d: 'boolean', 1127 | e: 'boolean', 1128 | f: 'boolean', 1129 | g: 'boolean', 1130 | h: 'boolean', 1131 | n: 'boolean', 1132 | u: 'boolean', 1133 | i: 'boolean', 1134 | j: 'boolean', 1135 | }, value); 1136 | value.should.eql({ 1137 | a: true, 1138 | b: false, 1139 | c: false, 1140 | d: true, 1141 | e: true, 1142 | f: true, 1143 | g: true, 1144 | h: false, 1145 | n: null, 1146 | u: undefined, 1147 | i: false, 1148 | j: true, 1149 | }); 1150 | }); 1151 | 1152 | it('should convertType support customize', () => { 1153 | var value = { int: 123 }; 1154 | var res = parameterWithConvert.validate({ 1155 | int: { 1156 | type: 'int', 1157 | convertType: 'string', 1158 | }, 1159 | }, value); 1160 | res[0].message.should.equal('should be an integer'); 1161 | value.int.should.equal('123'); 1162 | }); 1163 | 1164 | it('should convertType not work with object', () => { 1165 | var value = { int: {} }; 1166 | var res = parameterWithConvert.validate({ 1167 | int: 'int', 1168 | }, value); 1169 | res[0].message.should.equal('should be an integer'); 1170 | value.int.should.eql({}); 1171 | }); 1172 | 1173 | it('should convertType support function', () => { 1174 | var value = { int: 'x' }; 1175 | var res = parameterWithConvert.validate({ 1176 | int: { 1177 | type: 'int', 1178 | convertType(v, obj) { 1179 | obj.should.equal(value); 1180 | if (v === 'x') return 1; 1181 | return 0; 1182 | }, 1183 | }, 1184 | }, value); 1185 | should.not.exist(res); 1186 | value.int.should.equal(1); 1187 | }); 1188 | 1189 | describe('validate with options.widelyUndefined', () => { 1190 | it('should convert null / NaN / "" to undefiend', () => { 1191 | var value = { 1192 | number: NaN, 1193 | string: '', 1194 | trimString: ' ', 1195 | date: null, 1196 | foo: 'test string', 1197 | bar: 123, 1198 | byRule: '', 1199 | }; 1200 | var res = parameterWithWidelyUndefined.validate({ 1201 | number: 'number?', 1202 | string: 'string?', 1203 | trimString: { type: 'string?', trim: true }, 1204 | date: 'date?', 1205 | foo: 'string', 1206 | bar: 'int', 1207 | byRule: { type: 'string?', widelyUndefined: false }, 1208 | }, value); 1209 | should.not.exist(res); 1210 | value.should.eql({ 1211 | number: undefined, 1212 | string: undefined, 1213 | trimString: undefined, 1214 | date: undefined, 1215 | foo: 'test string', 1216 | bar: 123, 1217 | byRule: '', 1218 | }); 1219 | }); 1220 | }); 1221 | 1222 | describe('default', () => { 1223 | it('should default work', () => { 1224 | var value = { 1225 | string: '', 1226 | trimString: '\t\t\t\n ', 1227 | foo: null, 1228 | bar: 123, 1229 | }; 1230 | var res = parameter.validate({ 1231 | string: { type: 'string?', default: 'string' }, 1232 | trimString: { type: 'string?', trim: true, default: 'trimString' }, 1233 | foo: { type: 'string?', default: 'foo' }, 1234 | bar: { type: 'int?', default: 1200 }, 1235 | hello: { type: 'string?', default: 'world' }, 1236 | bool: { type: 'boolean?', default: false }, 1237 | }, value); 1238 | should.not.exist(res); 1239 | value.should.eql({ 1240 | string: '', // notice: string '' is not undefined here 1241 | trimString: '', 1242 | foo: 'foo', 1243 | bar: 123, 1244 | hello: 'world', 1245 | bool: false, 1246 | }); 1247 | }); 1248 | 1249 | it('should default work with widelyUndefined', function() { 1250 | var value = { 1251 | number: NaN, 1252 | string: '', 1253 | trimString: ' ', 1254 | date: null, 1255 | foo: 'test string', 1256 | bar: 123, 1257 | }; 1258 | var res = parameterWithWidelyUndefined.validate({ 1259 | number: { type: 'number?', default: 100 }, 1260 | string: { type: 'string?', default: 'string' }, 1261 | trimString: { type: 'string?', trim: true, default: 'trimString' }, 1262 | date: { type: 'date?', default: 100 }, 1263 | foo: { type: 'string?', default: 'foo' }, 1264 | bar: { type: 'int?', default: 1200 }, 1265 | hello: { type: 'string?', default: 'world' }, 1266 | }, value); 1267 | should.not.exist(res); 1268 | value.should.eql({ 1269 | number: 100, 1270 | string: 'string', 1271 | trimString: 'trimString', 1272 | date: 100, 1273 | foo: 'test string', 1274 | bar: 123, 1275 | hello: 'world', 1276 | }); 1277 | }); 1278 | }); 1279 | }); 1280 | --------------------------------------------------------------------------------