feat: strictKeywords option to report unknown keywords, closes #781

master
Evgeny Poberezkin 2019-03-03 10:49:16 +00:00
parent 9a28689340
commit e993bd6b4e
6 changed files with 99 additions and 2 deletions

View File

@ -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

View File

@ -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',

View File

@ -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) + '\'';
}

View File

@ -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';

View File

@ -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": {

View File

@ -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');
}
};
}
});