diff --git a/lib/compile/index.js b/lib/compile/index.js index 37952dc..d097ed9 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -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] }; } diff --git a/lib/compile/macro.js b/lib/compile/macro.js index 100eebb..d94abe9 100644 --- a/lib/compile/macro.js +++ b/lib/compile/macro.js @@ -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 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 { {{?}} diff --git a/spec/custom.spec.js b/spec/custom.spec.js index dfbfe0e..30b3906 100644 --- a/spec/custom.spec.js +++ b/spec/custom.spec.js @@ -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']];