isvalid 2.10.4 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -1
- package/README.md +37 -2
- package/lib/errors/validation.js +1 -1
- package/lib/formalize.js +30 -3
- package/lib/ranges.js +45 -31
- package/lib/validate.js +32 -3
- package/package.json +7 -7
- package/test/formalize.js +10 -4
- package/test/ranges.js +17 -6
- package/test/validate.js +22 -1
package/.eslintrc.js
CHANGED
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* [`enum`](#enum)
|
|
43
43
|
- [`Number` Validators](#number-validators)
|
|
44
44
|
* [`range`](#range)
|
|
45
|
+
* [`float`](#float)
|
|
45
46
|
- [Custom Types](#custom-types)
|
|
46
47
|
* [`post`](#post)
|
|
47
48
|
+ [Example](#example-1)
|
|
@@ -448,6 +449,26 @@ Examples:
|
|
|
448
449
|
}
|
|
449
450
|
````
|
|
450
451
|
|
|
452
|
+
> Negative values can be wrapped in parentheses.
|
|
453
|
+
|
|
454
|
+
````javascript
|
|
455
|
+
{
|
|
456
|
+
type: Array,
|
|
457
|
+
len: '(-2)-2',
|
|
458
|
+
schema: { … }
|
|
459
|
+
}
|
|
460
|
+
````
|
|
461
|
+
|
|
462
|
+
> It also supports non-integer values.
|
|
463
|
+
|
|
464
|
+
````javascript
|
|
465
|
+
{
|
|
466
|
+
type: Array,
|
|
467
|
+
len: '(-2.2)-2.2',
|
|
468
|
+
schema: { … }
|
|
469
|
+
}
|
|
470
|
+
````
|
|
471
|
+
|
|
451
472
|
> An array that should have at least 2 items, exactly 5 items or 8 or more items.
|
|
452
473
|
|
|
453
474
|
##### `unique`
|
|
@@ -498,7 +519,7 @@ This does not do any actual validation. Instead it trims the input in both ends
|
|
|
498
519
|
##### `len`
|
|
499
520
|
Type: `String` or `Number`
|
|
500
521
|
|
|
501
|
-
This ensures that the string's length is within a specified range. You can use the same formatting as [`Array`'s `len`](#len) validator.
|
|
522
|
+
This ensures that the string's length is within a specified range. You can use the same formatting as [`Array`'s `len`](#len) validator described above (except it does not support ranges with negative values or non-integers).
|
|
502
523
|
|
|
503
524
|
##### `match`
|
|
504
525
|
Type: `RegExp`
|
|
@@ -535,7 +556,21 @@ Type: `Number`or `String`
|
|
|
535
556
|
|
|
536
557
|
This ensures that the number is within a certain range. If not the validator throws an error.
|
|
537
558
|
|
|
538
|
-
The `range` validator uses the same formatting as the [`Array`'s `len`](#len) validator described above.
|
|
559
|
+
The `range` validator uses the same formatting as the [`Array`'s `len`](#len) validator described above (except it does not support ranges with negative values or non-integers).
|
|
560
|
+
|
|
561
|
+
##### `float`
|
|
562
|
+
Type: `String` of value: `'allow'`, `'deny'`, `'round'`, `'floor'`, `'ceil'`
|
|
563
|
+
|
|
564
|
+
This tells the validator how to handle non-integers.
|
|
565
|
+
|
|
566
|
+
The validator has five options:
|
|
567
|
+
* `'allow'` Allow non-integer values.
|
|
568
|
+
* `'deny'` Throw a `ValidationError` if the value is a non-integer.
|
|
569
|
+
* `'round'` Round value to nearest integer.
|
|
570
|
+
* `'floor'` Round to integer less than or equal to value.
|
|
571
|
+
* `'ceil'` Round to integer bigger than or equal to value.
|
|
572
|
+
|
|
573
|
+
> Default is `'allow'`.
|
|
539
574
|
|
|
540
575
|
#### Custom Types
|
|
541
576
|
|
package/lib/errors/validation.js
CHANGED
|
@@ -17,7 +17,7 @@ module.exports = exports = class ValidationError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
static fromError(keyPath, schema, validator, err) {
|
|
20
|
-
let validationError = new ValidationError(keyPath, schema, validator, err.message);
|
|
20
|
+
let validationError = new ValidationError(err.keyPath || keyPath, err.schema || schema, err.validator || validator, err.message);
|
|
21
21
|
validationError.stack = err.stack;
|
|
22
22
|
validationError.underlying = err;
|
|
23
23
|
return validationError;
|
package/lib/formalize.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const merge = require('merge'),
|
|
4
4
|
SchemaError = require('./errors/schema.js'),
|
|
5
|
-
utils = require('./utils.js')
|
|
5
|
+
utils = require('./utils.js'),
|
|
6
|
+
ranges = require('./ranges.js');
|
|
6
7
|
|
|
7
8
|
const finalize = (formalizedSchema, nonFormalizedSchema) => {
|
|
8
9
|
|
|
@@ -77,7 +78,7 @@ const formalizeAny = (schema, options = {}) => {
|
|
|
77
78
|
return formalizeObject({ type: Object, schema: schema }, schema, options);
|
|
78
79
|
}
|
|
79
80
|
if ('array' == utils.instanceTypeName(schema)) {
|
|
80
|
-
if (schema.length === 0)
|
|
81
|
+
if (schema.length === 0) return formalizeAny({ type: Array }, schema. options);
|
|
81
82
|
return formalizeArray({ type: Array, schema: schema[0] }, schema, options);
|
|
82
83
|
}
|
|
83
84
|
if ((typeof schema === 'string' && schema.length) || (typeof schema === 'function' && utils.typeName(schema) !== undefined)) {
|
|
@@ -125,7 +126,8 @@ const formalizeAny = (schema, options = {}) => {
|
|
|
125
126
|
'enum': [ 'array' ]
|
|
126
127
|
});
|
|
127
128
|
if (utils.isSameType('number', utils.typeName(type))) merge(validators, {
|
|
128
|
-
'range': [ 'string', 'number' ]
|
|
129
|
+
'range': [ 'string', 'number' ],
|
|
130
|
+
'float': [ 'string' ]
|
|
129
131
|
});
|
|
130
132
|
}
|
|
131
133
|
|
|
@@ -209,6 +211,31 @@ const formalizeAny = (schema, options = {}) => {
|
|
|
209
211
|
);
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
// Check number float
|
|
215
|
+
if (typeof formalizedSchema.float === 'string' &&
|
|
216
|
+
['allow', 'deny', 'round', 'floor', 'ceil'].indexOf(formalizedSchema.float) == -1) {
|
|
217
|
+
throw new SchemaError(
|
|
218
|
+
schema,
|
|
219
|
+
'Validator `float` must have value `\'allow\'`, `\'deny\'`, `\'round\'`, `\'floor\'` or `\'ceil\'`.'
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Check len
|
|
224
|
+
if (typeof formalizedSchema.len !== 'undefined') {
|
|
225
|
+
formalizedSchema.len = ranges.formalize(formalizedSchema.len, {
|
|
226
|
+
allowNegative: false,
|
|
227
|
+
allowNonIntegers: false
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check range
|
|
232
|
+
if (typeof formalizedSchema.range !== 'undefined') {
|
|
233
|
+
formalizedSchema.range = ranges.formalize(formalizedSchema.range, {
|
|
234
|
+
allowNegative: true,
|
|
235
|
+
allowNonIntegers: true
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
212
239
|
// Check string enums
|
|
213
240
|
if (typeof formalizedSchema.enum !== 'undefined') {
|
|
214
241
|
if (formalizedSchema.enum.length < 1) {
|
package/lib/ranges.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
const
|
|
4
|
+
SchemaError = require('./errors/schema.js');
|
|
5
|
+
|
|
6
|
+
module.exports.testIndex = function(ranges, value) {
|
|
7
|
+
return ranges.some(({ lower, upper }) => {
|
|
8
|
+
return lower <= value && upper >= value;
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
module.exports.formalize = function(ranges, options) {
|
|
4
13
|
|
|
5
14
|
// Convert to string if ranges is a Number.
|
|
6
15
|
if (ranges !== undefined && typeof ranges === 'number') {
|
|
@@ -12,45 +21,50 @@ module.exports.testIndex = function(ranges, index) {
|
|
|
12
21
|
throw new Error('Ranges must be a number or a string expressed as: ex. \'-2,4-6,8,10-\'.');
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
24
|
+
return ranges
|
|
25
|
+
.split(',')
|
|
26
|
+
.map((range) => {
|
|
19
27
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
for (let idx in r) {
|
|
28
|
+
// Get the boundaries of the range.
|
|
29
|
+
let boundaries = range.split(/(?<!\()-/);
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
// Low and high boundaries are the same where only one number is specified.
|
|
32
|
+
if (boundaries.length == 1) boundaries = [ boundaries[0], boundaries[0] ];
|
|
33
|
+
// Throw an error if there is not exactly to boundaries.
|
|
34
|
+
if (boundaries.length != 2) throw new SchemaError('Malformed range \'' + range + '\'.');
|
|
26
35
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
// Test for malformed boundaries
|
|
37
|
+
for (let bIdx = 0 ; bIdx < 2 ; bIdx++) {
|
|
38
|
+
if (!/^\(?[-0-9.]*\)?$/.test(boundaries[bIdx])) throw new SchemaError('Malformed boundary \'' + boundaries[bIdx] + '\'.');
|
|
39
|
+
}
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
for (let bIdx = 0 ; bIdx < 2 ; bIdx++) {
|
|
34
|
-
if (!/^[0-9]*$/.test(boundaries[bIdx])) throw new Error('Malformed boundary \'' + boundaries[bIdx] + '\'.');
|
|
35
|
-
}
|
|
41
|
+
boundaries = boundaries.map((boundary) => boundary.match(/^\(?([-0-9.]*)\)?$/)[1] || '');
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
let lower = boundaries[0];
|
|
44
|
+
let upper = boundaries[1];
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
if ((options || {}).allowNegative === false && (lower < 0 || upper < 0)) {
|
|
47
|
+
throw new SchemaError('Boundary cannot be a negative value.');
|
|
48
|
+
}
|
|
43
49
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
if ((options || {}).allowNonInteger === false && (!Number.isInteger(lower) || !Number.isInteger(upper))) {
|
|
51
|
+
throw new SchemaError('Boundary cannot be a non-integer value.');
|
|
52
|
+
}
|
|
47
53
|
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
// If no lower boundary is specified we use -Infinity
|
|
55
|
+
if (lower.length === 0) lower = -Infinity;
|
|
56
|
+
else lower = parseFloat(lower);
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
// If no higher boundary is specified we use Infinity;
|
|
59
|
+
if (upper.length === 0) upper = Infinity;
|
|
60
|
+
else upper = parseFloat(upper);
|
|
61
|
+
|
|
62
|
+
if (lower > upper) {
|
|
63
|
+
throw new SchemaError('Malformed range \'' + range +'\'');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { lower, upper };
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
return false;
|
|
68
|
+
});
|
|
55
69
|
|
|
56
70
|
};
|
package/lib/validate.js
CHANGED
|
@@ -30,6 +30,9 @@ const validateObject = async (data, schema, options, keyPath, validatedData) =>
|
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// If there is no schema we just return the object.
|
|
34
|
+
if (typeof schema.schema === 'undefined') return data;
|
|
35
|
+
|
|
33
36
|
// Find unknown keys
|
|
34
37
|
for (let key in data) {
|
|
35
38
|
if (schema.schema[key] === undefined) {
|
|
@@ -87,9 +90,11 @@ const validateArray = async (data, schema, options, keyPath, validatedData) => {
|
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
if (typeof schema.schema !== 'undefined') {
|
|
94
|
+
data = await Promise.all(data.map((data, idx) => {
|
|
95
|
+
return validateAny(data, schema.schema, options, keyPath.concat([idx]), validatedData);
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
93
98
|
|
|
94
99
|
if ((schema.len || options.defaults.len) && !ranges.testIndex((schema.len || options.defaults.len), data.length)) {
|
|
95
100
|
throw new ValidationError(
|
|
@@ -201,6 +206,30 @@ const validateNumber = async (data, schema, options, keyPath) => {
|
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
|
|
209
|
+
if (schema.float || options.defaults.float) {
|
|
210
|
+
if (!Number.isInteger(data)) {
|
|
211
|
+
switch (schema.float) {
|
|
212
|
+
case 'deny':
|
|
213
|
+
throw new ValidationError(
|
|
214
|
+
keyPath,
|
|
215
|
+
schema._nonFormalizedSchema,
|
|
216
|
+
'unknownKeys',
|
|
217
|
+
(schema.errors || {}).float || customErrorMessage(((options.errorMessages || {}).number || {}).float || 'Number must be an integer.'));
|
|
218
|
+
case 'round':
|
|
219
|
+
data = Math.round(data);
|
|
220
|
+
break;
|
|
221
|
+
case 'floor':
|
|
222
|
+
data = Math.floor(data);
|
|
223
|
+
break;
|
|
224
|
+
case 'ceil':
|
|
225
|
+
data = Math.ceil(data);
|
|
226
|
+
break;
|
|
227
|
+
default:
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
204
233
|
return data;
|
|
205
234
|
|
|
206
235
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "isvalid",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Async JSON validation library for node.js.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"keywords": [
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
"merge": "^2.1.1"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"body-parser": "^1.
|
|
22
|
-
"chai": "^4.
|
|
21
|
+
"body-parser": "^1.20.0",
|
|
22
|
+
"chai": "^4.3.6",
|
|
23
23
|
"chai-as-promised": "^7.1.1",
|
|
24
|
-
"eslint": "^8.
|
|
25
|
-
"express": "^4.17.
|
|
26
|
-
"mocha": "^9.
|
|
27
|
-
"supertest": "^6.
|
|
24
|
+
"eslint": "^8.12.0",
|
|
25
|
+
"express": "^4.17.3",
|
|
26
|
+
"mocha": "^9.2.2",
|
|
27
|
+
"supertest": "^6.2.2"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "./node_modules/mocha/bin/mocha ./test/index.js"
|
package/test/formalize.js
CHANGED
|
@@ -12,9 +12,6 @@ const f = (...args) => {
|
|
|
12
12
|
|
|
13
13
|
describe('schema', function() {
|
|
14
14
|
describe('formalizer', function() {
|
|
15
|
-
it('should throw an error if array shortcut contains no object.', () => {
|
|
16
|
-
expect(f([])).to.throw(SchemaError);
|
|
17
|
-
});
|
|
18
15
|
it('should throw an error if schema is garbage value.', () => {
|
|
19
16
|
expect(f(123)).to.throw(SchemaError);
|
|
20
17
|
});
|
|
@@ -168,10 +165,19 @@ describe('schema', function() {
|
|
|
168
165
|
equal: '123'
|
|
169
166
|
})).to.have.property('equal').equal('123');
|
|
170
167
|
});
|
|
171
|
-
it('should come back with a priority if non is provided', () => {
|
|
168
|
+
it('should come back with a priority if non is provided.', () => {
|
|
172
169
|
expect(formalize({
|
|
173
170
|
type: Number
|
|
174
171
|
})).to.have.property('priority').equal(10);
|
|
175
172
|
});
|
|
173
|
+
it ('should throw error if number range is not valid.', () => {
|
|
174
|
+
expect(f({ type: Number, range: 'abc-abc' })).to.throw(SchemaError);
|
|
175
|
+
});
|
|
176
|
+
it ('should throw error if string len is negative.', () => {
|
|
177
|
+
expect(f({ type: String, len: '-2-' })).to.throw(SchemaError);
|
|
178
|
+
});
|
|
179
|
+
it ('should throw error if array len is negative.', () => {
|
|
180
|
+
expect(f({ type: Array, len: '-2-' })).to.throw(SchemaError);
|
|
181
|
+
});
|
|
176
182
|
});
|
|
177
183
|
});
|
package/test/ranges.js
CHANGED
|
@@ -3,31 +3,42 @@
|
|
|
3
3
|
const expect = require('chai').expect,
|
|
4
4
|
ranges = require('../lib/ranges.js');
|
|
5
5
|
|
|
6
|
+
function r(range, value) {
|
|
7
|
+
const formalized = ranges.formalize(range);
|
|
8
|
+
return ranges.testIndex(formalized, value);
|
|
9
|
+
}
|
|
10
|
+
|
|
6
11
|
describe('ranges', function() {
|
|
7
12
|
it ('should throw an error if ranges is not a string.', function() {
|
|
8
13
|
expect(function() {
|
|
9
|
-
|
|
14
|
+
r([123], 1);
|
|
10
15
|
}).to.throw(Error);
|
|
11
16
|
});
|
|
12
17
|
it ('should throw no error if ranges is a number.', function() {
|
|
13
18
|
expect(function() {
|
|
14
|
-
|
|
19
|
+
r(1, 1);
|
|
15
20
|
}).not.to.throw(Error);
|
|
16
21
|
});
|
|
17
22
|
it ('should throw error if ranges is string but format is invalid.', function() {
|
|
18
23
|
expect(function() {
|
|
19
|
-
|
|
24
|
+
r('abc', 1);
|
|
20
25
|
}).to.throw(Error);
|
|
21
26
|
});
|
|
22
27
|
it ('should throw error if index is not set.', function() {
|
|
23
28
|
expect(function() {
|
|
24
|
-
|
|
29
|
+
r(1);
|
|
25
30
|
});
|
|
26
31
|
});
|
|
27
32
|
it ('should return true if index is within range.', function() {
|
|
28
|
-
expect(
|
|
33
|
+
expect(r('-2,4-6,8,10-', 2)).to.equal(true);
|
|
29
34
|
});
|
|
30
35
|
it ('should return false if index is not within range.', function() {
|
|
31
|
-
expect(
|
|
36
|
+
expect(r('-2,4-6,8,10-', 3)).to.equal(false);
|
|
37
|
+
});
|
|
38
|
+
it ('should allow negative values wrapped in parentheses.', function() {
|
|
39
|
+
expect(r('(-2)-', -1)).to.equal(true);
|
|
40
|
+
});
|
|
41
|
+
it ('should allow for decimal values.', function() {
|
|
42
|
+
expect(r('(-2.2)-2.2', 0.1)).to.equal(true);
|
|
32
43
|
});
|
|
33
44
|
});
|
package/test/validate.js
CHANGED
|
@@ -360,6 +360,9 @@ describe('validate', function() {
|
|
|
360
360
|
});
|
|
361
361
|
describe('object validator', function() {
|
|
362
362
|
commonTests.all(Object, {}, 123);
|
|
363
|
+
it('should come back with same input if sub-schema is not provided.', () => {
|
|
364
|
+
return expect(isvalid({}, {})).to.eventually.eql({});
|
|
365
|
+
});
|
|
363
366
|
it('should come out with same input as output if keys can validate.', () => {
|
|
364
367
|
let s = isvalid({
|
|
365
368
|
awesome: true,
|
|
@@ -480,7 +483,10 @@ describe('validate', function() {
|
|
|
480
483
|
});
|
|
481
484
|
describe('array validator', function() {
|
|
482
485
|
commonTests.all(Array, [], 123);
|
|
483
|
-
it('should come
|
|
486
|
+
it('should come back with same input if no sub-schema is provided.', () => {
|
|
487
|
+
return expect(isvalid([], [])).to.eventually.eql([]);
|
|
488
|
+
});
|
|
489
|
+
it('should come back with same input as output if array can validate.', () => {
|
|
484
490
|
let s = isvalid([{
|
|
485
491
|
awesome: true,
|
|
486
492
|
why: 'it just is!'
|
|
@@ -706,6 +712,21 @@ describe('validate', function() {
|
|
|
706
712
|
return expect(isvalid(123, Number))
|
|
707
713
|
.to.eventually.equal(123);
|
|
708
714
|
});
|
|
715
|
+
it('should throw error if non-integers are not allowed.', () => {
|
|
716
|
+
expect(isvalid(2.2, { type: Number, float: 'deny' })).to.eventually.throw(ValidationError);
|
|
717
|
+
});
|
|
718
|
+
it ('should come back with non-integer values if they are allowed.', () => {
|
|
719
|
+
expect(isvalid(2.2, { type: Number })).to.eventually.equal(2.2);
|
|
720
|
+
});
|
|
721
|
+
it ('should come back with number rounded if `float` is set to `round`.', () => {
|
|
722
|
+
expect(isvalid(2.5, { type: Number, float: 'round' })).to.eventually.equal(3);
|
|
723
|
+
});
|
|
724
|
+
it ('should come back with number ceiled if `float` is set to `ceil`.', () => {
|
|
725
|
+
expect(isvalid(2.2, { type: Number, float: 'ceil' })).to.eventually.equal(3);
|
|
726
|
+
});
|
|
727
|
+
it ('should come back with number floored if `float` is set to `floor`.', () => {
|
|
728
|
+
expect(isvalid(2.8, { type: Number, float: 'ceil' })).to.eventually.equal(2);
|
|
729
|
+
});
|
|
709
730
|
describe('range', function() {
|
|
710
731
|
it('should come back with error if input is not within range.', () => {
|
|
711
732
|
return expect(isvalid(1, { type: Number, range: '2-4' }))
|