From 3f251876a904a58077f3abe03cb5600aaac3e64d Mon Sep 17 00:00:00 2001 From: Issac Gerges Date: Fri, 22 May 2020 11:47:25 -0500 Subject: [PATCH] Add option for strictNumbers. Resolves #1128. --- README.md | 5 +++- lib/ajv.d.ts | 1 + lib/compile/util.js | 9 +++--- lib/dot/uniqueItems.jst | 2 +- lib/dot/validate.jst | 4 +-- spec/options/strictNumbers.spec.js | 47 ++++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 spec/options/strictNumbers.spec.js diff --git a/README.md b/README.md index a8757398ed..465786aabb 100644 --- a/README.md +++ b/README.md @@ -1156,6 +1156,7 @@ Defaults: // strict mode options strictDefaults: false, strictKeywords: false, + strictNumbers: false, // asynchronous validation options: transpile: undefined, // requires ajv-async package // advanced options: @@ -1250,7 +1251,9 @@ Defaults: - `false` (default) - unknown keywords are not reported - `true` - if an unknown keyword is present, throw an error - `"log"` - if an unknown keyword is present, log warning - +- _strictNumbers_: validate numbers strictly, failing validation for NaN and Infinity. Option values: + - `false` (default) - NaN or Infinity will pass validation for a number type + - `true` - NaN or Infinity will not pass validation for a number type ##### Asynchronous validation options diff --git a/lib/ajv.d.ts b/lib/ajv.d.ts index d988c3a996..cc2881b33b 100644 --- a/lib/ajv.d.ts +++ b/lib/ajv.d.ts @@ -183,6 +183,7 @@ declare namespace ajv { coerceTypes?: boolean | 'array'; strictDefaults?: boolean | 'log'; strictKeywords?: boolean | 'log'; + strictNumbers?: boolean; async?: boolean | string; transpile?: string | ((code: string) => string); meta?: boolean | object; diff --git a/lib/compile/util.js b/lib/compile/util.js index 0efa00111c..489d201f54 100644 --- a/lib/compile/util.js +++ b/lib/compile/util.js @@ -36,7 +36,7 @@ function copy(o, to) { } -function checkDataType(dataType, data, negate) { +function checkDataType(dataType, data, strictNumbers, negate) { var EQUAL = negate ? ' !== ' : ' === ' , AND = negate ? ' || ' : ' && ' , OK = negate ? '!' : '' @@ -50,14 +50,15 @@ function checkDataType(dataType, data, negate) { case 'integer': return '(typeof ' + data + EQUAL + '"number"' + AND + NOT + '(' + data + ' % 1)' + AND + data + EQUAL + data + ')'; + case 'number': return '(typeof ' + data + EQUAL + '"' + dataType + '"' + (strictNumbers ? (AND + OK + 'isFinite(' + data + ')') : '') + ')'; default: return 'typeof ' + data + EQUAL + '"' + dataType + '"'; } } -function checkDataTypes(dataTypes, data) { +function checkDataTypes(dataTypes, data, strictNumbers) { switch (dataTypes.length) { - case 1: return checkDataType(dataTypes[0], data, true); + case 1: return checkDataType(dataTypes[0], data, strictNumbers, true); default: var code = ''; var types = toHash(dataTypes); @@ -70,7 +71,7 @@ function checkDataTypes(dataTypes, data) { } if (types.number) delete types.integer; for (var t in types) - code += (code ? ' && ' : '' ) + checkDataType(t, data, true); + code += (code ? ' && ' : '' ) + checkDataType(t, data, strictNumbers, true); return code; } diff --git a/lib/dot/uniqueItems.jst b/lib/dot/uniqueItems.jst index 22f82f99d8..e69b8308d2 100644 --- a/lib/dot/uniqueItems.jst +++ b/lib/dot/uniqueItems.jst @@ -38,7 +38,7 @@ for (;i--;) { var item = {{=$data}}[i]; {{ var $method = 'checkDataType' + ($typeIsArray ? 's' : ''); }} - if ({{= it.util[$method]($itemType, 'item', true) }}) continue; + if ({{= it.util[$method]($itemType, 'item', it.opts.strictNumbers, true) }}) continue; {{? $typeIsArray}} if (typeof item == 'string') item = '"' + item; {{?}} diff --git a/lib/dot/validate.jst b/lib/dot/validate.jst index f8a1edfc0e..bae653ff6e 100644 --- a/lib/dot/validate.jst +++ b/lib/dot/validate.jst @@ -140,7 +140,7 @@ , $method = $typeIsArray ? 'checkDataTypes' : 'checkDataType'; }} - if ({{= it.util[$method]($typeSchema, $data, true) }}) { + if ({{= it.util[$method]($typeSchema, $data, it.opts.strictNumbers, true) }}) { #}} {{? it.schema.$ref && $refKeywords }} @@ -192,7 +192,7 @@ {{~ it.RULES:$rulesGroup }} {{? $shouldUseGroup($rulesGroup) }} {{? $rulesGroup.type }} - if ({{= it.util.checkDataType($rulesGroup.type, $data) }}) { + if ({{= it.util.checkDataType($rulesGroup.type, $data, it.opts.strictNumbers) }}) { {{?}} {{? it.opts.useDefaults }} {{? $rulesGroup.type == 'object' && it.schema.properties }} diff --git a/spec/options/strictNumbers.spec.js b/spec/options/strictNumbers.spec.js new file mode 100644 index 0000000000..1dfe8437c8 --- /dev/null +++ b/spec/options/strictNumbers.spec.js @@ -0,0 +1,47 @@ +'use strict'; + +var Ajv = require('../ajv'); + +describe('structNumbers option', function() { + var ajv; + describe('strictNumbers default', testWithoutStrictNumbers(new Ajv())); + describe('strictNumbers = false', testWithoutStrictNumbers(new Ajv({strictNumbers: false}))); + describe('strictNumbers = true', function() { + beforeEach(function () { + ajv = new Ajv({strictNumbers: true}); + }); + + it('should fail validation for NaN/Infinity as type number', function() { + var validate = ajv.compile({type: 'number'}); + validate(1).should.equal(true); + validate(NaN).should.equal(false); + validate(Infinity).should.equal(false); + }); + + it('should not impact validation for NaN as type integer', function() { + var validate = ajv.compile({type: 'integer'}); + validate(1).should.equal(true); + validate(NaN).should.equal(false); + validate(Infinity).should.equal(true); + }); + }); +}); + + +function testWithoutStrictNumbers(_ajv) { + return function () { + it('should NOT fail validation for NaN/Infinity as type number', function() { + var validate = _ajv.compile({type: 'number'}); + validate(1).should.equal(true); + validate(NaN).should.equal(true); + validate(Infinity).should.equal(true); + }); + + it('should not impact validation for NaN/Infinity as type integer', function() { + var validate = _ajv.compile({type: 'integer'}); + validate(1).should.equal(true); + validate(NaN).should.equal(false); + validate(Infinity).should.equal(true); + }); + }; +}