├── .babelrc ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── WIKI ├── demo ├── index.array.js ├── index.html └── index.js ├── dist └── slime-validator.umd.js ├── docs ├── apis.md └── index.md ├── index.d.ts ├── index.js ├── package.json ├── packages ├── enum.js ├── is_email.js ├── is_url.js ├── max_length.js ├── max_num.js ├── min_length.js ├── min_num.js ├── not_empty.js ├── regexp.js └── required.js ├── test ├── default.test.js ├── plugin.test.js ├── rule.test.js ├── schema.test.js └── sign.rule.test.js ├── utils.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "> 1%", 9 | "last 2 versions", 10 | "Android >= 3.2", 11 | "Firefox >= 20", 12 | "iOS 7" 13 | ] 14 | } 15 | } 16 | ] 17 | ], 18 | "plugins": [] 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | - 12 5 | - node 6 | email: 7 | after_success: 8 | env: 9 | NODE_ENV: JEST 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guideline 2 | 3 | Thanks for considering to contribute this project. All issues and pull requests are highly appreciated. 4 | 5 | ## Pull Requests 6 | 7 | Before sending pull request to this project, please read and follow guidelines below. 8 | 9 | 1. Test: Make sure to test your code. 10 | 11 | Add device mode, API version, related log, screenshots and other related information in your pull request if possible. 12 | 13 | NOTE: We assume all your contribution can be licensed under the [Apache License 2.0](LICENSE). 14 | 15 | ## Issues 16 | 17 | We love clearly described issues. :) 18 | 19 | Following information can help us to resolve the issue faster. 20 | 21 | * Device mode and hardware information. 22 | * API version. 23 | * Logs. 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | 8 | 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 13 | 14 | 15 | 1. Definitions. 16 | 17 | 18 | 19 | 20 | "License" shall mean the terms and conditions for use, reproduction, 21 | 22 | and distribution as defined by Sections 1 through 9 of this document. 23 | 24 | 25 | 26 | 27 | "Licensor" shall mean the copyright owner or entity authorized by 28 | 29 | the copyright owner that is granting the License. 30 | 31 | 32 | 33 | 34 | "Legal Entity" shall mean the union of the acting entity and all 35 | 36 | other entities that control, are controlled by, or are under common 37 | 38 | control with that entity. For the purposes of this definition, 39 | 40 | "control" means (i) the power, direct or indirect, to cause the 41 | 42 | direction or management of such entity, whether by contract or 43 | 44 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 45 | 46 | outstanding shares, or (iii) beneficial ownership of such entity. 47 | 48 | 49 | 50 | 51 | "You" (or "Your") shall mean an individual or Legal Entity 52 | 53 | exercising permissions granted by this License. 54 | 55 | 56 | 57 | 58 | "Source" form shall mean the preferred form for making modifications, 59 | 60 | including but not limited to software source code, documentation 61 | 62 | source, and configuration files. 63 | 64 | 65 | 66 | 67 | "Object" form shall mean any form resulting from mechanical 68 | 69 | transformation or translation of a Source form, including but 70 | 71 | not limited to compiled object code, generated documentation, 72 | 73 | and conversions to other media types. 74 | 75 | 76 | 77 | 78 | "Work" shall mean the work of authorship, whether in Source or 79 | 80 | Object form, made available under the License, as indicated by a 81 | 82 | copyright notice that is included in or attached to the work 83 | 84 | (an example is provided in the Appendix below). 85 | 86 | 87 | 88 | 89 | "Derivative Works" shall mean any work, whether in Source or Object 90 | 91 | form, that is based on (or derived from) the Work and for which the 92 | 93 | editorial revisions, annotations, elaborations, or other modifications 94 | 95 | represent, as a whole, an original work of authorship. For the purposes 96 | 97 | of this License, Derivative Works shall not include works that remain 98 | 99 | separable from, or merely link (or bind by name) to the interfaces of, 100 | 101 | the Work and Derivative Works thereof. 102 | 103 | 104 | 105 | 106 | "Contribution" shall mean any work of authorship, including 107 | 108 | the original version of the Work and any modifications or additions 109 | 110 | to that Work or Derivative Works thereof, that is intentionally 111 | 112 | submitted to Licensor for inclusion in the Work by the copyright owner 113 | 114 | or by an individual or Legal Entity authorized to submit on behalf of 115 | 116 | the copyright owner. For the purposes of this definition, "submitted" 117 | 118 | means any form of electronic, verbal, or written communication sent 119 | 120 | to the Licensor or its representatives, including but not limited to 121 | 122 | communication on electronic mailing lists, source code control systems, 123 | 124 | and issue tracking systems that are managed by, or on behalf of, the 125 | 126 | Licensor for the purpose of discussing and improving the Work, but 127 | 128 | excluding communication that is conspicuously marked or otherwise 129 | 130 | designated in writing by the copyright owner as "Not a Contribution." 131 | 132 | 133 | 134 | 135 | "Contributor" shall mean Licensor and any individual or Legal Entity 136 | 137 | on behalf of whom a Contribution has been received by Licensor and 138 | 139 | subsequently incorporated within the Work. 140 | 141 | 142 | 143 | 144 | 2. Grant of Copyright License. Subject to the terms and conditions of 145 | 146 | this License, each Contributor hereby grants to You a perpetual, 147 | 148 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 149 | 150 | copyright license to reproduce, prepare Derivative Works of, 151 | 152 | publicly display, publicly perform, sublicense, and distribute the 153 | 154 | Work and such Derivative Works in Source or Object form. 155 | 156 | 157 | 158 | 159 | 3. Grant of Patent License. Subject to the terms and conditions of 160 | 161 | this License, each Contributor hereby grants to You a perpetual, 162 | 163 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 164 | 165 | (except as stated in this section) patent license to make, have made, 166 | 167 | use, offer to sell, sell, import, and otherwise transfer the Work, 168 | 169 | where such license applies only to those patent claims licensable 170 | 171 | by such Contributor that are necessarily infringed by their 172 | 173 | Contribution(s) alone or by combination of their Contribution(s) 174 | 175 | with the Work to which such Contribution(s) was submitted. If You 176 | 177 | institute patent litigation against any entity (including a 178 | 179 | cross-claim or counterclaim in a lawsuit) alleging that the Work 180 | 181 | or a Contribution incorporated within the Work constitutes direct 182 | 183 | or contributory patent infringement, then any patent licenses 184 | 185 | granted to You under this License for that Work shall terminate 186 | 187 | as of the date such litigation is filed. 188 | 189 | 190 | 191 | 192 | 4. Redistribution. You may reproduce and distribute copies of the 193 | 194 | Work or Derivative Works thereof in any medium, with or without 195 | 196 | modifications, and in Source or Object form, provided that You 197 | 198 | meet the following conditions: 199 | 200 | 201 | 202 | 203 | (a) You must give any other recipients of the Work or 204 | 205 | Derivative Works a copy of this License; and 206 | 207 | 208 | 209 | 210 | (b) You must cause any modified files to carry prominent notices 211 | 212 | stating that You changed the files; and 213 | 214 | 215 | 216 | 217 | (c) You must retain, in the Source form of any Derivative Works 218 | 219 | that You distribute, all copyright, patent, trademark, and 220 | 221 | attribution notices from the Source form of the Work, 222 | 223 | excluding those notices that do not pertain to any part of 224 | 225 | the Derivative Works; and 226 | 227 | 228 | 229 | 230 | (d) If the Work includes a "NOTICE" text file as part of its 231 | 232 | distribution, then any Derivative Works that You distribute must 233 | 234 | include a readable copy of the attribution notices contained 235 | 236 | within such NOTICE file, excluding those notices that do not 237 | 238 | pertain to any part of the Derivative Works, in at least one 239 | 240 | of the following places: within a NOTICE text file distributed 241 | 242 | as part of the Derivative Works; within the Source form or 243 | 244 | documentation, if provided along with the Derivative Works; or, 245 | 246 | within a display generated by the Derivative Works, if and 247 | 248 | wherever such third-party notices normally appear. The contents 249 | 250 | of the NOTICE file are for informational purposes only and 251 | 252 | do not modify the License. You may add Your own attribution 253 | 254 | notices within Derivative Works that You distribute, alongside 255 | 256 | or as an addendum to the NOTICE text from the Work, provided 257 | 258 | that such additional attribution notices cannot be construed 259 | 260 | as modifying the License. 261 | 262 | 263 | 264 | 265 | You may add Your own copyright statement to Your modifications and 266 | 267 | may provide additional or different license terms and conditions 268 | 269 | for use, reproduction, or distribution of Your modifications, or 270 | 271 | for any such Derivative Works as a whole, provided Your use, 272 | 273 | reproduction, and distribution of the Work otherwise complies with 274 | 275 | the conditions stated in this License. 276 | 277 | 278 | 279 | 280 | 5. Submission of Contributions. Unless You explicitly state otherwise, 281 | 282 | any Contribution intentionally submitted for inclusion in the Work 283 | 284 | by You to the Licensor shall be under the terms and conditions of 285 | 286 | this License, without any additional terms or conditions. 287 | 288 | Notwithstanding the above, nothing herein shall supersede or modify 289 | 290 | the terms of any separate license agreement you may have executed 291 | 292 | with Licensor regarding such Contributions. 293 | 294 | 295 | 296 | 297 | 6. Trademarks. This License does not grant permission to use the trade 298 | 299 | names, trademarks, service marks, or product names of the Licensor, 300 | 301 | except as required for reasonable and customary use in describing the 302 | 303 | origin of the Work and reproducing the content of the NOTICE file. 304 | 305 | 306 | 307 | 308 | 7. Disclaimer of Warranty. Unless required by applicable law or 309 | 310 | agreed to in writing, Licensor provides the Work (and each 311 | 312 | Contributor provides its Contributions) on an "AS IS" BASIS, 313 | 314 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 315 | 316 | implied, including, without limitation, any warranties or conditions 317 | 318 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 319 | 320 | PARTICULAR PURPOSE. You are solely responsible for determining the 321 | 322 | appropriateness of using or redistributing the Work and assume any 323 | 324 | risks associated with Your exercise of permissions under this License. 325 | 326 | 327 | 328 | 329 | 8. Limitation of Liability. In no event and under no legal theory, 330 | 331 | whether in tort (including negligence), contract, or otherwise, 332 | 333 | unless required by applicable law (such as deliberate and grossly 334 | 335 | negligent acts) or agreed to in writing, shall any Contributor be 336 | 337 | liable to You for damages, including any direct, indirect, special, 338 | 339 | incidental, or consequential damages of any character arising as a 340 | 341 | result of this License or out of the use or inability to use the 342 | 343 | Work (including but not limited to damages for loss of goodwill, 344 | 345 | work stoppage, computer failure or malfunction, or any and all 346 | 347 | other commercial damages or losses), even if such Contributor 348 | 349 | has been advised of the possibility of such damages. 350 | 351 | 352 | 353 | 354 | 9. Accepting Warranty or Additional Liability. While redistributing 355 | 356 | the Work or Derivative Works thereof, You may choose to offer, 357 | 358 | and charge a fee for, acceptance of support, warranty, indemnity, 359 | 360 | or other liability obligations and/or rights consistent with this 361 | 362 | License. However, in accepting such obligations, You may act only 363 | 364 | on Your own behalf and on Your sole responsibility, not on behalf 365 | 366 | of any other Contributor, and only if You agree to indemnify, 367 | 368 | defend, and hold each Contributor harmless for any liability 369 | 370 | incurred by, or claims asserted against, such Contributor by reason 371 | 372 | of your accepting any such warranty or additional liability. 373 | 374 | 375 | 376 | 377 | END OF TERMS AND CONDITIONS 378 | 379 | 380 | 381 | 382 | APPENDIX: How to apply the Apache License to your work. 383 | 384 | 385 | 386 | 387 | To apply the Apache License to your work, attach the following 388 | 389 | boilerplate notice, with the fields enclosed by brackets "{}" 390 | 391 | replaced with your own identifying information. (Don't include 392 | 393 | the brackets!) The text should be enclosed in the appropriate 394 | 395 | comment syntax for the file format. We also recommend that a 396 | 397 | file or class name and description of purpose be included on the 398 | 399 | same "printed page" as the copyright notice for easier 400 | 401 | identification within third-party archives. 402 | 403 | 404 | 405 | 406 | Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. 407 | 408 | 409 | 410 | 411 | Licensed under the Apache License, Version 2.0 (the "License"); 412 | 413 | you may not use this file except in compliance with the License. 414 | 415 | You may obtain a copy of the License at 416 | 417 | 418 | 419 | 420 | http://www.apache.org/licenses/LICENSE-2.0 421 | 422 | 423 | 424 | 425 | Unless required by applicable law or agreed to in writing, software 426 | 427 | distributed under the License is distributed on an "AS IS" BASIS, 428 | 429 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 430 | 431 | See the License for the specific language governing permissions and 432 | 433 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version][npm-image]][npm-url] 2 | [![NPM quality][quality-image]][quality-url] 3 | [![npm download][download-image]][download-url] 4 | [![build status][travis-image]][travis-url] 5 | 6 | [travis-image]: https://travis-ci.org/didi/slime-validator.svg?branch=master&status=passed 7 | [travis-url]: https://travis-ci.org/didi/slime-validator 8 | [download-image]: https://img.shields.io/npm/dm/slime-validator.svg?style=flat-square 9 | [download-url]: https://npmjs.org/package/slime-validator 10 | [npm-image]: https://img.shields.io/npm/v/slime-validator.svg?style=flat-square 11 | [npm-url]: https://npmjs.org/package/slime-validator 12 | [quality-image]: http://npm.packagequality.com/shield/slime-validator.svg?style=flat-square 13 | [quality-url]: http://packagequality.com/#?package=slime-validator 14 | [codecov-image]: https://img.shields.io/codecov/c/github/didi/slime-validator.svg?style=flat-square 15 | [codecov-url]: https://codecov.io/gh/didi/slime-validator 16 | 17 | # slime-validator 18 | `slime-validator` is a JavaScript library of validation based on `Plugin` system and make data validation be easy. 19 | 20 | # Description 21 | `slime-validator` make data validation with less code, save more than 20% code than others. 22 | 23 | Plugin system let you make your own validate rules very easy and complex validate rule too. Both on Browser and Nodejs. 24 | 25 | # Installation 26 | 27 | ### Using npm 28 | ```sh 29 | $ npm install slime-validator -save 30 | ``` 31 | 32 | ### Browser 33 | ```js 34 | 35 | ``` 36 | 37 | # Basic Usage 38 | 39 | ### For es module 40 | ```js 41 | import Validator from 'slime-validator' 42 | 43 | // One 44 | const V1 = new Validator({ Required:true }) 45 | console.log(V1.validate('Hello world')) // Output: null 46 | console.log(V1.validate()) // Output: Input is required 47 | 48 | // Two 49 | console.log(Validator.validate({ Required:true }, 'Hello world')) // Output: null 50 | console.log(Validator.validate({ Required:true }, null)) // Output: Input is required 51 | ``` 52 | 53 | ### For CDN 54 | ```html 55 | 56 | 57 | 66 | 67 | 68 | 69 | 70 | ``` 71 | 72 | # Custom Validation Rule 73 | ```js 74 | Validator.usePlugin({ 75 | tagName: 'IsNotRequired', 76 | message(field, value, opts) { 77 | return `${field} Check failed` 78 | }, 79 | validate(field, value, opts) { 80 | return false 81 | } 82 | }, true) // true means to replace the exist rule with the same name. 83 | 84 | const V7 = new Validator({ 85 | field: { IsNotRequired: true } 86 | }) 87 | console.log(V7.validate({ 88 | field: "Something" 89 | })) // Output:{ field: 'field Check failed' } 90 | ``` 91 | 92 | For more information you can read [Document](docs/index.md) 93 | 94 | # Who use? 95 |
96 | 97 | 98 |
99 | 100 | -------------------------------------------------------------------------------- /WIKI: -------------------------------------------------------------------------------- 1 | # 更加简单易用的JS校验库 2 | 3 | `slime-validator`是一个结合目前主流校验库能力,集各家所长,并提供更加简单易用的校验能力。相比其他库可以节约校验相关代码量`20%`左右,在复杂参数校验的情况下甚至能达到`30%`以上`(这里是单纯的指代码字母总数量对比)`。并且支持字段`联动校验`,因此你也可以通过`slime-validator`更加复杂的校验功能,这是其它校验库很少支持的能力。尤其是`validatorjs`,往往需要自己重新封装。 4 | 5 | * 关于字段`联动校验`可以参看文末的例子。 6 | 7 | # 如何使用 8 | `slime-validator`可以应用于后端,也可以应用于前端,但是最初设计是针对后端设计的,因为前端绝大多数情况下都是直接使用`antd`或者`elementUI`,而它们都内置校验库,所以没有太大必要使用`slime-validator`。 9 | 10 | ## 安装 11 | 12 | ```js 13 | $ npm i slime-validator -save 14 | ``` 15 | 16 | 当然也提供了通过`script`标签直接引入的方式,但是由于没有发布`CDN`。需要自行解决引入源的问题。 17 | 18 | ## 如何使用 19 | `slime-validator`有两种校验方法,一种是每次创建一个`Validator`对象。这样可以重复使用这个对象直接对参数进行预期规则的校验。另一种是通过`Validator.validate`函数,通过传入校验规则和待校验对象进行校验。和其他校验库用法类似。 20 | 21 | ```js 22 | import Validator from 'slime-validator' 23 | 24 | // 用法一 25 | const V1 = new Validator({ Required:true }) 26 | console.log(V1.validate('Hello world')) // 输出: null 27 | console.log(V1.validate()) // 输出: Input is required 28 | 29 | // 用法二 30 | console.log(Validator.validate({ Required:true }, 'Hello world')) // 输出: null 31 | console.log(Validator.validate({ Required:true }, null)) // 输出: Input is required 32 | ``` 33 | 34 | ## 如何校验一个对象参数 35 | 校验对象,是校验非常普遍的一个需求场景。`slime-validator`自然而然也必须支持这个能力。 36 | 37 | ```js 38 | import Validator from 'slime-validator' 39 | 40 | // 校验一个对象 41 | const V2 = new Validator({ 42 | username: { Required: true, MinLength: 3 }, // 用户名必填,且不小于3个字符 43 | age: { Required: true, MinNum: 18 }, // 年龄必填,且需要大于18岁 44 | address: { MaxLength: 10 } // 地址选填,最大不超过10个字 45 | }) 46 | console.log(V2.validate({ 47 | username: 'ryouaki', 48 | age: 17 49 | })) // 输出:{ age: 'Min value of age is 18' } 50 | ``` 51 | * 也可以直接使用`Validator.validate`来校验。 52 | 53 | ## 如何校验一个嵌套对象 54 | 在复杂的业务场景,我们需要校验的对象会非常复杂,这个时候,对于像`validatorjs`就要额外封装才能满足需要,而`async-validator`一样需要用一些额外字段去识别这是什么类型的待校验对象。 55 | 56 | 但是在`slime-validator`中,只需要一个`$fields`字段就完成了所以功能,在内部会自动识别对象还是数据还是对象加数组,因此开发者可以少书写很多代码。校验的对象越复杂,收益越明显。 57 | 58 | ```js 59 | // 校验一个嵌套对象 60 | const V3 = new Validator({ 61 | person: { 62 | $fields: { 63 | height: { Required: true }, 64 | age: { Required: true }, 65 | weight: { Required: true } 66 | } 67 | } 68 | }) 69 | console.log(V3.validate({ 70 | person: { 71 | height: 175, 72 | age: 18 73 | } 74 | })) // 输出:{"person":{"weight":"weight is required"}} 75 | 76 | // 校验一个嵌套数组 77 | const V4 = new Validator({ 78 | persons: { 79 | MinLength: 2, 80 | $fields: { 81 | name: { Required: true } 82 | } 83 | } 84 | }) 85 | console.log(V4.validate({ 86 | persons: [] 87 | })) // 输出:{"persons":"Min length of persons is 2"} 88 | console.log(V4.validate({ 89 | persons: [{},{name: "太君是我,别开枪"},{}] // 索引1的元素有name所以错误信息是null 90 | })) // 输出:{"persons":[{"name":"name is required"},null,{"name":"name is required"}]} 91 | ``` 92 | * 也可以直接使用`Validator.validate`来校验。 93 | 94 | * `在这里,当待校验对象是一个数组的时候,返需要用填充null来弥补占位,而其它可以参考的库在这块支持相对较弱,所以,目前也没有一个比较好的Idea。` 95 | 96 | ## 如何自定义信息和校验规则 97 | 通常,虽然校验规则是一样的,但是提示信息可能不一样,或者在大部分情况下提示信息是一样的,但是校验规则可能略有不同。这个时候我们可以通过传入`message`和`validate`两个参数来针对性修改。 98 | 99 | ```js 100 | // 使用自定义消息覆盖规则默认消息 101 | const V5 = new Validator({ 102 | field: {Required: true, message: '我是消息'} 103 | })5 104 | console.log(V5.validate({ 105 | })) // 输出:{ field: '我是消息' } 106 | 107 | // 使用自定义规则覆盖默认规则 108 | const V6 = new Validator({ 109 | field: {Required: true, validate(field, value, opts) { 110 | return false // 永远校验失败 111 | }} 112 | }) 113 | console.log(V6.validate({ 114 | field: "我有数据" 115 | })) // 输出:{ field: 'field is required' } 116 | ``` 117 | * 也可以直接使用`Validator.validate`来校验。 118 | 119 | ## 关于自定义扩展 120 | 默认提供的校验规则往往很难满足所有业务场景,而且在具体的业务处理过程中,也希望有一些规则可以被反复的复用。因此`slime-validator`提供了插件的能力,一旦注册插件,即可在全局使用该规则。这就像`Vue`的`plugin`是一样的。只要`use`一个组件以后,在任何地方都可以直接使用了。 121 | 122 | ```js 123 | // 自定义校验规则插件 124 | Validator.usePlugin({ 125 | tagName: 'IsNotRequired', 126 | message(field, value, opts) { 127 | // field为校验的字段名,value为当前字段的值,opts为附加信息,包括tagName,当前规则名,tagValue当前规则的值,root,校验对象,parent,当前校验值的上一级父对象。 128 | return `${field} 校验不通过` 129 | }, 130 | validate(field, value, opts) { 131 | return false 132 | } 133 | }, true) // 通过第二个参数传入true可以覆盖目前系统中已经注册的同名校验规则。 134 | 135 | const V7 = new Validator({ 136 | field: { IsNotRequired: true } 137 | }) 138 | console.log(V7.validate({ 139 | field: "我有数据" 140 | })) // 输出:{ field: 'field 校验不通过' } 141 | ``` 142 | * 也可以直接使用`Validator.validate`来校验。 143 | 144 | ## 联动校验 145 | 根据不同的国家,选择不同的类型进行联动校验。 146 | ```js 147 | // type_rules.js 148 | import Validator from 'slime-validator'; 149 | 150 | Validator.usePlugin({ 151 | tagName: "CheckType", // 注册一个CheckType校验规则 152 | message(field, value, opts) { 153 | const { tagValue, parent } = opts; // 通过parent 我们可以很容易拿到依赖的父级对象。 154 | return `当前值为 ${value} 有效值为 ${tagValue[parent.country]}` 155 | }, 156 | validate(field, value, opts) { 157 | const { tagValue, parent } = opts; 158 | return tagValue[parent.country].indexOf(value) > -1 159 | } 160 | }) 161 | 162 | // TypeController.js 163 | const rules = { 164 | country: { 165 | Required: true, 166 | Enum: ["CN", "USA", "JP"], message: "国家内容类型不正确" // 国家字段只能是"CN", "USA", "JP"其中之一。 167 | }, 168 | type: { Required: true, CheckType: { 169 | CN: [1, 3], 170 | USA: [1, 2, 3], 171 | JP: [2, 3] 172 | }} 173 | } 174 | 175 | const V = new Validator(rules); 176 | 177 | // CN 178 | console.log("CN", JSON.stringify(Validator.validate(rules, { 179 | country: "CN", 180 | type: 1 181 | }))) 182 | console.log("CN", JSON.stringify(V.validate({ 183 | country: "CN", 184 | type: 2 185 | }))) 186 | 187 | // USA 188 | console.log("USA", JSON.stringify(Validator.validate(rules, { 189 | country: "USA", 190 | type: 1 191 | }))) 192 | console.log("USA", JSON.stringify(V.validate({ 193 | country: "USA", 194 | type: 2 195 | }))) 196 | 197 | // JP 198 | console.log("JP", JSON.stringify(Validator.validate(rules, { 199 | country: "JP", 200 | type: 1 201 | }))) 202 | console.log("JP", JSON.stringify(V.validate({ 203 | country: "JP", 204 | type: 2 205 | }))) 206 | ``` 207 | 208 | * 如果使用`new Validator`的方式代码量会更少,所以还是推荐使用对象的方式使用。 209 | ```js 210 | $ CN null 211 | $ CN {"type":"当前值为 2 有效值为 1,3"} 212 | $ USA null 213 | $ USA null 214 | $ JP {"type":"当前值为 1 有效值为 2,3"} 215 | $ JP null 216 | ``` 217 | 218 | ## 更多的内置校验规则 219 | ### Required 必填 220 | 进行必填校验,值为`true`或者`false`。 221 | 222 | ```js 223 | { Required: true } 224 | ``` 225 | 226 | ### Enum 枚举校验 227 | 校验值是否为枚举范围内的值,值为包含基础数据类型的数组,内部是通过值比较实现的。 228 | 229 | ```js 230 | { Enum: [0, 1, 2, 3] } 231 | ``` 232 | 233 | ### NotEmpty 非空校验 234 | 判断待校验对象是否为空,支持字符串,数组,对象的校验,值为`true`或者`false`。 235 | 236 | ```js 237 | { NotEmpty: true } 238 | ``` 239 | 240 | ### MaxLength 和 MinLength 241 | 最大长度和最小长度校验,支持数组,字符串,值为数字。 242 | 243 | ```js 244 | { MinLength: 1, MaxLength: 10 } 245 | ``` 246 | 247 | ### MaxNum 和 MinNum 248 | 最大数值和最小数值校验,值为数字。 249 | 250 | ```js 251 | { MinLength: 1, MaxLength: 10 } 252 | ``` 253 | 254 | ### RegExp 正则校验 255 | 正则匹配校验,值为正则对象或者正则字符串。 256 | 257 | ```js 258 | { RegExp: /^a/g } 259 | { RegExp: '^a' } 260 | ``` 261 | 262 | ### IsEmail 校验 263 | email校验,值为email地址字符串。 264 | 265 | ```js 266 | { 267 | field: { IsEmail: true } 268 | } 269 | ``` 270 | ### IsURL 校验 271 | http|https 校验,值为url地址字符串。 272 | 273 | ```js 274 | {field: [{ IsURL: true }]} 275 | ``` 276 | -------------------------------------------------------------------------------- /demo/index.array.js: -------------------------------------------------------------------------------- 1 | const Validator = require('./../index'); 2 | 3 | let ret = null 4 | try { 5 | const v = new Validator({ 6 | field: { 7 | $fields: { 8 | field1: { Required: true }, 9 | field2: [{ Required: true }, { $fields: [{ Enum: [1, 2, 3] }] }] 10 | } 11 | } 12 | }) 13 | ret = v.validate({ field: [{ field1: null }, { field2: [4, 1, 9] }] }) 14 | } finally { 15 | console.log(JSON.stringify(ret)) 16 | } 17 | 18 | try { 19 | const v = new Validator({ 20 | field: { 21 | $fields: { Required: true, MinNum: 200 } 22 | } 23 | }) 24 | ret = v.validate({ field: [1, 2000, 2] }) 25 | } finally { 26 | console.log(JSON.stringify(ret)) 27 | } 28 | 29 | // 校验一个嵌套数组 30 | const V4 = new Validator({ 31 | persons: { 32 | MinLength: 2, 33 | $fields: { 34 | name: { Required: true } 35 | } 36 | } 37 | }) 38 | console.log(JSON.stringify(V4.validate({ 39 | persons: [] 40 | }))) // 输出:{"persons":"Min length of persons is 2"} 41 | console.log(JSON.stringify(V4.validate({ 42 | persons: [{},{name: "太君是我,别开枪"},{}] 43 | }))) // 输出:{"persons":{"0":{"name":"name is required"},{"1":{"name":"name is required"}}} 44 | 45 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | const Validator = require('./../index.js'); 2 | 3 | Validator.usePlugin({ 4 | tagName: "AwardType", 5 | message(field, value, opts) { 6 | const { tagValue, parent } = opts; 7 | return `该类型奖励只允许在指定国家开放, 当前值为 ${value} 有效值为 ${tagValue[parent.country]}` 8 | }, 9 | validate(field, value, opts) { 10 | const { tagValue, parent } = opts; 11 | return tagValue[parent.country].indexOf(value) > -1 12 | } 13 | }) 14 | 15 | const rules = { 16 | country: { Required: true, Enum: ["BR", "MX", "JP"], message: "国家内容不合法" }, 17 | award_type: { Required: true, AwardType: { 18 | BR: [1, 3], 19 | MX: [1, 2, 3], 20 | JP: [2, 3] 21 | }} 22 | } 23 | 24 | const V = new Validator(rules); 25 | 26 | // BR 27 | console.log("BR", JSON.stringify(Validator.validate(rules, { 28 | country: "BR", 29 | award_type: 1 30 | }))) 31 | console.log("BR", JSON.stringify(V.validate({ 32 | country: "BR", 33 | award_type: 2 34 | }))) 35 | 36 | // MX 37 | console.log("MX", JSON.stringify(Validator.validate(rules, { 38 | country: "MX", 39 | award_type: 1 40 | }))) 41 | console.log("MX", JSON.stringify(V.validate({ 42 | country: "MX", 43 | award_type: 2 44 | }))) 45 | 46 | // JP 47 | console.log("JP", JSON.stringify(Validator.validate(rules, { 48 | country: "JP", 49 | award_type: 1 50 | }))) 51 | console.log("JP", JSON.stringify(V.validate({ 52 | country: "JP", 53 | award_type: 2 54 | }))) -------------------------------------------------------------------------------- /dist/slime-validator.umd.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SlimeValidator=t():e.SlimeValidator=t()}(self,(function(){return e={45:function(e,t,n){var r=["message","validate","$fields"];function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){for(var n=0;n=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}function u(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var c=n(712),s=c.isArray,l=c.isObject,f=c.isString,g=c.isFunction,v={},d="Input";function m(){return!0}function p(e,t,n,a){var i=t,c=e;l(t)||(i=u({},d,i),c=u({},d,c));for(var b=Object.keys(c),h=b.length,y=null,x=0;x0&&(S=!0,N=u({},j,U))}else if(s(O)){for(var C=[],D=O.length,_=0;_0&&(S=!0),C.push(l(O[_])||null===B?B:B[j])}S&&(N=u({},j,C))}else console.warn("$fields for ".concat(j," will be ignored"))}S&&(j===d?y=N[j]:null===y?y=u({},j,N[j]):y[j]=N[j])}return y}var b=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(a(this,e),!l(t)&&!s(t))throw new Error("Schema must be an Object or Array");this.schema=t}var t,n;return t=e,(n=[{key:"validate",value:function(e){return p(this.schema,e,e,e)}}])&&i(t.prototype,n),Object.defineProperty(t,"prototype",{writable:!1}),e}();b.validate=function(e,t){if(!l(e)&&!s(e))throw new Error("Schema must be an Object or Array");return p(e,t,t,t)},b.usePlugin=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if(!l(e))throw new Error("Parameter must be Object ant can not be empty");var n=e.tagName,r=e.message,a=e.validate;if(!f(n)||n.length<=0)throw new Error("tagName must be String and not empty.");if(!g(r)||!g(a))throw new Error("message and validate must be Function.");if(!t&&void 0!==v[n])throw new Error("Plugin [ ".concat(n," ] is existed"));void 0!==v[n]&&console.warn("Plugin [ ".concat(n," ] is existed and will be replaced")),v[n]={message:r,validate:a}},b.usePlugin(n(566)),b.usePlugin(n(82)),b.usePlugin(n(178)),b.usePlugin(n(680)),b.usePlugin(n(434)),b.usePlugin(n(563)),b.usePlugin(n(556)),b.usePlugin(n(466)),b.usePlugin(n(832)),b.usePlugin(n(153)),e.exports=b},178:function(e,t,n){var r=n(712).isArray;e.exports={tagName:"Enum",message:function(e,t,n){return"".concat(e," with value ").concat(t," is incorrect")},validate:function(e,t,n){var a=n.tagValue;return!t||!r(a)||a.indexOf(t)>-1}}},832:function(e,t,n){var r=n(712).isString,a=/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;e.exports={tagName:"IsEmail",message:function(e,t,n){return"".concat(e," is not a email")},validate:function(e,t,n){return!n.tagValue||!t||!!r(t)&&!!t.match(a)}}},153:function(e,t,n){var r=n(712).isString;e.exports={tagName:"IsURL",message:function(e,t,n){return"".concat(e," is not a url")},validate:function(e,t,n){var a=n.tagValue,i=n.allowDataUrl,o=void 0!==i&&i,u=n.allowLocal,c=void 0!==u&&u,s=n.schemes,l=void 0===s?["http","https"]:s;if(!a||!t)return!0;if(!r(t))return!1;var f="^(?:(?:"+l.join("|")+")://)(?:\\S+(?::\\S*)?@)?(?:",g="(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))";return c?g+="?":f+="(?!(?: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})",f+="(?:[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]+)*"+g+")(?::\\d{2,5})?(?:[/?#]\\S*)?$",o&&(f="(?:"+f+")|(?:^data:(?:\\w+\\/[-+.\\w]+(?:;[\\w=]+)*)?(?:;base64)?,[A-Za-z0-9-_.!~\\*'();\\/?:@&=+$,%]*$)"),!!t.match(f)}}},680:function(e,t,n){var r=n(712),a=r.isArray,i=r.isString;e.exports={tagName:"MaxLength",message:function(e,t,n){var r=n.tagValue,a=void 0===r?0:r;return"Max length of ".concat(e," is ").concat(a)},validate:function(e,t,n){var r=n.tagValue,o=void 0===r?0:r;return!t||!!(a(t)&&t.length<=o)||!!(i(t)&&t.length<=o)}}},563:function(e,t,n){var r=n(712).isNumber;e.exports={tagName:"MaxNum",message:function(e,t,n){var r=n.tagValue,a=void 0===r?0:r;return"Max value of ".concat(e," is ").concat(a)},validate:function(e,t,n){var a=n.tagValue,i=void 0===a?0:a;return!t||!!(r(t)&&t<=i)}}},434:function(e,t,n){var r=n(712),a=r.isArray,i=r.isString;e.exports={tagName:"MinLength",message:function(e,t,n){var r=n.tagValue,a=void 0===r?0:r;return"Min length of ".concat(e," is ").concat(a)},validate:function(e,t,n){var r=n.tagValue,o=void 0===r?0:r;return!t&&""!==t||!!(a(t)&&t.length>=o)||!!(i(t)&&t.length>=o)}}},556:function(e,t,n){var r=n(712).isNumber;e.exports={tagName:"MinNum",message:function(e,t,n){var r=n.tagValue,a=void 0===r?0:r;return"Min value of ".concat(e," is ").concat(a)},validate:function(e,t,n){var a=n.tagValue,i=void 0===a?0:a;return!t||!!(r(t)&&t>=i)}}},82:function(e,t,n){var r=n(712),a=r.isArray,i=r.isString,o=r.isObject;e.exports={tagName:"NotEmpty",message:function(e,t,n){return"".concat(e," is empty")},validate:function(e,t,n){var r=n.tagValue;return!(void 0!==r&&r&&null!=t&&!(a(t)&&t.length>0)&&!(i(t)&&t.length>0)&&!(o(t)&&Object.keys(t).length>0))}}},466:function(e,t,n){var r=n(712),a=r.isString,i=r.isRegExp;e.exports={tagName:"RegExp",message:function(e,t,n){return"Test ".concat(e," is failed")},validate:function(e,t,n){var r=n.tagValue;return a(r)&&(r=new RegExp(r)),!i(r)||r.test(t)}}},566:function(e){e.exports={tagName:"Required",message:function(e,t,n){return"".concat(e," is required")},validate:function(e,t,n){var r=n.tagValue;return!(void 0!==r&&r)||null!=t}}},712:function(e){e.exports={isArray:function(e){return Array.isArray(e)},isObject:function(e){return"[object Object]"===Object.prototype.toString.call(e)},isString:function(e){return"[object String]"===Object.prototype.toString.call(e)},isFunction:function(e){return"[object Function]"===Object.prototype.toString.call(e)},isNumber:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&!isNaN(e)},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)}}}},t={},function n(r){var a=t[r];if(void 0!==a)return a.exports;var i=t[r]={exports:{}};return e[r](i,i.exports,n),i.exports}(45);var e,t})); -------------------------------------------------------------------------------- /docs/apis.md: -------------------------------------------------------------------------------- 1 | # Required 必填 2 | 进行必填校验,值为`true`或者`false`。 3 | 4 | ```js 5 | { Required: true } 6 | ``` 7 | 8 | # Enum 枚举校验 9 | 校验值是否为枚举范围内的值,值为包含基础数据类型的数组,内部是通过值比较实现的。 10 | 11 | ```js 12 | { Enum: [0, 1, 2, 3] } 13 | ``` 14 | 15 | # NotEmpty 非空校验 16 | 判断待校验对象是否为空,支持字符串,数组,对象的校验,值为`true`或者`false`。 17 | 18 | ```js 19 | { NotEmpty: true } 20 | ``` 21 | 22 | # MaxLength 和 MinLength 23 | 最大长度和最小长度校验,支持数组,字符串,值为数字。 24 | 25 | ```js 26 | { MinLength: 1, MaxLength: 10 } 27 | ``` 28 | 29 | # MaxNum 和 MinNum 30 | 最大数值和最小数值校验,值为数字。 31 | 32 | ```js 33 | { MinLength: 1, MaxLength: 10 } 34 | ``` 35 | 36 | # RegExp 正则校验 37 | 正则匹配校验,值为正则对象或者正则字符串。 38 | 39 | ```js 40 | { RegExp: /^a/g } 41 | { RegExp: '^a' } 42 | ``` 43 | 44 | # IsEmail 校验 45 | email校验,值为email地址字符串。 46 | 47 | ```js 48 | { field: { IsEmail: true } } 49 | ``` 50 | # IsURL 校验 51 | http|https 校验,值为url地址字符串。 52 | 53 | ```js 54 | { field: [{ IsURL: true }] } 55 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # 更加简单易用的JS校验库 2 | 3 | `slime-validator`是一个结合目前主流校验库能力,集各家所长,并提供更加简单易用的校验能力。相比其他库可以节约校验相关代码量`20%`左右,在复杂参数校验的情况下甚至能达到`30%`以上`(这里是单纯的指代码字母总数量对比)`。并且支持字段`联动校验`,因此你也可以通过`slime-validator`更加复杂的校验功能,这是其它校验库很少支持的能力。尤其是`validatorjs`,往往需要自己重新封装。 4 | 5 | * 关于字段`联动校验`可以参看文末的例子。 6 | 7 | # 如何使用 8 | `slime-validator`可以应用于后端,也可以应用于前端,但是最初设计是针对后端设计的,因为前端绝大多数情况下都是直接使用`antd`或者`elementUI`,而它们都内置校验库,所以没有太大必要使用`slime-validator`。 9 | 10 | ## 安装 11 | 12 | ```js 13 | $ npm i slime-validator -save 14 | ``` 15 | 16 | 当然也提供了通过`script`标签直接引入的方式,但是由于没有发布`CDN`。需要自行解决引入源的问题。 17 | 18 | ## 如何使用 19 | `slime-validator`有两种校验方法,一种是每次创建一个`Validator`对象。这样可以重复使用这个对象直接对参数进行预期规则的校验。另一种是通过`Validator.validate`函数,通过传入校验规则和待校验对象进行校验。和其他校验库用法类似。 20 | 21 | ```js 22 | import Validator from 'slime-validator' 23 | 24 | // 用法一 25 | const V1 = new Validator({ Required:true }) 26 | console.log(V1.validate('Hello world')) // 输出: null 27 | console.log(V1.validate()) // 输出: Input is required 28 | 29 | // 用法二 30 | console.log(Validator.validate({ Required:true }, 'Hello world')) // 输出: null 31 | console.log(Validator.validate({ Required:true }, null)) // 输出: Input is required 32 | ``` 33 | 34 | ## 如何校验一个对象参数 35 | 校验对象,是校验非常普遍的一个需求场景。`slime-validator`自然而然也必须支持这个能力。 36 | 37 | ```js 38 | import Validator from 'slime-validator' 39 | 40 | // 校验一个对象 41 | const V2 = new Validator({ 42 | username: { Required: true, MinLength: 3 }, // 用户名必填,且不小于3个字符 43 | age: { Required: true, MinNum: 18 }, // 年龄必填,且需要大于18岁 44 | address: { MaxLength: 10 } // 地址选填,最大不超过10个字 45 | }) 46 | console.log(V2.validate({ 47 | username: 'ryouaki', 48 | age: 17 49 | })) // 输出:{ age: 'Min value of age is 18' } 50 | ``` 51 | * 也可以直接使用`Validator.validate`来校验。 52 | 53 | ## 如何校验一个嵌套对象 54 | 在复杂的业务场景,我们需要校验的对象会非常复杂,这个时候,对于像`validatorjs`就要额外封装才能满足需要,而`async-validator`一样需要用一些额外字段去识别这是什么类型的待校验对象。 55 | 56 | 但是在`slime-validator`中,只需要一个`$fields`字段就完成了所以功能,在内部会自动识别对象还是数据还是对象加数组,因此开发者可以少书写很多代码。校验的对象越复杂,收益越明显。 57 | 58 | ```js 59 | // 校验一个嵌套对象 60 | const V3 = new Validator({ 61 | person: { 62 | $fields: { 63 | height: { Required: true }, 64 | age: { Required: true }, 65 | weight: { Required: true } 66 | } 67 | } 68 | }) 69 | console.log(V3.validate({ 70 | person: { 71 | height: 175, 72 | age: 18 73 | } 74 | })) // 输出:{"person":{"weight":"weight is required"}} 75 | 76 | // 校验一个嵌套数组 77 | const V4 = new Validator({ 78 | persons: { 79 | MinLength: 2, 80 | $fields: { 81 | name: { Required: true } 82 | } 83 | } 84 | }) 85 | console.log(V4.validate({ 86 | persons: [] 87 | })) // 输出:{"persons":"Min length of persons is 2"} 88 | console.log(V4.validate({ 89 | persons: [{},{name: "太君是我,别开枪"},{}] // 索引1的元素有name所以错误信息是null 90 | })) // 输出:{"persons":[{"name":"name is required"},null,{"name":"name is required"}]} 91 | ``` 92 | * 也可以直接使用`Validator.validate`来校验。 93 | 94 | * `在这里,当待校验对象是一个数组的时候,返需要用填充null来弥补占位,而其它可以参考的库在这块支持相对较弱,所以,目前也没有一个比较好的Idea。` 95 | 96 | ## 如何自定义信息和校验规则 97 | 通常,虽然校验规则是一样的,但是提示信息可能不一样,或者在大部分情况下提示信息是一样的,但是校验规则可能略有不同。这个时候我们可以通过传入`message`和`validate`两个参数来针对性修改。 98 | 99 | ```js 100 | // 使用自定义消息覆盖规则默认消息 101 | const V5 = new Validator({ 102 | field: {Required: true, message: '我是消息'} 103 | })5 104 | console.log(V5.validate({ 105 | })) // 输出:{ field: '我是消息' } 106 | 107 | // 使用自定义规则覆盖默认规则 108 | const V6 = new Validator({ 109 | field: {Required: true, validate(field, value, opts) { 110 | return false // 永远校验失败 111 | }} 112 | }) 113 | console.log(V6.validate({ 114 | field: "我有数据" 115 | })) // 输出:{ field: 'field is required' } 116 | ``` 117 | * 也可以直接使用`Validator.validate`来校验。 118 | 119 | ## 关于自定义扩展 120 | 默认提供的校验规则往往很难满足所有业务场景,而且在具体的业务处理过程中,也希望有一些规则可以被反复的复用。因此`slime-validator`提供了插件的能力,一旦注册插件,即可在全局使用该规则。这就像`Vue`的`plugin`是一样的。只要`use`一个组件以后,在任何地方都可以直接使用了。 121 | 122 | ```js 123 | // 自定义校验规则插件 124 | Validator.usePlugin({ 125 | tagName: 'IsNotRequired', 126 | message(field, value, opts) { 127 | // field为校验的字段名,value为当前字段的值,opts为附加信息,包括tagName,当前规则名,tagValue当前规则的值,root,校验对象,parent,当前校验值的上一级父对象。 128 | return `${field} 校验不通过` 129 | }, 130 | validate(field, value, opts) { 131 | return false 132 | } 133 | }, true) // 通过第二个参数传入true可以覆盖目前系统中已经注册的同名校验规则。 134 | 135 | const V7 = new Validator({ 136 | field: { IsNotRequired: true } 137 | }) 138 | console.log(V7.validate({ 139 | field: "我有数据" 140 | })) // 输出:{ field: 'field 校验不通过' } 141 | ``` 142 | * 也可以直接使用`Validator.validate`来校验。 143 | 144 | ## 联动校验 145 | 根据不同的国家,选择不同的类型进行联动校验。 146 | ```js 147 | // type_rules.js 148 | import Validator from 'slime-validator'; 149 | 150 | Validator.usePlugin({ 151 | tagName: "CheckType", // 注册一个CheckType校验规则 152 | message(field, value, opts) { 153 | const { tagValue, parent } = opts; // 通过parent 我们可以很容易拿到依赖的父级对象。 154 | return `当前值为 ${value} 有效值为 ${tagValue[parent.country]}` 155 | }, 156 | validate(field, value, opts) { 157 | const { tagValue, parent } = opts; 158 | return tagValue[parent.country].indexOf(value) > -1 159 | } 160 | }) 161 | 162 | // TypeController.js 163 | const rules = { 164 | country: { 165 | Required: true, 166 | Enum: ["CN", "USA", "JP"], message: "国家内容类型不正确" // 国家字段只能是"CN", "USA", "JP"其中之一。 167 | }, 168 | type: { Required: true, CheckType: { 169 | CN: [1, 3], 170 | USA: [1, 2, 3], 171 | JP: [2, 3] 172 | }} 173 | } 174 | 175 | const V = new Validator(rules); 176 | 177 | // CN 178 | console.log("CN", JSON.stringify(Validator.validate(rules, { 179 | country: "CN", 180 | type: 1 181 | }))) 182 | console.log("CN", JSON.stringify(V.validate({ 183 | country: "CN", 184 | type: 2 185 | }))) 186 | 187 | // USA 188 | console.log("USA", JSON.stringify(Validator.validate(rules, { 189 | country: "USA", 190 | type: 1 191 | }))) 192 | console.log("USA", JSON.stringify(V.validate({ 193 | country: "USA", 194 | type: 2 195 | }))) 196 | 197 | // JP 198 | console.log("JP", JSON.stringify(Validator.validate(rules, { 199 | country: "JP", 200 | type: 1 201 | }))) 202 | console.log("JP", JSON.stringify(V.validate({ 203 | country: "JP", 204 | type: 2 205 | }))) 206 | ``` 207 | 208 | * 如果使用`new Validator`的方式代码量会更少,所以还是推荐使用对象的方式使用。 209 | ```js 210 | $ CN null 211 | $ CN {"type":"当前值为 2 有效值为 1,3"} 212 | $ USA null 213 | $ USA null 214 | $ JP {"type":"当前值为 1 有效值为 2,3"} 215 | $ JP null 216 | ``` 217 | 218 | ## 更多的内置校验规则 219 | 目前已经内置了一部分校验规则可以参考这里[内置校验API文档](apis.md) 220 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** Declaration file generated by dts-gen */ 2 | 3 | export = slime_validator; 4 | 5 | declare class slime_validator { 6 | constructor(...args: any[]); 7 | 8 | validate(...args: any[]): void; 9 | 10 | static usePlugin(plugin: any, replace: any): void; 11 | 12 | static validate(schema: any, target: any): any; 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { 2 | isArray, 3 | isObject, 4 | isString, 5 | isFunction 6 | } = require('./utils'); 7 | 8 | const __$$plugins = {}; 9 | const __$$DEFAULT = "Input"; 10 | 11 | function defaultValidate () { 12 | return true 13 | } 14 | 15 | function doValidate(schema, target, root, parent) { 16 | let _target = target; 17 | let _schema = schema; 18 | 19 | if (!isObject(target)) { 20 | _target = { 21 | [__$$DEFAULT]: _target 22 | } 23 | 24 | _schema = { 25 | [__$$DEFAULT]: _schema 26 | } 27 | } 28 | 29 | const fields = Object.keys(_schema); 30 | const fieldNum = fields.length; 31 | let errors = null; 32 | // 之所以不用forEach,是因为原始for循环的内存效率和执行效率都更优。在大流量场景下有一定性能收益。 33 | for (let i = 0; i < fieldNum; i++) { 34 | const field = fields[i]; 35 | const rules = _schema[field]; 36 | const value = _target[field]; 37 | let hasError = false; 38 | let err = null; 39 | parent = target; 40 | 41 | let rs = isArray(rules) ? rules : [rules]; 42 | const ruleNum = rs.length; 43 | for (let j = 0; j < ruleNum && !hasError; j++) { 44 | const rule = rs[j]; 45 | 46 | let { 47 | message, 48 | validate, 49 | $fields, // 我赌你的校验对象中没有 $fields 这个字段。如果有,我叫你爸爸。 50 | ...tags 51 | } = rule; 52 | 53 | // 优先处理非嵌套结构校验规则。 54 | const tagNames = Object.keys(tags); 55 | const tagNums = tagNames.length; 56 | for (let n = 0; n < tagNums && !hasError; n++) { 57 | const tagName = tagNames[n]; 58 | const plugin = __$$plugins[tagName]; 59 | 60 | if (!plugin) { // 插件没有注册 61 | throw new Error(`Plugin with tagName = ${tagName} is not registered!`); 62 | } 63 | 64 | const ef = validate || plugin.validate || defaultValidate; 65 | const options = { 66 | tagName, 67 | tagValue: tags[tagName], 68 | root, 69 | parent 70 | } 71 | if (ef(field, value, options) === false) { 72 | let msg = ''; 73 | if (message && isFunction(message)) { 74 | msg = message(field, value, options); 75 | } else if (message && isString(message)) { 76 | msg = message 77 | } else { 78 | msg = plugin.message(field, value, options) 79 | } 80 | 81 | hasError = true 82 | err = { 83 | [field]: msg 84 | } 85 | } 86 | } 87 | 88 | // 嵌套校验规则。需要是先通过其它单项规则后如果没有校验问题的情况下才进行。 89 | // 比如我是一个数组,要求数组内必须大于一个元素。此时。如果数组为空,就没必要进行嵌套校验了 90 | if ($fields &&!hasError) { 91 | if (isObject(value)) { 92 | const e = doValidate($fields, value, root, target); 93 | if (isObject(e) && Object.keys(e).length > 0) { 94 | hasError = true; 95 | err = { 96 | [field]: e 97 | } 98 | } 99 | } else if (isArray(value)) { 100 | const errs = []; 101 | const len = value.length; 102 | 103 | for (let k = 0; k < len; k++) { 104 | let e = isObject(value[k]) ? 105 | doValidate($fields, value[k], root, target) 106 | : doValidate({[field]: $fields}, {[field]: value[k]}, root, target); 107 | if (Object.keys(e || {}).length > 0) { 108 | hasError = true; 109 | } 110 | errs.push((isObject(value[k]) || e === null) ? e : e[field]); 111 | } 112 | 113 | if (hasError) { 114 | err = { 115 | [field]: errs 116 | } 117 | } 118 | } else { 119 | console.warn(`$fields for ${field} will be ignored`); 120 | } 121 | } 122 | } 123 | if (hasError) { 124 | if (field === __$$DEFAULT) { 125 | errors = err[field] 126 | } else { 127 | errors === null ? errors = { 128 | [field]: err[field] 129 | } : errors[field] = err[field] 130 | } 131 | } 132 | } 133 | 134 | return errors; 135 | } 136 | 137 | class Validator { 138 | constructor(schema = {}) { 139 | if (!isObject(schema) && !isArray(schema)) { 140 | throw new Error('Schema must be an Object or Array'); 141 | } 142 | this.schema = schema; 143 | } 144 | 145 | validate(target) { 146 | return doValidate(this.schema, target, target, target); 147 | } 148 | } 149 | 150 | Validator.validate = function (schema, target) { 151 | if (!isObject(schema) && !isArray(schema)) { 152 | throw new Error('Schema must be an Object or Array'); 153 | } 154 | return doValidate(schema, target, target, target); 155 | } 156 | 157 | Validator.usePlugin = function (plugin, replace = false) { 158 | if (!isObject(plugin)) { 159 | throw new Error('Parameter must be Object ant can not be empty') 160 | } 161 | 162 | const { 163 | tagName, 164 | message, 165 | validate 166 | } = plugin; 167 | 168 | if (!isString(tagName) || tagName.length <= 0) { 169 | throw new Error('tagName must be String and not empty.') 170 | } 171 | 172 | if (!isFunction(message) || !isFunction(validate)) { 173 | throw new Error('message and validate must be Function.') 174 | } 175 | 176 | if (!replace && __$$plugins[tagName] !== undefined) { 177 | throw new Error(`Plugin [ ${tagName} ] is existed`) 178 | } 179 | 180 | if (__$$plugins[tagName] !== undefined) { 181 | console.warn(`Plugin [ ${tagName} ] is existed and will be replaced`) 182 | } 183 | 184 | __$$plugins[tagName] = { 185 | message, 186 | validate 187 | } 188 | } 189 | 190 | Validator.usePlugin(require('./packages/required')); 191 | Validator.usePlugin(require('./packages/not_empty')); 192 | Validator.usePlugin(require('./packages/enum')); 193 | Validator.usePlugin(require('./packages/max_length')); 194 | Validator.usePlugin(require('./packages/min_length')); 195 | Validator.usePlugin(require('./packages/max_num')); 196 | Validator.usePlugin(require('./packages/min_num')); 197 | Validator.usePlugin(require('./packages/regexp')); 198 | Validator.usePlugin(require('./packages/is_email')); 199 | Validator.usePlugin(require('./packages/is_url')); 200 | 201 | module.exports = Validator; 202 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slime-validator", 3 | "version": "1.1.1", 4 | "description": "slime-validator is a JavaScript library of validation based on `Plugin` system and make data validation be easy.", 5 | "main": "index.js", 6 | "module": "dist/slime-validator.umd.js", 7 | "types": "index.d.ts", 8 | "scripts": { 9 | "test": "jest", 10 | "dev": "jest --watch", 11 | "build": "webpack --config webpack.config.js" 12 | }, 13 | "keywords": [ 14 | "validator", 15 | "validate", 16 | "validation", 17 | "slime-validator" 18 | ], 19 | "files": [ 20 | "index.js", 21 | "utils.js", 22 | "package.json", 23 | "LICENSE", 24 | "README.md", 25 | "packages", 26 | "dist", 27 | "index.d.ts" 28 | ], 29 | "author": "ryouaki(46517115@qq.com)", 30 | "license": "Apache", 31 | "devDependencies": { 32 | "@babel/preset-env": "^7.13.12", 33 | "babel-loader": "^8.2.2", 34 | "jest": "^26.6.3", 35 | "webpack": "^5.30.0", 36 | "webpack-cli": "^4.6.0" 37 | }, 38 | "engines": { 39 | "node": ">=10.0.0" 40 | }, 41 | "bugs": { 42 | "url": "https://github.com/didi/slime-validator/issues" 43 | }, 44 | "repository": { 45 | "type": "git", 46 | "url": "git+https://github.com/didi/slime-validator.git" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/enum.js: -------------------------------------------------------------------------------- 1 | const { 2 | isArray 3 | } = require('../utils') 4 | 5 | module.exports = { 6 | tagName: "Enum", 7 | message (field, value, opts) { 8 | return `${field} with value ${value} is incorrect` 9 | }, 10 | validate (field, value, opts) { 11 | const { tagValue } = opts; 12 | 13 | if (!value) { 14 | return true; 15 | } 16 | 17 | if (!isArray(tagValue)) { 18 | return true; // 枚举值列表不对 19 | } 20 | 21 | return tagValue.indexOf(value) > -1 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /packages/is_email.js: -------------------------------------------------------------------------------- 1 | const { 2 | isString 3 | } = require('../utils') 4 | 5 | const reg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 6 | 7 | module.exports = { 8 | tagName: "IsEmail", 9 | message (field, value, opts) { 10 | return `${field} is not a email` 11 | }, 12 | validate (field, value, opts) { 13 | const { tagValue } = opts; 14 | if (!tagValue || !value) { 15 | return true; 16 | } 17 | 18 | if (isString(value)) { 19 | return !!value.match(reg) 20 | } 21 | 22 | return false 23 | } 24 | } -------------------------------------------------------------------------------- /packages/is_url.js: -------------------------------------------------------------------------------- 1 | const { 2 | isString 3 | } = require('../utils') 4 | 5 | module.exports = { 6 | tagName: "IsURL", 7 | message (field, value, opts) { 8 | return `${field} is not a url` 9 | }, 10 | validate (field, value, opts) { 11 | // 暂不支持 下面属性 12 | // allowDataUrl 是否是数据地址 base64 13 | // allowLocal 是否是本地url 14 | // schemes 15 | const { tagValue, allowDataUrl = false, allowLocal = false, schemes = ['http', 'https'] } = opts; 16 | if (!tagValue || !value) { 17 | return true; 18 | } 19 | 20 | if (!isString(value)) { 21 | return false; 22 | } 23 | // https://gist.github.com/dperini/729294 24 | 25 | let regex = 26 | "^" + 27 | // protocol identifier 28 | "(?:(?:" + schemes.join("|") + ")://)" + 29 | // user:pass authentication 30 | "(?:\\S+(?::\\S*)?@)?" + 31 | "(?:"; 32 | 33 | let tld = "(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))"; 34 | 35 | if (allowLocal) { 36 | tld += "?"; 37 | } else { 38 | regex += 39 | // IP address exclusion 40 | // private & local networks 41 | "(?!(?:10|127)(?:\\.\\d{1,3}){3})" + 42 | "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" + 43 | "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})"; 44 | } 45 | 46 | regex += 47 | // IP address dotted notation octets 48 | // excludes loopback network 0.0.0.0 49 | // excludes reserved space >= 224.0.0.0 50 | // excludes network & broacast addresses 51 | // (first & last IP address of each class) 52 | "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + 53 | "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + 54 | "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + 55 | "|" + 56 | // host name 57 | "(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)" + 58 | // domain name 59 | "(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*" + 60 | tld + 61 | ")" + 62 | // port number 63 | "(?::\\d{2,5})?" + 64 | // resource path 65 | "(?:[/?#]\\S*)?" + 66 | "$"; 67 | 68 | if (allowDataUrl) { 69 | // RFC 2397 70 | let mediaType = "\\w+\\/[-+.\\w]+(?:;[\\w=]+)*"; 71 | let urlChar = "[A-Za-z0-9-_.!~\\*'();\\/?:@&=+$,%]*"; 72 | let dataUrl = "data:(?:"+mediaType+")?(?:;base64)?,"+urlChar; 73 | regex = "(?:"+regex+")|(?:^"+dataUrl+"$)"; 74 | } 75 | 76 | return !!value.match(regex) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/max_length.js: -------------------------------------------------------------------------------- 1 | const { 2 | isArray, 3 | isString 4 | } = require('../utils') 5 | 6 | module.exports = { 7 | tagName: "MaxLength", 8 | message (field, value, opts) { 9 | const { tagValue = 0 } = opts; 10 | return `Max length of ${field} is ${tagValue}` 11 | }, 12 | validate (field, value, opts) { 13 | const { tagValue = 0 } = opts; 14 | if (!value) { 15 | return true; 16 | } 17 | // Array 18 | if (isArray(value) && value.length <= tagValue) { 19 | return true; 20 | } 21 | // String 22 | if (isString(value) && value.length <= tagValue) { 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/max_num.js: -------------------------------------------------------------------------------- 1 | const { 2 | isNumber 3 | } = require('../utils') 4 | 5 | module.exports = { 6 | tagName: "MaxNum", 7 | message (field, value, opts) { 8 | const { tagValue = 0 } = opts; 9 | return `Max value of ${field} is ${tagValue}` 10 | }, 11 | validate (field, value, opts) { 12 | const { tagValue = 0 } = opts; 13 | if (!value) { 14 | return true; 15 | } 16 | if (isNumber(value) && value <= tagValue) { 17 | return true; 18 | } 19 | 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/min_length.js: -------------------------------------------------------------------------------- 1 | const { 2 | isArray, 3 | isString 4 | } = require('../utils') 5 | 6 | module.exports = { 7 | tagName: "MinLength", 8 | message (field, value, opts) { 9 | const { tagValue = 0 } = opts; 10 | return `Min length of ${field} is ${tagValue}` 11 | }, 12 | validate (field, value, opts) { 13 | const { tagValue = 0 } = opts; 14 | if (!value && value !== '') { 15 | return true; 16 | } 17 | // Array 18 | if (isArray(value) && value.length >= tagValue) { 19 | return true; 20 | } 21 | // String 22 | if (isString(value) && value.length >= tagValue) { 23 | return true; 24 | } 25 | 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/min_num.js: -------------------------------------------------------------------------------- 1 | const { 2 | isNumber 3 | } = require('../utils') 4 | 5 | module.exports = { 6 | tagName: "MinNum", 7 | message (field, value, opts) { 8 | const { tagValue = 0 } = opts; 9 | return `Min value of ${field} is ${tagValue}` 10 | }, 11 | validate (field, value, opts) { 12 | const { tagValue = 0 } = opts; 13 | 14 | if (!value) { 15 | return true; 16 | } 17 | if (isNumber(value) && value >= tagValue) { 18 | return true; 19 | } 20 | 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/not_empty.js: -------------------------------------------------------------------------------- 1 | const { 2 | isArray, 3 | isString, 4 | isObject 5 | } = require('../utils') 6 | 7 | module.exports = { 8 | tagName: "NotEmpty", 9 | message (field, value, opts) { 10 | return `${field} is empty` 11 | }, 12 | validate (field, value, opts) { 13 | const { tagValue = false } = opts; 14 | if (!tagValue || value === null || value === undefined) { 15 | return true; 16 | } 17 | // Array is empty 18 | if (isArray(value) && value.length > 0) { 19 | return true; 20 | } 21 | // String is empty 22 | if (isString(value) && value.length > 0) { 23 | return true; 24 | } 25 | // Object is empty 26 | if (isObject(value) && Object.keys(value).length > 0) { 27 | return true; 28 | } 29 | 30 | return false; 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /packages/regexp.js: -------------------------------------------------------------------------------- 1 | const { 2 | isString, 3 | isRegExp 4 | } = require('../utils') 5 | 6 | module.exports = { 7 | tagName: "RegExp", 8 | message (field, value, opts) { 9 | return `Test ${field} is failed` 10 | }, 11 | validate (field, value, opts) { 12 | const { tagValue } = opts; 13 | let reg = tagValue; 14 | 15 | if (isString(reg)) { 16 | reg = new RegExp(reg); 17 | } 18 | 19 | if (isRegExp(reg)) { 20 | return reg.test(value); 21 | } 22 | 23 | return true 24 | } 25 | } -------------------------------------------------------------------------------- /packages/required.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tagName: "Required", 3 | message (field, value, opts) { 4 | return `${field} is required` 5 | }, 6 | validate (field, value, opts) { 7 | const { tagValue = false } = opts; 8 | if (!tagValue) { 9 | return true; 10 | } 11 | if (value === null || value === undefined) { 12 | return false; 13 | } 14 | return true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/default.test.js: -------------------------------------------------------------------------------- 1 | const Validator = require('./../index'); 2 | 3 | describe("默认Rule校验", () => { 4 | it("Required Success", () => { 5 | let ret = null 6 | try { 7 | const v = new Validator({ 8 | field: [{ Required: true }] 9 | }) 10 | ret = v.validate({field: "ok"}) 11 | } finally { 12 | expect(JSON.stringify(ret)) 13 | .toBe(JSON.stringify(null)); 14 | } 15 | 16 | ret = Validator.validate({ 17 | field: [{ Required: true }] 18 | }, {field: "ok"}); 19 | 20 | expect(JSON.stringify(ret)) 21 | .toBe(JSON.stringify(null)); 22 | }); 23 | 24 | it("Required Failed", () => { 25 | let ret = null 26 | try { 27 | const v = new Validator({ 28 | field: [{ Required: true }] 29 | }) 30 | ret = v.validate({}) 31 | } finally { 32 | expect(JSON.stringify(ret)) 33 | .toBe(JSON.stringify({ 34 | "field": "field is required" 35 | })); 36 | } 37 | 38 | ret = Validator.validate({ 39 | field: [{ Required: true }] 40 | }, {}); 41 | 42 | expect(JSON.stringify(ret)) 43 | .toBe(JSON.stringify({ 44 | "field": "field is required" 45 | })); 46 | }); 47 | 48 | it("Enum Success", () => { 49 | let ret = null 50 | try { 51 | const v = new Validator({ 52 | field: [{ Enum: [1, 2, 3]}] 53 | }) 54 | ret = v.validate({field: 1}) 55 | } finally { 56 | expect(JSON.stringify(ret)) 57 | .toBe(JSON.stringify(null)); 58 | } 59 | 60 | ret = Validator.validate({ 61 | field: [{ Enum: [1, 2, 3]}] 62 | }, {field: 1}); 63 | 64 | expect(JSON.stringify(ret)) 65 | .toBe(JSON.stringify(null)); 66 | }); 67 | 68 | it("Enum Failed", () => { 69 | let ret = null 70 | try { 71 | const v = new Validator({ 72 | field: [{ Enum: [1, 2, 3]}] 73 | }) 74 | ret = v.validate({field: 1111}) 75 | } finally { 76 | expect(JSON.stringify(ret)) 77 | .toBe(JSON.stringify({ 78 | "field": "field with value 1111 is incorrect" 79 | })); 80 | } 81 | 82 | ret = Validator.validate({ 83 | field: [{ Enum: [1, 2, 3]}] 84 | }, {field: 1111}); 85 | 86 | expect(JSON.stringify(ret)) 87 | .toBe(JSON.stringify({ 88 | "field": "field with value 1111 is incorrect" 89 | })); 90 | }); 91 | 92 | it("NotEmpty String Success", () => { 93 | let ret = null 94 | try { 95 | const v = new Validator({ 96 | field: [{ NotEmpty: true }] 97 | }) 98 | ret = v.validate({field: 'test'}) 99 | } finally { 100 | expect(JSON.stringify(ret)) 101 | .toBe(JSON.stringify(null)); 102 | } 103 | 104 | ret = Validator.validate({ 105 | field: [{ NotEmpty: true }] 106 | }, {field: 'test'}); 107 | 108 | expect(JSON.stringify(ret)) 109 | .toBe(JSON.stringify(null)); 110 | }); 111 | 112 | it("NotEmpty String Failed", () => { 113 | let ret = null 114 | try { 115 | const v = new Validator({ 116 | field: [{ NotEmpty: true }] 117 | }) 118 | ret = v.validate({field: ''}) 119 | } finally { 120 | expect(JSON.stringify(ret)) 121 | .toBe(JSON.stringify({"field":"field is empty"})); 122 | } 123 | 124 | ret = Validator.validate({ 125 | field: [{ NotEmpty: true }] 126 | }, {field: ''}); 127 | 128 | expect(JSON.stringify(ret)) 129 | .toBe(JSON.stringify({"field":"field is empty"})); 130 | }); 131 | 132 | it("NotEmpty Array Success", () => { 133 | let ret = null 134 | try { 135 | const v = new Validator({ 136 | field: [{ NotEmpty: true }] 137 | }) 138 | ret = v.validate({field: [1]}) 139 | } finally { 140 | expect(JSON.stringify(ret)) 141 | .toBe(JSON.stringify(null)); 142 | } 143 | 144 | ret = Validator.validate({ 145 | field: [{ NotEmpty: true }] 146 | }, {field: [1]}); 147 | 148 | expect(JSON.stringify(ret)) 149 | .toBe(JSON.stringify(null)); 150 | }); 151 | 152 | it("NotEmpty Array Failed", () => { 153 | let ret = null 154 | try { 155 | const v = new Validator({ 156 | field: [{ NotEmpty: true }] 157 | }) 158 | ret = v.validate({field: []}) 159 | } finally { 160 | expect(JSON.stringify(ret)) 161 | .toBe(JSON.stringify({"field":"field is empty"})); 162 | } 163 | 164 | ret = Validator.validate({ 165 | field: [{ NotEmpty: true }] 166 | }, {field: []}); 167 | 168 | expect(JSON.stringify(ret)) 169 | .toBe(JSON.stringify({"field":"field is empty"})); 170 | }); 171 | 172 | it("NotEmpty Object Success", () => { 173 | let ret = null 174 | try { 175 | const v = new Validator({ 176 | field: [{ NotEmpty: true }] 177 | }) 178 | ret = v.validate({field: {a : 1}}) 179 | } finally { 180 | expect(JSON.stringify(ret)) 181 | .toBe(JSON.stringify(null)); 182 | } 183 | 184 | ret = Validator.validate({ 185 | field: [{ NotEmpty: true }] 186 | }, {field: {a : 1}}); 187 | 188 | expect(JSON.stringify(ret)) 189 | .toBe(JSON.stringify(null)); 190 | }); 191 | 192 | it("NotEmpty Object Failed", () => { 193 | let ret = null 194 | try { 195 | const v = new Validator({ 196 | field: [{ NotEmpty: true }] 197 | }) 198 | ret = v.validate({field: {}}) 199 | } finally { 200 | expect(JSON.stringify(ret)) 201 | .toBe(JSON.stringify({"field":"field is empty"})); 202 | } 203 | 204 | ret = Validator.validate({ 205 | field: [{ NotEmpty: true }] 206 | }, {field: {}}); 207 | 208 | expect(JSON.stringify(ret)) 209 | .toBe(JSON.stringify({"field":"field is empty"})); 210 | }); 211 | 212 | it("MaxLength String Success", () => { 213 | let ret = null 214 | try { 215 | const v = new Validator({ 216 | field: [{ MaxLength: 10 }] 217 | }) 218 | ret = v.validate({field: '111'}) 219 | } finally { 220 | expect(JSON.stringify(ret)) 221 | .toBe(JSON.stringify(null)); 222 | } 223 | 224 | ret = Validator.validate({ 225 | field: [{ NotEmpty: true }] 226 | }, {field: '111'}); 227 | 228 | expect(JSON.stringify(ret)) 229 | .toBe(JSON.stringify(null)); 230 | }); 231 | 232 | it("MaxLength String Failed", () => { 233 | let ret = null 234 | try { 235 | const v = new Validator({ 236 | field: [{ MaxLength: 10 }] 237 | }) 238 | ret = v.validate({field: '01234567890'}) 239 | } finally { 240 | expect(JSON.stringify(ret)) 241 | .toBe(JSON.stringify({"field":"Max length of field is 10"})); 242 | } 243 | 244 | ret = Validator.validate({ 245 | field: [{ MaxLength: 10 }] 246 | }, {field: '01234567890'}); 247 | 248 | expect(JSON.stringify(ret)) 249 | .toBe(JSON.stringify({"field":"Max length of field is 10"})); 250 | }); 251 | 252 | it("MaxLength Array Success", () => { 253 | let ret = null 254 | try { 255 | const v = new Validator({ 256 | field: [{ MaxLength: 10 }] 257 | }) 258 | ret = v.validate({field: [1, 2, 3]}) 259 | } finally { 260 | expect(JSON.stringify(ret)) 261 | .toBe(JSON.stringify(null)); 262 | } 263 | 264 | ret = Validator.validate({ 265 | field: [{ MaxLength: 10 }] 266 | }, {field: [1, 2, 3]}); 267 | 268 | expect(JSON.stringify(ret)) 269 | .toBe(JSON.stringify(null)); 270 | }); 271 | 272 | it("MaxLength Array Failed", () => { 273 | let ret = null 274 | try { 275 | const v = new Validator({ 276 | field: [{ MaxLength: 10 }] 277 | }) 278 | ret = v.validate({field: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]}) 279 | } finally { 280 | expect(JSON.stringify(ret)) 281 | .toBe(JSON.stringify({"field":"Max length of field is 10"})); 282 | } 283 | 284 | ret = Validator.validate({ 285 | field: [{ MaxLength: 10 }] 286 | }, {field: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]}); 287 | 288 | expect(JSON.stringify(ret)) 289 | .toBe(JSON.stringify({"field":"Max length of field is 10"})); 290 | }); 291 | 292 | it("MinLength String Success", () => { 293 | let ret = null 294 | try { 295 | const v = new Validator({ 296 | field: [{ MinLength: 10 }] 297 | }) 298 | ret = v.validate({field: '01234567890'}) 299 | } finally { 300 | expect(JSON.stringify(ret)) 301 | .toBe(JSON.stringify(null)); 302 | } 303 | 304 | ret = Validator.validate({ 305 | field: [{ MinLength: 10 }] 306 | }, {field: '01234567890'}); 307 | 308 | expect(JSON.stringify(ret)) 309 | .toBe(JSON.stringify(null)); 310 | }); 311 | 312 | it("MinLength String Failed", () => { 313 | let ret = null 314 | try { 315 | const v = new Validator({ 316 | field: [{ MinLength: 10 }] 317 | }) 318 | ret = v.validate({field: '0'}) 319 | } finally { 320 | expect(JSON.stringify(ret)) 321 | .toBe(JSON.stringify({"field":"Min length of field is 10"})); 322 | } 323 | 324 | ret = Validator.validate({ 325 | field: [{ MinLength: 10 }] 326 | }, {field: '0'}); 327 | 328 | expect(JSON.stringify(ret)) 329 | .toBe(JSON.stringify({"field":"Min length of field is 10"})); 330 | }); 331 | 332 | it("MinLength Array Success", () => { 333 | let ret = null 334 | try { 335 | const v = new Validator({ 336 | field: [{ MinLength: 10 }] 337 | }) 338 | ret = v.validate({field: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]}) 339 | } finally { 340 | expect(JSON.stringify(ret)) 341 | .toBe(JSON.stringify(null)); 342 | } 343 | 344 | ret = Validator.validate({ 345 | field: [{ MinLength: 10 }] 346 | }, {field: [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1]}); 347 | 348 | expect(JSON.stringify(ret)) 349 | .toBe(JSON.stringify(null)); 350 | }); 351 | 352 | it("MinLength Array Failed", () => { 353 | let ret = null 354 | try { 355 | const v = new Validator({ 356 | field: [{ MinLength: 10 }] 357 | }) 358 | ret = v.validate({field: [1, 2, 3]}) 359 | } finally { 360 | expect(JSON.stringify(ret)) 361 | .toBe(JSON.stringify({"field":"Min length of field is 10"})); 362 | } 363 | 364 | ret = Validator.validate({ 365 | field: [{ MinLength: 10 }] 366 | }, {field: [1, 2, 3, ]}); 367 | 368 | expect(JSON.stringify(ret)) 369 | .toBe(JSON.stringify({"field":"Min length of field is 10"})); 370 | }); 371 | 372 | it("MaxNum Success", () => { 373 | let ret = null 374 | try { 375 | const v = new Validator({ 376 | field: [{ MaxNum: 10 }] 377 | }) 378 | ret = v.validate({field: 9}) 379 | } finally { 380 | expect(JSON.stringify(ret)) 381 | .toBe(JSON.stringify(null)); 382 | } 383 | 384 | ret = Validator.validate({ 385 | field: [{ MaxNum: 10 }] 386 | }, {field: 9}); 387 | 388 | expect(JSON.stringify(ret)) 389 | .toBe(JSON.stringify(null)); 390 | }); 391 | 392 | it("MaxNum Failed", () => { 393 | let ret = null 394 | try { 395 | const v = new Validator({ 396 | field: [{ MaxNum: 10 }] 397 | }) 398 | ret = v.validate({field: 11}) 399 | } finally { 400 | expect(JSON.stringify(ret)) 401 | .toBe(JSON.stringify({"field":"Max value of field is 10"})); 402 | } 403 | 404 | ret = Validator.validate({ 405 | field: [{ MaxNum: 10 }] 406 | }, {field: 11}); 407 | 408 | expect(JSON.stringify(ret)) 409 | .toBe(JSON.stringify({"field":"Max value of field is 10"})); 410 | }); 411 | 412 | it("MinNum Success", () => { 413 | let ret = null 414 | try { 415 | const v = new Validator({ 416 | field: [{ MinNum: 10 }] 417 | }) 418 | ret = v.validate({field: 19}) 419 | } finally { 420 | expect(JSON.stringify(ret)) 421 | .toBe(JSON.stringify(null)); 422 | } 423 | 424 | ret = Validator.validate({ 425 | field: [{ MinNum: 10 }] 426 | }, {field: 19}); 427 | 428 | expect(JSON.stringify(ret)) 429 | .toBe(JSON.stringify(null)); 430 | }); 431 | 432 | it("MinNum Failed", () => { 433 | let ret = null 434 | try { 435 | const v = new Validator({ 436 | field: [{ MinNum: 10 }] 437 | }) 438 | ret = v.validate({field: 6}) 439 | } finally { 440 | expect(JSON.stringify(ret)) 441 | .toBe(JSON.stringify({"field":"Min value of field is 10"})); 442 | } 443 | 444 | ret = Validator.validate({ 445 | field: [{ MinNum: 10 }] 446 | }, {field: 6}); 447 | 448 | expect(JSON.stringify(ret)) 449 | .toBe(JSON.stringify({"field":"Min value of field is 10"})); 450 | }); 451 | 452 | it("RegExp Success", () => { 453 | let ret = null 454 | try { 455 | const v = new Validator({ 456 | field: [{ RegExp: /^a/g }] 457 | }) 458 | ret = v.validate({field: 'atest'}) 459 | } finally { 460 | expect(JSON.stringify(ret)) 461 | .toBe(JSON.stringify(null)); 462 | } 463 | 464 | ret = Validator.validate({ 465 | field: [{ RegExp: /^a/g }] 466 | }, {field: 'atest'}); 467 | 468 | expect(JSON.stringify(ret)) 469 | .toBe(JSON.stringify(null)); 470 | }); 471 | 472 | it("RegExp Success", () => { 473 | let ret = null 474 | try { 475 | const v = new Validator({ 476 | field: [{ RegExp: '^a' }] 477 | }) 478 | ret = v.validate({field: 'atest'}) 479 | } finally { 480 | expect(JSON.stringify(ret)) 481 | .toBe(JSON.stringify(null)); 482 | } 483 | 484 | ret = Validator.validate({ 485 | field: [{ RegExp: '^a' }] 486 | }, {field: 'atest'}); 487 | 488 | expect(JSON.stringify(ret)) 489 | .toBe(JSON.stringify(null)); 490 | }); 491 | 492 | it("RegExp Failed", () => { 493 | let ret = null 494 | try { 495 | const v = new Validator({ 496 | field: [{ RegExp: /^a/g }] 497 | }) 498 | ret = v.validate({field: 'test'}) 499 | } finally { 500 | expect(JSON.stringify(ret)) 501 | .toBe(JSON.stringify({"field":"Test field is failed"})); 502 | } 503 | 504 | ret = Validator.validate({ 505 | field: [{ RegExp: '^a' }] 506 | }, {field: 'test'}); 507 | 508 | expect(JSON.stringify(ret)) 509 | .toBe(JSON.stringify({"field":"Test field is failed"})); 510 | }); 511 | 512 | it("IsEmail Success", () => { 513 | let ret = null 514 | try { 515 | const v = new Validator({ 516 | field: { IsEmail: true } 517 | }) 518 | ret = v.validate({field: 'benmo1602@gmail.com'}) 519 | } finally { 520 | expect(JSON.stringify(ret)).toBe(JSON.stringify(null)); 521 | } 522 | ret = Validator.validate({field: [{ IsEmail: true }]}, {field: 'benmo1602@gmail.com'}); 523 | 524 | expect(JSON.stringify(ret)) 525 | .toBe(JSON.stringify(null)); 526 | }); 527 | 528 | it("IsEmail Failed", () => { 529 | let ret = null 530 | try { 531 | const v = new Validator({ 532 | field: { IsEmail: true } 533 | }) 534 | ret = v.validate({field: 'benmo1602.com'}) 535 | } finally { 536 | expect(JSON.stringify(ret)).toBe(JSON.stringify({field: 'field is not a email'})); 537 | } 538 | ret = Validator.validate({field: [{ IsEmail: true }]}, {field: 'benmo1602.com'}); 539 | 540 | expect(JSON.stringify(ret)).toBe(JSON.stringify({field: 'field is not a email'})); 541 | }); 542 | 543 | it("IsURL Success", () => { 544 | let ret = null 545 | try { 546 | const v = new Validator({ 547 | field: { IsURL: true } 548 | }) 549 | ret = v.validate({field: 'https://gist.github.com/dperini/729294'}) 550 | } finally { 551 | expect(JSON.stringify(ret)).toBe(JSON.stringify(null)); 552 | } 553 | ret = Validator.validate({field: [{ IsURL: true }]}, {field: 'https://gist.github.com/dperini/729294'}); 554 | 555 | expect(JSON.stringify(ret)) 556 | .toBe(JSON.stringify(null)); 557 | }); 558 | 559 | it("IsURL Failed", () => { 560 | let ret = null 561 | try { 562 | const v = new Validator({ 563 | field: { IsURL: true } 564 | }) 565 | ret = v.validate({field: 'gist.github.com/dperini/729294'}) 566 | } finally { 567 | expect(JSON.stringify(ret)).toBe(JSON.stringify({field: 'field is not a url'})); 568 | } 569 | ret = Validator.validate({field: [{ IsURL: true }]}, {field: 'gist.github.com/dperini/729294'}); 570 | expect(JSON.stringify(ret)).toBe(JSON.stringify({field: 'field is not a url'})); 571 | }); 572 | }) 573 | -------------------------------------------------------------------------------- /test/plugin.test.js: -------------------------------------------------------------------------------- 1 | const Validator = require('./../index'); 2 | 3 | describe("插件校验", () => { 4 | it("usePlugin校验,空参数", () => { 5 | let ret = null 6 | try { 7 | ret = Validator.usePlugin() 8 | } catch (err) { 9 | expect(err.message).toBe('Parameter must be Object ant can not be empty'); 10 | } finally { 11 | expect(ret).toBe(null); 12 | } 13 | }); 14 | 15 | it("usePlugin校验,非法参数 []", () => { 16 | let ret = null 17 | try { 18 | ret = Validator.usePlugin([]) 19 | } catch (err) { 20 | expect(err.message).toBe('Parameter must be Object ant can not be empty'); 21 | } finally { 22 | expect(ret).toBe(null); 23 | } 24 | }); 25 | 26 | it("usePlugin校验,非法参数 Date", () => { 27 | let ret = null 28 | try { 29 | ret = Validator.usePlugin(new Date()) 30 | } catch (err) { 31 | expect(err.message).toBe('Parameter must be Object ant can not be empty'); 32 | } finally { 33 | expect(ret).toBe(null); 34 | } 35 | }); 36 | 37 | it("usePlugin校验,非法参数 ''", () => { 38 | let ret = null 39 | try { 40 | ret = Validator.usePlugin("") 41 | } catch (err) { 42 | expect(err.message).toBe('Parameter must be Object ant can not be empty'); 43 | } finally { 44 | expect(ret).toBe(null); 45 | } 46 | }); 47 | 48 | it("usePlugin校验,非法参数 缺失tagName", () => { 49 | let ret = null 50 | try { 51 | ret = Validator.usePlugin({ 52 | 53 | }) 54 | } catch (err) { 55 | expect(err.message).toBe('tagName must be String and not empty.'); 56 | } finally { 57 | expect(ret).toBe(null); 58 | } 59 | }); 60 | 61 | it("usePlugin校验,非法参数 缺失message", () => { 62 | let ret = null 63 | try { 64 | ret = Validator.usePlugin({ 65 | tagName: "jest" 66 | }) 67 | } catch (err) { 68 | expect(err.message).toBe('message and validate must be Function.'); 69 | } finally { 70 | expect(ret).toBe(null); 71 | } 72 | }); 73 | 74 | it("usePlugin校验,非法参数 message", () => { 75 | let ret = null 76 | try { 77 | ret = Validator.usePlugin({ 78 | tagName: "jest", 79 | message: "" 80 | }) 81 | } catch (err) { 82 | expect(err.message).toBe('message and validate must be Function.'); 83 | } finally { 84 | expect(ret).toBe(null); 85 | } 86 | }); 87 | 88 | it("usePlugin校验,非法参数 缺失validate", () => { 89 | let ret = null 90 | try { 91 | ret = Validator.usePlugin({ 92 | tagName: "jest", 93 | message() {} 94 | }) 95 | } catch (err) { 96 | expect(err.message).toBe('message and validate must be Function.'); 97 | } finally { 98 | expect(ret).toBe(null); 99 | } 100 | }); 101 | 102 | it("usePlugin校验,非法参数 validate", () => { 103 | let ret = null 104 | try { 105 | ret = Validator.usePlugin({ 106 | tagName: "jest", 107 | message() {}, 108 | validate: "" 109 | }) 110 | } catch (err) { 111 | expect(err.message).toBe('message and validate must be Function.'); 112 | } finally { 113 | expect(ret).toBe(null); 114 | } 115 | }); 116 | 117 | it("usePlugin校验,All right", () => { 118 | let ret = null 119 | try { 120 | ret = Validator.usePlugin({ 121 | tagName: "jest", 122 | message() {}, 123 | validate() {} 124 | }) 125 | } finally { 126 | expect(ret).toBe(undefined); 127 | } 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/rule.test.js: -------------------------------------------------------------------------------- 1 | const Validator = require('./../index'); 2 | 3 | describe("Rule校验", () => { 4 | it("tag没有注册", () => { 5 | let ret = null 6 | try { 7 | const v = new Validator({ 8 | field: [{ required: true }] 9 | }) 10 | ret = v.validate({}) 11 | } catch (e) { 12 | expect(e.message) 13 | .toBe('Plugin with tagName = required is not registered!'); 14 | } 15 | }); 16 | 17 | it("一个rule中一个tag", () => { 18 | let ret = null 19 | try { 20 | const v = new Validator({ 21 | field: [{ Required: true }] 22 | }) 23 | ret = v.validate({}) 24 | } finally { 25 | expect(JSON.stringify(ret)) 26 | .toBe(JSON.stringify({ 27 | "field": "field is required" 28 | })); 29 | } 30 | }); 31 | 32 | it("单一值校验", () => { 33 | let ret = null 34 | try { 35 | const v = new Validator({ Enum: [true, false] }) 36 | ret = v.validate(1) 37 | } finally { 38 | expect(JSON.stringify(ret)) 39 | .toBe(JSON.stringify("Input with value 1 is incorrect")); 40 | } 41 | }); 42 | 43 | it("一个rule中多个tag", () => { 44 | let ret1 = null 45 | let ret2 = null 46 | try { 47 | const v = new Validator({ 48 | field: [{ Required: true, NotEmpty: true }] 49 | }) 50 | ret1 = v.validate({}) 51 | ret2 = v.validate({ field: '' }) 52 | } finally { 53 | expect(JSON.stringify(ret1)) 54 | .toBe(JSON.stringify({ 55 | "field": "field is required" 56 | })); 57 | expect(JSON.stringify(ret2)) 58 | .toBe(JSON.stringify({ 59 | "field": "field is empty" 60 | })); 61 | } 62 | }); 63 | 64 | it("多个rule中多个tag", () => { 65 | let ret1 = null 66 | let ret2 = null 67 | try { 68 | const v = new Validator({ 69 | field: [{ Required: true }, { NotEmpty: true }] 70 | }) 71 | ret1 = v.validate({}) 72 | ret2 = v.validate({ field: '' }) 73 | } finally { 74 | expect(JSON.stringify(ret1)) 75 | .toBe(JSON.stringify({ 76 | "field": "field is required" 77 | })); 78 | expect(JSON.stringify(ret2)) 79 | .toBe(JSON.stringify({ 80 | "field": "field is empty" 81 | })); 82 | } 83 | }); 84 | 85 | it("一个rule中多个tag, 自定义覆盖消息", () => { 86 | let ret1 = null 87 | let ret2 = null 88 | try { 89 | const v = new Validator({ 90 | field: [{ Required: true, NotEmpty: true, message: "覆盖消息" }] 91 | }) 92 | ret1 = v.validate({}) 93 | ret2 = v.validate({ field: '' }) 94 | } finally { 95 | expect(JSON.stringify(ret1)) 96 | .toBe(JSON.stringify({ 97 | "field": "覆盖消息" 98 | })); 99 | expect(JSON.stringify(ret2)) 100 | .toBe(JSON.stringify({ 101 | "field": "覆盖消息" 102 | })); 103 | } 104 | }); 105 | 106 | it("自定义规则覆盖消息", () => { 107 | let ret = null 108 | try { 109 | const v = new Validator({ 110 | field: [{ Required: true, validate() { return false } }] 111 | }) 112 | ret = v.validate({ field: '' }) 113 | } finally { 114 | expect(JSON.stringify(ret)) 115 | .toBe(JSON.stringify({ 116 | "field": "field is required" 117 | })); 118 | } 119 | }); 120 | 121 | it("嵌套对象,一层属性", () => { 122 | let ret = null 123 | try { 124 | const v = new Validator({ 125 | field: [{ 126 | Required: true, $fields: { 127 | field1: [{ Required: true }] 128 | } 129 | }] 130 | }) 131 | ret = v.validate({ field: {} }) 132 | } finally { 133 | expect(JSON.stringify(ret)) 134 | .toBe(JSON.stringify({ 135 | "field": { field1: 'field1 is required' } 136 | })); 137 | } 138 | }); 139 | 140 | it("嵌套对象,一层属性多属性", () => { 141 | let ret = null 142 | try { 143 | const v = new Validator({ 144 | field: [{ 145 | Required: true, $fields: { 146 | field1: [{ Required: true }], 147 | field2: [{ Required: true }] 148 | } 149 | }] 150 | }) 151 | ret = v.validate({ field: {} }) 152 | } finally { 153 | expect(JSON.stringify(ret)) 154 | .toBe(JSON.stringify({ 155 | "field": { 156 | field1: 'field1 is required', 157 | field2: 'field2 is required' 158 | } 159 | })); 160 | } 161 | }); 162 | 163 | it("嵌套对象,二层属性", () => { 164 | let ret = null 165 | try { 166 | const v = new Validator({ 167 | field: [{ 168 | Required: true, $fields: { 169 | field1: [{ 170 | Required: true, $fields: { 171 | field2: [{ Required: true }] 172 | } 173 | }] 174 | } 175 | }] 176 | }) 177 | ret = v.validate({ field: { field1: {} } }) 178 | } finally { 179 | expect(JSON.stringify(ret)) 180 | .toBe(JSON.stringify({ 181 | "field": { field1: { field2: 'field2 is required' } } 182 | })); 183 | } 184 | }); 185 | 186 | it("嵌套对象,二层属性多属性", () => { 187 | let ret = null 188 | try { 189 | const v = new Validator({ 190 | field: [{ 191 | Required: true, $fields: { 192 | field1: [{ 193 | Required: true, $fields: { 194 | field2: [{ Required: true }] 195 | } 196 | }], 197 | field2: [{ 198 | Required: true, $fields: { 199 | field2: [{ Required: true }] 200 | } 201 | }] 202 | } 203 | }] 204 | }) 205 | ret = v.validate({ field: { field1: {} } }) 206 | } finally { 207 | expect(JSON.stringify(ret)) 208 | .toBe(JSON.stringify({ 209 | "field": { 210 | field1: { 211 | field2: 'field2 is required' 212 | }, 213 | field2: 'field2 is required' 214 | } 215 | })); 216 | } 217 | }); 218 | 219 | it("嵌套数组 基本元素数组 多tag", () => { 220 | let ret = null 221 | try { 222 | const v = new Validator({ 223 | field: [{ $fields: { Required: true, Enum: [1, 2, 3] } }] 224 | }) 225 | ret = v.validate({ field: [4, 1, undefined] }) 226 | } finally { 227 | expect(JSON.stringify(ret)) 228 | .toBe(JSON.stringify({ 229 | "field": ["field with value 4 is incorrect",null,"field is required"] 230 | })); 231 | } 232 | }); 233 | 234 | it("嵌套数组 复合元素数组 数组内对象字段校验", () => { 235 | let ret = null 236 | try { 237 | const v = new Validator({ 238 | field: [{ 239 | $fields: { 240 | field1: [{ Required: true }] 241 | } 242 | }] 243 | }) 244 | ret = v.validate({ field: [{ field1: null }] }) 245 | } finally { 246 | expect(JSON.stringify(ret)) 247 | .toBe(JSON.stringify({ "field": [{ 248 | "field1": "field1 is required" 249 | }] })); 250 | } 251 | }); 252 | 253 | it("嵌套数组 复合元素数组 数组内对象多字段校验", () => { 254 | let ret = null 255 | try { 256 | const v = new Validator({ 257 | field: [{ 258 | $fields: { 259 | field1: [{ Required: true }], 260 | field2: [{ Required: true }] 261 | } 262 | }] 263 | }) 264 | ret = v.validate({ field: [{ field1: null }] }) 265 | } finally { 266 | expect(JSON.stringify(ret)) 267 | .toBe(JSON.stringify({ 268 | "field": [{ 269 | "field1": "field1 is required", 270 | "field2": "field2 is required" 271 | }] 272 | })); 273 | } 274 | }); 275 | 276 | it("嵌套数组 复合元素数组 数组内多元素多字段校验", () => { 277 | let ret = null 278 | try { 279 | const v = new Validator({ 280 | field: [{ 281 | $fields: { 282 | field1: [{ Required: true }], 283 | field2: [{ Required: true }] 284 | } 285 | }] 286 | }) 287 | ret = v.validate({ field: [{ field1: null }, { field2: "" }] }) 288 | } finally { 289 | expect(JSON.stringify(ret)) 290 | .toBe(JSON.stringify({ 291 | "field": [{ 292 | "field1": "field1 is required", 293 | "field2": "field2 is required" 294 | }, { 295 | "field1": "field1 is required" 296 | }] 297 | })); 298 | } 299 | }); 300 | 301 | it("嵌套数组 复合元素数组 数组内对象,数组混合校验", () => { 302 | let ret = null 303 | try { 304 | const v = new Validator({ 305 | field: [{ 306 | $fields: { 307 | field1: [{ Required: true }], 308 | field2: [{ Required: true }, { $fields: [{ Enum: [1, 2, 3] }] }] 309 | } 310 | }] 311 | }) 312 | ret = v.validate({ field: [{ field1: null }, { field2: [4] }] }) 313 | } finally { 314 | expect(JSON.stringify(ret)) 315 | .toBe(JSON.stringify({ 316 | "field": [{ 317 | "field1": "field1 is required", 318 | "field2": "field2 is required" 319 | }, { 320 | "field1": "field1 is required", 321 | "field2": ["field2 with value 4 is incorrect"] 322 | }] 323 | })); 324 | } 325 | }); 326 | 327 | it("嵌套数组 复合元素数组 多层嵌套", () => { 328 | let ret = null 329 | try { 330 | const v = new Validator({ 331 | field: [{ 332 | $fields: { 333 | field1: [{ Required: true }], 334 | field2: [{ Required: true }, { $fields: [{ Enum: [1, 2, 3] }] }] 335 | } 336 | }] 337 | }) 338 | ret = v.validate({ field: [{ field1: null }, { field2: [4] }] }) 339 | } finally { 340 | expect(JSON.stringify(ret)) 341 | .toBe(JSON.stringify({ 342 | "field": [{ 343 | "field1": "field1 is required", 344 | "field2": "field2 is required" 345 | }, { 346 | "field1": "field1 is required", 347 | "field2": ["field2 with value 4 is incorrect"] 348 | }] 349 | })); 350 | } 351 | }); 352 | }) -------------------------------------------------------------------------------- /test/schema.test.js: -------------------------------------------------------------------------------- 1 | const Validator = require('../index'); 2 | 3 | describe("Schema校验", () => { 4 | it("Validator构造器测试,空参数", () => { 5 | try { 6 | new Validator() 7 | } catch (err) { 8 | expect(err.message).toBe('Schema must be an Object or Array'); 9 | } 10 | }); 11 | 12 | it("Validator构造器测试,非法参数 null", () => { 13 | try { 14 | new Validator(null) 15 | } catch (err) { 16 | expect(err.message).toBe('Schema must be an Object or Array'); 17 | } 18 | }); 19 | 20 | it("Validator构造器测试,非法参数 ''", () => { 21 | try { 22 | new Validator('') 23 | } catch (err) { 24 | expect(err.message).toBe('Schema must be an Object or Array'); 25 | } 26 | }); 27 | 28 | it("Validator构造器测试,非法参数 Date", () => { 29 | try { 30 | new Validator(new Date()) 31 | } catch (err) { 32 | expect(err.message).toBe('Schema must be an Object or Array'); 33 | } 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/sign.rule.test.js: -------------------------------------------------------------------------------- 1 | const Validator = require('./../index'); 2 | 3 | describe("Rule校验", () => { 4 | it("tag没有注册", () => { 5 | let ret = null 6 | try { 7 | const v = new Validator({ 8 | field: { required: true } 9 | }) 10 | ret = v.validate({}) 11 | } catch (e) { 12 | expect(e.message) 13 | .toBe('Plugin with tagName = required is not registered!'); 14 | } 15 | }); 16 | 17 | it("一个rule中一个tag", () => { 18 | let ret = null 19 | try { 20 | const v = new Validator({ 21 | field: { Required: true } 22 | }) 23 | ret = v.validate({}) 24 | } finally { 25 | expect(JSON.stringify(ret)) 26 | .toBe(JSON.stringify({ 27 | "field": "field is required" 28 | })); 29 | } 30 | }); 31 | 32 | it("单一值校验", () => { 33 | let ret = null 34 | try { 35 | const v = new Validator({ Enum: [true, false] }) 36 | ret = v.validate(1) 37 | } finally { 38 | expect(JSON.stringify(ret)) 39 | .toBe(JSON.stringify("Input with value 1 is incorrect")); 40 | } 41 | }); 42 | 43 | it("一个rule中多个tag", () => { 44 | let ret1 = null 45 | let ret2 = null 46 | try { 47 | const v = new Validator({ 48 | field: { Required: true, NotEmpty: true } 49 | }) 50 | ret1 = v.validate({}) 51 | ret2 = v.validate({ field: '' }) 52 | } finally { 53 | expect(JSON.stringify(ret1)) 54 | .toBe(JSON.stringify({ 55 | "field": "field is required" 56 | })); 57 | expect(JSON.stringify(ret2)) 58 | .toBe(JSON.stringify({ 59 | "field": "field is empty" 60 | })); 61 | } 62 | }) 63 | 64 | it("一个rule中多个tag, 自定义覆盖消息", () => { 65 | let ret1 = null 66 | let ret2 = null 67 | try { 68 | const v = new Validator({ 69 | field: { Required: true, NotEmpty: true, message: "覆盖消息" } 70 | }) 71 | ret1 = v.validate({}) 72 | ret2 = v.validate({ field: '' }) 73 | } finally { 74 | expect(JSON.stringify(ret1)) 75 | .toBe(JSON.stringify({ 76 | "field": "覆盖消息" 77 | })); 78 | expect(JSON.stringify(ret2)) 79 | .toBe(JSON.stringify({ 80 | "field": "覆盖消息" 81 | })); 82 | } 83 | }); 84 | 85 | it("自定义规则覆盖消息", () => { 86 | let ret = null 87 | try { 88 | const v = new Validator({ 89 | field: { Required: true, validate() { return false } } 90 | }) 91 | ret = v.validate({ field: '' }) 92 | } finally { 93 | expect(JSON.stringify(ret)) 94 | .toBe(JSON.stringify({ 95 | "field": "field is required" 96 | })); 97 | } 98 | }); 99 | 100 | it("嵌套对象,一层属性", () => { 101 | let ret = null 102 | try { 103 | const v = new Validator({ 104 | field: [{ 105 | Required: true, $fields: { 106 | field1: { Required: true } 107 | } 108 | }] 109 | }) 110 | ret = v.validate({ field: {} }) 111 | } finally { 112 | expect(JSON.stringify(ret)) 113 | .toBe(JSON.stringify({ 114 | "field": { field1: 'field1 is required' } 115 | })); 116 | } 117 | }); 118 | 119 | it("嵌套对象,一层属性多属性", () => { 120 | let ret = null 121 | try { 122 | const v = new Validator({ 123 | field: { 124 | Required: true, $fields: { 125 | field1: { Required: true }, 126 | field2: { Required: true } 127 | } 128 | } 129 | }) 130 | ret = v.validate({ field: {} }) 131 | } finally { 132 | expect(JSON.stringify(ret)) 133 | .toBe(JSON.stringify({ 134 | "field": { 135 | field1: 'field1 is required', 136 | field2: 'field2 is required' 137 | } 138 | })); 139 | } 140 | }); 141 | 142 | it("嵌套对象,二层属性", () => { 143 | let ret = null 144 | try { 145 | const v = new Validator({ 146 | field: { 147 | Required: true, $fields: { 148 | field1: { 149 | Required: true, $fields: { 150 | field2: { Required: true } 151 | } 152 | } 153 | } 154 | } 155 | }) 156 | ret = v.validate({ field: { field1: {} } }) 157 | } finally { 158 | expect(JSON.stringify(ret)) 159 | .toBe(JSON.stringify({ 160 | "field": { field1: { field2: 'field2 is required' } } 161 | })); 162 | } 163 | }); 164 | 165 | it("嵌套对象,二层属性多属性", () => { 166 | let ret = null 167 | try { 168 | const v = new Validator({ 169 | field: { 170 | Required: true, $fields: { 171 | field1: { 172 | Required: true, $fields: { 173 | field2: { Required: true } 174 | } 175 | }, 176 | field2: { 177 | Required: true, $fields: { 178 | field2: { Required: true } 179 | } 180 | } 181 | } 182 | } 183 | }) 184 | ret = v.validate({ field: { field1: {} } }) 185 | } finally { 186 | expect(JSON.stringify(ret)) 187 | .toBe(JSON.stringify({ 188 | "field": { 189 | field1: { 190 | field2: 'field2 is required' 191 | }, 192 | field2: 'field2 is required' 193 | } 194 | })); 195 | } 196 | }); 197 | 198 | it("嵌套数组 基本元素数组 多tag", () => { 199 | let ret = null 200 | try { 201 | const v = new Validator({ 202 | field: { $fields: { Required: true, Enum: [1, 2, 3] } } 203 | }) 204 | ret = v.validate({ field: [4, 1, undefined] }) 205 | } finally { 206 | expect(JSON.stringify(ret)) 207 | .toBe(JSON.stringify({ 208 | "field": ["field with value 4 is incorrect", null, "field is required"] 209 | })); 210 | } 211 | }); 212 | 213 | it("嵌套数组 复合元素数组 数组内对象字段校验", () => { 214 | let ret = null 215 | try { 216 | const v = new Validator({ 217 | field: { 218 | $fields: { 219 | field1: { Required: true } 220 | } 221 | } 222 | }) 223 | ret = v.validate({ field: [{ field1: null }] }) 224 | } finally { 225 | expect(JSON.stringify(ret)) 226 | .toBe(JSON.stringify({"field":[{"field1":"field1 is required"}]})); 227 | } 228 | }); 229 | 230 | it("嵌套数组 复合元素数组 数组内对象多字段校验", () => { 231 | let ret = null 232 | try { 233 | const v = new Validator({ 234 | field: { 235 | $fields: { 236 | field1: { Required: true }, 237 | field2: { Required: true } 238 | } 239 | } 240 | }) 241 | ret = v.validate({ field: [{ field1: null }] }) 242 | } finally { 243 | expect(JSON.stringify(ret)) 244 | .toBe(JSON.stringify({ 245 | "field": [{"field1":"field1 is required","field2":"field2 is required"}] 246 | })); 247 | } 248 | }); 249 | 250 | it("嵌套数组 复合元素数组 数组内多元素多字段校验", () => { 251 | let ret = null 252 | try { 253 | const v = new Validator({ 254 | field: { 255 | $fields: { 256 | field1: { Required: true }, 257 | field2: { Required: true } 258 | } 259 | } 260 | }) 261 | ret = v.validate({ field: [{ field1: null }, { field2: "" }] }) 262 | } finally { 263 | expect(JSON.stringify(ret)) 264 | .toBe(JSON.stringify({ 265 | "field": [{ 266 | "field1": "field1 is required", 267 | "field2": "field2 is required" 268 | }, { 269 | "field1": "field1 is required" 270 | }] 271 | })); 272 | } 273 | }); 274 | 275 | it("嵌套数组 复合元素数组 数组内对象,数组混合校验", () => { 276 | let ret = null 277 | try { 278 | const v = new Validator({ 279 | field: { 280 | $fields: { 281 | field1: { Required: true }, 282 | field2: [{ Required: true }, { $fields: [{ Enum: [1, 2, 3] }] }] 283 | } 284 | } 285 | }) 286 | ret = v.validate({ field: [{ field1: null }, { field2: [4] }] }) 287 | } finally { 288 | expect(JSON.stringify(ret)) 289 | .toBe(JSON.stringify({ 290 | "field": [{ 291 | "field1": "field1 is required", 292 | "field2": "field2 is required" 293 | }, { 294 | "field1": "field1 is required", 295 | "field2": ["field2 with value 4 is incorrect"] 296 | }] 297 | })); 298 | } 299 | }); 300 | 301 | it("嵌套数组 复合元素数组 多层嵌套", () => { 302 | let ret = null 303 | try { 304 | const v = new Validator({ 305 | field: { 306 | $fields: { 307 | field1: { Required: true }, 308 | field2: [{ Required: true }, { $fields: [{ Enum: [1, 2, 3] }] }] 309 | } 310 | } 311 | }) 312 | ret = v.validate({ field: [{ field1: null }, { field2: [4] }] }) 313 | } finally { 314 | expect(JSON.stringify(ret)) 315 | .toBe(JSON.stringify({ 316 | "field": [{ 317 | "field1": "field1 is required", 318 | "field2": "field2 is required" 319 | }, { 320 | "field1": "field1 is required", 321 | "field2": ["field2 with value 4 is incorrect"] 322 | }] 323 | })); 324 | } 325 | }); 326 | 327 | it("数组类型校验 返回错误信息格式校验 1", () => { 328 | let ret = null 329 | try { 330 | const v = new Validator({ 331 | field: { 332 | $fields: { 333 | field1: { Required: true }, 334 | field2: [{ Required: true }, { $fields: [{ Enum: [1, 2, 3] }] }] 335 | } 336 | } 337 | }) 338 | ret = v.validate({ field: [{ field1: null }, { field2: [4, 1, 9] }] }) 339 | } finally { 340 | expect(JSON.stringify(ret)) 341 | .toBe(JSON.stringify({ 342 | "field": [{ 343 | "field1": "field1 is required", 344 | "field2": "field2 is required" 345 | }, { 346 | "field1": "field1 is required", 347 | "field2": ["field2 with value 4 is incorrect", null, "field2 with value 9 is incorrect"] 348 | }] 349 | })); 350 | } 351 | }); 352 | 353 | it("数组类型校验 返回错误信息格式校验 2", () => { 354 | let ret = null 355 | try { 356 | const v = new Validator({ 357 | field: { 358 | $fields: { Required: true, MinNum: 200 } 359 | } 360 | }) 361 | ret = v.validate({ field: [1, 2000, 2] }) 362 | } finally { 363 | expect(JSON.stringify(ret)) 364 | .toBe(JSON.stringify({ 365 | "field": ["Min value of field is 200", null, "Min value of field is 200"] 366 | })); 367 | } 368 | }); 369 | }) -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | isArray(arr) { 3 | return Array.isArray(arr) 4 | }, 5 | isObject(o) { 6 | return Object.prototype.toString.call(o) === "[object Object]" 7 | }, 8 | isString(s) { 9 | return Object.prototype.toString.call(s) === "[object String]" 10 | }, 11 | isFunction(f) { 12 | return Object.prototype.toString.call(f) === "[object Function]" 13 | }, 14 | isNumber(n) { 15 | return Object.prototype.toString.call(n) === "[object Number]" && !isNaN(n) 16 | }, 17 | isRegExp(r) { 18 | return Object.prototype.toString.call(r) === "[object RegExp]" 19 | } 20 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = (env) => { 4 | const plugins = []; 5 | 6 | return { 7 | entry: { 8 | validator: './index.js' 9 | }, 10 | mode: 'production', 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | filename: 'slime-validator.umd.js', 14 | libraryTarget: 'umd', 15 | library: 'SlimeValidator' 16 | }, 17 | target: ['web', 'es5'], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | loader: 'babel-loader', 23 | exclude: /node_modules/ 24 | } 25 | ] 26 | }, 27 | plugins: plugins 28 | } 29 | } --------------------------------------------------------------------------------