├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── packages └── semantic-validator │ └── package.json ├── scripts └── build.sh ├── src ├── index.ts └── lib │ ├── converters.ts │ ├── definition.ts │ ├── is │ ├── basic.ts │ ├── index.ts │ ├── list.ts │ ├── math.ts │ ├── text.ts │ └── time.ts │ ├── op │ ├── basic.ts │ ├── converter.ts │ └── index.ts │ ├── utils.ts │ └── validators.ts ├── test ├── cases │ └── some-simple-posts.ts ├── is │ ├── basic.ts │ ├── list.ts │ ├── math.ts │ ├── text.ts │ └── time.ts ├── op │ ├── basic.ts │ └── converter.ts └── tsconfig.json ├── tsconfig.json └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true 4 | }, 5 | "parser": "@typescript-eslint/parser", 6 | "parserOptions": { 7 | "ecmaFeatures": { 8 | "jsx": false 9 | } 10 | }, 11 | "plugins": [ 12 | "@typescript-eslint" 13 | ], 14 | "extends": [ 15 | "airbnb-base", 16 | "plugin:@typescript-eslint/recommended", 17 | "plugin:import/errors", 18 | "plugin:import/warnings", 19 | "plugin:import/typescript" 20 | ], 21 | "settings": { 22 | "import/resolver": { 23 | "typescript": {} 24 | } 25 | }, 26 | "rules": { 27 | "max-len": [ 28 | "error", 29 | { 30 | "code": 100 31 | } 32 | ], 33 | "prefer-destructuring": "off", 34 | "dot-notation": "off", 35 | "object-shorthand": "off", 36 | "import/prefer-default-export": "off", 37 | "import/no-named-as-default-member": "off", 38 | "@typescript-eslint/indent": [ 39 | "error", 40 | 2 41 | ], 42 | "@typescript-eslint/prefer-interface": "off", 43 | "@typescript-eslint/no-explicit-any": "off", 44 | "@typescript-eslint/explicit-function-return-type": [ 45 | "warn", 46 | { 47 | "allowExpressions": true, 48 | "allowTypedFunctionExpressions": true, 49 | "allowHigherOrderFunctions": true 50 | } 51 | ] 52 | }, 53 | "overrides": [ 54 | { 55 | "files": [ 56 | "test/**/*.ts" 57 | ], 58 | "env": { 59 | "es6": true, 60 | "jest": true 61 | }, 62 | "settings": { 63 | "import/resolver": { 64 | "typescript": { 65 | "directory": "./test" 66 | } 67 | } 68 | } 69 | } 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Thumbnails 7 | ._* 8 | 9 | # Windows thumbnail cache files 10 | Thumbs.db 11 | ehthumbs.db 12 | ehthumbs_vista.db 13 | 14 | # Dump file 15 | *.stackdump 16 | 17 | # Folder config file 18 | [Dd]esktop.ini 19 | 20 | # Logs 21 | logs 22 | *.log 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Runtime data 28 | pids 29 | *.pid 30 | *.seed 31 | *.pid.lock 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (https://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # IDE 55 | .vscode 56 | .idea 57 | 58 | # Test coverage directory 59 | coverage 60 | 61 | # Distribution files 62 | dist 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '10' 5 | branches: 6 | only: 7 | - master 8 | cache: 9 | directories: 10 | - node_modules 11 | before_install: 12 | - npm update 13 | install: 14 | - npm install 15 | script: 16 | - npm run test 17 | - npm run test:coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ChenZiTW 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Semantic Validator 2 | 3 | A functional semantic validator tool for validation of several types and rich content, inspired by prop-types from React. 4 | 5 | [![npm version](https://badge.fury.io/js/semantic-validator.svg)](https://badge.fury.io/js/semantic-validator) 6 | [![Build Status](https://travis-ci.com/chenzitw/semantic-validator.svg?branch=master)](https://travis-ci.com/chenzitw/semantic-validator) 7 | [![Coverage Status](https://coveralls.io/repos/github/chenzitw/semantic-validator/badge.svg?branch=master)](https://coveralls.io/github/chenzitw/semantic-validator?branch=master) 8 | 9 | 10 | ## Get started 11 | 12 | ### Using NPM or Yarn 13 | 14 | First, install it via npm. 15 | ```sh 16 | npm i semantic-validator 17 | ``` 18 | Then, import it from dependencies and enjoy it. 19 | ```javascript 20 | import { op, is } from 'semantic-validator'; 21 | 22 | const validate = op.shape({ 23 | id: is.integer(), 24 | tags: op.every(is.string()), 25 | }); 26 | ``` 27 | 28 | ### Using script tag 29 | 30 | First, add a script tag with to the HTML page. 31 | ```html 32 | 33 | ``` 34 | Then, just enjoy it. 35 | ```javascript 36 | var op = semanticValidator.op; 37 | var is = semanticValidator.is; 38 | 39 | var validate = op.shape({ 40 | id: is.integer(), 41 | tags: op.every(is.string()) 42 | }); 43 | ``` 44 | 45 | 46 | ## Usage 47 | 48 | The base unit of the semantic validator is a function which accepts the value as an argument for validation. We define it in TypeScript like below. 49 | ```typescript 50 | type Validator = (val: T) => boolean; 51 | ``` 52 | 53 | This validator tool has several methods in two types: validator operator (op) and validator creator (is). 54 | 55 | For example, we can call a validator creator such as `is.integer()` to create a validator which expects the number should be an integer. We can also call `is.greaterThan(100)` to create a validator which expects the number is greater than 100. 56 | If we want to match both conditions, we can use a validator operator like `op.and(validator1, validator2)` to combine two validators. 57 | 58 | ```javascript 59 | import { op, is } from 'semantic-validator'; 60 | 61 | const isInt = is.integer(); 62 | isInt(12); // => true; 63 | isInt(12.34); // => false; 64 | 65 | const isGt100 = is.greaterThan(100); 66 | isGt100(150); // => true; 67 | isGt100(50); // => false; 68 | 69 | const isIntGt100 = op.and( 70 | is.integer(), 71 | is.greaterThan(100), 72 | ); 73 | isIntGt100(123); // => true; 74 | isIntGt100(123.456); // => false; 75 | isIntGt100(12); // => false; 76 | ``` 77 | 78 | You can also integrate other validation tools together. Just follow the validator pattern. 79 | 80 | For example, there is a method `isUUID(str, version)` for validating a string. We can make a validator like `val => isUUID(val, 5)` and compose with other validator operators. 81 | 82 | ```javascript 83 | import { op, is } from 'semantic-validator'; 84 | 85 | const validate = op.shape({ 86 | id: (val => isUUID(val, 5)), 87 | name: is.string(), 88 | }); 89 | validate({ id: '6fad3a7b-161b-5e10-b265-8d522f3f35b5', name: 'Agent K' }); // => true; 90 | validate({ id: 'abc', name: 'Agent K' }); // => false; 91 | ``` 92 | 93 | 94 | ### Cheatsheet 95 | 96 | | Method | Description | 97 | | ---------------------------------------------------------------- | ---------------------------------------------------------------- | 98 | | **Basic validator operators** | | 99 | | > **`op.so(validator)`** | Pass the validator. | 100 | | > **`op.not(validator)`** | Not pass the validator. | 101 | | > **`op.and(...validators)`** | Pass all validators. | 102 | | > **`op.or(...validators)`** | Pass any validators. | 103 | | > **`op.every(validator)`** | Pass the validator on all elements in an array. | 104 | | > **`op.some(validator)`** | Pass the validator on any elements in an array. | 105 | | > **`op.shape({ ...validatorOfKeys })`** | Pass all validators for each properties of an object. | 106 | | > **`op.exact({ ...validatorOfKeys })`** | Pass all validators for each properties of an exact object. | 107 | | **Converter validator operators** | | 108 | | > **`op.convert(converter, validator)`** | Pass the validator after converted. | 109 | | > **`op.toFloat(validator)`** | Pass the validator after converted to a float. | 110 | | > **`op.toInteger(validator)`** | Pass the validator after converted to an integer. | 111 | | > **`op.toLength(validator)`** | Pass the validator after converted to the length. | 112 | | > **`op.toSplit(separator, validator)`** | Pass the validator after splitted the string as an array. | 113 | | > **`op.toKeys(validator)`** | Pass the validator after converted to keys of an object. | 114 | | > **`op.toValues(validator)`** | Pass the validator after converted to values of an object. | 115 | | > **`op.toDate(validator)`** | Pass the validator after converted to a date object. | 116 | | **Basic validator creators** | | 117 | | > **`is.same(value)`** | Is the same as the base value? | 118 | | > **`is.oneOf(...values)`** | Is the same as any base values? | 119 | | > **`is.defined()`** | Is defined (not undefined)? | 120 | | > **`is.notDefined()`** | Is undefined? | 121 | | > **`is.nul()`** | Is null? | 122 | | > **`is.nil()`** | Is undefined or null? | 123 | | > **`is.bool()`** | Is a boolean? | 124 | | > **`is.number()`** | Is a number? | 125 | | > **`is.string()`** | Is a string? | 126 | | > **`is.object()`** | Is a non null object? | 127 | | > **`is.func()`** | Is a function? | 128 | | > **`is.symbol()`** | Is a symbol? | 129 | | > **`is.instanceOf(constructor)`** | Is an instance of the constructor (class)? | 130 | | > **`is.float()`** | Is a valid (not `NaN` or `Infinity`) float? | 131 | | > **`is.integer()`** | Is a valid (not `NaN` or `Infinity`) integer? | 132 | | > **`is.array()`** | Is an array? | 133 | | > **`is.date()`** | Is a valid (not `Invalid Date`) date object? | 134 | | **Math validator creators** | | 135 | | > **`is.equalTo(num)`** | Is the number equal to the base number? | 136 | | > **`is.greaterThan(num)`** | Is the number greater than the base number? | 137 | | > **`is.atLeast(num)`** | Is the number at least the base number? | 138 | | > **`is.lessThan(num)`** | Is the number less than the base number? | 139 | | > **`is.atMost(num)`** | Is the number at most the base number? | 140 | | > **`is.between(min, max)`** | Is the number between the base numbers? | 141 | | > **`is.fromTo(min, max)`** | Is the number from and to the base numbers? | 142 | | **Text validator creators** | | 143 | | > **`is.match(regexp)`** | Is the string match the regular expression? | 144 | | > **`is.startsWith(wording)`** | Is the string starts with the wording? | 145 | | > **`is.endsWith(wording)`** | Is the string ends with the wording? | 146 | | > **`is.contains(wording)`** | Is the string contains the wording? | 147 | | **List validator creators** | | 148 | | > **`is.includes(...includings)`** | Is the array includes all includings? | 149 | | > **`is.excludes(...excludings)`** | Is the array excludes all excludings? | 150 | | > **`is.restrictedBy(...allowedItems)`** | Is the array only includes allowed items? | 151 | | > **`is.distinct()`** | Is items of the array are all different? | 152 | | **Date validator creators** | | 153 | | > **`is.moment(date)`** | Is the date object at the moment of the base date object? | 154 | | > **`is.laterThan(date)`** | Is the date object later than the base date object? | 155 | | > **`is.atEarliest(date)`** | Is the date object at earliest the base date object? | 156 | | > **`is.earlierThan(date)`** | Is the date object earlier than the base date object? | 157 | | > **`is.atLatest(date)`** | Is the date object at latest the base date object? | 158 | 159 | 160 | ### Validator operator reference 161 | 162 | #### Basic 163 | 164 | **op: so** 165 | Will be valid when the validator returns true. It is usually not necessary. 166 | ```javascript 167 | op.so(validator) 168 | ``` 169 | Example: 170 | ```javascript 171 | const validate = op.so(is.same('hello')); 172 | validate('hello'); // => true 173 | validate('bye'); // => false 174 | ``` 175 | 176 | **op: not** 177 | Will be valid when the validator returns false. 178 | ```javascript 179 | op.not(validator) 180 | ``` 181 | Example: 182 | ```javascript 183 | const validate = op.not(is.same('hello')); 184 | validate('bye'); // => true 185 | validate('hello'); // => false 186 | ``` 187 | 188 | **op: and** 189 | Will be valid when all validators return true. 190 | ```javascript 191 | op.and(validator1, validator2, ...validators) 192 | ``` 193 | Example: 194 | ```javascript 195 | const validate = op.and(is.integer(), is.greaterThan(100)); 196 | validate(120); // => true 197 | validate(80); // => false 198 | validate(123.456); // => false 199 | ``` 200 | 201 | **op: or** 202 | Will be valid when any validator returns true. 203 | ```javascript 204 | op.or(validator1, validator2, ...validators) 205 | ``` 206 | Example: 207 | ```javascript 208 | const validate = op.or(is.nul(), is.integer()); 209 | validate(null); // => true 210 | validate(123); // => true 211 | validate('abc'); // => false 212 | ``` 213 | 214 | **op: every** 215 | Will be valid when validator returns true on all elements in the array. 216 | ```javascript 217 | op.every(validator) 218 | ``` 219 | Example: 220 | ```javascript 221 | const validate = op.every(is.integer()); 222 | validate([1, 2, 3]); // => true 223 | validate([1, 2.22, 3]); // => false 224 | ``` 225 | 226 | **op: some** 227 | Will be valid when validator returns true on any element in the array. 228 | ```javascript 229 | op.some(validator) 230 | ``` 231 | Example: 232 | ```javascript 233 | const validate = op.some(is.integer()); 234 | validate([1, 2, 3]); // => true 235 | validate([1.11, 2, 3.33]); // => true 236 | validate([1.11, 2.22, 3.33]); // => false 237 | ``` 238 | 239 | **op: shape** 240 | Will be valid when value is an object and all validator returns true on each key. 241 | ```javascript 242 | op.shape({ 243 | [key1]: validator1, 244 | [key2]: validator2, 245 | ...keysWithValidator, 246 | }) 247 | ``` 248 | Example: 249 | ```javascript 250 | const validate = op.shape({ 251 | id: is.integer(), 252 | name: is.string(), 253 | }); 254 | validate({ id: 123, name: 'Mr. Sandman' }); // => true 255 | validate({ id: 123, name: 'Mr. Sandman', active: true }); // => true 256 | validate({ id: 123 }); // => false 257 | validate({ id: 123, name: 456 }); // => false 258 | ``` 259 | 260 | **op: exact** 261 | Will be valid when value is an object with specific keys and all validator returns true on each key. 262 | ```javascript 263 | op.exact({ 264 | [key1]: validator1, 265 | [key2]: validator2, 266 | ...keysWithValidator, 267 | }) 268 | ``` 269 | Example: 270 | ```javascript 271 | const validate = op.exact({ 272 | id: is.integer(), 273 | name: is.string(), 274 | }); 275 | validate({ id: 123, name: 'Mr. Sandman' }); // => true 276 | validate({ id: 123, name: 'Mr. Sandman', active: true }); // => false 277 | validate({ id: 123 }); // => false 278 | validate({ id: 123, name: 456 }); // => false 279 | ``` 280 | 281 | 282 | #### Converter 283 | 284 | **op: convert** 285 | Will be valid when successfully converts the value and the validation is success. 286 | ```javascript 287 | op.convert(converter, validator) 288 | ``` 289 | Example: 290 | ```javascript 291 | const validate = op.convert( 292 | numeric => parseFloat(numeric), 293 | is.greaterThan(100) 294 | ); 295 | validate('150'); // => true 296 | validate('50'); // => false 297 | ``` 298 | 299 | **op: to float** 300 | Will be valid when successfully converts to a float and the validation is success. 301 | ```javascript 302 | op.toFloat(validator) 303 | ``` 304 | Example: 305 | ```javascript 306 | const validate = op.toFloat(is.greaterThan(1.2)); 307 | validate('1.5'); // => true 308 | validate('two'); // => false 309 | validate('0.8'); // => false 310 | ``` 311 | 312 | **op: to integer** 313 | Will be valid when successfully converts to an integer and the validation is success. 314 | ```javascript 315 | op.toInteger(validator) 316 | ``` 317 | Example: 318 | ```javascript 319 | const validate = op.toInteger(is.greaterThan(100)); 320 | validate('150'); // => true 321 | validate('two'); // => false 322 | validate('123.456'); // => false 323 | validate('50'); // => false 324 | ``` 325 | 326 | **op: to length** 327 | Will be valid when successfully get the length of an array or string and the validation is success. 328 | ```javascript 329 | op.toLength(validator) 330 | ``` 331 | Example: 332 | ```javascript 333 | const validate = op.toLength(is.atLeast(3)); 334 | validate(['a', 'b', 'c']); // => true 335 | validate('abc'); // => true 336 | validate(['a', 'b']); // => false 337 | validate('ab'); // => false 338 | validate(3); // => false 339 | ``` 340 | 341 | **op: to split** 342 | Will be valid when successfully split to the array and the validation is success. 343 | ```javascript 344 | op.toSplit(separator, validator) 345 | ``` 346 | Example: 347 | ```javascript 348 | const validate = op.toSplit(',', op.every(is.startsWith('c'))); 349 | validate('candy,cookie,coffee'); // => true 350 | validate('candy,cookie,tea'); // => false 351 | validate(123); // => false 352 | ``` 353 | 354 | **op: to keys** 355 | Will be valid when successfully get keys of an object and the validation is success. 356 | ```javascript 357 | op.toKeys(validator) 358 | ``` 359 | Example: 360 | ```javascript 361 | const validate = op.toKeys(op.every(is.oneOf('id', 'name'))); 362 | validate({ id: 123, name: 'Mario' }); // => true 363 | validate({ id: 123, name: 'Mario', age: 20 }); // => false 364 | ``` 365 | 366 | **op: to values** 367 | Will be valid when successfully get values of an object and the validation is success. 368 | ```javascript 369 | op.toValues(validator) 370 | ``` 371 | Example: 372 | ```javascript 373 | const validate = op.toValues(op.every(is.integer())); 374 | validate({ people: 64, seats: 80 }); // => true 375 | validate({ people: 640, seats: 'many' }); // => false 376 | ``` 377 | 378 | 379 | ### Validator creator reference 380 | 381 | #### Basic 382 | 383 | **is: same** 384 | Will be valid when base value and compare value are the same by using the SameValueZero algorithm. 385 | ```javascript 386 | is.same(baseValue) 387 | ``` 388 | Example: 389 | ```javascript 390 | const validate = is.same('hello'); 391 | validate('hello'); // => true 392 | ``` 393 | 394 | **is: one of** 395 | Will be valid when one of base values and compare value are the same by using the SameValueZero algorithm. 396 | ```javascript 397 | is.oneOf(...baseValues) 398 | ``` 399 | Example: 400 | ```javascript 401 | const validate = is.oneOf(100, '100', 'one hundred'); 402 | validate('100'); // => true 403 | ``` 404 | 405 | **is: defined** 406 | Will be valid when the value is defined (not undefined). 407 | ```javascript 408 | is.defined() 409 | ``` 410 | Example: 411 | ```javascript 412 | const validate = is.defined(); 413 | validate(123); // => true 414 | validate(undefined); // => false 415 | ``` 416 | 417 | **is: not defined** 418 | Will be valid when the value is not defined (undefined). 419 | ```javascript 420 | is.notDefined() 421 | ``` 422 | Example: 423 | ```javascript 424 | const validate = is.notDefined(); 425 | validate(undefined); // => true 426 | validate(123); // => false 427 | ``` 428 | 429 | **is: nul** 430 | Will be valid when the value is null. 431 | ```javascript 432 | is.nul() 433 | ``` 434 | Example: 435 | ```javascript 436 | const validate = is.nul(); 437 | validate(null); // => true 438 | validate(123); // => false 439 | ``` 440 | 441 | **is: nil** 442 | Will be valid when the value is undefined or null. 443 | ```javascript 444 | is.nil() 445 | ``` 446 | Example: 447 | ```javascript 448 | const validate = is.nil(); 449 | validate(undefined); // => true 450 | validate(null); // => true 451 | validate(123); // => false 452 | ``` 453 | 454 | **is: bool** 455 | Will be valid when the value is a boolean. 456 | ```javascript 457 | is.bool() 458 | ``` 459 | Example: 460 | ```javascript 461 | const validate = is.bool(); 462 | validate(false); // => true 463 | validate(123); // => false 464 | ``` 465 | 466 | **is: number** 467 | Will be valid when the value is a number. 468 | ```javascript 469 | is.number() 470 | ``` 471 | Example: 472 | ```javascript 473 | const validate = is.number(); 474 | validate(123); // => true 475 | validate('abc'); // => false 476 | ``` 477 | 478 | **is: string** 479 | Will be valid when the value is a string. 480 | ```javascript 481 | is.string() 482 | ``` 483 | Example: 484 | ```javascript 485 | const validate = is.string(); 486 | validate('abc'); // => true 487 | validate(123); // => false 488 | ``` 489 | 490 | **is: object** 491 | Will be valid when the value is a non null object. 492 | ```javascript 493 | is.object() 494 | ``` 495 | Example: 496 | ```javascript 497 | const validate = is.object(); 498 | validate({ key: 'value' }); // => true 499 | validate({}); // => true 500 | validate(null); // => false 501 | validate(123); // => false 502 | ``` 503 | 504 | **is: func** 505 | Will be valid when the value is a function. 506 | ```javascript 507 | is.func() 508 | ``` 509 | Example: 510 | ```javascript 511 | const validate = is.func(); 512 | validate(() => {}); // => true 513 | validate(123); // => false 514 | ``` 515 | 516 | **is: symbol** 517 | Will be valid when the value is a symbol. 518 | ```javascript 519 | is.symbol() 520 | ``` 521 | Example: 522 | ```javascript 523 | const validate = is.symbol(); 524 | validate(Symbol('abc')); // => true 525 | validate('abc'); // => false 526 | ``` 527 | 528 | **is: instance of** 529 | Will be valid when the value is the instance of a constructor. 530 | ```javascript 531 | is.instanceOf(constructor) 532 | ``` 533 | Example: 534 | ```javascript 535 | const validate = is.instanceOf(Date); 536 | validate(new Date()); // => true 537 | validate(123); // => false 538 | ``` 539 | 540 | **is: float** 541 | Will be valid when the value is a float and not NaN or infinity. 542 | ```javascript 543 | is.float() 544 | ``` 545 | Example: 546 | ```javascript 547 | const validate = is.float(); 548 | validate(123.456); // => true 549 | validate(123); // => true 550 | validate('123'); // => false 551 | ``` 552 | 553 | **is: integer** 554 | Will be valid when the value is a integer and not NaN or infinity. 555 | ```javascript 556 | is.integer() 557 | ``` 558 | Example: 559 | ```javascript 560 | const validate = is.integer(); 561 | validate(123); // => true 562 | validate(123.456); // => false 563 | ``` 564 | 565 | **is: array** 566 | Will be valid when the value is an array object. 567 | ```javascript 568 | is.array() 569 | ``` 570 | Example: 571 | ```javascript 572 | const validate = is.array(); 573 | validate([1, 2, 3]); // => true 574 | ``` 575 | 576 | **is: date** 577 | Will be valid when the value is a valid date object. 578 | ```javascript 579 | is.date() 580 | ``` 581 | Example: 582 | ```javascript 583 | const validate = is.date(); 584 | validate(new Date()); // => true 585 | validate(new Date('invalid date')); // => false 586 | validate('invalid date'); // => false 587 | ``` 588 | 589 | 590 | #### Math 591 | 592 | **is: equal to** 593 | Will be valid when the value is equal to the number. 594 | ```javascript 595 | is.equalTo(num) 596 | ``` 597 | Example: 598 | ```javascript 599 | const validate = is.equalTo(100); 600 | validate(100); // true 601 | validate(200); // false 602 | ``` 603 | 604 | **is: greater than** 605 | Will be valid when the value is greater than the number. 606 | ```javascript 607 | is.greaterThan(num) 608 | ``` 609 | Example: 610 | ```javascript 611 | const validate = is.greaterThan(100); 612 | validate(101); // => true 613 | validate(100); // => false 614 | ``` 615 | 616 | **is: at least** 617 | Will be valid when the value is at least the number. 618 | ```javascript 619 | is.atLeast(num) 620 | ``` 621 | Example: 622 | ```javascript 623 | const validate = is.atLeast(100); 624 | validate(100); // => true 625 | validate(99); // => false 626 | ``` 627 | 628 | **is: less than** 629 | Will be valid when the value is less than the number. 630 | ```javascript 631 | is.lessThan(num) 632 | ``` 633 | Example: 634 | ```javascript 635 | const validate = is.lessThan(200); 636 | validate(199); // => true 637 | validate(200); // => false 638 | ``` 639 | 640 | **is: at most** 641 | Will be valid when the value is at most the number. 642 | ```javascript 643 | is.atMost(num) 644 | ``` 645 | Example: 646 | ```javascript 647 | const validate = is.atMost(200); 648 | validate(200); // => true 649 | validate(201); // => false 650 | ``` 651 | 652 | **is: between** 653 | Will be valid when the value is between the numbers. 654 | ```javascript 655 | is.between(min, max) 656 | ``` 657 | Example: 658 | ```javascript 659 | const validate = is.between(0, 10); 660 | validate(5); // => true 661 | validate(0); // => false 662 | validate(10); // => false 663 | ``` 664 | 665 | **is: from to** 666 | Will be valid when the value is from and to the numbers. 667 | ```javascript 668 | is.fromTo(min, max) 669 | ``` 670 | Example: 671 | ```javascript 672 | const validate = is.fromTo(1, 9); 673 | validate(1); // => true 674 | validate(9); // => true 675 | validate(0); // => false 676 | validate(10); // => false 677 | ``` 678 | 679 | 680 | #### Text 681 | 682 | **is: match** 683 | Will be valid when the value matches the regular expression. 684 | ```javascript 685 | is.match(regexp) 686 | ``` 687 | Example: 688 | ```javascript 689 | const validate = is.match(/^b[aeiou]t$/); 690 | validate('bat'); // => true 691 | validate('brt'); // => false 692 | ``` 693 | 694 | **is: starts with** 695 | Will be valid when the value starts with the wording. 696 | ```javascript 697 | is.startsWith(wording) 698 | ``` 699 | Example: 700 | ```javascript 701 | const validate = is.startsWith('net'); 702 | validate('network'); // => true 703 | validate('artwork'); // => false 704 | ``` 705 | 706 | **is: ends with** 707 | Will be valid when the value ends with the wording. 708 | ```javascript 709 | is.endsWith(wording) 710 | ``` 711 | Example: 712 | ```javascript 713 | const validate = is.endsWith('fox'); 714 | validate('firefox'); // => true 715 | validate('firewall'); // => false 716 | ``` 717 | 718 | **is: contains** 719 | Will be valid when the value contains the wording. 720 | ```javascript 721 | is.contains(wording) 722 | ``` 723 | Example: 724 | ```javascript 725 | const validate = is.contains('lie'); 726 | validate('believe'); // => true 727 | validate('behave'); // => false 728 | ``` 729 | 730 | 731 | #### List 732 | 733 | **is: includes** 734 | Will be valid when the array value includes all includings. 735 | ```javascript 736 | is.includes(including1, including2, ...includings) 737 | ``` 738 | Example: 739 | ```javascript 740 | const validate = is.includes(10, 20, 30); 741 | validate([0, 10, 20, 30, 40]); // => true 742 | validate([0, 20, 40]); // => false 743 | ``` 744 | 745 | **is: excludes** 746 | Will be valid when the array value excludes all excludings. 747 | ```javascript 748 | is.excludes(excluding1, excluding2, ...excludings) 749 | ``` 750 | Example: 751 | ```javascript 752 | const validate = is.excludes(5, 15, 25); 753 | validate([0, 10, 20, 30]); // => true 754 | validate([5, 10]); // => false 755 | ``` 756 | 757 | **is: restricted by** 758 | Will be valid when the array value is restricted by allowed items. 759 | ```javascript 760 | is.restrictedBy(allowedItem1, allowedItem2, ...allowedItems) 761 | ``` 762 | Example: 763 | ```javascript 764 | const validate = is.restrictedBy(5, 10, 15, 20); 765 | validate([10, 20]); // => true 766 | validate([5, 10, 15, 20, 25, 30]); // => false 767 | ``` 768 | 769 | **is: distinct** 770 | Will be valid when items of array value are all different. 771 | ```javascript 772 | is.distinct() 773 | ``` 774 | Example: 775 | ```javascript 776 | const validate = is.distinct(); 777 | validate([1, 2, 3]); // => true 778 | validate([1, 2, 3, 3]]); // => false 779 | ``` 780 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | moduleNameMapper: { 4 | '^semantic-validator$': '/src', 5 | '^semantic-validator/(.*)$': '/src/$1', 6 | }, 7 | testMatch: ['/test/**/*.ts'], 8 | globals: { 9 | 'ts-jest': { 10 | tsConfig: '/test/tsconfig.json' 11 | } 12 | }, 13 | collectCoverage: true, 14 | collectCoverageFrom: [ 15 | '/src/lib/op/**/*.ts', 16 | '/src/lib/is/**/*.ts', 17 | ], 18 | coverageReporters: ['lcov', 'text-summary'], 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "semantic-validator-dev", 3 | "version": "0.1.1", 4 | "description": "A functional semantic validator tool for validation of several types and rich content.", 5 | "private": true, 6 | "author": "ChenZi ", 7 | "license": "MIT", 8 | "repository": { 9 | "url": "https://github.com/chenzitw/semantic-validator.git", 10 | "type": "git" 11 | }, 12 | "scripts": { 13 | "start": "ts-node --files -O '{\"module\":\"commonjs\"}' -r tsconfig-paths/register ./src", 14 | "lint": "tsc --noEmit && eslint {src,test}/**/* --quiet", 15 | "test": "jest --ForceExit", 16 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 17 | "build": "bash ./scripts/build.sh", 18 | "publish": "npm publish dist" 19 | }, 20 | "devDependencies": { 21 | "@types/jest": "^24.0.15", 22 | "@types/node": "^12.0.10", 23 | "@types/validator": "^10.11.1", 24 | "@typescript-eslint/eslint-plugin": "^1.11.0", 25 | "@typescript-eslint/parser": "^1.11.0", 26 | "coveralls": "^3.0.4", 27 | "eslint": "^6.0.1", 28 | "eslint-config-airbnb-base": "^13.1.0", 29 | "eslint-import-resolver-typescript": "^1.1.1", 30 | "eslint-plugin-import": "^2.18.0", 31 | "jest": "^24.8.0", 32 | "ts-jest": "^24.0.2", 33 | "ts-loader": "^6.0.4", 34 | "ts-node": "^8.3.0", 35 | "tsconfig-paths": "^3.8.0", 36 | "typescript": "^3.5.2", 37 | "webpack": "^4.35.2", 38 | "webpack-cli": "^3.3.5" 39 | }, 40 | "dependencies": {} 41 | } 42 | -------------------------------------------------------------------------------- /packages/semantic-validator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "semantic-validator", 3 | "version": "0.1.1", 4 | "description": "A functional semantic validator tool for validation of several types and rich content.", 5 | "author": "ChenZi ", 6 | "license": "MIT", 7 | "repository": { 8 | "url": "https://github.com/chenzitw/semantic-validator.git", 9 | "type": "git" 10 | }, 11 | "main": "index.js", 12 | "devDependencies": {}, 13 | "dependencies": {} 14 | } 15 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | rm -rf ./dist 2 | mkdir dist 3 | NODE_ENV=production tsc --build 4 | NODE_ENV=production webpack --display 5 | cp ./packages/semantic-validator/package.json ./dist/package.json 6 | cp ./README.md ./dist/README.md 7 | cp ./LICENSE ./dist/LICENSE -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import op from './lib/op'; 2 | import is from './lib/is'; 3 | 4 | export { 5 | op, 6 | is, 7 | }; 8 | -------------------------------------------------------------------------------- /src/lib/converters.ts: -------------------------------------------------------------------------------- 1 | const convertAnyToNumber = (value: boolean | number | string): (undefined | number) => { 2 | switch (typeof value) { 3 | case 'boolean': return Number(value); 4 | case 'number': return (Number.isFinite(value)) ? value : undefined; 5 | case 'string': return (/^[-+]?[0-9]*\.?[0-9]+$/.test(value)) ? Number(value) : undefined; 6 | default: return undefined; 7 | } 8 | }; 9 | 10 | export const floatConverter = (val: boolean | number | string): number => { 11 | const numericVal = convertAnyToNumber(val); 12 | if (numericVal === undefined) { 13 | throw new Error(); 14 | } 15 | if (!Number.isFinite(numericVal)) { 16 | throw new Error(); 17 | } 18 | return numericVal; 19 | }; 20 | 21 | export const integerConverter = (val: boolean | number | string): number => { 22 | const numericVal = convertAnyToNumber(val); 23 | if (numericVal === undefined) { 24 | throw new Error(); 25 | } 26 | if (!Number.isInteger(numericVal)) { 27 | throw new Error(); 28 | } 29 | return numericVal; 30 | }; 31 | 32 | export const lengthConverter = (val: string | any[]): number => { 33 | const length = val.length; 34 | if (length === undefined) { 35 | throw new Error(); 36 | } 37 | return length; 38 | }; 39 | 40 | export const keysConverter = Object.keys as ((val: T) => (keyof T)[]); 41 | 42 | export const valuesConverter = Object.values as ((val: T) => T[keyof T][]); 43 | 44 | export const dateConverter = (val: number): Date => { 45 | if (!Number.isInteger(val)) { 46 | throw new Error(); 47 | } 48 | const date = new Date(val); 49 | if (Number.isNaN(date.getTime())) { 50 | throw new Error(); 51 | } 52 | return date; 53 | }; 54 | -------------------------------------------------------------------------------- /src/lib/definition.ts: -------------------------------------------------------------------------------- 1 | export type Validator = (val: T) => boolean; 2 | 3 | export type SomeObject = { 4 | [K in string]: any; 5 | }; 6 | 7 | export type ShapeValidation = { 8 | [K in keyof Partial]: Validator; 9 | } 10 | 11 | export type ExactValidation = { 12 | [K in keyof T]: Validator; 13 | } 14 | 15 | export type SomeFunction = (args: any[]) => any; 16 | -------------------------------------------------------------------------------- /src/lib/is/basic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | } from '../definition'; 4 | import { 5 | isSameValueZero, 6 | } from '../utils'; 7 | import { 8 | definedValidator, 9 | notDefinedValidator, 10 | nulValidator, 11 | nilValidator, 12 | booleanValidator, 13 | numberValidator, 14 | stringValidator, 15 | objectValidator, 16 | functionValidator, 17 | symbolValidator, 18 | floatValidator, 19 | integerValidator, 20 | arrayValidator, 21 | dateValidator, 22 | } from '../validators'; 23 | 24 | /** 25 | * Is the same as the base value? 26 | */ 27 | export const same = (target: any): Validator => ( 28 | val => isSameValueZero(target, val) 29 | ); 30 | 31 | /** 32 | * Is the same as any base values? 33 | */ 34 | export const oneOf = (...list: any[]): Validator => ( 35 | val => list.some(item => isSameValueZero(item, val)) 36 | ); 37 | 38 | /** 39 | * Is defined (not undefined)? 40 | */ 41 | export const defined = (): Validator => definedValidator; 42 | 43 | /** 44 | * Is undefined? 45 | */ 46 | export const notDefined = (): Validator => notDefinedValidator; 47 | 48 | /** 49 | * Is null? 50 | */ 51 | export const nul = (): Validator => nulValidator; 52 | 53 | /** 54 | * Is undefined or null? 55 | */ 56 | export const nil = (): Validator => nilValidator; 57 | 58 | /** 59 | * Is a boolean? 60 | */ 61 | export const bool = (): Validator => booleanValidator; 62 | 63 | /** 64 | * Is a number? 65 | */ 66 | export const number = (): Validator => numberValidator; 67 | 68 | /** 69 | * Is a string? 70 | */ 71 | export const string = (): Validator => stringValidator; 72 | 73 | /** 74 | * Is a non null object? 75 | */ 76 | export const object = (): Validator => objectValidator; 77 | 78 | /** 79 | * Is a function? 80 | */ 81 | export const func = (): Validator => functionValidator; 82 | 83 | /** 84 | * Is a symbol? 85 | */ 86 | export const symbol = (): Validator => symbolValidator; 87 | 88 | /** 89 | * Is an instance of the constructor (class)? 90 | */ 91 | export const instanceOf = (constructor: any): Validator => val => (val instanceof constructor); 92 | 93 | /** 94 | * Is a valid (not `NaN` or `Infinity`) float? 95 | */ 96 | export const float = (): Validator => floatValidator; 97 | 98 | /** 99 | * Is a valid (not `NaN` or `Infinity`) integer? 100 | */ 101 | export const integer = (): Validator => integerValidator; 102 | 103 | /** 104 | * Is an array? 105 | */ 106 | export const array = (): Validator => arrayValidator; 107 | 108 | /** 109 | * Is a valid (not `Invalid Date`) date object? 110 | */ 111 | export const date = (): Validator => dateValidator; 112 | -------------------------------------------------------------------------------- /src/lib/is/index.ts: -------------------------------------------------------------------------------- 1 | import * as basic from './basic'; 2 | import * as math from './math'; 3 | import * as text from './text'; 4 | import * as list from './list'; 5 | 6 | const is = { 7 | ...basic, 8 | ...math, 9 | ...text, 10 | ...list, 11 | }; 12 | 13 | export default is; 14 | -------------------------------------------------------------------------------- /src/lib/is/list.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | } from '../definition'; 4 | import { 5 | distinctValidator, 6 | } from '../validators'; 7 | 8 | /** 9 | * Is the array includes all includings? 10 | */ 11 | export const includes = (...includings: any[]): Validator => ( 12 | list => (Array.isArray(list) && includings.every(including => list.includes(including))) 13 | ); 14 | 15 | /** 16 | * Is the array excludes all excludings? 17 | */ 18 | export const excludes = (...excludings: any[]): Validator => ( 19 | list => (Array.isArray(list) && excludings.every(excluding => !list.includes(excluding))) 20 | ); 21 | 22 | /** 23 | * Is the array only includes allowed items? 24 | */ 25 | export const restrictedBy = (...whitelist: any[]): Validator => ( 26 | list => (Array.isArray(list) && list.every(item => whitelist.includes(item))) 27 | ); 28 | 29 | /** 30 | * Is items of the array are all different? 31 | */ 32 | export const distinct = (): Validator => distinctValidator; 33 | -------------------------------------------------------------------------------- /src/lib/is/math.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | } from '../definition'; 4 | import { 5 | falsyValidator, 6 | } from '../validators'; 7 | 8 | /** 9 | * Is the number equal to the base number? 10 | */ 11 | export const equalTo = (target: number): Validator => { 12 | if (!Number.isFinite(target)) { 13 | return falsyValidator; 14 | } 15 | return ( 16 | num => (Number.isFinite(num) && (num === target)) 17 | ); 18 | }; 19 | 20 | /** 21 | * Is the number greater than the base number? 22 | */ 23 | export const greaterThan = (target: number): Validator => { 24 | if (!Number.isFinite(target)) { 25 | return falsyValidator; 26 | } 27 | return ( 28 | num => (Number.isFinite(num) && (num > target)) 29 | ); 30 | }; 31 | 32 | /** 33 | * Is the number at least the base number? 34 | */ 35 | export const atLeast = (target: number): Validator => { 36 | if (!Number.isFinite(target)) { 37 | return falsyValidator; 38 | } 39 | return ( 40 | num => (Number.isFinite(num) && (num >= target)) 41 | ); 42 | }; 43 | 44 | /** 45 | * Is the number less than the base number? 46 | */ 47 | export const lessThan = (target: number): Validator => { 48 | if (!Number.isFinite(target)) { 49 | return falsyValidator; 50 | } 51 | return ( 52 | num => (Number.isFinite(num) && (num < target)) 53 | ); 54 | }; 55 | 56 | /** 57 | * Is the number at most the base number? 58 | */ 59 | export const atMost = (target: number): Validator => { 60 | if (!Number.isFinite(target)) { 61 | return falsyValidator; 62 | } 63 | return ( 64 | num => (Number.isFinite(num) && (num <= target)) 65 | ); 66 | }; 67 | 68 | /** 69 | * Is the number between the base numbers? 70 | */ 71 | export const between = (min: number, max: number): Validator => { 72 | if (!Number.isFinite(min) || !Number.isFinite(max)) { 73 | return falsyValidator; 74 | } 75 | return ( 76 | num => (Number.isFinite(num) && (num > Math.min(min, max)) && (num < Math.max(min, max))) 77 | ); 78 | }; 79 | 80 | /** 81 | * Is the number from and to the base numbers? 82 | */ 83 | export const fromTo = (min: number, max: number): Validator => { 84 | if (!Number.isFinite(min) || !Number.isFinite(max)) { 85 | return falsyValidator; 86 | } 87 | return ( 88 | num => (Number.isFinite(num) && (num >= Math.min(min, max)) && (num <= Math.max(min, max))) 89 | ); 90 | }; 91 | -------------------------------------------------------------------------------- /src/lib/is/text.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | } from '../definition'; 4 | import { 5 | falsyValidator, 6 | } from '../validators'; 7 | 8 | /** 9 | * Is the string match the regular expression? 10 | */ 11 | export const match = (regExp: RegExp): Validator => { 12 | if (!(regExp instanceof RegExp)) { 13 | return falsyValidator; 14 | } 15 | return ( 16 | text => ((typeof text === 'string') && regExp.test(text)) 17 | ); 18 | }; 19 | 20 | /** 21 | * Is the string starts with the wording? 22 | */ 23 | export const startsWith = (wording: string): Validator => { 24 | if (typeof wording !== 'string') { 25 | return falsyValidator; 26 | } 27 | return ( 28 | text => ((typeof text === 'string') && text.startsWith(wording)) 29 | ); 30 | }; 31 | 32 | /** 33 | * Is the string ends with the wording? 34 | */ 35 | export const endsWith = (wording: string): Validator => { 36 | if (typeof wording !== 'string') { 37 | return falsyValidator; 38 | } 39 | return ( 40 | text => ((typeof text === 'string') && text.endsWith(wording)) 41 | ); 42 | }; 43 | 44 | /** 45 | * Is the string contains the wording? 46 | */ 47 | export const contains = (wording: string): Validator => { 48 | if (typeof wording !== 'string') { 49 | return falsyValidator; 50 | } 51 | return ( 52 | text => ((typeof text === 'string') && text.includes(wording)) 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/lib/is/time.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | } from '../definition'; 4 | import { 5 | falsyValidator, 6 | } from '../validators'; 7 | 8 | const isValidDate = (date: Date): boolean => ( 9 | (date instanceof Date) && !Number.isNaN(date.getTime()) 10 | ); 11 | 12 | /** 13 | * Is the date object at the moment of the base date object? 14 | */ 15 | export const moment = (base: Date): Validator => { 16 | if (!isValidDate(base)) { 17 | return falsyValidator; 18 | } 19 | return ( 20 | time => (isValidDate(time) && (time.getTime() === base.getTime())) 21 | ); 22 | }; 23 | 24 | /** 25 | * Is the date object later than the base date object? 26 | */ 27 | export const laterThan = (base: Date): Validator => { 28 | if (!isValidDate(base)) { 29 | return falsyValidator; 30 | } 31 | return ( 32 | time => (isValidDate(time) && (time.getTime() > base.getTime())) 33 | ); 34 | }; 35 | 36 | /** 37 | * Is the date object at earliest the base date object? 38 | */ 39 | export const atEarliest = (base: Date): Validator => { 40 | if (!isValidDate(base)) { 41 | return falsyValidator; 42 | } 43 | return ( 44 | time => (isValidDate(time) && (time.getTime() >= base.getTime())) 45 | ); 46 | }; 47 | 48 | /** 49 | * Is the date object earlier than the base date object? 50 | */ 51 | export const earlierThan = (base: Date): Validator => { 52 | if (!isValidDate(base)) { 53 | return falsyValidator; 54 | } 55 | return ( 56 | time => (isValidDate(time) && (time.getTime() < base.getTime())) 57 | ); 58 | }; 59 | 60 | /** 61 | * Is the date object at latest the base date object? 62 | */ 63 | export const atLatest = (base: Date): Validator => { 64 | if (!isValidDate(base)) { 65 | return falsyValidator; 66 | } 67 | return ( 68 | time => (isValidDate(time) && (time.getTime() <= base.getTime())) 69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /src/lib/op/basic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | SomeObject, 4 | ShapeValidation, 5 | ExactValidation, 6 | } from '../definition'; 7 | import { 8 | enhancedObjectEntries, 9 | haveSameObjectKeys, 10 | isShapeOrExactValidation, 11 | } from '../utils'; 12 | import { 13 | falsyValidator, 14 | } from '../validators'; 15 | 16 | /** 17 | * Pass the validator. 18 | */ 19 | export const so = (validator: Validator): Validator => ( 20 | val => validator(val) 21 | ); 22 | 23 | /** 24 | * Not pass the validator. 25 | */ 26 | export const not = (validator: Validator): Validator => ( 27 | val => !validator(val) 28 | ); 29 | 30 | /** 31 | * Pass all validators. 32 | */ 33 | export const and = (...validators: Validator[]): Validator => ( 34 | val => validators.every(validator => validator(val)) 35 | ); 36 | 37 | /** 38 | * Pass any validators. 39 | */ 40 | export const or = (...validators: Validator[]): Validator => ( 41 | val => validators.some(validator => validator(val)) 42 | ); 43 | 44 | /** 45 | * Pass the validator on all elements in an array. 46 | */ 47 | export const every = (validator: Validator): Validator => ( 48 | list => (Array.isArray(list) && list.every(item => validator(item))) 49 | ); 50 | 51 | /** 52 | * Pass the validator on any elements in an array. 53 | */ 54 | export const some = (validator: Validator): Validator => ( 55 | list => (Array.isArray(list) && list.some(item => validator(item))) 56 | ); 57 | 58 | /** 59 | * Pass all validators for each properties of an object. 60 | */ 61 | export const shape = ( 62 | /* eslint-disable-next-line arrow-parens */ 63 | (shapeValidation: ShapeValidation): Validator => { 64 | if (!isShapeOrExactValidation(shapeValidation)) { 65 | return falsyValidator; 66 | } 67 | return ( 68 | obj => (true 69 | && (typeof obj === 'object') 70 | && (obj !== null) 71 | && enhancedObjectEntries(shapeValidation).every(([key, validator]) => validator(obj[key])) 72 | ) 73 | ); 74 | } 75 | ); 76 | 77 | /** 78 | * Pass all validators for each properties of an exact object. 79 | */ 80 | export const exact = ( 81 | /* eslint-disable-next-line arrow-parens */ 82 | (exactValidation: ExactValidation): Validator => { 83 | if (!isShapeOrExactValidation(exactValidation)) { 84 | return falsyValidator; 85 | } 86 | return ( 87 | obj => (true 88 | && (typeof obj === 'object') 89 | && (obj !== null) 90 | && haveSameObjectKeys(obj, exactValidation) 91 | && enhancedObjectEntries(exactValidation).every(([key, validator]) => validator(obj[key])) 92 | ) 93 | ); 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /src/lib/op/converter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Validator, 3 | } from '../definition'; 4 | import { 5 | floatConverter, 6 | integerConverter, 7 | lengthConverter, 8 | keysConverter, 9 | valuesConverter, 10 | dateConverter, 11 | } from '../converters'; 12 | 13 | /** 14 | * Pass the validator after converted. 15 | */ 16 | export const convert = ( 17 | converter: (original: T1) => T2, 18 | validator?: Validator, 19 | ): Validator => ( 20 | (val: T1): boolean => { 21 | let convertedVal; 22 | try { 23 | convertedVal = converter(val); 24 | } catch (error) { 25 | return false; 26 | } 27 | return (validator !== undefined) ? validator(convertedVal) : true; 28 | } 29 | ); 30 | 31 | /** 32 | * Pass the validator after converted to a float. 33 | */ 34 | export const toFloat = (validator?: Validator): Validator => ( 35 | convert(floatConverter, validator) 36 | ); 37 | 38 | /** 39 | * Pass the validator after converted to an integer. 40 | */ 41 | export const toInteger = (validator?: Validator): Validator => ( 42 | convert(integerConverter, validator) 43 | ); 44 | 45 | /** 46 | * Pass the validator after converted to the length. 47 | */ 48 | export const toLength = (validator?: Validator): Validator => ( 49 | convert(lengthConverter, validator) 50 | ); 51 | 52 | /** 53 | * Pass the validator after splitted the string as an array. 54 | */ 55 | export const toSplit = ( 56 | separator: string, 57 | validator?: Validator, 58 | ): Validator => ( 59 | convert((val: string) => val.split(separator), validator) 60 | ); 61 | 62 | /** 63 | * Pass the validator after converted to keys of an object. 64 | */ 65 | export const toKeys = (validator?: Validator<(keyof T)[]>): Validator => ( 66 | convert(keysConverter, validator) 67 | ); 68 | 69 | /** 70 | * Pass the validator after converted to values of an object. 71 | */ 72 | export const toValues = (validator?: Validator<(T[keyof T])[]>): Validator => ( 73 | convert(valuesConverter, validator) 74 | ); 75 | 76 | /** 77 | * Pass the validator after converted to a date object. 78 | */ 79 | export const toDate = (validator?: Validator): Validator => ( 80 | convert(dateConverter, validator) 81 | ); 82 | -------------------------------------------------------------------------------- /src/lib/op/index.ts: -------------------------------------------------------------------------------- 1 | import * as basic from './basic'; 2 | import * as converter from './converter'; 3 | 4 | const op = { 5 | ...basic, 6 | ...converter, 7 | }; 8 | 9 | export default op; 10 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * enhancedObjectEntries 3 | * Refer from [issue](https://github.com/Microsoft/TypeScript/issues/21826#issuecomment-479851685). 4 | */ 5 | export const enhancedObjectEntries = Object.entries as (o: T) => [keyof T, T[keyof T]][]; 6 | 7 | /** 8 | * isSameValueZero 9 | * Refer from [repo](https://github.com/domenic/especially/blob/master/abstract-operations.js#L126). 10 | */ 11 | export const isSameValueZero = (x: any, y: any): boolean => ( 12 | ((x === 0) && (y === 0)) || Object.is(x, y) 13 | ); 14 | 15 | export const uniqueArray = (list: T[]): T[] => Array.from(new Set(list)); 16 | 17 | export const haveSameObjectKeys = (base: object, compare: object): boolean => (true 18 | && Object.keys(base).every(key => Object.prototype.propertyIsEnumerable.call(compare, key)) 19 | && Object.keys(compare).every(key => Object.prototype.propertyIsEnumerable.call(base, key)) 20 | ); 21 | 22 | export const isShapeOrExactValidation = (validation: any): validation is object => (true 23 | && (typeof validation === 'object') 24 | && (validation !== null) 25 | && Object.values(validation).every(validator => typeof validator === 'function') 26 | ); 27 | -------------------------------------------------------------------------------- /src/lib/validators.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SomeFunction, 3 | } from './definition'; 4 | import { 5 | uniqueArray, 6 | } from './utils'; 7 | 8 | export const trulyValidator = ( 9 | (): boolean => true 10 | ); 11 | 12 | export const falsyValidator = ( 13 | (): boolean => false 14 | ); 15 | 16 | export const definedValidator = ( 17 | (val: any): val is undefined => (typeof val !== 'undefined') 18 | ); 19 | 20 | export const notDefinedValidator = ( 21 | (val: any): boolean => (typeof val === 'undefined') 22 | ); 23 | 24 | export const nulValidator = ( 25 | (val: any): val is null => (val === null) 26 | ); 27 | 28 | export const nilValidator = ( 29 | (val: any): val is (undefined | null) => ((typeof val === 'undefined') || (val === null)) 30 | ); 31 | 32 | export const booleanValidator = ( 33 | (val: any): val is boolean => (typeof val === 'boolean') 34 | ); 35 | 36 | export const numberValidator = ( 37 | (val: any): val is number => (typeof val === 'number') 38 | ); 39 | 40 | export const stringValidator = ( 41 | (val: any): val is string => (typeof val === 'string') 42 | ); 43 | 44 | export const objectValidator = ( 45 | (val: any): val is object => ((typeof val === 'object') && (val !== null)) 46 | ); 47 | 48 | export const functionValidator = ( 49 | (val: any): val is SomeFunction => (typeof val === 'function') 50 | ); 51 | 52 | export const symbolValidator = ( 53 | (val: any): val is symbol => (typeof val === 'symbol') 54 | ); 55 | 56 | export const floatValidator = ( 57 | (val: any): val is number => Number.isFinite(val) 58 | ); 59 | 60 | export const integerValidator = ( 61 | (val: any): val is number => Number.isInteger(val) 62 | ); 63 | 64 | export const arrayValidator = ( 65 | (val: any): val is any[] => (Array.isArray(val)) 66 | ); 67 | 68 | export const dateValidator = ( 69 | (val: any): val is Date => ((val instanceof Date) && !Number.isNaN(val.getTime())) 70 | ); 71 | 72 | export const distinctValidator = ( 73 | (val: any[]): val is any[] => (Array.isArray(val) && (val.length === uniqueArray(val).length)) 74 | ); 75 | -------------------------------------------------------------------------------- /test/cases/some-simple-posts.ts: -------------------------------------------------------------------------------- 1 | import { op, is } from 'semantic-validator'; 2 | 3 | describe('normal case 01', (): void => { 4 | const validator = op.every( 5 | op.shape({ 6 | id: is.integer(), 7 | content: is.string(), 8 | attachement: op.or( 9 | is.nul(), 10 | op.exact({ 11 | type: is.same('image'), 12 | url: is.string(), 13 | }), 14 | op.exact({ 15 | type: is.same('location'), 16 | coordinates: op.exact({ 17 | lat: op.and( 18 | is.float(), 19 | is.atLeast(-90), 20 | is.atMost(90), 21 | ), 22 | lng: op.and( 23 | is.float(), 24 | is.atLeast(-180), 25 | is.atMost(180), 26 | ), 27 | }), 28 | }), 29 | ), 30 | createdAt: is.date(), 31 | }), 32 | ); 33 | 34 | it('should return true when posts are valid', (): void => { 35 | const posts = [ 36 | { 37 | id: 10000001, 38 | content: 'some-content', 39 | author: 'User One', 40 | attachement: { 41 | type: 'image', 42 | url: 'http://image.com/image-0001.jpg', 43 | }, 44 | createdAt: new Date('2019-06-23T08:24:00Z'), 45 | }, 46 | { 47 | id: 10000002, 48 | content: 'some-content', 49 | author: 'User Two', 50 | attachement: { 51 | type: 'location', 52 | coordinates: { 53 | lat: 25.061027, 54 | lng: 121.5289492, 55 | }, 56 | }, 57 | createdAt: new Date('2019-06-23T08:25:00Z'), 58 | notImportantProp: 'not-important-value', 59 | }, 60 | { 61 | id: 10000003, 62 | content: 'some-content', 63 | author: 'User Three', 64 | attachement: null, 65 | createdAt: new Date('2019-06-23T08:26:00Z'), 66 | notImportantProp: 'not-important-value', 67 | }, 68 | ]; 69 | 70 | expect(validator(posts)).toBe(true); 71 | }); 72 | 73 | it('should return false when posts are invalid', (): void => { 74 | const posts = [ 75 | { 76 | id: 10000001, 77 | content: 'some-content', 78 | author: 'User One', 79 | attachement: { 80 | type: 'location', 81 | url: 'http://image.com/image-0001.jpg', 82 | }, 83 | createdAt: new Date('2019-06-23T08:24:00Z'), 84 | }, 85 | ]; 86 | 87 | expect(validator(posts)).toBe(false); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/is/basic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | same, 3 | oneOf, 4 | defined, 5 | notDefined, 6 | nul, 7 | nil, 8 | bool, 9 | number, 10 | string, 11 | object, 12 | func, 13 | symbol, 14 | instanceOf, 15 | float, 16 | integer, 17 | array, 18 | date, 19 | } from 'semantic-validator/lib/is/basic'; 20 | 21 | describe('basic validator creators', () => { 22 | describe('is: same', () => { 23 | it('should return true when values are the same', () => { 24 | expect(same(undefined)(undefined)).toBe(true); 25 | expect(same(null)(null)).toBe(true); 26 | expect(same(true)(true)).toBe(true); 27 | expect(same(123)(123)).toBe(true); 28 | expect(same(123.456)(123.456)).toBe(true); 29 | expect(same(NaN)(NaN)).toBe(true); 30 | expect(same(+0)(-0)).toBe(true); 31 | expect(same('abc')('abc')).toBe(true); 32 | const list = [1, 2, 3]; 33 | expect(same(list)(list)).toBe(true); 34 | const obj = { key: 'value' }; 35 | expect(same(obj)(obj)).toBe(true); 36 | }); 37 | it('should return false when values are not the same', () => { 38 | expect(same(undefined)(null)).toBe(false); 39 | expect(same(true)(false)).toBe(false); 40 | expect(same(123)(123.456)).toBe(false); 41 | expect(same('abc')('def')).toBe(false); 42 | expect(same([1, 2, 3])([1, 2, 3])).toBe(false); 43 | expect(same({ key: 'value' })({ key: 'value' })).toBe(false); 44 | }); 45 | }); 46 | 47 | describe('is: one of', () => { 48 | it('should return true when the value is one of target', () => { 49 | expect(oneOf(1, 2, 3)(2)).toBe(true); 50 | expect(oneOf('a', 'b', 'c')('b')).toBe(true); 51 | }); 52 | it('should return false when the value is not one of target', () => { 53 | expect(oneOf(1, 2, 3)(4)).toBe(false); 54 | expect(oneOf('a', 'b', 'c')('d')).toBe(false); 55 | }); 56 | }); 57 | 58 | describe('is: defined', () => { 59 | it('should return true when the value is defined', () => { 60 | expect(defined()(null)).toBe(true); 61 | expect(defined()(true)).toBe(true); 62 | expect(defined()(123)).toBe(true); 63 | expect(defined()('abc')).toBe(true); 64 | }); 65 | it('should return false when the value is not defined', () => { 66 | expect(defined()(undefined)).toBe(false); 67 | }); 68 | }); 69 | 70 | describe('is: not defined', () => { 71 | it('should return true when the value is not defined', () => { 72 | expect(notDefined()(undefined)).toBe(true); 73 | }); 74 | it('should return false when the value is defined', () => { 75 | expect(notDefined()(null)).toBe(false); 76 | expect(notDefined()(true)).toBe(false); 77 | expect(notDefined()(123)).toBe(false); 78 | expect(notDefined()('abc')).toBe(false); 79 | }); 80 | }); 81 | 82 | describe('is: nul', () => { 83 | it('should return true when the value is nul', () => { 84 | expect(nul()(null)).toBe(true); 85 | }); 86 | it('should return false when the value is not nul', () => { 87 | expect(nul()(undefined)).toBe(false); 88 | expect(nul()(true)).toBe(false); 89 | }); 90 | }); 91 | 92 | describe('is: nil', () => { 93 | it('should return true when the value is nil', () => { 94 | expect(nil()(null)).toBe(true); 95 | expect(nil()(undefined)).toBe(true); 96 | }); 97 | it('should return false when the value is not nil', () => { 98 | expect(nil()(true)).toBe(false); 99 | }); 100 | }); 101 | 102 | describe('is: bool', () => { 103 | it('should return true when the value is a bool', () => { 104 | expect(bool()(false)).toBe(true); 105 | }); 106 | it('should return false when the value is not a bool', () => { 107 | expect(bool()(123)).toBe(false); 108 | }); 109 | }); 110 | 111 | describe('is: number', () => { 112 | it('should return true when the value is a number', () => { 113 | expect(number()(123)).toBe(true); 114 | }); 115 | it('should return false when the value is not a number', () => { 116 | expect(number()('abc')).toBe(false); 117 | }); 118 | }); 119 | 120 | describe('is: string', () => { 121 | it('should return true when the value is a string', () => { 122 | expect(string()('abc')).toBe(true); 123 | }); 124 | it('should return false when the value is not a string', () => { 125 | expect(string()(123)).toBe(false); 126 | }); 127 | }); 128 | 129 | describe('is: object', () => { 130 | it('should return true when the value is a object', () => { 131 | expect(object()({ key: 'value' })).toBe(true); 132 | expect(object()({})).toBe(true); 133 | }); 134 | it('should return false when the value is not a object', () => { 135 | expect(object()(null)).toBe(false); 136 | expect(object()(123)).toBe(false); 137 | }); 138 | }); 139 | 140 | describe('is: func', () => { 141 | it('should return true when the value is a func', () => { 142 | expect(func()(() => {})).toBe(true); 143 | }); 144 | it('should return false when the value is not a func', () => { 145 | expect(func()(123)).toBe(false); 146 | }); 147 | }); 148 | 149 | describe('is: symbol', () => { 150 | it('should return true when the value is a symbol', () => { 151 | expect(symbol()(Symbol('abc'))).toBe(true); 152 | }); 153 | it('should return false when the value is not a symbol', () => { 154 | expect(symbol()('abc')).toBe(false); 155 | }); 156 | }); 157 | 158 | describe('is: instance of', () => { 159 | it('should return true when the value is the instance of a constructor', () => { 160 | expect(instanceOf(Date)(new Date())).toBe(true); 161 | }); 162 | it('should return false when the value is not the instance of a constructor', () => { 163 | expect(instanceOf(Date)(123)).toBe(false); 164 | }); 165 | }); 166 | 167 | describe('is: float', () => { 168 | it('should return true when the value is a float', () => { 169 | expect(float()(123.456)).toBe(true); 170 | expect(float()(123)).toBe(true); 171 | }); 172 | it('should return false when the value is not a float', () => { 173 | expect(float()('123')).toBe(false); 174 | }); 175 | }); 176 | 177 | describe('is: integer', () => { 178 | it('should return true when the value is a integer', () => { 179 | expect(integer()(123)).toBe(true); 180 | }); 181 | it('should return false when the value is not a integer', () => { 182 | expect(integer()(123.456)).toBe(false); 183 | }); 184 | }); 185 | 186 | describe('is: array', () => { 187 | it('should return true when the value is an array', () => { 188 | expect(array()([1, 2, 3])).toBe(true); 189 | }); 190 | it('should return false when the value is not an array', () => { 191 | expect(array()({})).toBe(false); 192 | }); 193 | }); 194 | 195 | describe('is: date', () => { 196 | it('should return true when the value is a valid date object', () => { 197 | expect(date()(new Date())).toBe(true); 198 | }); 199 | it('should return false when the value is not a valid date object', () => { 200 | expect(date()(new Date('invalid date'))).toBe(false); 201 | expect(date()(new Date('invalid date'))).toBe(false); 202 | }); 203 | }); 204 | }); 205 | -------------------------------------------------------------------------------- /test/is/list.ts: -------------------------------------------------------------------------------- 1 | import { 2 | includes, 3 | excludes, 4 | restrictedBy, 5 | distinct, 6 | } from 'semantic-validator/lib/is/list'; 7 | 8 | describe('list validator creators', () => { 9 | describe('is: includes', () => { 10 | it('should return true when the array value includes all includings', () => { 11 | expect(includes(10, 20, 30)([0, 10, 20, 30, 40])).toBe(true); 12 | }); 13 | it('should return false when the array value not includes all includings', () => { 14 | expect(includes(10, 20, 30)([0, 20, 40])).toBe(false); 15 | expect(includes(10, 20, 30)(123 as any)).toBe(false); 16 | }); 17 | }); 18 | 19 | describe('is: excludes', () => { 20 | it('should return true when the array value excludes all includings', () => { 21 | expect(excludes(5, 15, 25)([0, 10, 20, 30])).toBe(true); 22 | }); 23 | it('should return false when the array value not excludes all includings', () => { 24 | expect(excludes(5, 15, 25)([5, 10])).toBe(false); 25 | expect(excludes(5, 15, 25)(123 as any)).toBe(false); 26 | }); 27 | }); 28 | 29 | describe('is: restricted by', () => { 30 | it('should return true when the array value is restricted by allowed items', () => { 31 | expect(restrictedBy(5, 10, 15, 20)([10, 20])).toBe(true); 32 | }); 33 | it('should return false when the array value is not restricted by allowed items', () => { 34 | expect(restrictedBy(5, 10, 15, 20)([5, 10, 15, 20, 25, 30])).toBe(false); 35 | expect(restrictedBy(5, 10, 15, 20)(123 as any)).toBe(false); 36 | }); 37 | }); 38 | 39 | describe('is: distinct', () => { 40 | it('should return true when the array value are all different', () => { 41 | expect(distinct()([1, 2, 3])).toBe(true); 42 | }); 43 | it('should return false when the array value are not all different', () => { 44 | expect(distinct()([1, 2, 3, 3])).toBe(false); 45 | expect(distinct()(123 as any)).toBe(false); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/is/math.ts: -------------------------------------------------------------------------------- 1 | import { 2 | equalTo, 3 | greaterThan, 4 | atLeast, 5 | lessThan, 6 | atMost, 7 | between, 8 | fromTo, 9 | } from 'semantic-validator/lib/is/math'; 10 | 11 | describe('math validator creators', () => { 12 | describe('is: equal to', () => { 13 | it('should return true when the value is equal to the number', () => { 14 | expect(equalTo(100)(100)).toBe(true); 15 | }); 16 | it('should return false when the value is not equal to the number', () => { 17 | expect(equalTo(Infinity)(100)).toBe(false); 18 | expect(equalTo(100)(200)).toBe(false); 19 | expect(equalTo(100)(-100)).toBe(false); 20 | }); 21 | }); 22 | 23 | describe('is: greater than', () => { 24 | it('should return true when the value is greater than the number', () => { 25 | expect(greaterThan(100)(101)).toBe(true); 26 | }); 27 | it('should return false when the value is not greater than the number', () => { 28 | expect(greaterThan(Infinity)(100)).toBe(false); 29 | expect(greaterThan(100)(100)).toBe(false); 30 | expect(greaterThan(100)(99)).toBe(false); 31 | }); 32 | }); 33 | 34 | describe('is: at least', () => { 35 | it('should return true when the value is at least the number', () => { 36 | expect(atLeast(100)(101)).toBe(true); 37 | expect(atLeast(100)(100)).toBe(true); 38 | }); 39 | it('should return false when the value is not at least the number', () => { 40 | expect(atLeast(Infinity)(100)).toBe(false); 41 | expect(atLeast(100)(99)).toBe(false); 42 | }); 43 | }); 44 | 45 | describe('is: less than', () => { 46 | it('should return true when the value is less than the number', () => { 47 | expect(lessThan(200)(199)).toBe(true); 48 | }); 49 | it('should return false when the value is not less than the number', () => { 50 | expect(lessThan(Infinity)(100)).toBe(false); 51 | expect(lessThan(200)(200)).toBe(false); 52 | expect(lessThan(200)(201)).toBe(false); 53 | }); 54 | }); 55 | 56 | describe('is: at most', () => { 57 | it('should return true when the value is at most the number', () => { 58 | expect(atMost(200)(199)).toBe(true); 59 | expect(atMost(200)(200)).toBe(true); 60 | }); 61 | it('should return false when the value is not at most the number', () => { 62 | expect(atMost(Infinity)(100)).toBe(false); 63 | expect(atMost(200)(201)).toBe(false); 64 | }); 65 | }); 66 | 67 | describe('is: between', () => { 68 | it('should return true when the value is between the numbers', () => { 69 | expect(between(1, 10)(5)).toBe(true); 70 | }); 71 | it('should return false when the value is not between the numbers', () => { 72 | expect(between(1, Infinity)(100)).toBe(false); 73 | expect(between(1, 10)(0)).toBe(false); 74 | expect(between(1, 10)(1)).toBe(false); 75 | expect(between(1, 10)(10)).toBe(false); 76 | expect(between(1, 10)(11)).toBe(false); 77 | }); 78 | }); 79 | 80 | describe('is: from to', () => { 81 | it('should return true when the value is from to the numbers', () => { 82 | expect(fromTo(1, 10)(1)).toBe(true); 83 | expect(fromTo(0, 10)(5)).toBe(true); 84 | expect(fromTo(1, 10)(10)).toBe(true); 85 | }); 86 | it('should return false when the value is not from to the numbers', () => { 87 | expect(fromTo(1, Infinity)(100)).toBe(false); 88 | expect(fromTo(1, 10)(0)).toBe(false); 89 | expect(fromTo(1, 10)(11)).toBe(false); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/is/text.ts: -------------------------------------------------------------------------------- 1 | import { 2 | match, 3 | startsWith, 4 | endsWith, 5 | contains, 6 | } from 'semantic-validator/lib/is/text'; 7 | 8 | describe('text validator creators', () => { 9 | describe('is: match', () => { 10 | it('should return true when the string value matches the regular expression', () => { 11 | expect(match(/^b[aeiou]t$/)('bat')).toBe(true); 12 | }); 13 | it('should return false when the string value not matches the regular expression', () => { 14 | expect(match(123 as any)('never')).toBe(false); 15 | expect(match(/^b[aeiou]t$/)('brt')).toBe(false); 16 | expect(match(/^b[aeiou]t$/)(123 as any)).toBe(false); 17 | }); 18 | }); 19 | 20 | describe('is: starts with', () => { 21 | it('should return true when the string value starts with the wording', () => { 22 | expect(startsWith('net')('network')).toBe(true); 23 | }); 24 | it('should return false when the string value not starts with the wording', () => { 25 | expect(startsWith(123 as any)('never')).toBe(false); 26 | expect(startsWith('net')('artwork')).toBe(false); 27 | expect(startsWith('net')(123 as any)).toBe(false); 28 | }); 29 | }); 30 | 31 | describe('is: ends with', () => { 32 | it('should return true when the string value ends with the wording', () => { 33 | expect(endsWith('fox')('firefox')).toBe(true); 34 | }); 35 | it('should return false when the string value not ends with the wording', () => { 36 | expect(endsWith(123 as any)('never')).toBe(false); 37 | expect(endsWith('fox')('firewall')).toBe(false); 38 | expect(endsWith('fox')(123 as any)).toBe(false); 39 | }); 40 | }); 41 | 42 | describe('is: contains', () => { 43 | it('should return true when the string value contains the wording', () => { 44 | expect(contains('lie')('believe')).toBe(true); 45 | }); 46 | it('should return false when the string value not contains the wording', () => { 47 | expect(contains(123 as any)('never')).toBe(false); 48 | expect(contains('lie')('behave')).toBe(false); 49 | expect(contains('lie')(123 as any)).toBe(false); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /test/is/time.ts: -------------------------------------------------------------------------------- 1 | import { 2 | moment, 3 | laterThan, 4 | atEarliest, 5 | earlierThan, 6 | atLatest, 7 | } from 'semantic-validator/lib/is/time'; 8 | 9 | describe('time validator creators', () => { 10 | describe('is: moment', () => { 11 | it('should return true when the date object is at the moment of the base', () => { 12 | expect(moment(new Date(1500000000000))(new Date(1500000000000))).toBe(true); 13 | }); 14 | it('should return false when the date object is not at the moment of the base', () => { 15 | expect(moment(new Date(1500000000000))(new Date(1400000000000))).toBe(false); 16 | expect(moment(new Date('invalid date'))(new Date(1500000000000))).toBe(false); 17 | expect(moment(new Date(1500000000000))(new Date('invalid date'))).toBe(false); 18 | expect(moment(123 as any)(new Date(1500000000000))).toBe(false); 19 | expect(moment(new Date(1500000000000))(123 as any)).toBe(false); 20 | }); 21 | }); 22 | 23 | describe('is: later than', () => { 24 | it('should return true when the date object is later than the base', () => { 25 | expect(laterThan(new Date(1500000000000))(new Date(1500000000001))).toBe(true); 26 | }); 27 | it('should return false when the date object is not later than the base', () => { 28 | expect(laterThan(new Date(1500000000000))(new Date(1500000000000))).toBe(false); 29 | expect(laterThan(new Date(1500000000000))(new Date(1499999999999))).toBe(false); 30 | expect(laterThan(new Date('invalid date'))(new Date(1500000000000))).toBe(false); 31 | expect(laterThan(new Date(1500000000000))(new Date('invalid date'))).toBe(false); 32 | expect(laterThan(new Date(1500000000000))(123 as any)).toBe(false); 33 | }); 34 | }); 35 | 36 | describe('is: at earliest', () => { 37 | it('should return true when the date object is at earliest the base', () => { 38 | expect(atEarliest(new Date(1500000000000))(new Date(1500000000000))).toBe(true); 39 | expect(atEarliest(new Date(1500000000000))(new Date(1500000000001))).toBe(true); 40 | }); 41 | it('should return false when the date object is not at earliest the base', () => { 42 | expect(atEarliest(new Date(1500000000000))(new Date(1499999999999))).toBe(false); 43 | expect(atEarliest(new Date('invalid date'))(new Date(1500000000000))).toBe(false); 44 | expect(atEarliest(new Date(1500000000000))(new Date('invalid date'))).toBe(false); 45 | expect(atEarliest(new Date(1500000000000))(123 as any)).toBe(false); 46 | }); 47 | }); 48 | 49 | describe('is: earlier than', () => { 50 | it('should return true when the date object is earlier than the base', () => { 51 | expect(earlierThan(new Date(1500000000000))(new Date(1499999999999))).toBe(true); 52 | }); 53 | it('should return false when the date object is not earlier than the base', () => { 54 | expect(earlierThan(new Date(1500000000000))(new Date(1500000000000))).toBe(false); 55 | expect(earlierThan(new Date(1500000000000))(new Date(1500000000001))).toBe(false); 56 | expect(earlierThan(new Date('invalid date'))(new Date(1500000000000))).toBe(false); 57 | expect(earlierThan(new Date(1500000000000))(new Date('invalid date'))).toBe(false); 58 | expect(earlierThan(new Date(1500000000000))(123 as any)).toBe(false); 59 | }); 60 | }); 61 | 62 | describe('is: at latest', () => { 63 | it('should return true when the date object is at latest the base', () => { 64 | expect(atLatest(new Date(1500000000000))(new Date(1500000000000))).toBe(true); 65 | expect(atLatest(new Date(1500000000000))(new Date(1499999999999))).toBe(true); 66 | }); 67 | it('should return false when the date object is not at latest the base', () => { 68 | expect(atLatest(new Date(1500000000000))(new Date(1500000000001))).toBe(false); 69 | expect(atLatest(new Date('invalid date'))(new Date(1500000000000))).toBe(false); 70 | expect(atLatest(new Date(1500000000000))(new Date('invalid date'))).toBe(false); 71 | expect(atLatest(new Date(1500000000000))(123 as any)).toBe(false); 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/op/basic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | so, 3 | not, 4 | and, 5 | or, 6 | every, 7 | some, 8 | shape, 9 | exact, 10 | } from 'semantic-validator/lib/op/basic'; 11 | 12 | describe('basic validator operators', () => { 13 | describe('op: so', () => { 14 | const is = { 15 | same: (target: string) => (val: string) => (target === val), 16 | }; 17 | it('should return true when the validator returns true', () => { 18 | expect(so(is.same('hello'))('hello')).toBe(true); 19 | }); 20 | it('should return false when the validator not returns true', () => { 21 | expect(so(is.same('hello'))('bye')).toBe(false); 22 | }); 23 | }); 24 | 25 | describe('op: not', () => { 26 | const is = { 27 | same: (target: string) => (val: string) => (target === val), 28 | }; 29 | it('should return true when the validator returns false', () => { 30 | expect(not(is.same('hello'))('bye')).toBe(true); 31 | }); 32 | it('should return false when the validator not returns false', () => { 33 | expect(not(is.same('hello'))('hello')).toBe(false); 34 | }); 35 | }); 36 | 37 | describe('op: and', () => { 38 | const is = { 39 | integer: () => (val: any) => Number.isInteger(val), 40 | greaterThan: (num: number) => (val: any) => (val > num), 41 | }; 42 | it('should return true when all validators return true', () => { 43 | expect(and(is.integer(), is.greaterThan(100))(120)).toBe(true); 44 | }); 45 | it('should return false when not all validators return true', () => { 46 | expect(and(is.integer(), is.greaterThan(100))(80)).toBe(false); 47 | expect(and(is.integer(), is.greaterThan(100))(123.456)).toBe(false); 48 | }); 49 | }); 50 | 51 | describe('op: or', () => { 52 | const is = { 53 | nul: () => (val: any) => (val === null), 54 | integer: () => (val: any) => Number.isInteger(val), 55 | }; 56 | it('should return true when any validators return true', () => { 57 | expect(or(is.nul(), is.integer())(null)).toBe(true); 58 | expect(or(is.nul(), is.integer())(123)).toBe(true); 59 | }); 60 | it('should return false when not any validators return true', () => { 61 | expect(or(is.nul(), is.integer())('abc')).toBe(false); 62 | }); 63 | }); 64 | 65 | describe('op: every', () => { 66 | const is = { 67 | integer: () => (val: any) => Number.isInteger(val), 68 | }; 69 | it('should return true when the validator returns true on all elements', () => { 70 | expect(every(is.integer())([1, 2, 3])).toBe(true); 71 | }); 72 | it('should return false when the validator not returns true on all elements', () => { 73 | expect(every(is.integer())([1, 2.22, 3])).toBe(false); 74 | }); 75 | }); 76 | 77 | describe('op: some', () => { 78 | const is = { 79 | integer: () => (val: any) => Number.isInteger(val), 80 | }; 81 | it('should return true when the validator returns true on all elements', () => { 82 | expect(some(is.integer())([1, 2, 3])).toBe(true); 83 | expect(some(is.integer())([1.11, 2, 3.33])).toBe(true); 84 | }); 85 | it('should return false when the validator not returns true on all elements', () => { 86 | expect(some(is.integer())([1.11, 2.22, 3.33])).toBe(false); 87 | }); 88 | }); 89 | 90 | describe('op: shape', () => { 91 | const is = { 92 | integer: () => (val: any) => Number.isInteger(val), 93 | string: () => (val: any) => (typeof val === 'string'), 94 | }; 95 | it('should return true when all key validators returns true', () => { 96 | expect( 97 | shape({ 98 | id: is.integer(), 99 | name: is.string(), 100 | })({ 101 | id: 123, 102 | name: 'Mr. Sandman', 103 | }), 104 | ).toBe(true); 105 | expect( 106 | shape({ 107 | id: is.integer(), 108 | name: is.string(), 109 | })({ 110 | id: 123, 111 | name: 'Mr. Sandman', 112 | active: true, 113 | }), 114 | ).toBe(true); 115 | }); 116 | it('should return false when not all key validators returns true', () => { 117 | expect( 118 | shape(123 as any)({ 119 | id: 123, 120 | name: 'Mr. Sandman', 121 | }), 122 | ).toBe(false); 123 | expect( 124 | shape({ 125 | id: is.integer(), 126 | name: is.string(), 127 | })({ 128 | id: 123, 129 | } as any), 130 | ).toBe(false); 131 | expect( 132 | shape({ 133 | id: is.integer(), 134 | name: is.string(), 135 | })({ 136 | id: 123, 137 | name: 456, 138 | } as any), 139 | ).toBe(false); 140 | }); 141 | }); 142 | 143 | describe('op: exact', () => { 144 | const is = { 145 | integer: () => (val: any) => Number.isInteger(val), 146 | string: () => (val: any) => (typeof val === 'string'), 147 | }; 148 | it('should return true when all key validators returns true on exact object', () => { 149 | expect( 150 | exact({ 151 | id: is.integer(), 152 | name: is.string(), 153 | })({ 154 | id: 123, 155 | name: 'Mr. Sandman', 156 | }), 157 | ).toBe(true); 158 | }); 159 | it('should return false when not all key validators returns true on exact object', () => { 160 | expect( 161 | exact(123 as any)({ 162 | id: 123, 163 | name: 'Mr. Sandman', 164 | }), 165 | ).toBe(false); 166 | expect( 167 | exact({ 168 | id: is.integer(), 169 | name: is.string(), 170 | })({ 171 | id: 123, 172 | name: 'Mr. Sandman', 173 | active: true, 174 | } as any), 175 | ).toBe(false); 176 | expect( 177 | exact({ 178 | id: is.integer(), 179 | name: is.string(), 180 | })({ 181 | id: 123, 182 | } as any), 183 | ).toBe(false); 184 | expect( 185 | exact({ 186 | id: is.integer(), 187 | name: is.string(), 188 | })({ 189 | id: 123, 190 | name: 456, 191 | } as any), 192 | ).toBe(false); 193 | }); 194 | }); 195 | }); 196 | -------------------------------------------------------------------------------- /test/op/converter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | convert, 3 | toFloat, 4 | toInteger, 5 | toLength, 6 | toSplit, 7 | toKeys, 8 | toValues, 9 | toDate, 10 | } from 'semantic-validator/lib/op/converter'; 11 | 12 | describe('converter validator operators', () => { 13 | describe('op: convert', () => { 14 | const is = { 15 | greaterThan: (num: number) => (val: any) => (val > num), 16 | }; 17 | it('should return true when successfully convert the value and validate.', () => { 18 | expect( 19 | convert( 20 | (numeric: string) => parseFloat(numeric), 21 | is.greaterThan(100), 22 | )('150'), 23 | ).toBe(true); 24 | expect( 25 | convert( 26 | (numeric: string) => parseFloat(numeric), 27 | )('150'), 28 | ).toBe(true); 29 | }); 30 | it('should return true when not successfully convert the value and validate', () => { 31 | expect( 32 | convert( 33 | (numeric: string) => parseFloat(numeric), 34 | is.greaterThan(100), 35 | )('50'), 36 | ).toBe(false); 37 | }); 38 | }); 39 | 40 | describe('op: to float', () => { 41 | const is = { 42 | greaterThan: (num: number) => (val: any) => (val > num), 43 | }; 44 | it('should return true when successfully convert to a float and validate', () => { 45 | expect(toFloat(is.greaterThan(1.2))('1.5')).toBe(true); 46 | }); 47 | it('should return true when not successfully convert to a float and validate', () => { 48 | expect(toFloat(is.greaterThan(1.2))('two')).toBe(false); 49 | expect(toFloat(is.greaterThan(1.2))('0.8')).toBe(false); 50 | }); 51 | }); 52 | 53 | describe('op: to integer', () => { 54 | const is = { 55 | greaterThan: (num: number) => (val: any) => (val > num), 56 | }; 57 | it('should return true when successfully convert to an integer and validate', () => { 58 | expect(toInteger(is.greaterThan(100))('150')).toBe(true); 59 | }); 60 | it('should return true when not successfully convert to an integer and validate', () => { 61 | expect(toInteger(is.greaterThan(100))('two')).toBe(false); 62 | expect(toInteger(is.greaterThan(100))('123.456')).toBe(false); 63 | expect(toInteger(is.greaterThan(100))('50')).toBe(false); 64 | }); 65 | }); 66 | 67 | describe('op: to length', () => { 68 | const is = { 69 | atLeast: (num: number) => (val: any) => (val >= num), 70 | }; 71 | it('should return true when successfully get the length and validate', () => { 72 | expect(toLength(is.atLeast(3))(['a', 'b', 'c'])).toBe(true); 73 | expect(toLength(is.atLeast(3))('abc')).toBe(true); 74 | }); 75 | it('should return true when not successfully get the length and validate', () => { 76 | expect(toLength(is.atLeast(3))(['a', 'b'])).toBe(false); 77 | expect(toLength(is.atLeast(3))('ab')).toBe(false); 78 | expect(toLength(is.atLeast(3))(3)).toBe(false); 79 | }); 80 | }); 81 | 82 | describe('op: to split', () => { 83 | const every = (validator: (val: any) => boolean): (list: any[]) => boolean => ( 84 | (list: any[]) => (Array.isArray(list) && list.every(item => validator(item))) 85 | ); 86 | const is = { 87 | startsWith: (wording: string) => (val: string) => (val.startsWith(wording)), 88 | }; 89 | it('should return true when successfully split to the array and validate', () => { 90 | expect( 91 | toSplit( 92 | ',', 93 | every(is.startsWith('c')), 94 | )('candy,cookie,coffee'), 95 | ).toBe(true); 96 | }); 97 | it('should return true when not successfully split to the array and validate', () => { 98 | expect( 99 | toSplit( 100 | ',', 101 | every(is.startsWith('c')), 102 | )('candy,cookie,tea'), 103 | ).toBe(false); 104 | expect( 105 | toSplit( 106 | ',', 107 | every(is.startsWith('c')), 108 | )(123), 109 | ).toBe(false); 110 | }); 111 | }); 112 | 113 | describe('op: to keys', () => { 114 | const every = (validator: (val: any) => boolean): (list: any[]) => boolean => ( 115 | (list: any[]) => (Array.isArray(list) && list.every(item => validator(item))) 116 | ); 117 | const is = { 118 | oneOf: (...items: any[]) => (val: any) => (items.includes(val)), 119 | }; 120 | it('should return true when successfully get keys of an object and validate', () => { 121 | expect( 122 | toKeys( 123 | every(is.oneOf('id', 'name')), 124 | )({ id: 123, name: 'Mario' }), 125 | ).toBe(true); 126 | }); 127 | it('should return true when not successfully get keys of an object and validate', () => { 128 | expect( 129 | toKeys( 130 | every(is.oneOf('id', 'name')), 131 | )({ id: 123, name: 'Mario', age: 20 }), 132 | ).toBe(false); 133 | }); 134 | }); 135 | 136 | describe('op: to values', () => { 137 | const every = (validator: (val: any) => boolean): (list: any[]) => boolean => ( 138 | (list: any[]) => (Array.isArray(list) && list.every(item => validator(item))) 139 | ); 140 | const is = { 141 | integer: () => (val: any) => Number.isInteger(val), 142 | }; 143 | it('should return true when successfully get values of an object and validate', () => { 144 | expect( 145 | toValues( 146 | every(is.integer()), 147 | )({ people: 64, seats: 80 }), 148 | ).toBe(true); 149 | }); 150 | it('should return true when not successfully get values of an object and validate', () => { 151 | expect( 152 | toValues( 153 | every(is.integer()), 154 | )({ people: 640, seats: 'many' }), 155 | ).toBe(false); 156 | }); 157 | }); 158 | 159 | describe('op: to date', () => { 160 | const is = { 161 | moment: (base: Date) => (date: Date) => (date.getTime() === base.getTime()), 162 | }; 163 | it('should return true when successfully convert to a date object and validate', () => { 164 | expect(toDate(is.moment(new Date(1561889036718)))(1561889036718)).toBe(true); 165 | }); 166 | it('should return true when not successfully convert to a date object and validate', () => { 167 | expect(toDate(is.moment(new Date(1561889036718)))(1500000000000)).toBe(false); 168 | expect(toDate(is.moment(new Date(1561889036718)))('invalid date')).toBe(false); 169 | expect(toDate(is.moment(new Date(1561889036718)))(123 as any)).toBe(false); 170 | }); 171 | }); 172 | }); 173 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "strict": true, 7 | "strictNullChecks": true, 8 | "strictPropertyInitialization": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "paths": { 13 | "semantic-validator": [ 14 | "../src" 15 | ], 16 | "semantic-validator/*": [ 17 | "../src/*" 18 | ] 19 | }, 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "declaration": true, 9 | "strict": true, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "outDir": "dist", 16 | "target": "es5", 17 | "lib": [ 18 | "es2015", 19 | ] 20 | }, 21 | "include": [ 22 | "src/**/*.ts" 23 | ], 24 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './src/index.ts', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | filename: 'bundle.js', 9 | library: { 10 | root: 'semanticValidator', 11 | commonjs: 'semantic-validator', 12 | amd: 'semantic-validator' 13 | }, 14 | libraryTarget: 'umd' 15 | }, 16 | resolve: { 17 | extensions: ['.ts', '.js'], 18 | }, 19 | devtool: "source-map", 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.ts$/, 24 | use: [ 25 | { 26 | loader: 'ts-loader', 27 | options: { 28 | compilerOptions: { 29 | baseUrl: "./", 30 | module: "commonjs", 31 | moduleResolution: "node", 32 | sourceMap: true, 33 | target: "es5", 34 | lib: [ 35 | "es2015", 36 | ] 37 | }, 38 | }, 39 | }, 40 | ], 41 | }, 42 | ], 43 | }, 44 | }; 45 | --------------------------------------------------------------------------------