Skip to content

Commit

Permalink
feat: strictKeywords option to report unknown keywords, closes #781
Browse files Browse the repository at this point in the history
  • Loading branch information
epoberezkin committed Mar 3, 2019
1 parent 9a28689 commit e993bd6
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 2 deletions.
12 changes: 11 additions & 1 deletion README.md
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/compile/rules.js
Expand Up @@ -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',
Expand Down
7 changes: 7 additions & 0 deletions lib/compile/util.js
Expand Up @@ -17,6 +17,7 @@ module.exports = {
finalCleanUpCode: finalCleanUpCode,
schemaHasRules: schemaHasRules,
schemaHasRulesExcept: schemaHasRulesExcept,
schemaUnknownRules: schemaUnknownRules,
toQuotedString: toQuotedString,
getPathExpr: getPathExpr,
getPath: getPath,
Expand Down Expand Up @@ -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) + '\'';
}
Expand Down
11 changes: 11 additions & 0 deletions lib/dot/validate.jst
Expand Up @@ -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';
Expand Down
3 changes: 3 additions & 0 deletions spec/custom.spec.js
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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": {
Expand Down
66 changes: 66 additions & 0 deletions 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');
}
};
}
});

0 comments on commit e993bd6

Please sign in to comment.