├── .gitignore ├── .travis.yml ├── README.md ├── dist ├── index.d.ts ├── index.js └── validators │ ├── absence.d.ts │ ├── absence.js │ ├── array-exclusion.d.ts │ ├── array-exclusion.js │ ├── array-inclusion.d.ts │ ├── array-inclusion.js │ ├── array-length.d.ts │ ├── array-length.js │ ├── block.d.ts │ ├── block.js │ ├── bson-object-id.d.ts │ ├── bson-object-id.js │ ├── index.d.ts │ ├── index.js │ ├── number-size.d.ts │ ├── number-size.js │ ├── presence.d.ts │ ├── presence.js │ ├── string-base64.d.ts │ ├── string-base64.js │ ├── string-date.d.ts │ ├── string-date.js │ ├── string-email.d.ts │ ├── string-email.js │ ├── string-eth-address.d.ts │ ├── string-eth-address.js │ ├── string-exclusion.d.ts │ ├── string-exclusion.js │ ├── string-fqdn.d.ts │ ├── string-fqdn.js │ ├── string-hex-color.d.ts │ ├── string-hex-color.js │ ├── string-hexadecimal.d.ts │ ├── string-hexadecimal.js │ ├── string-inclusion.d.ts │ ├── string-inclusion.js │ ├── string-json.d.ts │ ├── string-json.js │ ├── string-length.d.ts │ ├── string-length.js │ ├── string-lowercase.d.ts │ ├── string-lowercase.js │ ├── string-match.d.ts │ ├── string-match.js │ ├── string-uppercase.d.ts │ ├── string-uppercase.js │ ├── string-uuid.d.ts │ └── string-uuid.js ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── index.ts └── validators │ ├── absence.ts │ ├── array-exclusion.ts │ ├── array-inclusion.ts │ ├── array-length.ts │ ├── block.ts │ ├── bson-object-id.ts │ ├── index.ts │ ├── number-size.ts │ ├── presence.ts │ ├── string-base64.ts │ ├── string-date.ts │ ├── string-email.ts │ ├── string-eth-address.ts │ ├── string-exclusion.ts │ ├── string-fqdn.ts │ ├── string-hex-color.ts │ ├── string-hexadecimal.ts │ ├── string-inclusion.ts │ ├── string-json.ts │ ├── string-length.ts │ ├── string-lowercase.ts │ ├── string-match.ts │ ├── string-uppercase.ts │ └── string-uuid.ts ├── tests ├── index.js └── validators │ ├── absence.js │ ├── array-exclusion.js │ ├── array-inclusion.js │ ├── array-length.js │ ├── block.js │ ├── bson-object-id.js │ ├── number-size.js │ ├── presence.js │ ├── string-base64.js │ ├── string-date.js │ ├── string-email.js │ ├── string-eth-address.js │ ├── string-exclusion.js │ ├── string-fqdn.js │ ├── string-hex-color.js │ ├── string-hexadecimal.js │ ├── string-inclusion.js │ ├── string-json.js │ ├── string-length.js │ ├── string-lowercase.js │ ├── string-match.js │ ├── string-uppercase.js │ └── string-uuid.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | node_modules 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "7" 5 | - "6" 6 | - "5" 7 | - "4" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build Status](https://travis-ci.org/xpepermint/validatablejs.svg?branch=master) [![NPM Version](https://badge.fury.io/js/validatable.svg)](https://badge.fury.io/js/validatable) [![Dependency Status](https://gemnasium.com/xpepermint/validatablejs.svg)](https://gemnasium.com/xpepermint/validatablejs) 2 | 3 | # validatable.js 4 | 5 | > A library for synchronous and asynchronous input validation. 6 | 7 | This is a light weight open source package for use on **server** or in **browser** (using module bundler). The source code is available on [GitHub](https://github.com/xpepermint/validatablejs) where you can also find our [issue tracker](https://github.com/xpepermint/validatablejs/issues). 8 | 9 | ## Related Projects 10 | 11 | * [RawModel.js](https://github.com/xpepermint/rawmodeljs): Strongly-typed JavaScript object with support for validation and error handling. 12 | * [Typeable.js](https://github.com/xpepermint/typeablejs): A library for checking and casting types. 13 | * [Handleable.js](https://github.com/xpepermint/handleablejs): A library for synchronous and asynchronous error handling. 14 | 15 | ## Install 16 | 17 | Run the command below to install the package. 18 | 19 | ``` 20 | $ npm install --save validatable 21 | ``` 22 | 23 | This package uses promises thus you need to use [Promise polyfill](https://github.com/taylorhakes/promise-polyfill) when promises are not supported. 24 | 25 | ## Example 26 | 27 | ```js 28 | import {Validator} from 'validatable'; 29 | 30 | let v = new Validator(); 31 | 32 | let e = await v.validate( 33 | '', // value to validate 34 | [ // list of validations 35 | { 36 | validator: 'presence', // validator name 37 | message: '%{foo} must be present', // validator options 38 | foo: 'bar' // a custom variable for the message 39 | } 40 | ] 41 | ); // -> list of errors 42 | ``` 43 | 44 | See the `./tests` folder for details. 45 | 46 | ## API 47 | 48 | **Validator({failFast, validators, context})** 49 | 50 | > A core validation class. 51 | 52 | | Option | Type | Required | Default | Description 53 | |--------|------|----------|---------|------------ 54 | | failFast | Boolean | No | false | When set to `true`, the validation stops after the first validation error. 55 | | validators | Object | No | built-in validators | Object with custom validators (this variable is merged with built-in validators thus you can override a validator key if you need to). 56 | | context | Object | No | null | A context reference which is applied to each validator method. 57 | 58 | ```js 59 | import {Validator} from 'validatable'; 60 | 61 | let v = new Validator({ 62 | failFast: true, 63 | validators: { 64 | async coolness ({value, recipe}}) { return value === 'cool' } // custom validator 65 | }, 66 | context: null 67 | }); 68 | ``` 69 | 70 | **Validator.prototype.validate(value, recipes)**: Promise(Object[]) 71 | 72 | > Validates a value against the provided options. 73 | 74 | | Option | Type | Required | Default | Description 75 | |--------|------|----------|---------|------------ 76 | | value | Any | Yes | - | A value to validate. 77 | | recipes | Array | Yes | [] | A configuration object describing validators. 78 | 79 | ```js 80 | let value = 'John Smith'; 81 | let recipe = { 82 | validator: 'presence', // [required] validator name 83 | message: '%{foo} must be present', // [optional] validation error message (note that you can insert related recipe values by using the %{key} syntax) 84 | code: 422, // [optional] validation error code 85 | condition: () => true, // [optional] a condition which switches the validation on/off 86 | foo: 'bar' // [optional] a custom variable 87 | }; 88 | let recipes = [recipe]; 89 | await v.validate(value, recipes); 90 | ``` 91 | 92 | ### Built-in Validators 93 | 94 | **absence** 95 | 96 | > Validates that the specified field is blank. 97 | 98 | **arrayExclusion** 99 | 100 | > Validates that the specified field is not in an array of values. 101 | 102 | | Option | Type | Required | Default | Description 103 | |--------|------|----------|---------|------------ 104 | | values | Array | Yes | - | Array of restricted values. 105 | 106 | **arrayInclusion** 107 | 108 | > Validates that the specified field is in an array of values. 109 | 110 | | Option | Type | Required | Default | Description 111 | |--------|------|----------|---------|------------ 112 | | values | Array | Yes | - | Array of allowed values. 113 | 114 | **arrayLength** 115 | 116 | > Validates the size of an array. 117 | 118 | | Option | Type | Required | Default | Description 119 | |--------|------|----------|---------|------------ 120 | | min | Number | No | - | Allowed minimum items count. 121 | | minOrEqual | Number | No | - | Allowed minimum items count (allowing equal). 122 | | max | Number | No | - | Allowed maximum items count. 123 | | maxOrEqual | Number | No | - | Allowed maximum items count (allowing equal). 124 | 125 | **block** 126 | 127 | > Validates the specified field against the provided block function. If the function returns true then the field is treated as valid. 128 | 129 | | Option | Type | Required | Default | Description 130 | |--------|------|----------|---------|------------ 131 | | block | Function,Promise | Yes | - | Synchronous or asynchronous function (e.g. `async () => true`) 132 | 133 | ```js 134 | let recipe = { 135 | validator: 'block', 136 | message: 'must be present', 137 | async block (value, recipe) { return true } 138 | }; 139 | ``` 140 | 141 | **BSONObjectID** 142 | 143 | > Validates that the specified field is a valid hex-encoded representation of a [MongoDB ObjectID](http://docs.mongodb.org/manual/reference/object-id/). 144 | 145 | **numberSize** 146 | 147 | > Validates the size of a number. 148 | 149 | | Option | Type | Required | Default | Description 150 | |--------|------|----------|---------|------------ 151 | | min | Number | No | - | Allowed minimum value. 152 | | minOrEqual | Number | No | - | Allowed minimum value (allowing equal). 153 | | max | Number | No | - | Allowed maximum value. 154 | | maxOrEqual | Number | No | - | Allowed maximum value (allowing equal). 155 | 156 | **presence** 157 | 158 | > Validates that the specified field is not blank. 159 | 160 | **stringBase64** 161 | 162 | > Validates that the specified field is base64 encoded string. 163 | 164 | **stringDate** 165 | 166 | > Validates that the specified field is a date string. 167 | 168 | | Option | Type | Required | Default | Description 169 | |--------|------|----------|----------|----------- 170 | | iso | Boolean | No | false | When `true` only ISO-8601 date format is accepted. 171 | 172 | **stringEmail** 173 | 174 | > Validates that the specified field is an email. 175 | 176 | | Option | Type | Required | Default | Description 177 | |--------|------|----------|---------|------------ 178 | | allowDisplayName | Boolean | No | false | When set to true, the validator will also match `name
`. 179 | | allowUtf8LocalPart | Boolean | No | false | When set to false, the validator will not allow any non-English UTF8 character in email address' local part. 180 | | requireTld | Boolean | No | true | When set to false, email addresses without having TLD in their domain will also be matched. 181 | 182 | **stringETHAddress** 183 | 184 | > Checks if the string represents an ethereum address. 185 | 186 | **stringExclusion** 187 | 188 | > Checks if the string does not contain the seed. 189 | 190 | | Option | Type | Required | Default | Description 191 | |--------|------|----------|---------|------------ 192 | | seed | String | Yes | - | The seed which should exist in the string. 193 | 194 | **stringFQDN** 195 | 196 | > Validates that the specified field is a fully qualified domain name (e.g. domain.com). 197 | 198 | | Option | Type | Required | Default | Description 199 | |--------|------|----------|---------|------------ 200 | | requireTld | Boolean | No | true | Require top-level domain name. 201 | | allowUnderscores | Boolean | No | false | Allow string to include underscores. 202 | | allowTrailingDot | Boolean | No | false | Allow string to include a trailing dot. 203 | 204 | **stringHexColor** 205 | 206 | > Validates that the specified field is a hexadecimal color string. 207 | 208 | **stringHexadecimal** 209 | 210 | > Validates that the specified field is a hexadecimal number. 211 | 212 | **stringInclusion** 213 | 214 | > Checks if the string contains the seed. 215 | 216 | | Option | Type | Required | Default | Description 217 | |--------|------|----------|---------|------------ 218 | | seed | String | Yes | - | The seed which should exist in the string. 219 | 220 | **stringJSON** 221 | 222 | > Validates that the specified field is a stringified JSON string. 223 | 224 | **stringLength** 225 | 226 | > Validates the length of the specified field. 227 | 228 | | Option | Type | Required | Default | Description 229 | |--------|------|----------|---------|------------ 230 | | bytes | Boolean | No | false | When `true` the number of bytes is returned. 231 | | min | Number | No | - | Allowed minimum number of characters. 232 | | minOrEqual | Number | No | - | Allowed minimum value number of characters (allowing equal). 233 | | max | Number | No | - | Allowed maximum number of characters. 234 | | maxOrEqual | Number | No | - | Allowed maximum number of characters (allowing equal). 235 | 236 | **stringLowercase** 237 | 238 | > Validates that the specified field is lowercase. 239 | 240 | **stringMatch** 241 | 242 | > Validates that the specified field matches the pattern. 243 | 244 | | Key | Type | Required | Default | Description 245 | |-----|------|----------|---------|------------ 246 | | regexp | RegExp | Yes | - | Regular expression pattern. 247 | 248 | **stringUppercase** 249 | 250 | > Validates that the specified field is uppercase. 251 | 252 | **stringUUID** 253 | 254 | > Validates that the specified field is a [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier). 255 | 256 | | Option | Type | Required | Default | Description 257 | |--------|------|----------|---------|------------ 258 | | version | Integer | No | - | UUID version (1, 2, 3, 4 or 5). 259 | 260 | ## License (MIT) 261 | 262 | ``` 263 | Copyright (c) 2016 Kristijan Sedlak 264 | 265 | Permission is hereby granted, free of charge, to any person obtaining a copy 266 | of this software and associated documentation files (the "Software"), to deal 267 | in the Software without restriction, including without limitation the rights 268 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 269 | copies of the Software, and to permit persons to whom the Software is 270 | furnished to do so, subject to the following conditions: 271 | 272 | The above copyright notice and this permission notice shall be included in 273 | all copies or substantial portions of the Software. 274 | 275 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 276 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 277 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 278 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 279 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 280 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 281 | THE SOFTWARE. 282 | ``` 283 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface ValidatorRecipe { 2 | validator: string; 3 | message?: string | (() => string); 4 | code?: number; 5 | condition?: () => boolean | Promise; 6 | [key: string]: any; 7 | } 8 | export interface ValidatorError { 9 | validator: string; 10 | message: string; 11 | code: number; 12 | } 13 | export declare class Validator { 14 | failFast: boolean; 15 | validators: { 16 | [name: string]: () => boolean | Promise; 17 | }; 18 | context: any; 19 | constructor({failFast, validators, context}?: { 20 | failFast?: boolean; 21 | validators?: { 22 | [name: string]: () => boolean | Promise; 23 | }; 24 | context?: any; 25 | }); 26 | _createValidatorError(recipe: ValidatorRecipe): ValidatorError; 27 | _createString(template: string, data: any): string; 28 | validate(value: any, recipes?: ValidatorRecipe[]): Promise; 29 | } 30 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | var __generator = (this && this.__generator) || function (thisArg, body) { 11 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 12 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 13 | function verb(n) { return function (v) { return step([n, v]); }; } 14 | function step(op) { 15 | if (f) throw new TypeError("Generator is already executing."); 16 | while (_) try { 17 | if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t; 18 | if (y = 0, t) op = [0, t.value]; 19 | switch (op[0]) { 20 | case 0: case 1: t = op; break; 21 | case 4: _.label++; return { value: op[1], done: false }; 22 | case 5: _.label++; y = op[1]; op = [0]; continue; 23 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 24 | default: 25 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 26 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 27 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 28 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 29 | if (t[2]) _.ops.pop(); 30 | _.trys.pop(); continue; 31 | } 32 | op = body.call(thisArg, _); 33 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 34 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 35 | } 36 | }; 37 | exports.__esModule = true; 38 | var merge = require("lodash.merge"); 39 | var typeable_1 = require("typeable"); 40 | var builtInValidators = require("./validators"); 41 | /* 42 | * A core validation class. 43 | */ 44 | var Validator = /** @class */ (function () { 45 | /* 46 | * Class constructor. 47 | */ 48 | function Validator(_a) { 49 | var _b = _a === void 0 ? {} : _a, _c = _b.failFast, failFast = _c === void 0 ? false : _c, _d = _b.validators, validators = _d === void 0 ? {} : _d, _e = _b.context, context = _e === void 0 ? null : _e; 50 | this.failFast = failFast; 51 | this.validators = merge(builtInValidators, validators); 52 | this.context = context; 53 | } 54 | /* 55 | * Returns a new instance of ValidatorError instance. 56 | */ 57 | Validator.prototype._createValidatorError = function (recipe) { 58 | var validator = recipe.validator, _a = recipe.code, code = _a === void 0 ? 422 : _a; 59 | var message = typeof recipe.message === 'function' 60 | ? recipe.message() 61 | : recipe.message; 62 | message = this._createString(message, recipe); // apply variables to a message 63 | return { validator: validator, message: message, code: code }; 64 | }; 65 | /* 66 | * Replaces variables in a string (e.g. `%{variable}`) with object key values. 67 | */ 68 | Validator.prototype._createString = function (template, data) { 69 | if (!template) { 70 | return template; 71 | } 72 | for (var key in data) { 73 | template = template.replace("%{" + key + "}", data[key]); 74 | } 75 | return template; 76 | }; 77 | /* 78 | * Validates the `value` against the `validations`. 79 | */ 80 | Validator.prototype.validate = function (value, recipes) { 81 | if (recipes === void 0) { recipes = []; } 82 | return __awaiter(this, void 0, void 0, function () { 83 | var _this = this; 84 | var errors, _loop_1, this_1, _i, recipes_1, recipe, state_1; 85 | return __generator(this, function (_a) { 86 | switch (_a.label) { 87 | case 0: 88 | errors = []; 89 | _loop_1 = function (recipe) { 90 | var condition, result, name, validator, isValid; 91 | return __generator(this, function (_a) { 92 | switch (_a.label) { 93 | case 0: 94 | condition = recipe.condition; 95 | if (!condition) return [3 /*break*/, 2]; 96 | return [4 /*yield*/, condition.call(this_1.context, value, recipe)]; 97 | case 1: 98 | result = _a.sent(); 99 | if (!result) 100 | return [2 /*return*/, "continue"]; 101 | _a.label = 2; 102 | case 2: 103 | name = recipe.validator; 104 | validator = this_1.validators[name]; 105 | if (!validator) { 106 | throw new Error("Unknown validator " + name); 107 | } 108 | return [4 /*yield*/, Promise.all((typeable_1.isArray(value) ? value : [value]) 109 | .map(function (v) { return validator.call(_this.context, v, recipe); })).then(function (r) { return r.indexOf(false) === -1; })]; 110 | case 3: 111 | isValid = _a.sent(); 112 | if (!isValid) { 113 | errors.push(this_1._createValidatorError(recipe)); 114 | if (this_1.failFast) 115 | return [2 /*return*/, "break"]; 116 | } 117 | return [2 /*return*/]; 118 | } 119 | }); 120 | }; 121 | this_1 = this; 122 | _i = 0, recipes_1 = recipes; 123 | _a.label = 1; 124 | case 1: 125 | if (!(_i < recipes_1.length)) return [3 /*break*/, 4]; 126 | recipe = recipes_1[_i]; 127 | return [5 /*yield**/, _loop_1(recipe)]; 128 | case 2: 129 | state_1 = _a.sent(); 130 | if (state_1 === "break") 131 | return [3 /*break*/, 4]; 132 | _a.label = 3; 133 | case 3: 134 | _i++; 135 | return [3 /*break*/, 1]; 136 | case 4: return [2 /*return*/, errors]; 137 | } 138 | }); 139 | }); 140 | }; 141 | return Validator; 142 | }()); 143 | exports.Validator = Validator; 144 | -------------------------------------------------------------------------------- /dist/validators/absence.d.ts: -------------------------------------------------------------------------------- 1 | export declare function absence(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/absence.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function absence(value) { 5 | return typeable_1.isAbsent(value); 6 | } 7 | exports.absence = absence; 8 | -------------------------------------------------------------------------------- /dist/validators/array-exclusion.d.ts: -------------------------------------------------------------------------------- 1 | export interface ArrayExclusionOptions { 2 | values?: any[]; 3 | } 4 | export declare function arrayExclusion(value: any, options?: ArrayExclusionOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/array-exclusion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var array_inclusion_1 = require("./array-inclusion"); 4 | function arrayExclusion(value, options) { 5 | if (options === void 0) { options = {}; } 6 | return !array_inclusion_1.arrayInclusion(value, options); 7 | } 8 | exports.arrayExclusion = arrayExclusion; 9 | -------------------------------------------------------------------------------- /dist/validators/array-inclusion.d.ts: -------------------------------------------------------------------------------- 1 | export interface ArrayInclusionOptions { 2 | values?: any[]; 3 | } 4 | export declare function arrayInclusion(value: any, options?: ArrayInclusionOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/array-inclusion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function arrayInclusion(value, options) { 5 | if (options === void 0) { options = {}; } 6 | var values = options.values; 7 | if (!typeable_1.isArray(values)) 8 | return false; 9 | return values.indexOf(value) !== -1; 10 | } 11 | exports.arrayInclusion = arrayInclusion; 12 | -------------------------------------------------------------------------------- /dist/validators/array-length.d.ts: -------------------------------------------------------------------------------- 1 | export interface ArrayLengthOptions { 2 | min?: number; 3 | minOrEqual?: number; 4 | max?: number; 5 | maxOrEqual?: number; 6 | } 7 | export declare function arrayLength(value: any, options?: ArrayLengthOptions): boolean; 8 | -------------------------------------------------------------------------------- /dist/validators/array-length.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function arrayLength(value, options) { 5 | if (options === void 0) { options = {}; } 6 | if (!typeable_1.isArray(value)) 7 | return false; 8 | var size = value.length; 9 | var min = options.min, minOrEqual = options.minOrEqual, max = options.max, maxOrEqual = options.maxOrEqual; 10 | if (typeable_1.isNumber(min) && !(size > min)) 11 | return false; 12 | if (typeable_1.isNumber(minOrEqual) && !(size >= minOrEqual)) 13 | return false; 14 | if (typeable_1.isNumber(max) && !(size < max)) 15 | return false; 16 | if (typeable_1.isNumber(maxOrEqual) && !(size <= maxOrEqual)) 17 | return false; 18 | return true; 19 | } 20 | exports.arrayLength = arrayLength; 21 | -------------------------------------------------------------------------------- /dist/validators/block.d.ts: -------------------------------------------------------------------------------- 1 | export interface BlockOptions { 2 | block?: () => boolean | Promise; 3 | } 4 | export declare function block(value: any, options?: BlockOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/block.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | function block(value, options) { 4 | if (options === void 0) { options = {}; } 5 | if (!options) 6 | return false; 7 | var block = options.block; 8 | if (block) { 9 | return block.call(this, value, options); 10 | } 11 | return false; 12 | } 13 | exports.block = block; 14 | -------------------------------------------------------------------------------- /dist/validators/bson-object-id.d.ts: -------------------------------------------------------------------------------- 1 | export declare function BSONObjectID(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/bson-object-id.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | var string_hexadecimal_1 = require("./string-hexadecimal"); 5 | function BSONObjectID(value) { 6 | value = typeable_1.toString(value); 7 | return (string_hexadecimal_1.stringHexadecimal(value) 8 | && value.length === 24); 9 | } 10 | exports.BSONObjectID = BSONObjectID; 11 | -------------------------------------------------------------------------------- /dist/validators/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './absence'; 2 | export * from './array-exclusion'; 3 | export * from './array-inclusion'; 4 | export * from './array-length'; 5 | export * from './block'; 6 | export * from './bson-object-id'; 7 | export * from './number-size'; 8 | export * from './presence'; 9 | export * from './string-base64'; 10 | export * from './string-date'; 11 | export * from './string-email'; 12 | export * from './string-exclusion'; 13 | export * from './string-fqdn'; 14 | export * from './string-hex-color'; 15 | export * from './string-hexadecimal'; 16 | export * from './string-inclusion'; 17 | export * from './string-json'; 18 | export * from './string-length'; 19 | export * from './string-lowercase'; 20 | export * from './string-match'; 21 | export * from './string-uppercase'; 22 | export * from './string-uuid'; 23 | export * from './string-eth-address'; 24 | -------------------------------------------------------------------------------- /dist/validators/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | exports.__esModule = true; 6 | __export(require("./absence")); 7 | __export(require("./array-exclusion")); 8 | __export(require("./array-inclusion")); 9 | __export(require("./array-length")); 10 | __export(require("./block")); 11 | __export(require("./bson-object-id")); 12 | __export(require("./number-size")); 13 | __export(require("./presence")); 14 | __export(require("./string-base64")); 15 | __export(require("./string-date")); 16 | __export(require("./string-email")); 17 | __export(require("./string-exclusion")); 18 | __export(require("./string-fqdn")); 19 | __export(require("./string-hex-color")); 20 | __export(require("./string-hexadecimal")); 21 | __export(require("./string-inclusion")); 22 | __export(require("./string-json")); 23 | __export(require("./string-length")); 24 | __export(require("./string-lowercase")); 25 | __export(require("./string-match")); 26 | __export(require("./string-uppercase")); 27 | __export(require("./string-uuid")); 28 | __export(require("./string-eth-address")); 29 | -------------------------------------------------------------------------------- /dist/validators/number-size.d.ts: -------------------------------------------------------------------------------- 1 | export interface NumberSizeOptions { 2 | min?: number; 3 | minOrEqual?: number; 4 | max?: number; 5 | maxOrEqual?: number; 6 | } 7 | export declare function numberSize(value: any, options?: NumberSizeOptions): boolean; 8 | -------------------------------------------------------------------------------- /dist/validators/number-size.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function numberSize(value, options) { 5 | if (options === void 0) { options = {}; } 6 | if (!typeable_1.isNumber(value)) 7 | return false; 8 | var min = options.min, minOrEqual = options.minOrEqual, max = options.max, maxOrEqual = options.maxOrEqual; 9 | if (typeable_1.isNumber(min) && !(value > min)) 10 | return false; 11 | if (typeable_1.isNumber(minOrEqual) && !(value >= minOrEqual)) 12 | return false; 13 | if (typeable_1.isNumber(max) && !(value < max)) 14 | return false; 15 | if (typeable_1.isNumber(maxOrEqual) && !(value <= maxOrEqual)) 16 | return false; 17 | return true; 18 | } 19 | exports.numberSize = numberSize; 20 | -------------------------------------------------------------------------------- /dist/validators/presence.d.ts: -------------------------------------------------------------------------------- 1 | export declare function presence(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/presence.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function presence(value) { 5 | return typeable_1.isPresent(value); 6 | } 7 | exports.presence = presence; 8 | -------------------------------------------------------------------------------- /dist/validators/string-base64.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringBase64(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-base64.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | var BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/; 5 | function stringBase64(value) { 6 | if (!typeable_1.isString(value)) 7 | return false; 8 | return BASE64_REGEX.test(value); 9 | } 10 | exports.stringBase64 = stringBase64; 11 | -------------------------------------------------------------------------------- /dist/validators/string-date.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringDateOptions { 2 | iso?: boolean; 3 | } 4 | export declare function stringDate(value: any, recipe?: StringDateOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/string-date.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | var ISO8601_REGEX = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; 5 | function stringDate(value, recipe) { 6 | if (recipe === void 0) { recipe = {}; } 7 | if (!typeable_1.isString(value)) 8 | return false; 9 | var date = Date.parse(value); 10 | if (!date) 11 | return false; 12 | var iso = recipe.iso; 13 | if (iso) { 14 | return ISO8601_REGEX.test(value); 15 | } 16 | return true; 17 | } 18 | exports.stringDate = stringDate; 19 | -------------------------------------------------------------------------------- /dist/validators/string-email.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringEmailOptions { 2 | allowDisplayName?: boolean; 3 | allowUtf8LocalPart?: boolean; 4 | requireTld?: boolean; 5 | } 6 | export declare function stringEmail(value: any, recipe?: StringEmailOptions): boolean; 7 | -------------------------------------------------------------------------------- /dist/validators/string-email.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | var string_fqdn_1 = require("./string-fqdn"); 5 | var string_length_1 = require("./string-length"); 6 | var DISPLAY_NAME_REGEX = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\s]*<(.+)>$/i; 7 | var EMAIL_USER_REGEX = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i; 8 | var QUOTED_EMAIL_USER_REGEX = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i; 9 | var EMAIL_USER_UTF8_REGEX = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i; 10 | var QUOTED_EMAIL_USER_UTF8_REGEX = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i; 11 | function stringEmail(value, recipe) { 12 | if (recipe === void 0) { recipe = {}; } 13 | if (!typeable_1.isString(value)) 14 | return false; 15 | var _a = recipe.allowDisplayName, allowDisplayName = _a === void 0 ? false : _a, _b = recipe.allowUtf8LocalPart, allowUtf8LocalPart = _b === void 0 ? false : _b, _c = recipe.requireTld, requireTld = _c === void 0 ? true : _c; 16 | if (allowDisplayName) { 17 | var displayEmail = value.match(DISPLAY_NAME_REGEX); 18 | if (displayEmail) { 19 | value = displayEmail[1]; 20 | } 21 | } 22 | var parts = value.split('@'); 23 | var domain = parts.pop(); 24 | var user = parts.join('@'); 25 | var lowerDomain = domain.toLowerCase(); 26 | if (lowerDomain === 'gmail.com' || lowerDomain === 'googlemail.com') { 27 | user = user.replace(/\./g, '').toLowerCase(); 28 | } 29 | if (!string_length_1.stringLength(user, { bytes: true, max: 64 }) || !string_length_1.stringLength(domain, { bytes: true, max: 256 })) { 30 | return false; 31 | } 32 | else if (!string_fqdn_1.stringFQDN(domain, { requireTld: requireTld })) { 33 | return false; 34 | } 35 | else if (user[0] === '"') { 36 | user = user.slice(1, user.length - 1); 37 | return allowUtf8LocalPart 38 | ? QUOTED_EMAIL_USER_UTF8_REGEX.test(user) 39 | : QUOTED_EMAIL_USER_REGEX.test(user); 40 | } 41 | var pattern = allowUtf8LocalPart 42 | ? EMAIL_USER_UTF8_REGEX 43 | : EMAIL_USER_REGEX; 44 | var userParts = user.split('.'); 45 | for (var i = 0; i < userParts.length; i++) { 46 | if (!pattern.test(userParts[i])) { 47 | return false; 48 | } 49 | } 50 | return true; 51 | } 52 | exports.stringEmail = stringEmail; 53 | -------------------------------------------------------------------------------- /dist/validators/string-eth-address.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringETHAddress(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-eth-address.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringETHAddress(value) { 5 | if (!typeable_1.isString(value)) 6 | return false; 7 | return /^0x[a-fA-F0-9]{40}$/i.test(value); 8 | } 9 | exports.stringETHAddress = stringETHAddress; 10 | -------------------------------------------------------------------------------- /dist/validators/string-exclusion.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringExclusionOptions { 2 | seed?: string; 3 | } 4 | export declare function stringExclusion(value: any, options?: StringExclusionOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/string-exclusion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var string_inclusion_1 = require("./string-inclusion"); 4 | function stringExclusion(value, options) { 5 | if (options === void 0) { options = {}; } 6 | return !string_inclusion_1.stringInclusion(value, options); 7 | } 8 | exports.stringExclusion = stringExclusion; 9 | -------------------------------------------------------------------------------- /dist/validators/string-fqdn.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringFQDNOptions { 2 | requireTld?: boolean; 3 | allowUnderscores?: boolean; 4 | allowTrailingDot?: boolean; 5 | } 6 | export declare function stringFQDN(value: any, options?: StringFQDNOptions): boolean; 7 | -------------------------------------------------------------------------------- /dist/validators/string-fqdn.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringFQDN(value, options) { 5 | if (options === void 0) { options = {}; } 6 | var _a = options.requireTld, requireTld = _a === void 0 ? true : _a, _b = options.allowUnderscores, allowUnderscores = _b === void 0 ? false : _b, _c = options.allowTrailingDot, allowTrailingDot = _c === void 0 ? false : _c; 7 | if (!typeable_1.isString(value)) 8 | return false; 9 | if (allowTrailingDot && value[value.length - 1] === '.') { 10 | value = value.substring(0, value.length - 1); 11 | } 12 | var parts = value.split('.'); 13 | if (requireTld) { 14 | var tld = parts.pop(); 15 | if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { 16 | return false; 17 | } 18 | } 19 | for (var part = void 0, i = 0; i < parts.length; i++) { 20 | part = parts[i]; 21 | if (allowUnderscores) { 22 | if (part.indexOf('__') >= 0) { 23 | return false; 24 | } 25 | else { 26 | part = part.replace(/_/g, ''); 27 | } 28 | } 29 | if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) { 30 | return false; 31 | } 32 | else if (/[\uff01-\uff5e]/.test(part)) { 33 | return false; // disallow full-width chars 34 | } 35 | else if (part[0] === '-' || part[part.length - 1] === '-') { 36 | return false; 37 | } 38 | } 39 | return true; 40 | } 41 | exports.stringFQDN = stringFQDN; 42 | -------------------------------------------------------------------------------- /dist/validators/string-hex-color.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringHexColor(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-hex-color.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringHexColor(value) { 5 | if (!typeable_1.isString(value)) 6 | return false; 7 | return /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i.test(value); 8 | } 9 | exports.stringHexColor = stringHexColor; 10 | -------------------------------------------------------------------------------- /dist/validators/string-hexadecimal.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringHexadecimal(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-hexadecimal.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringHexadecimal(value) { 5 | if (!typeable_1.isString(value)) 6 | return false; 7 | return /^[0-9A-F]+$/i.test(value); 8 | } 9 | exports.stringHexadecimal = stringHexadecimal; 10 | -------------------------------------------------------------------------------- /dist/validators/string-inclusion.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringInclusionOptions { 2 | seed?: string; 3 | } 4 | export declare function stringInclusion(value: any, options?: StringInclusionOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/string-inclusion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringInclusion(value, options) { 5 | if (options === void 0) { options = {}; } 6 | if (!typeable_1.isString(value)) 7 | return false; 8 | var seed = options.seed; 9 | return value.indexOf(typeable_1.toString(seed)) >= 0; 10 | } 11 | exports.stringInclusion = stringInclusion; 12 | -------------------------------------------------------------------------------- /dist/validators/string-json.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringJSON(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-json.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringJSON(value) { 5 | if (!typeable_1.isString(value)) 6 | return false; 7 | try { 8 | var obj = JSON.parse(value); 9 | return !!obj && typeof obj === 'object'; 10 | } 11 | catch (e) { } 12 | return false; 13 | } 14 | exports.stringJSON = stringJSON; 15 | ; 16 | -------------------------------------------------------------------------------- /dist/validators/string-length.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringLengthOptions { 2 | bytes?: boolean; 3 | min?: number; 4 | minOrEqual?: number; 5 | max?: number; 6 | maxOrEqual?: number; 7 | } 8 | export declare function stringLength(value: any, recipe?: StringLengthOptions): boolean; 9 | -------------------------------------------------------------------------------- /dist/validators/string-length.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringLength(value, recipe) { 5 | if (recipe === void 0) { recipe = {}; } 6 | if (!typeable_1.isString(value)) 7 | return false; 8 | var _a = recipe.bytes, bytes = _a === void 0 ? false : _a, min = recipe.min, minOrEqual = recipe.minOrEqual, max = recipe.max, maxOrEqual = recipe.maxOrEqual; 9 | var len = bytes 10 | ? encodeURI(value).split(/%..|./).length - 1 11 | : value.length; 12 | if (typeable_1.isNumber(min) && !(len > min)) 13 | return false; 14 | if (typeable_1.isNumber(minOrEqual) && !(len >= minOrEqual)) 15 | return false; 16 | if (typeable_1.isNumber(max) && !(len < max)) 17 | return false; 18 | if (typeable_1.isNumber(maxOrEqual) && !(len <= maxOrEqual)) 19 | return false; 20 | return true; 21 | } 22 | exports.stringLength = stringLength; 23 | -------------------------------------------------------------------------------- /dist/validators/string-lowercase.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringLowercase(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-lowercase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringLowercase(value) { 5 | if (!typeable_1.isString(value)) 6 | return false; 7 | return value === value.toLowerCase(); 8 | } 9 | exports.stringLowercase = stringLowercase; 10 | ; 11 | -------------------------------------------------------------------------------- /dist/validators/string-match.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringMatchOptions { 2 | regexp?: RegExp; 3 | } 4 | export declare function stringMatch(value: any, recipe?: StringMatchOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/string-match.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringMatch(value, recipe) { 5 | if (recipe === void 0) { recipe = {}; } 6 | if (!typeable_1.isString(value)) 7 | return false; 8 | var regexp = recipe.regexp; 9 | return regexp.test(value); 10 | } 11 | exports.stringMatch = stringMatch; 12 | ; 13 | -------------------------------------------------------------------------------- /dist/validators/string-uppercase.d.ts: -------------------------------------------------------------------------------- 1 | export declare function stringUppercase(value: any): boolean; 2 | -------------------------------------------------------------------------------- /dist/validators/string-uppercase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | function stringUppercase(value) { 5 | if (!typeable_1.isString(value)) 6 | return false; 7 | return value === value.toUpperCase(); 8 | } 9 | exports.stringUppercase = stringUppercase; 10 | ; 11 | -------------------------------------------------------------------------------- /dist/validators/string-uuid.d.ts: -------------------------------------------------------------------------------- 1 | export interface StringUUIDOptions { 2 | version?: number; 3 | } 4 | export declare function stringUUID(value: any, recipe?: StringUUIDOptions): boolean; 5 | -------------------------------------------------------------------------------- /dist/validators/string-uuid.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var typeable_1 = require("typeable"); 4 | var V1_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 5 | var V2_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[2][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 6 | var V3_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[3][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 7 | var V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 8 | var V5_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 9 | function stringUUID(value, recipe) { 10 | if (recipe === void 0) { recipe = {}; } 11 | if (!typeable_1.isString(value)) 12 | return false; 13 | var version = recipe.version; 14 | switch (version) { 15 | case 1: 16 | return V1_REGEX.test(value); 17 | case 2: 18 | return V2_REGEX.test(value); 19 | case 3: 20 | return V3_REGEX.test(value); 21 | case 4: 22 | return V4_REGEX.test(value); 23 | case 5: 24 | return V5_REGEX.test(value); 25 | } 26 | return (V1_REGEX.test(value) 27 | || V2_REGEX.test(value) 28 | || V3_REGEX.test(value) 29 | || V4_REGEX.test(value) 30 | || V5_REGEX.test(value)); 31 | } 32 | exports.stringUUID = stringUUID; 33 | ; 34 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["dist/*"], 3 | "ext": "js,ts" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validatable", 3 | "version": "0.34.2", 4 | "description": "A library for synchronous and asynchronous input validation.", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "ava": { 8 | "files": [ 9 | "./tests/*.js", 10 | "./tests/**/*.js" 11 | ], 12 | "concurrency": 4, 13 | "failFast": true 14 | }, 15 | "scripts": { 16 | "clean": "rm -Rf ./dist", 17 | "build": "npm run clean; tsc", 18 | "prepublish": "npm run build", 19 | "test": "npm run build && ava" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/xpepermint/validatablejs.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/xpepermint/validatablejs/issues" 27 | }, 28 | "homepage": "https://github.com/xpepermint/validatablejs#readme", 29 | "keywords": [ 30 | "validatable", 31 | "validation", 32 | "validating", 33 | "validate", 34 | "valid", 35 | "isvalid", 36 | "check", 37 | "checking", 38 | "block" 39 | ], 40 | "author": "Kristijan Sedlak (Xpepermint)", 41 | "license": "MIT", 42 | "devDependencies": { 43 | "@types/lodash.merge": "^4.6.3", 44 | "ava": "^0.25.0", 45 | "typescript": "^2.8.1" 46 | }, 47 | "dependencies": { 48 | "lodash.merge": "^4.6.1", 49 | "typeable": "^2.4.1" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import merge = require('lodash.merge'); 2 | import {isArray} from 'typeable'; 3 | import * as builtInValidators from './validators'; 4 | 5 | /* 6 | * Recipe type definition. 7 | */ 8 | 9 | export interface ValidatorRecipe { 10 | validator: string; 11 | message?: string | (() => string); 12 | code?: number; 13 | condition?: () => boolean | Promise; 14 | [key: string]: any; 15 | } 16 | 17 | /* 18 | * Error type definition. 19 | */ 20 | 21 | export interface ValidatorError { 22 | validator: string; 23 | message: string; 24 | code: number; 25 | } 26 | 27 | /* 28 | * A core validation class. 29 | */ 30 | 31 | export class Validator { 32 | failFast: boolean; 33 | validators: {[name: string]: () => boolean | Promise}; 34 | context: any; 35 | 36 | /* 37 | * Class constructor. 38 | */ 39 | 40 | constructor ({ 41 | failFast = false, 42 | validators = {}, 43 | context = null 44 | }: { 45 | failFast?: boolean, 46 | validators?: {[name: string]: () => boolean | Promise}, 47 | context?: any 48 | } = {}) { 49 | this.failFast = failFast; 50 | this.validators = merge(builtInValidators, validators); 51 | this.context = context; 52 | } 53 | 54 | /* 55 | * Returns a new instance of ValidatorError instance. 56 | */ 57 | 58 | _createValidatorError (recipe: ValidatorRecipe): ValidatorError { 59 | let {validator, code = 422} = recipe; 60 | 61 | let message = typeof recipe.message === 'function' 62 | ? recipe.message() 63 | : recipe.message; 64 | message = this._createString(message, recipe); // apply variables to a message 65 | 66 | return {validator, message, code}; 67 | } 68 | 69 | /* 70 | * Replaces variables in a string (e.g. `%{variable}`) with object key values. 71 | */ 72 | 73 | _createString (template: string, data: any): string { 74 | if (!template) { 75 | return template; 76 | } 77 | 78 | for (let key in data) { 79 | template = template.replace(`%{${key}}`, data[key]); 80 | } 81 | 82 | return template; 83 | } 84 | 85 | /* 86 | * Validates the `value` against the `validations`. 87 | */ 88 | 89 | async validate (value: any, recipes: ValidatorRecipe[] = []): Promise { 90 | let errors = []; 91 | 92 | for (let recipe of recipes) { 93 | let condition = recipe.condition; 94 | if (condition) { 95 | let result = await condition.call(this.context, value, recipe); 96 | if (!result) continue; 97 | } 98 | 99 | let name = recipe.validator; 100 | let validator = this.validators[name]; 101 | if (!validator) { 102 | throw new Error(`Unknown validator ${name}`); 103 | } 104 | 105 | let isValid = await Promise.all( 106 | (isArray(value) ? value : [value]) 107 | .map((v) => validator.call(this.context, v, recipe)) 108 | ).then((r) => r.indexOf(false) === -1); 109 | 110 | if (!isValid) { 111 | errors.push( 112 | this._createValidatorError(recipe) 113 | ); 114 | 115 | if (this.failFast) break; 116 | } 117 | } 118 | 119 | return errors; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/validators/absence.ts: -------------------------------------------------------------------------------- 1 | import { isAbsent } from 'typeable'; 2 | 3 | export function absence (value: any): boolean { 4 | return isAbsent(value); 5 | } 6 | -------------------------------------------------------------------------------- /src/validators/array-exclusion.ts: -------------------------------------------------------------------------------- 1 | import { arrayInclusion } from './array-inclusion'; 2 | 3 | export interface ArrayExclusionOptions { 4 | values?: any[]; 5 | } 6 | 7 | export function arrayExclusion (value: any, options: ArrayExclusionOptions = {}): boolean { 8 | return !arrayInclusion(value, options); 9 | } 10 | -------------------------------------------------------------------------------- /src/validators/array-inclusion.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from 'typeable'; 2 | 3 | export interface ArrayInclusionOptions { 4 | values?: any[]; 5 | } 6 | 7 | export function arrayInclusion (value: any, options: ArrayInclusionOptions = {}): boolean { 8 | let { values } = options; 9 | 10 | if (!isArray(values)) return false; 11 | 12 | return values.indexOf(value) !== -1; 13 | } 14 | -------------------------------------------------------------------------------- /src/validators/array-length.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isNumber } from 'typeable'; 2 | 3 | export interface ArrayLengthOptions { 4 | min?: number; 5 | minOrEqual?: number; 6 | max?: number; 7 | maxOrEqual?: number; 8 | } 9 | 10 | export function arrayLength (value: any, options: ArrayLengthOptions = {}): boolean { 11 | if (!isArray(value)) return false; 12 | 13 | let size = value.length; 14 | let { min, minOrEqual, max, maxOrEqual } = options; 15 | if (isNumber(min) && !(size > min)) return false; 16 | if (isNumber(minOrEqual) && !(size >= minOrEqual)) return false; 17 | if (isNumber(max) && !(size < max)) return false; 18 | if (isNumber(maxOrEqual) && !(size <= maxOrEqual)) return false; 19 | return true; 20 | } 21 | -------------------------------------------------------------------------------- /src/validators/block.ts: -------------------------------------------------------------------------------- 1 | export interface BlockOptions { 2 | block?: () => boolean | Promise; 3 | } 4 | 5 | export function block (value: any, options: BlockOptions = {}): boolean { 6 | if (!options) return false; 7 | 8 | let { block } = options; 9 | if (block) { 10 | return block.call(this, value, options); 11 | } 12 | return false; 13 | } 14 | -------------------------------------------------------------------------------- /src/validators/bson-object-id.ts: -------------------------------------------------------------------------------- 1 | import { toString } from 'typeable'; 2 | import { stringHexadecimal } from './string-hexadecimal'; 3 | 4 | export function BSONObjectID (value: any): boolean { 5 | value = toString(value); 6 | 7 | return ( 8 | stringHexadecimal(value) 9 | && value.length === 24 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/validators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './absence'; 2 | export * from './array-exclusion'; 3 | export * from './array-inclusion'; 4 | export * from './array-length'; 5 | export * from './block'; 6 | export * from './bson-object-id'; 7 | export * from './number-size'; 8 | export * from './presence'; 9 | export * from './string-base64'; 10 | export * from './string-date'; 11 | export * from './string-email'; 12 | export * from './string-exclusion'; 13 | export * from './string-fqdn'; 14 | export * from './string-hex-color'; 15 | export * from './string-hexadecimal'; 16 | export * from './string-inclusion'; 17 | export * from './string-json'; 18 | export * from './string-length'; 19 | export * from './string-lowercase'; 20 | export * from './string-match'; 21 | export * from './string-uppercase'; 22 | export * from './string-uuid'; 23 | export * from './string-eth-address'; 24 | -------------------------------------------------------------------------------- /src/validators/number-size.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from 'typeable'; 2 | 3 | export interface NumberSizeOptions { 4 | min?: number; 5 | minOrEqual?: number; 6 | max?: number; 7 | maxOrEqual?: number; 8 | } 9 | 10 | export function numberSize (value: any, options: NumberSizeOptions = {}): boolean { 11 | if (!isNumber(value)) return false; 12 | 13 | let {min, minOrEqual, max, maxOrEqual} = options; 14 | if (isNumber(min) && !(value > min)) return false; 15 | if (isNumber(minOrEqual) && !(value >= minOrEqual)) return false; 16 | if (isNumber(max) && !(value < max)) return false; 17 | if (isNumber(maxOrEqual) && !(value <= maxOrEqual)) return false; 18 | return true; 19 | } 20 | -------------------------------------------------------------------------------- /src/validators/presence.ts: -------------------------------------------------------------------------------- 1 | import { isPresent } from 'typeable'; 2 | 3 | export function presence (value: any): boolean { 4 | return isPresent(value); 5 | } 6 | -------------------------------------------------------------------------------- /src/validators/string-base64.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | const BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/; 4 | 5 | export function stringBase64 (value: any): boolean { 6 | if (!isString(value)) return false; 7 | 8 | return BASE64_REGEX.test(value); 9 | } 10 | -------------------------------------------------------------------------------- /src/validators/string-date.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | const ISO8601_REGEX = /^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/; 4 | 5 | export interface StringDateOptions { 6 | iso?: boolean; 7 | } 8 | 9 | export function stringDate (value: any, recipe: StringDateOptions = {}): boolean { 10 | if (!isString(value)) return false; 11 | 12 | let date = Date.parse(value); 13 | if (!date) return false; 14 | 15 | let { iso } = recipe; 16 | if (iso) { 17 | return ISO8601_REGEX.test(value); 18 | } 19 | return true; 20 | } 21 | -------------------------------------------------------------------------------- /src/validators/string-email.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | import { stringFQDN } from './string-fqdn'; 3 | import { stringLength } from './string-length'; 4 | 5 | const DISPLAY_NAME_REGEX = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\.\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\s]*<(.+)>$/i; 6 | const EMAIL_USER_REGEX = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~]+$/i; 7 | const QUOTED_EMAIL_USER_REGEX = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f]))*$/i; 8 | const EMAIL_USER_UTF8_REGEX = /^[a-z\d!#\$%&'\*\+\-\/=\?\^_`{\|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$/i; 9 | const QUOTED_EMAIL_USER_UTF8_REGEX = /^([\s\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|(\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*$/i; 10 | 11 | export interface StringEmailOptions { 12 | allowDisplayName?: boolean; 13 | allowUtf8LocalPart?: boolean; 14 | requireTld?: boolean; 15 | } 16 | 17 | export function stringEmail (value: any, recipe: StringEmailOptions = {}): boolean { 18 | if (!isString(value)) return false; 19 | 20 | let { allowDisplayName = false, allowUtf8LocalPart = false, requireTld = true } = recipe; 21 | if (allowDisplayName) { 22 | let displayEmail = value.match(DISPLAY_NAME_REGEX); 23 | if (displayEmail) { 24 | value = displayEmail[1]; 25 | } 26 | } 27 | 28 | let parts = value.split('@'); 29 | let domain = parts.pop(); 30 | let user = parts.join('@'); 31 | let lowerDomain = domain.toLowerCase(); 32 | if (lowerDomain === 'gmail.com' || lowerDomain === 'googlemail.com') { 33 | user = user.replace(/\./g, '').toLowerCase(); 34 | } 35 | 36 | if (!stringLength(user, {bytes: true, max: 64}) || !stringLength(domain, {bytes: true, max: 256})) { 37 | return false; 38 | } 39 | else if (!stringFQDN(domain, {requireTld: requireTld })) { 40 | return false; 41 | } 42 | else if (user[0] === '"') { 43 | user = user.slice(1, user.length - 1); 44 | return allowUtf8LocalPart 45 | ? QUOTED_EMAIL_USER_UTF8_REGEX.test(user) 46 | : QUOTED_EMAIL_USER_REGEX.test(user); 47 | } 48 | 49 | let pattern = allowUtf8LocalPart 50 | ? EMAIL_USER_UTF8_REGEX 51 | : EMAIL_USER_REGEX; 52 | let userParts = user.split('.'); 53 | for (let i = 0; i < userParts.length; i++) { 54 | if (!pattern.test(userParts[i])) { 55 | return false; 56 | } 57 | } 58 | 59 | return true; 60 | } 61 | -------------------------------------------------------------------------------- /src/validators/string-eth-address.ts: -------------------------------------------------------------------------------- 1 | import {isString} from 'typeable'; 2 | 3 | export function stringETHAddress (value: any): boolean { 4 | if (!isString(value)) return false; 5 | 6 | return /^0x[a-fA-F0-9]{40}$/i.test(value); 7 | } 8 | -------------------------------------------------------------------------------- /src/validators/string-exclusion.ts: -------------------------------------------------------------------------------- 1 | import { stringInclusion } from './string-inclusion'; 2 | 3 | export interface StringExclusionOptions { 4 | seed?: string; 5 | } 6 | 7 | export function stringExclusion (value: any, options: StringExclusionOptions = {}): boolean { 8 | return !stringInclusion(value, options); 9 | } 10 | -------------------------------------------------------------------------------- /src/validators/string-fqdn.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export interface StringFQDNOptions { 4 | requireTld?: boolean; 5 | allowUnderscores?: boolean; 6 | allowTrailingDot?: boolean; 7 | } 8 | 9 | export function stringFQDN (value: any, options: StringFQDNOptions = {}): boolean { 10 | let { requireTld = true, allowUnderscores = false, allowTrailingDot = false } = options; 11 | 12 | if (!isString(value)) return false; 13 | 14 | if (allowTrailingDot && value[value.length - 1] === '.') { 15 | value = value.substring(0, value.length - 1); 16 | } 17 | 18 | let parts = value.split('.'); 19 | 20 | if (requireTld) { 21 | let tld = parts.pop(); 22 | 23 | if (!parts.length || !/^([a-z\u00a1-\uffff]{2,}|xn[a-z0-9-]{2,})$/i.test(tld)) { 24 | return false; 25 | } 26 | } 27 | 28 | for (let part, i = 0; i < parts.length; i++) { 29 | part = parts[i]; 30 | 31 | if (allowUnderscores) { 32 | if (part.indexOf('__') >= 0) { 33 | return false; 34 | } 35 | else { 36 | part = part.replace(/_/g, ''); 37 | } 38 | } 39 | 40 | if (!/^[a-z\u00a1-\uffff0-9-]+$/i.test(part)) { 41 | return false; 42 | } 43 | else if (/[\uff01-\uff5e]/.test(part)) { 44 | return false; // disallow full-width chars 45 | } 46 | else if (part[0] === '-' || part[part.length - 1] === '-') { 47 | return false; 48 | } 49 | } 50 | return true; 51 | } 52 | -------------------------------------------------------------------------------- /src/validators/string-hex-color.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export function stringHexColor (value: any): boolean { 4 | if (!isString(value)) return false; 5 | 6 | return /^#?([0-9A-F]{3}|[0-9A-F]{6})$/i.test(value); 7 | } 8 | -------------------------------------------------------------------------------- /src/validators/string-hexadecimal.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export function stringHexadecimal (value: any): boolean { 4 | if (!isString(value)) return false; 5 | 6 | return /^[0-9A-F]+$/i.test(value); 7 | } 8 | -------------------------------------------------------------------------------- /src/validators/string-inclusion.ts: -------------------------------------------------------------------------------- 1 | import { isString, toString } from 'typeable'; 2 | 3 | export interface StringInclusionOptions { 4 | seed?: string; 5 | } 6 | 7 | export function stringInclusion (value: any, options: StringInclusionOptions = {}): boolean { 8 | if (!isString(value)) return false; 9 | 10 | let {seed} = options; 11 | return value.indexOf(toString(seed)) >= 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/validators/string-json.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export function stringJSON (value: any): boolean { 4 | if (!isString(value)) return false; 5 | 6 | try { 7 | let obj = JSON.parse(value); 8 | return !!obj && typeof obj === 'object'; 9 | } catch (e) {} 10 | return false; 11 | }; 12 | -------------------------------------------------------------------------------- /src/validators/string-length.ts: -------------------------------------------------------------------------------- 1 | import { isString, isNumber } from 'typeable'; 2 | 3 | export interface StringLengthOptions { 4 | bytes?: boolean; 5 | min?: number; 6 | minOrEqual?: number; 7 | max?: number; 8 | maxOrEqual?: number; 9 | } 10 | 11 | export function stringLength (value: any, recipe: StringLengthOptions = {}): boolean { 12 | if (!isString(value)) return false; 13 | 14 | let { bytes = false, min, minOrEqual, max, maxOrEqual } = recipe; 15 | let len = bytes 16 | ? encodeURI(value).split(/%..|./).length - 1 17 | : value.length; 18 | 19 | if (isNumber(min) && !(len > min)) return false; 20 | if (isNumber(minOrEqual) && !(len >= minOrEqual)) return false; 21 | if (isNumber(max) && !(len < max)) return false; 22 | if (isNumber(maxOrEqual) && !(len <= maxOrEqual)) return false; 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /src/validators/string-lowercase.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export function stringLowercase (value: any): boolean { 4 | if (!isString(value)) return false; 5 | 6 | return value === value.toLowerCase(); 7 | }; 8 | -------------------------------------------------------------------------------- /src/validators/string-match.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export interface StringMatchOptions { 4 | regexp?: RegExp; 5 | } 6 | 7 | export function stringMatch (value: any, recipe: StringMatchOptions = {}): boolean { 8 | if (!isString(value)) return false; 9 | 10 | let { regexp } = recipe; 11 | return regexp.test(value); 12 | }; 13 | -------------------------------------------------------------------------------- /src/validators/string-uppercase.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | export function stringUppercase (value: any): boolean { 4 | if (!isString(value)) return false; 5 | 6 | return value === value.toUpperCase(); 7 | }; 8 | -------------------------------------------------------------------------------- /src/validators/string-uuid.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'typeable'; 2 | 3 | const V1_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 4 | const V2_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[2][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 5 | const V3_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[3][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 6 | const V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 7 | const V5_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 8 | 9 | export interface StringUUIDOptions { 10 | version?: number; 11 | } 12 | 13 | export function stringUUID (value: any, recipe: StringUUIDOptions = {}): boolean { 14 | if (!isString(value)) return false; 15 | 16 | let { version } = recipe; 17 | switch (version) { 18 | case 1: 19 | return V1_REGEX.test(value); 20 | case 2: 21 | return V2_REGEX.test(value); 22 | case 3: 23 | return V3_REGEX.test(value); 24 | case 4: 25 | return V4_REGEX.test(value); 26 | case 5: 27 | return V5_REGEX.test(value); 28 | } 29 | 30 | return ( 31 | V1_REGEX.test(value) 32 | || V2_REGEX.test(value) 33 | || V3_REGEX.test(value) 34 | || V4_REGEX.test(value) 35 | || V5_REGEX.test(value) 36 | ); 37 | }; 38 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import {Validator} from '../dist/index'; 3 | 4 | test('method `validate` should return a list of errors', async (t) => { 5 | let v = new Validator({ 6 | context: {who: 'foo'} 7 | }); 8 | let recipes = [ 9 | {validator: 'presence', message: 'is required'}, 10 | {validator: 'block', message: 'must be foo', async block (v) { return v === this.who}} 11 | ]; 12 | let errors = await v.validate('', recipes); 13 | 14 | t.is(errors.length, 2); 15 | t.is(errors[0].validator, 'presence'); 16 | t.is(errors[0].message, 'is required'); 17 | t.is(errors[0].code, 422); 18 | }); 19 | 20 | test('method `validate` should handle array values', async (t) => { 21 | let v = new Validator(); 22 | let recipes = [ 23 | {validator: 'presence', message: 'is required'}, 24 | ]; 25 | let errors = await v.validate(['', 'a'], recipes); 26 | 27 | t.is(errors.length, 1); 28 | t.is(errors[0].validator, 'presence'); 29 | }); 30 | 31 | test('method `validate` with onlyFirstError=true should return only one error', async (t) => { 32 | let v = new Validator({ 33 | failFast: true 34 | }); 35 | let recipes = [ 36 | {validator: 'presence', message: 'is required'}, 37 | {validator: 'block', message: 'must be foo', async block (v) { return v === this.who}} 38 | ]; 39 | let errors = await v.validate('', recipes); 40 | 41 | t.is(errors.length, 1); 42 | }); 43 | 44 | test('recipe `message` can be a function', async (t) => { 45 | let v = new Validator(); 46 | let recipes = [ 47 | {validator: 'presence', message: () => 'is required'} 48 | ]; 49 | let errors = await v.validate('', recipes); 50 | 51 | t.deepEqual(errors[0].message, 'is required'); 52 | }); 53 | 54 | test('recipe `message` variables %{...} should be replaced with related recipe variables', async (t) => { 55 | let v = new Validator(); 56 | let recipes = [ 57 | {validator: 'presence', message: () => '%{foo} is required', foo: 'bar'} 58 | ]; 59 | let errors = await v.validate('', recipes); 60 | 61 | t.deepEqual(errors[0].message, 'bar is required'); 62 | }); 63 | 64 | test('recipe `message` can blank or absent', async (t) => { 65 | let v = new Validator(); 66 | let recipes = [ 67 | {validator: 'presence', message: null }, 68 | {validator: 'presence', message: undefined }, 69 | {validator: 'presence' }, 70 | ]; 71 | let errors = await v.validate('', recipes); 72 | 73 | t.deepEqual(errors[0].message, null); 74 | t.deepEqual(errors[1].message, undefined); 75 | t.deepEqual(errors[2].message, undefined); 76 | }); 77 | 78 | test('recipe `condition` key can switch off the validator', async (t) => { 79 | let v = new Validator(); 80 | let recipes = [ 81 | {validator: 'presence', message: 'is required', condition: () => true}, 82 | {validator: 'presence', message: 'is required', condition: () => false}, 83 | {validator: 'presence', message: 'is required'} 84 | ]; 85 | let errors = await v.validate('', recipes); 86 | 87 | t.deepEqual(errors.length, 2); 88 | }); 89 | 90 | -------------------------------------------------------------------------------- /tests/validators/absence.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { absence } from '../../dist/validators'; 3 | 4 | test('fails when not blank', (t) => { 5 | t.is(absence('text'), false); 6 | }); 7 | 8 | test('passes when null', (t) => { 9 | t.is(absence(null), true); 10 | }); 11 | 12 | test('passes when undefined', (t) => { 13 | t.is(absence(), true); 14 | }); 15 | 16 | test('passes when blank', (t) => { 17 | t.is(absence(''), true); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/validators/array-exclusion.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { arrayExclusion } from '../../dist/validators'; 3 | 4 | test('fails when included in the list', (t) => { 5 | t.is(arrayExclusion(true, { values: [false] }), true); 6 | }); 7 | 8 | test('passes when not included in the list', (t) => { 9 | t.is(arrayExclusion(true, { values: [false, true] }), false); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/validators/array-inclusion.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { arrayInclusion } from '../../dist/validators'; 3 | 4 | test('fails when not included in the list', (t) => { 5 | t.is(arrayInclusion(true, { values: [false] }), false); 6 | }); 7 | 8 | test('passes when included in the list', (t) => { 9 | t.is(arrayInclusion(true, { values: [false, true] }), true); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/validators/array-length.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { arrayLength } from '../../dist/validators'; 3 | 4 | test('fails when not an array', (t) => { 5 | t.is(arrayLength(true), false); 6 | }); 7 | 8 | test('fails when too small', (t) => { 9 | t.is(arrayLength([1, 2], { min: 3 }), false); 10 | }); 11 | 12 | test('fails when too large', (t) => { 13 | t.is(arrayLength([1, 2, 3], { max: 2 }), false); 14 | }); 15 | 16 | test('passes without options', (t) => { 17 | t.is(arrayLength([1, 2, 3]), true); 18 | }); 19 | 20 | test('passes when valid', (t) => { 21 | t.is(arrayLength([1, 2, 3], { min: 2, max: 4 }), true); 22 | t.is(arrayLength([1, 2], { minOrEqual: 2 }), true); 23 | t.is(arrayLength([1, 2], { maxOrEqual: 2 }), true); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/validators/block.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { block } from '../../dist/validators'; 3 | 4 | test('passes with a valid synchronous block', async (t) => { 5 | t.is(await block('me', { block: (value) => value === 'me' }), true); 6 | }); 7 | 8 | test('passes with a valid synchronous block', async (t) => { 9 | t.is(await block('me', { block: async (value) => value === 'me' }), true); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/validators/bson-object-id.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { BSONObjectID } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(BSONObjectID(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(BSONObjectID('507f1f77bcf86cd7994390'), false); 10 | }); 11 | 12 | test('passes when valid', (t) => { 13 | t.is(BSONObjectID('507f1f77bcf86cd799439011'), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/number-size.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { numberSize } from '../../dist/validators'; 3 | 4 | test('fails when not a number', (t) => { 5 | t.is(numberSize(true), false); 6 | }); 7 | 8 | test('fails when too small', (t) => { 9 | t.is(numberSize(100, { min: 200 }), false); 10 | }); 11 | 12 | test('fails when too large', (t) => { 13 | t.is(numberSize(100, { max: 20 }), false); 14 | }); 15 | 16 | test('passes without options', (t) => { 17 | t.is(numberSize(100), true); 18 | }); 19 | 20 | test('passes when valid', (t) => { 21 | t.is(numberSize(100, { min: 10, max: 1000 }), true); 22 | t.is(numberSize(100, { minOrEqual: 100 }), true); 23 | t.is(numberSize(100, { maxOrEqual: 100 }), true); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/validators/presence.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { presence } from '../../dist/validators'; 3 | 4 | test('fails when null', (t) => { 5 | t.is(presence(null), false); 6 | }); 7 | 8 | test('fails when undefined', (t) => { 9 | t.is(presence(), false); 10 | }); 11 | 12 | test('fails when blank', (t) => { 13 | t.is(presence(''), false); 14 | }); 15 | 16 | test('passes when present', (t) => { 17 | t.is(presence('john'), true); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/validators/string-base64.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringBase64 } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringBase64(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringBase64('1'), false); 10 | t.is(stringBase64('12345'), false); 11 | t.is(stringBase64(''), false); 12 | t.is(stringBase64('Vml2YW11cyBmZXJtZtesting123'), false); 13 | t.is(stringBase64('Zg='), false); 14 | t.is(stringBase64('Z==='), false); 15 | t.is(stringBase64('Zm=8'), false); 16 | t.is(stringBase64('=m9vYg=='), false); 17 | t.is(stringBase64('Zm9vYmFy===='), false); 18 | }); 19 | 20 | test('passes when valid', (t) => { 21 | t.is(stringBase64('Zg=='), true); 22 | t.is(stringBase64('Zm8='), true); 23 | t.is(stringBase64('Zm9v'), true); 24 | t.is(stringBase64('Zm9vYg=='), true); 25 | t.is(stringBase64('Zm9vYmE='), true); 26 | t.is(stringBase64('Zm9vYmFy'), true); 27 | t.is(stringBase64('dGVzdA=='), true); 28 | t.is(stringBase64('TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4='), true); 29 | t.is(stringBase64('Vml2YW11cyBmZXJtZW50dW0gc2VtcGVyIHBvcnRhLg=='), true); 30 | t.is(stringBase64('U3VzcGVuZGlzc2UgbGVjdHVzIGxlbw=='), true); 31 | t.is(stringBase64('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuMPNS1Ufof9EW/M98FNwUAKrwflsqVxaxQjBQnHQmiI7Vac40t8x7pIb8gLGV6wL7sBTJiPovJ0V7y7oc0YerhKh0Rm4skP2z/jHwwZICgGzBvA0rH8xlhUiTvcwDCJ0kc+fh35hNt8srZQM4619FTgB66Xmp4EtVyhpQV+t02g6NzK72oZI0vnAvqhpkxLeLiMCyrI416wHm5TkukhxQmcL2a6hNOyu0ixX/x2kSFXApEnVrJ+/IxGyfyw8kf4N2IZpW5nEP847lpfj0SZZFwrd1mnfnDbYohX2zRptLy2ZUn06Qo9pkG5ntvFEPo9bfZeULtjYzIl6K8gJ2uGZHQIDAQAB'), true); 32 | }); 33 | -------------------------------------------------------------------------------- /tests/validators/string-date.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringDate } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringDate(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringDate('x'), false); 10 | }); 11 | 12 | test('fails when invalid iso8601', (t) => { 13 | t.is(stringDate('12.12.2016', { iso: true }), false); 14 | }); 15 | 16 | test('passes when valid', (t) => { 17 | t.is(stringDate('2009'), true); 18 | }); 19 | 20 | test('passes when valid iso8601', (t) => { 21 | t.is(stringDate('2009-12T12:34', {iso: true}), true); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/validators/string-email.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringEmail } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringEmail(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringEmail('john'), false); 10 | }); 11 | 12 | test('fails when display name', (t) => { 13 | t.is(stringEmail('John '), false); 14 | }); 15 | 16 | test('fails with UTF8 characters', (t) => { 17 | t.is(stringEmail('šžćč@domain.com'), false); 18 | }); 19 | 20 | test('fails without top-level domain name', (t) => { 21 | t.is(stringEmail('john@domain'), false); 22 | }); 23 | 24 | test('fails without top-level domain name', (t) => { 25 | t.is(stringEmail('john@domain', { requireTld: false }), true); 26 | t.is(stringEmail('john@domain', { requireTld: true }), false); 27 | }); 28 | 29 | test('passes with display name when allowDisplayName is true', (t) => { 30 | t.is(stringEmail('John ', { allowDisplayName: true }), true); 31 | t.is(stringEmail('John ', { allowDisplayName: false }), false); 32 | }); 33 | 34 | test('passes with UTF8 characters when allowUtf8LocalPart is true', (t) => { 35 | t.is(stringEmail('đšpŽĆČ@domain.com', { allowUtf8LocalPart: true }), true); 36 | t.is(stringEmail('đšpŽĆČ@domain.com', { allowUtf8LocalPart: false }), false); 37 | }); 38 | -------------------------------------------------------------------------------- /tests/validators/string-eth-address.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringETHAddress } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringETHAddress(true), false); 6 | }); 7 | 8 | test('fails on invalid address', (t) => { 9 | t.is(stringETHAddress('domain'), false); 10 | t.is(stringETHAddress('0x0'), false); 11 | }); 12 | 13 | test('passes on valid address', (t) => { 14 | t.is(stringETHAddress('0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed'), true); 15 | t.is(stringETHAddress('0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'), true); 16 | t.is(stringETHAddress('0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB'), true); 17 | t.is(stringETHAddress('0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb'), true); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/validators/string-exclusion.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringExclusion } from '../../dist/validators'; 3 | 4 | test('fails when a string', (t) => { 5 | t.is(stringExclusion(true, { seed: 'true' }), true); 6 | }); 7 | 8 | test('fails when containing the provided seed', (t) => { 9 | t.is(stringExclusion('my fake2 description', { seed: 'black' }), true); 10 | }); 11 | 12 | test('passes when not containing the provided seed', (t) => { 13 | t.is(stringExclusion('my fake description', { seed: 'fake' }), false); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-fqdn.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringFQDN } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringFQDN(true), false); 6 | }); 7 | 8 | test('fails without top-level domain name', (t) => { 9 | t.is(stringFQDN('domain'), false); 10 | }); 11 | 12 | test('fails when including underscore', (t) => { 13 | t.is(stringFQDN('do_main.com'), false); 14 | }); 15 | 16 | test('fails when including trailing dot', (t) => { 17 | t.is(stringFQDN('domain.com.'), false); 18 | }); 19 | 20 | test('passes with top-level domain name', (t) => { 21 | t.is(stringFQDN('domain.com'), true); 22 | }); 23 | 24 | test('passes when including underscore where allowUnderscores is true', (t) => { 25 | t.is(stringFQDN('do_main.com', { allowUnderscores: true }), true); 26 | }); 27 | 28 | test('passes when including trailing dot where allowTrailingDot is true', (t) => { 29 | t.is(stringFQDN('domain.com.', { allowTrailingDot: true }), true); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/validators/string-hex-color.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringHexColor } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringHexColor(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringHexColor('#ff'), false); 10 | }); 11 | 12 | test('passes when valid', (t) => { 13 | t.is(stringHexColor('#ff0034'), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-hexadecimal.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringHexadecimal } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringHexadecimal(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringHexadecimal('abcdefg'), false); 10 | }); 11 | 12 | test('passes when valid', (t) => { 13 | t.is(stringHexadecimal('ff0044'), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-inclusion.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringInclusion } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringInclusion(true, { seed: 'true' }), false); 6 | }); 7 | 8 | test('fails when not containing the provided seed', (t) => { 9 | t.is(stringInclusion('my fake2 description', { seed: 'black' }), false); 10 | }); 11 | 12 | test('passes when containing the provided seed', (t) => { 13 | t.is(stringInclusion('my fake description', { seed: 'fake' }), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-json.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringJSON } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringJSON(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringJSON('{key: "value"}'), false); 10 | }); 11 | 12 | test('passes when valid', (t) => { 13 | t.is(stringJSON('{"key": "value"}'), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-length.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringLength } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringLength(true), false); 6 | }); 7 | 8 | test('fails when too short', (t) => { 9 | t.is(stringLength('hello', { min: 10 }), false); 10 | }); 11 | 12 | test('fails when too long', (t) => { 13 | t.is(stringLength('hello', { max: 2 }), false); 14 | }); 15 | 16 | test('passes without options', (t) => { 17 | t.is(stringLength('hello'), true); 18 | }); 19 | 20 | test('supports bytes length', (t) => { 21 | t.is(stringLength('ašč', { bytes: true, max: 3 }), false); 22 | t.is(stringLength('ašč', { bytes: true, max: 6 }), true); 23 | t.is(stringLength('ašč', { minOrEqual: 3 }), true); 24 | t.is(stringLength('ašč', { maxOrEqual: 3 }), true); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/validators/string-lowercase.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringLowercase } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringLowercase(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringLowercase('Hello'), false); 10 | }); 11 | 12 | test('passes when valid', (t) => { 13 | t.is(stringLowercase('hello'), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-match.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringMatch } from '../../dist/validators'; 3 | 4 | test('passes with a valid pattern', (t) => { 5 | t.is(stringMatch('me', { regexp: /me/i }), true); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/validators/string-uppercase.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringUppercase } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringUppercase(true), false); 6 | }); 7 | 8 | test('fails when invalid', (t) => { 9 | t.is(stringUppercase('Hello'), false); 10 | }); 11 | 12 | test('passes when valid', (t) => { 13 | t.is(stringUppercase('HELLO'), true); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/validators/string-uuid.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { stringUUID } from '../../dist/validators'; 3 | 4 | test('fails when not a string', (t) => { 5 | t.is(stringUUID(true), false); 6 | }); 7 | 8 | test('passes for valid v1', (t) => { 9 | t.is(stringUUID('857b3f0a-a777-11e5-bf7f-feff819cdc9f', { version: 1 }), true); 10 | t.is(stringUUID('857b4504-a777-11e5-bf7f-feff819cdc9f', { version: 1 }), true); 11 | }); 12 | 13 | test('passes for valid v2', (t) => { 14 | t.is(stringUUID('a14e3bb3-d7a3-2ea8-9481-881eaf75fdc5', { version: 2 }), true); 15 | t.is(stringUUID('5a3c2348-6e2f-280e-aade-7dc8afdb18b9', { version: 2 }), true); 16 | }); 17 | 18 | test('passes for valid v3', (t) => { 19 | t.is(stringUUID('49072879-c5c6-3b4e-9900-34e5df285522', { version: 3 }), true); 20 | t.is(stringUUID('5a3c2348-6e2f-380e-aade-7dc8afdb18b9', { version: 3 }), true); 21 | }); 22 | 23 | test('passes for valid v4', (t) => { 24 | t.is(stringUUID('82ca85b8-7841-42f0-80d8-48bbe11a005b', { version: 4 }), true); 25 | t.is(stringUUID('58dbb3a5-a95a-4120-b4e0-483eea26ab74', { version: 4 }), true); 26 | }); 27 | 28 | test('passes for valid v5', (t) => { 29 | t.is(stringUUID('482d11be-b03f-5ff3-b99d-9b6ceef18874', { version: 5 }), true); 30 | t.is(stringUUID('6a5b4d3f-02cf-5e2d-89d5-2f2163bb69f9', { version: 5 }), true); 31 | }); 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es3", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "outDir": "dist", 8 | "declaration": true, 9 | "lib": ["es2015.promise", "es5"] 10 | } 11 | } 12 | --------------------------------------------------------------------------------