feat: support for required dependencies of custom keyword (keywords that must be present in the same schema)

master
Evgeny Poberezkin 2019-02-09 20:28:33 +00:00
parent f080c91e9a
commit fdfbd4402a
5 changed files with 74 additions and 8 deletions

View File

@ -92,6 +92,8 @@ In some cases it is the best approach to define keywords, but it has the perform
All custom keywords types can have an optional `metaSchema` property in their definitions. It is a schema against which the value of keyword will be validated during schema compilation.
Custom keyword can also have an optional `dependencies` property in their definitions - it is a list of required keywords in a containing (parent) schema.
Example. `range` and `exclusiveRange` keywords using compiled schema:
```javascript

View File

@ -1005,6 +1005,7 @@ Keyword definition is an object with the following properties:
- _inline_: compiling function that returns code (as string)
- _schema_: an optional `false` value used with "validate" keyword to not pass schema
- _metaSchema_: an optional meta-schema for keyword schema
- _dependencies_: an optional list of properties that must be present in the parent schema - it will be checked during schema compilation
- _modifying_: `true` MUST be passed if keyword modifies data
- _valid_: pass `true`/`false` to pre-define validation result, the result returned from validation function will be ignored. This option cannot be used with macro keywords.
- _$data_: an optional `true` value to support [$data reference](#data-reference) as the value of custom keyword. The reference will be resolved at validation time. If the keyword has meta-schema it would be extended to allow $data and it will be used to validate the resolved value. Supporting $data reference requires that keyword has validating function (as the only option or in addition to compile, macro or inline function).

View File

@ -255,13 +255,22 @@ function compile(schema, root, localRefs, baseId) {
}
function useCustomRule(rule, schema, parentSchema, it) {
var validateSchema = rule.definition.validateSchema;
if (validateSchema && self._opts.validateSchema !== false) {
var valid = validateSchema(schema);
if (!valid) {
var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors);
if (self._opts.validateSchema == 'log') self.logger.error(message);
else throw new Error(message);
if (self._opts.validateSchema !== false) {
var deps = rule.definition.dependencies;
if (deps && !deps.every(function(keyword) {
return Object.prototype.hasOwnProperty.call(parentSchema, keyword);
})) {
throw new Error('parent schema must have all required keywords: ' + deps.join(','));
}
var validateSchema = rule.definition.validateSchema;
if (validateSchema) {
var valid = validateSchema(schema);
if (!valid) {
var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors);
if (self._opts.validateSchema == 'log') self.logger.error(message);
else throw new Error(message);
}
}
}

View File

@ -24,7 +24,7 @@ function addKeyword(keyword, definition) {
if (RULES.keywords[keyword])
throw new Error('Keyword ' + keyword + ' is already defined');
if (!IDENTIFIER.test(keyword))
if (!isIdentifier(keyword))
throw new Error('Keyword ' + keyword + ' is not a valid identifier');
if (definition) {
@ -45,6 +45,10 @@ function addKeyword(keyword, definition) {
if ($data && !definition.validate)
throw new Error('$data support: "validate" function is not defined');
var deps = definition.dependencies;
if (deps && !(Array.isArray(deps) && deps.every(isIdentifier)))
throw new Error('"dependencies" option should be a list of valid identifiers');
var metaSchema = definition.metaSchema;
if (metaSchema) {
if ($data) {
@ -93,6 +97,10 @@ function addKeyword(keyword, definition) {
if (!RULES.types[dataType]) throw new Error('Unknown type ' + dataType);
}
function isIdentifier(name) {
return typeof name == 'string' && IDENTIFIER.test(name);
}
return this;
}

View File

@ -1182,4 +1182,50 @@ describe('Custom keywords', function () {
});
});
});
describe('"dependencies" in keyword definition', function() {
it("should require properties in the parent schema", function() {
ajv.addKeyword('allRequired', {
macro: function(schema, parentSchema) {
return schema ? {required: Object.keys(parentSchema.properties)} : true;
},
metaSchema: {type: 'boolean'},
dependencies: ['properties']
});
var invalidSchema = {
allRequired: true
};
should.throw(function () {
ajv.compile(invalidSchema);
});
var schema = {
properties: {
foo: true
},
allRequired: true
};
var v = ajv.compile(schema);
v({foo: 1}) .should.equal(true);
v({}) .should.equal(false);
});
it("'dependencies'should be array of valid strings", function() {
ajv.addKeyword('newKeyword1', {
metaSchema: {type: 'boolean'},
dependencies: ['dep1', 'dep-2']
});
should.throw(function () {
ajv.addKeyword('newKeyword2', {
metaSchema: {type: 'boolean'},
dependencies: ['dep.1']
});
});
});
});
});