├── .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 |  [](https://badge.fury.io/js/validatable) [](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 |
--------------------------------------------------------------------------------