diff --git a/README.md b/README.md index b61da8b13..c858efd09 100644 --- a/README.md +++ b/README.md @@ -1072,7 +1072,9 @@ Defaults: removeAdditional: false, useDefaults: false, coerceTypes: false, + // strict mode options strictDefaults: false, + strictKeywords: false, // asynchronous validation options: transpile: undefined, // requires ajv-async package // advanced options: @@ -1154,10 +1156,18 @@ Defaults: - `false` (default) - no type coercion. - `true` - coerce scalar data types. - `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema). -- _strictDefaults_: specify behavior for ignored `default` keywords in schemas. Option values: + + +##### Strict mode options + +- _strictDefaults_: report ignored `default` keywords in schemas. Option values: - `false` (default) - ignored defaults are not reported - `true` - if an ignored default is present, throw an error - `"log"` - if an ignored default is present, log warning +- _strictKeywords_: report unknown keywords in schemas. Option values: + - `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 ##### Asynchronous validation options diff --git a/lib/compile/rules.js b/lib/compile/rules.js index 66f196a93..08b25aeb9 100644 --- a/lib/compile/rules.js +++ b/lib/compile/rules.js @@ -20,7 +20,7 @@ module.exports = function rules() { var ALL = [ 'type', '$comment' ]; var KEYWORDS = [ - '$schema', '$id', 'id', '$data', 'title', + '$schema', '$id', 'id', '$data', '$async', 'title', 'description', 'default', 'definitions', 'examples', 'readOnly', 'writeOnly', 'contentMediaType', 'contentEncoding', diff --git a/lib/compile/util.js b/lib/compile/util.js index 263891c33..0efa00111 100644 --- a/lib/compile/util.js +++ b/lib/compile/util.js @@ -17,6 +17,7 @@ module.exports = { finalCleanUpCode: finalCleanUpCode, schemaHasRules: schemaHasRules, schemaHasRulesExcept: schemaHasRulesExcept, + schemaUnknownRules: schemaUnknownRules, toQuotedString: toQuotedString, getPathExpr: getPathExpr, getPath: getPath, @@ -183,6 +184,12 @@ function schemaHasRulesExcept(schema, rules, exceptKeyword) { } +function schemaUnknownRules(schema, rules) { + if (typeof schema == 'boolean') return; + for (var key in schema) if (!rules[key]) return key; +} + + function toQuotedString(str) { return '\'' + escapeQuotes(str) + '\''; } diff --git a/lib/dot/validate.jst b/lib/dot/validate.jst index 12a2fe494..3c92ef441 100644 --- a/lib/dot/validate.jst +++ b/lib/dot/validate.jst @@ -20,6 +20,17 @@ , $id = it.self._getId(it.schema); }} +{{ + if (it.opts.strictKeywords) { + var $unknownKwd = it.util.schemaUnknownRules(it.schema, it.RULES.keywords); + if ($unknownKwd) { + var $keywordsMsg = 'unknown keyword: ' + $unknownKwd; + if (it.opts.strictKeywords === 'log') it.logger.warn($keywordsMsg); + else throw new Error($keywordsMsg); + } + } +}} + {{? it.isTop }} var validate = {{?$async}}{{it.async = true;}}async {{?}}function(data, dataPath, parentData, parentDataProperty, rootData) { 'use strict'; diff --git a/spec/custom.spec.js b/spec/custom.spec.js index 5b1b6f03a..2924fceea 100644 --- a/spec/custom.spec.js +++ b/spec/custom.spec.js @@ -415,6 +415,7 @@ describe('Custom keywords', function () { it('should correctly expand macros in macro expansions', function() { instances.forEach(function (_ajv) { _ajv.addKeyword('range', { type: 'number', macro: macroRange }); + _ajv.addKeyword('exclusiveRange', { metaSchema: {type: 'boolean'} }); _ajv.addKeyword('myContains', { type: 'array', macro: macroContains }); var schema = { @@ -811,6 +812,7 @@ describe('Custom keywords', function () { function testRangeKeyword(definition, customErrors, numErrors) { instances.forEach(function (_ajv) { _ajv.addKeyword('x-range', definition); + _ajv.addKeyword('exclusiveRange', {metaSchema: {type: 'boolean'}}); var schema = { "x-range": [2, 4] }; var validate = _ajv.compile(schema); @@ -849,6 +851,7 @@ describe('Custom keywords', function () { function testMultipleRangeKeyword(definition, numErrors) { instances.forEach(function (_ajv) { _ajv.addKeyword('x-range', definition); + _ajv.addKeyword('exclusiveRange', {metaSchema: {type: 'boolean'}}); var schema = { "properties": { diff --git a/spec/options/strictKeywords.spec.js b/spec/options/strictKeywords.spec.js new file mode 100644 index 000000000..b212d15c7 --- /dev/null +++ b/spec/options/strictKeywords.spec.js @@ -0,0 +1,66 @@ +'use strict'; + +var Ajv = require('../ajv'); +var should = require('../chai').should(); + + +describe('strictKeywords option', function() { + describe('strictKeywords = false', function() { + it('should NOT throw an error or log a warning given an unknown keyword', function() { + var output = {}; + var ajv = new Ajv({ + strictKeywords: false, + logger: getLogger(output) + }); + var schema = { + properties: {}, + unknownKeyword: 1 + }; + + ajv.compile(schema); + should.not.exist(output.warning); + }); + }); + + describe('strictKeywords = true', function() { + it('should throw an error given an unknown keyword in the schema root when strictKeywords is true', function() { + var ajv = new Ajv({strictKeywords: true}); + var schema = { + properties: {}, + unknownKeyword: 1 + }; + should.throw(function() { ajv.compile(schema); }); + }); + }); + + describe('strictKeywords = "log"', function() { + it('should log a warning given an unknown keyword in the schema root when strictKeywords is "log"', function() { + var output = {}; + var ajv = new Ajv({ + strictKeywords: 'log', + logger: getLogger(output) + }); + var schema = { + properties: {}, + unknownKeyword: 1 + }; + ajv.compile(schema); + should.equal(output.warning, 'unknown keyword: unknownKeyword'); + }); + }); + + + function getLogger(output) { + return { + log: function() { + throw new Error('log should not be called'); + }, + warn: function(warning) { + output.warning = warning; + }, + error: function() { + throw new Error('error should not be called'); + } + }; + } +});