support for "inline" custom keywords, #69
parent
87292f80ea
commit
ae5b4c0f45
|
@ -149,27 +149,32 @@ function compile(schema, root, localRefs, baseId) {
|
|||
return 'pattern' + index;
|
||||
}
|
||||
|
||||
function useCustomRule(rule, schema, parentSchema) {
|
||||
var compile = rule.definition.compile;
|
||||
function useCustomRule(rule, schema, parentSchema, it) {
|
||||
var compile = rule.definition.compile
|
||||
, inline = rule.definition.inline;
|
||||
|
||||
var key = rule.keyword;
|
||||
if (compile) {
|
||||
if (compile || inline) {
|
||||
key += '::' + stableStringify(schema);
|
||||
if (compile.length > 1) key += '::' + stableStringify(parentSchema);
|
||||
var usesParentSchema = (compile && compile.length > 1)
|
||||
|| (inline && inline.length > 2)
|
||||
|| rule.definition.parentSchema;
|
||||
if (usesParentSchema) key += '::' + stableStringify(parentSchema);
|
||||
}
|
||||
|
||||
var index = customRulesHash[key];
|
||||
if (index === undefined) {
|
||||
var validate = compile
|
||||
? compile.call(self, schema, parentSchema)
|
||||
: rule.definition.validate;
|
||||
: inline
|
||||
? inline.call(self, it, schema, parentSchema)
|
||||
: rule.definition.validate;
|
||||
index = customRulesHash[key] = customRules.length;
|
||||
customRules[index] = validate;
|
||||
}
|
||||
|
||||
return {
|
||||
code: 'customRule' + index,
|
||||
compiled: !!compile,
|
||||
validate: customRules[index]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ function expandMacros() {
|
|||
schemaCopy = util.copy(schema);
|
||||
|
||||
var success = true;
|
||||
out: // try to merge schemas
|
||||
out: // try to merge schemas without merging keywords
|
||||
for (i=0; i<expSchemas.length; i++) {
|
||||
var sch = expSchemas[i];
|
||||
for (key in sch) {
|
||||
|
|
|
@ -1,37 +1,66 @@
|
|||
{{
|
||||
var $schema = it.schema[$rule.keyword]
|
||||
, $ruleValidate = it.useCustomRule($rule, $schema, it.schema)
|
||||
, $ruleValidate = it.useCustomRule($rule, $schema, it.schema, it)
|
||||
, $ruleErrs = $ruleValidate.code + '.errors'
|
||||
, $schemaPath = it.schemaPath + '.' + $rule.keyword;
|
||||
}}
|
||||
{{=$ruleErrs}} = null;
|
||||
|
||||
{{? !$rule.definition.inline }}
|
||||
{{=$ruleErrs}} = null;
|
||||
{{?}}
|
||||
|
||||
{{## def.callRuleValidate:
|
||||
{{ var argsLen; }}
|
||||
{{=$ruleValidate.code}}.call(self
|
||||
{{? !$ruleValidate.compiled }}
|
||||
, validate.schema{{=$schemaPath}}
|
||||
, {{=$data}}
|
||||
{{? $ruleValidate.validate.length > 2 }}
|
||||
, validate.schema{{=it.schemaPath}}
|
||||
{{?}}
|
||||
{{? $rule.definition.inline }}
|
||||
{{? $rule.definition.statements }}
|
||||
valid{{=it.lvl}}
|
||||
{{??}}
|
||||
, {{=$data}}
|
||||
({{= $ruleValidate.validate }})
|
||||
{{?}}
|
||||
)
|
||||
{{??}}
|
||||
{{=$ruleValidate.code}}.call(self
|
||||
{{? $rule.definition.compile }}
|
||||
, {{=$data}}
|
||||
{{??}}
|
||||
, validate.schema{{=$schemaPath}}
|
||||
, {{=$data}}
|
||||
{{? $ruleValidate.validate.length > 2 }}
|
||||
, validate.schema{{=it.schemaPath}}
|
||||
{{?}}
|
||||
{{?}}
|
||||
)
|
||||
{{?}}
|
||||
#}}
|
||||
|
||||
if (!{{# def.callRuleValidate }}) {
|
||||
{{## def.extendErrors:
|
||||
{{ var $i = 'i' + $lvl; }}
|
||||
for (var {{=$i}}=0; {{=$i}}<{{=$ruleErrs}}.length; {{=$i}}++) {
|
||||
{{ var $ruleErr = 'ruleErr' + $lvl; }}
|
||||
var {{=$ruleErr}} = {{=$ruleErrs}}[{{=$i}}];
|
||||
{{=$ruleErr}}.dataPath = (dataPath || '') + {{= it.errorPath }};
|
||||
{{? it.opts.verbose || it.opts.i18n }}
|
||||
{{=$ruleErr}}.schema = validate.schema{{=$schemaPath}};
|
||||
{{?}}
|
||||
{{? it.opts.verbose }}
|
||||
{{=$ruleErr}}.data = {{=$data}};
|
||||
{{?}}
|
||||
}
|
||||
#}}
|
||||
|
||||
{{? $rule.definition.inline && $rule.definition.statements }}
|
||||
{{= $ruleValidate.validate }}
|
||||
{{?}}
|
||||
|
||||
if (!{{# def.callRuleValidate }}) {
|
||||
{{? $rule.definition.inline }}
|
||||
{{# def.error:'custom' }}
|
||||
{{??}}
|
||||
if (Array.isArray({{=$ruleErrs}})) {
|
||||
{{# def.extendErrors }}
|
||||
if (vErrors === null) vErrors = {{=$ruleErrs}};
|
||||
else vErrors.concat({{=$ruleErrs}});
|
||||
errors = vErrors.length;
|
||||
} else {
|
||||
{{# def.error:'custom' }}
|
||||
}
|
||||
{{? $breakOnError }}
|
||||
return false;
|
||||
} else {
|
||||
{{??}}
|
||||
}
|
||||
{{?}}
|
||||
{{?}}
|
||||
} {{? $breakOnError }} else { {{?}}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
var getAjvInstances = require('./ajv_instances')
|
||||
, should = require('chai').should()
|
||||
, equal = require('../lib/compile/equal');
|
||||
, equal = require('../lib/compile/equal')
|
||||
, doT = require('dot');
|
||||
|
||||
|
||||
describe('Custom keywords', function () {
|
||||
|
@ -18,6 +19,7 @@ describe('Custom keywords', function () {
|
|||
ajv = instances[0];
|
||||
});
|
||||
|
||||
|
||||
describe('custom rules', function() {
|
||||
var compileCount = 0;
|
||||
|
||||
|
@ -152,6 +154,7 @@ describe('Custom keywords', function () {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
describe('macro rules', function() {
|
||||
it('should add and validate rule with "macro" keyword', function() {
|
||||
testAddEvenKeyword({ macro: macroEven });
|
||||
|
@ -427,6 +430,19 @@ describe('Custom keywords', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should throw exception is macro expansion is an invalid schema', function() {
|
||||
ajv.addKeyword('invalid', { macro: macroInvalid });
|
||||
var schema = { "invalid": true };
|
||||
|
||||
should.throw(function() {
|
||||
var validate = ajv.compile(schema);
|
||||
});
|
||||
|
||||
function macroInvalid(schema) {
|
||||
return { "type": "invalid" };
|
||||
}
|
||||
});
|
||||
|
||||
function macroEven(schema) {
|
||||
if (schema === true) return { "multipleOf": 2 };
|
||||
if (schema === false) return { "not": { "multipleOf": 2 } };
|
||||
|
@ -450,6 +466,52 @@ describe('Custom keywords', function () {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
describe('inline rules', function() {
|
||||
it('should add and validate rule with "inline" code keyword', function() {
|
||||
testAddEvenKeyword({ type: 'number', inline: inlineEven });
|
||||
});
|
||||
|
||||
it('should pass parent schema to "inline" keyword', function() {
|
||||
testRangeKeyword({ type: 'number', inline: inlineRange, statements: true });
|
||||
});
|
||||
|
||||
it('should define "inline" keyword as template', function() {
|
||||
var inlineRangeTemplate = doT.compile("\
|
||||
{{ \
|
||||
var $data = 'data' + (it.dataLevel || '') \
|
||||
, $min = it.schema.range[0] \
|
||||
, $max = it.schema.range[1] \
|
||||
, $gt = it.schema.exclusiveRange ? '>' : '>=' \
|
||||
, $lt = it.schema.exclusiveRange ? '<' : '<='; \
|
||||
}} \
|
||||
var valid{{=it.lvl}} = {{=$data}} {{=$gt}} {{=$min}} && {{=$data}} {{=$lt}} {{=$max}}; \
|
||||
");
|
||||
|
||||
testRangeKeyword({
|
||||
type: 'number',
|
||||
inline: inlineRangeTemplate,
|
||||
parentSchema: true,
|
||||
statements: true
|
||||
});
|
||||
});
|
||||
|
||||
function inlineEven(it, schema) {
|
||||
var op = schema ? '===' : '!==';
|
||||
return 'data' + (it.dataLevel || '') + ' % 2 ' + op + ' 0';
|
||||
}
|
||||
|
||||
function inlineRange(it, schema, parentSchema) {
|
||||
var min = schema[0]
|
||||
, max = schema[1]
|
||||
, data = 'data' + (it.dataLevel || '')
|
||||
, gt = parentSchema.exclusiveRange ? ' > ' : ' >= '
|
||||
, lt = parentSchema.exclusiveRange ? ' < ' : ' <= ';
|
||||
return 'var valid' + it.lvl + ' = ' + data + gt + min + ' && ' + data + lt + max + ';';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function testAddEvenKeyword(definition) {
|
||||
instances.forEach(function (ajv) {
|
||||
ajv.addKeyword('even', definition);
|
||||
|
@ -518,6 +580,7 @@ describe('Custom keywords', function () {
|
|||
validate.errors .should.have.length(numErrors || 1);
|
||||
}
|
||||
|
||||
|
||||
describe('addKeyword method', function() {
|
||||
var TEST_TYPES = [ undefined, 'number', 'string', 'boolean', ['number', 'string']];
|
||||
|
||||
|
|
Loading…
Reference in New Issue