isvalid 2.10.5 → 3.0.0

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 CHANGED
@@ -5,7 +5,7 @@ module.exports = {
5
5
  'mocha': true
6
6
  },
7
7
  'parserOptions': {
8
- 'ecmaVersion': 2017
8
+ 'ecmaVersion': 2018
9
9
  },
10
10
  'extends': 'eslint:recommended',
11
11
  'rules': {
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/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
 
@@ -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
- module.exports.testIndex = function(ranges, index) {
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
- // Throw if index is not a number.
16
- if (index === undefined || typeof index !== 'number') {
17
- throw new Error('Index is not a number.');
18
- }
24
+ return ranges
25
+ .split(',')
26
+ .map((range) => {
19
27
 
20
- // Split into individual ranges.
21
- let r = ranges.split(',');
22
- for (let idx in r) {
28
+ // Get the boundaries of the range.
29
+ let boundaries = range.split(/(?<!\()-/);
23
30
 
24
- // Get the boundaries of the range.
25
- let boundaries = r[idx].split('-');
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
- // Low and high boundaries are the same where only one number is specified.
28
- if (boundaries.length == 1) boundaries = [ boundaries[0], boundaries[0] ];
29
- // Throw an error if there is not exactly to boundaries.
30
- if (boundaries.length != 2) throw new Error('Malformed range \'' + r[idx] + '\'.');
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
- // Test for malformed boundaries
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
- let lowBoundary = boundaries[0];
38
- let highBoundary = boundaries[1];
43
+ let lower = boundaries[0];
44
+ let upper = boundaries[1];
39
45
 
40
- // If no lower boundary is specified we use -Infinity
41
- if (lowBoundary.length === 0) lowBoundary = -Infinity;
42
- else lowBoundary = parseInt(lowBoundary);
46
+ if ((options || {}).allowNegative === false && (lower < 0 || upper < 0)) {
47
+ throw new SchemaError('Boundary cannot be a negative value.');
48
+ }
43
49
 
44
- // If no higher boundary is specified we use Infinity;
45
- if (highBoundary.length === 0) highBoundary = Infinity;
46
- else highBoundary = parseInt(highBoundary);
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
- // If index is within boundaries return true;
49
- if (index >= lowBoundary && index <= highBoundary) return true;
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
- // Index was not matched to any range.
54
- return false;
68
+ });
55
69
 
56
70
  };
package/lib/validate.js CHANGED
@@ -206,6 +206,30 @@ const validateNumber = async (data, schema, options, keyPath) => {
206
206
  }
207
207
  }
208
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
+
209
233
  return data;
210
234
 
211
235
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isvalid",
3
- "version": "2.10.5",
3
+ "version": "3.0.0",
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.19.0",
22
- "chai": "^4.2.0",
21
+ "body-parser": "^1.20.0",
22
+ "chai": "^4.3.6",
23
23
  "chai-as-promised": "^7.1.1",
24
- "eslint": "^8.2.0",
25
- "express": "^4.17.1",
26
- "mocha": "^9.1.3",
27
- "supertest": "^6.1.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
@@ -165,10 +165,19 @@ describe('schema', function() {
165
165
  equal: '123'
166
166
  })).to.have.property('equal').equal('123');
167
167
  });
168
- it('should come back with a priority if non is provided', () => {
168
+ it('should come back with a priority if non is provided.', () => {
169
169
  expect(formalize({
170
170
  type: Number
171
171
  })).to.have.property('priority').equal(10);
172
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
+ });
173
182
  });
174
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
- ranges.testIndex([123], 1);
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
- ranges.testIndex(1, 1);
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
- ranges.testIndex('abc', 1);
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
- ranges.testIndex(1);
29
+ r(1);
25
30
  });
26
31
  });
27
32
  it ('should return true if index is within range.', function() {
28
- expect(ranges.testIndex('-2,4-6,8,10-', 2)).to.equal(true);
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(ranges.testIndex('-2,4-6,8,10-', 3)).to.equal(false);
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
@@ -712,6 +712,21 @@ describe('validate', function() {
712
712
  return expect(isvalid(123, Number))
713
713
  .to.eventually.equal(123);
714
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
+ });
715
730
  describe('range', function() {
716
731
  it('should come back with error if input is not within range.', () => {
717
732
  return expect(isvalid(1, { type: Number, range: '2-4' }))