From 41ecdaff7cd19ac4cea0eac76bb458dd07854286 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Fri, 30 Dec 2016 17:44:18 +0000 Subject: [PATCH] refactor: contains implemented as a standard keyword, #367 --- lib/ajv.js | 7 ----- lib/compile/_rules.js | 1 + lib/compile/index.js | 2 +- lib/compile/rules.js | 2 +- lib/dot/contains.jst | 55 +++++++++++++++++++++++++++++++++++++++ lib/dot/errors.def | 3 +++ lib/dot/items.jst | 1 - lib/dot/validate.jst | 2 +- spec/boolean.spec.js | 4 +-- spec/extras/contains.json | 5 ++++ 10 files changed, 69 insertions(+), 13 deletions(-) create mode 100644 lib/dot/contains.jst diff --git a/lib/ajv.js b/lib/ajv.js index 4937f1e..106eb51 100644 --- a/lib/ajv.js +++ b/lib/ajv.js @@ -70,13 +70,6 @@ function Ajv(opts) { this._compilations = []; this.RULES = rules(); - this.addKeyword('contains', { - type: 'array', - macro: function (schema) { - return { not: { items: { not: schema } } }; - } - }); - opts.loopRequired = opts.loopRequired || Infinity; if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true; this._metaOpts = getMetaSchemaOptions(this); diff --git a/lib/compile/_rules.js b/lib/compile/_rules.js index 0c0eb23..3fe6997 100644 --- a/lib/compile/_rules.js +++ b/lib/compile/_rules.js @@ -6,6 +6,7 @@ module.exports = { allOf: require('../dotjs/allOf'), anyOf: require('../dotjs/anyOf'), const: require('../dotjs/const'), + contains: require('../dotjs/contains'), dependencies: require('../dotjs/dependencies'), 'enum': require('../dotjs/enum'), format: require('../dotjs/format'), diff --git a/lib/compile/index.js b/lib/compile/index.js index 0614b41..1f04fd1 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -112,7 +112,7 @@ function compile(schema, root, localRefs, baseId) { + sourceCode; if (opts.processCode) sourceCode = opts.processCode(sourceCode); - // console.log('\n\n\n *** \n', sourceCode); + // console.log('\n\n\n *** \n', JSON.stringify(sourceCode)); var validate; try { var makeValidate = new Function( diff --git a/lib/compile/rules.js b/lib/compile/rules.js index 4ad1b24..271b343 100644 --- a/lib/compile/rules.js +++ b/lib/compile/rules.js @@ -11,7 +11,7 @@ module.exports = function rules() { { type: 'string', rules: [ 'maxLength', 'minLength', 'pattern', 'format' ] }, { type: 'array', - rules: [ 'maxItems', 'minItems', 'uniqueItems', 'items' ] }, + rules: [ 'maxItems', 'minItems', 'uniqueItems', 'contains', 'items' ] }, { type: 'object', rules: [ 'maxProperties', 'minProperties', 'required', 'dependencies', 'propertyNames', { 'properties': ['additionalProperties', 'patternProperties'] } ] }, diff --git a/lib/dot/contains.jst b/lib/dot/contains.jst new file mode 100644 index 0000000..12751c5 --- /dev/null +++ b/lib/dot/contains.jst @@ -0,0 +1,55 @@ +{{# def.definitions }} +{{# def.errors }} +{{# def.setupKeyword }} +{{# def.setupNextLevel }} + + +{{ + var $idx = 'i' + $lvl + , $dataNxt = $it.dataLevel = it.dataLevel + 1 + , $nextData = 'data' + $dataNxt + , $currentBaseId = it.baseId + , $nonEmptySchema = {{# def.nonEmptySchema:$schema }}; +}} + +var {{=$errs}} = errors; +var {{=$valid}}; + +{{? $nonEmptySchema }} + {{# def.setCompositeRule }} + + {{ + $it.schema = $schema; + $it.schemaPath = $schemaPath; + $it.errSchemaPath = $errSchemaPath; + }} + + for (var {{=$idx}} = 0; {{=$idx}} < {{=$data}}.length; {{=$idx}}++) { + {{ + $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); + var $passData = $data + '[' + $idx + ']'; + $it.dataPathArr[$dataNxt] = $idx; + }} + + {{# def.generateSubschemaCode }} + {{# def.optimizeValidate }} + + if ({{=$nextValid}}) break; + } + + {{# def.resetCompositeRule }} + {{= $closingBraces }} + + if (!{{=$nextValid}}) { +{{??}} + if ({{=$data}}.length == 0) { +{{?}} + + {{# def.error:'contains' }} + } else { + {{? $nonEmptySchema }} + {{# def.resetErrors }} + {{?}} + {{? it.opts.allErrors }} } {{?}} + +{{# def.cleanUp }} diff --git a/lib/dot/errors.def b/lib/dot/errors.def index e35d3bf..698a59c 100644 --- a/lib/dot/errors.def +++ b/lib/dot/errors.def @@ -97,6 +97,7 @@ additionalProperties: "'should NOT have additional properties'", anyOf: "'should match some schema in anyOf'", const: "'should be equal to constant'", + contains: "'should contain a valid item'", dependencies: "'should have {{? $deps.length == 1 }}property {{= it.util.escapeQuotes($deps[0]) }}{{??}}properties {{= it.util.escapeQuotes($deps.join(\", \")) }}{{?}} when property {{= it.util.escapeQuotes($property) }} is present'", 'enum': "'should be equal to one of the allowed values'", format: "'should match format \"{{#def.concatSchemaEQ}}\"'", @@ -132,6 +133,7 @@ additionalProperties: "false", anyOf: "validate.schema{{=$schemaPath}}", const: "validate.schema{{=$schemaPath}}", + contains: "validate.schema{{=$schemaPath}}", dependencies: "validate.schema{{=$schemaPath}}", 'enum': "validate.schema{{=$schemaPath}}", format: "{{#def.schemaRefOrQS}}", @@ -166,6 +168,7 @@ additionalProperties: "{ additionalProperty: '{{=$additionalProperty}}' }", anyOf: "{}", const: "{}", + constains: "{}", dependencies: "{ property: '{{= it.util.escapeQuotes($property) }}', missingProperty: '{{=$missingProperty}}', depsCount: {{=$deps.length}}, deps: '{{= it.util.escapeQuotes($deps.length==1 ? $deps[0] : $deps.join(\", \")) }}' }", 'enum': "{ allowedValues: schema{{=$lvl}} }", format: "{ format: {{#def.schemaValueQS}} }", diff --git a/lib/dot/items.jst b/lib/dot/items.jst index a8b8aa7..8c0f5ac 100644 --- a/lib/dot/items.jst +++ b/lib/dot/items.jst @@ -90,7 +90,6 @@ var {{=$valid}}; $it.errSchemaPath = $errSchemaPath; }} {{# def.validateItems: 0 }} - {{# def.ifResultValid }} {{?}} {{? $breakOnError }} diff --git a/lib/dot/validate.jst b/lib/dot/validate.jst index ae194c2..6b76f4d 100644 --- a/lib/dot/validate.jst +++ b/lib/dot/validate.jst @@ -230,7 +230,7 @@ {{# def.cleanUp }} -{{? $top && $breakOnError }} +{{? $top }} {{# def.finalCleanUp }} {{?}} diff --git a/spec/boolean.spec.js b/spec/boolean.spec.js index eeb8a47..c4c3b3e 100644 --- a/spec/boolean.spec.js +++ b/spec/boolean.spec.js @@ -259,13 +259,13 @@ describe('boolean schemas', function() { describe('in contains', function() { describe('schema = true', function() { - it('should be valid with any property', function() { + it('should be valid with any items', function() { ajvs.forEach(test(true, true)); }); }); describe('schema = false', function() { - it('should be invalid with any property', function() { + it('should be invalid with any items', function() { ajvs.forEach(test(false, false)); }); }); diff --git a/spec/extras/contains.json b/spec/extras/contains.json index efcb7de..369c10a 100644 --- a/spec/extras/contains.json +++ b/spec/extras/contains.json @@ -20,6 +20,11 @@ "data": [1, 2, 3, 4], "valid": false }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, { "description": "not array is valid", "data": {},