support for "inline" custom keywords, #69

master
Evgeny Poberezkin 2015-11-16 22:22:33 +00:00
parent 87292f80ea
commit ae5b4c0f45
4 changed files with 124 additions and 27 deletions

View File

@ -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]
};
}

View File

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

View File

@ -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 { {{?}}

View File

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