diff --git a/lib/compile/index.js b/lib/compile/index.js index 217cf5713..122dc7bcf 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -341,9 +341,7 @@ function vars(arr, statement) { */ var co = require('co'); - var ucs2length = util.ucs2length; - var equal = require('./equal'); // this error is thrown by async schemas to return validation errors via exception diff --git a/lib/dot/custom.jst b/lib/dot/custom.jst index e92985a49..cf3ff1a94 100644 --- a/lib/dot/custom.jst +++ b/lib/dot/custom.jst @@ -5,9 +5,10 @@ {{ var $rule = this + , $definition = 'definition' + $lvl , $rDef = $rule.definition , $validate = $rDef.validate - , $compile = $rDef.compile + , $compile , $inline , $macro , $ruleValidate @@ -15,14 +16,18 @@ }} {{? $isData && $rDef.$data }} - {{ $validateCode = 'keywordValidate' + $lvl; }} - var {{=$validateCode}} = RULES.custom['{{=$keyword}}'].definition - {{? $validate }}.validate{{??}}.compile({{=$schemaValue}}, validate.schema{{=it.schemaPath}}){{?}}; + {{ + $validateCode = 'keywordValidate' + $lvl; + var $validateSchema = $rDef.validateSchema; + }} + var {{=$definition}} = RULES.custom['{{=$keyword}}'].definition; + var {{=$validateCode}} = {{=$definition}}.validate; {{??}} {{ $ruleValidate = it.useCustomRule($rule, $schema, it.schema, it); $schemaValue = 'validate.schema' + $schemaPath; $validateCode = $ruleValidate.code; + $compile = $rDef.compile; $inline = $rDef.inline; $macro = $rDef.macro; }} @@ -108,7 +113,7 @@ var valid{{=$lvl}}; {{ var $code = it.validate($it).replace(/validate\.schema/g, $validateCode); }} {{# def.resetCompositeRule }} {{= $code }} -{{?? $compile || $validate }} +{{?? !$inline }} {{# def.beginDefOut}} {{# def.callRuleValidate }} {{# def.storeDefOut:def_callRuleValidate }} @@ -131,7 +136,10 @@ var valid{{=$lvl}}; {{?}} -if (!{{# def.ruleValidationResult }}) { +if ({{? $validateSchema }} + !{{=$definition}}.validateSchema({{=$schemaValue}}) || + {{?}} + !{{# def.ruleValidationResult }}) { {{ $errorKeyword = $rule.keyword; }} {{# def.beginDefOut}} {{# def.error:'custom' }} @@ -153,7 +161,7 @@ if (!{{# def.ruleValidationResult }}) { } {{?}} {{?}} - {{?? $macro}} + {{?? $macro }} {{# def.extraError:'custom' }} {{??}} {{? $rDef.errors === false}} diff --git a/lib/keyword.js b/lib/keyword.js index d55a50b00..3cda7655b 100644 --- a/lib/keyword.js +++ b/lib/keyword.js @@ -30,14 +30,8 @@ module.exports = function addKeyword(keyword, definition) { } var $data = definition.$data === true && this._opts.v5; - if ($data) { - if (!definition.validate) { - if (definition.compile) - console.warn('$data support: it is recommended to define "validate" function'); - else - throw new Error('$data support: neither "validate" nor "compile" functions are defined'); - } - } + if ($data && !definition.validate) + throw new Error('$data support: neither "validate" nor "compile" functions are defined'); var metaSchema = definition.metaSchema; if (metaSchema) { diff --git a/spec/custom.spec.js b/spec/custom.spec.js index 7dcb77b0d..40e72bf46 100644 --- a/spec/custom.spec.js +++ b/spec/custom.spec.js @@ -530,15 +530,23 @@ describe('Custom keywords', function () { } }); - it('should validate "compiled" rule', function() { + it('should validate rule with "compile" and "validate" funcs', function() { + var compileCalled; testEvenKeyword$data({ type: 'number', $data: true, - compile: compileEven + compile: compileEven, + validate: validateEven }); - shouldBeInvalidSchema({ "even": "false" }); + compileCalled .should.equal(true); + + function validateEven(schema, data) { + if (typeof schema != 'boolean') return false; + return data % 2 ? !schema : schema; + } function compileEven(schema) { + compileCalled = true; if (typeof schema != 'boolean') throw new Error('The value of "even" keyword must be boolean'); return schema ? isEven : isOdd; } @@ -547,21 +555,123 @@ describe('Custom keywords', function () { function isOdd(data) { return data % 2 !== 0; } }); - it('should validate "interpreted" rule with meta-schema', function() { + it('should validate with "compile" and "validate" funcs with meta-schema', function() { + var compileCalled; testEvenKeyword$data({ type: 'number', $data: true, + compile: compileEven, validate: validateEven, metaSchema: { "type": "boolean" } }); + compileCalled .should.equal(true); + shouldBeInvalidSchema({ "even": "false" }); + + function validateEven(schema, data) { + return data % 2 ? !schema : schema; + } + + function compileEven(schema) { + compileCalled = true; + return schema ? isEven : isOdd; + } + + function isEven(data) { return data % 2 === 0; } + function isOdd(data) { return data % 2 !== 0; } + }); + + it('should validate rule with "macro" and "validate" funcs', function() { + var macroCalled; + testEvenKeyword$data({ + type: 'number', + $data: true, + macro: macroEven, + validate: validateEven + }, 2); + macroCalled .should.equal(true); + + function validateEven(schema, data) { + if (typeof schema != 'boolean') return false; + return data % 2 ? !schema : schema; + } + + function macroEven(schema) { + macroCalled = true; + if (schema === true) return { "multipleOf": 2 }; + if (schema === false) return { "not": { "multipleOf": 2 } }; + throw new Error('Schema for "even" keyword should be boolean'); + } + }); + + it('should validate with "macro" and "validate" funcs with meta-schema', function() { + var macroCalled; + testEvenKeyword$data({ + type: 'number', + $data: true, + macro: macroEven, + validate: validateEven, + metaSchema: { "type": "boolean" } + }, 2); + macroCalled .should.equal(true); shouldBeInvalidSchema({ "even": "false" }); function validateEven(schema, data) { return data % 2 ? !schema : schema; } + + function macroEven(schema) { + macroCalled = true; + if (schema === true) return { "multipleOf": 2 }; + if (schema === false) return { "not": { "multipleOf": 2 } }; + } }); - it('should fail if keyword definition has "$data" but no "validate" or "compile"', function() { + it('should validate rule with "inline" and "validate" funcs', function() { + var inlineCalled; + testEvenKeyword$data({ + type: 'number', + $data: true, + inline: inlineEven, + validate: validateEven + }); + inlineCalled .should.equal(true); + + function validateEven(schema, data) { + if (typeof schema != 'boolean') return false; + return data % 2 ? !schema : schema; + } + + function inlineEven(it, keyword, schema) { + inlineCalled = true; + var op = schema ? '===' : '!=='; + return 'data' + (it.dataLevel || '') + ' % 2 ' + op + ' 0'; + } + }); + + it('should validate with "inline" and "validate" funcs with meta-schema', function() { + var inlineCalled; + testEvenKeyword$data({ + type: 'number', + $data: true, + inline: inlineEven, + validate: validateEven, + metaSchema: { "type": "boolean" } + }); + inlineCalled .should.equal(true); + shouldBeInvalidSchema({ "even": "false" }); + + function validateEven(schema, data) { + return data % 2 ? !schema : schema; + } + + function inlineEven(it, keyword, schema) { + inlineCalled = true; + var op = schema ? '===' : '!=='; + return 'data' + (it.dataLevel || '') + ' % 2 ' + op + ' 0'; + } + }); + + it('should fail if keyword definition has "$data" but no "validate"', function() { should.throw(function() { ajv.addKeyword('even', { type: 'number', @@ -588,25 +698,34 @@ describe('Custom keywords', function () { function testEvenKeyword$data(definition, numErrors) { instances.forEach(function (ajv) { - var schema = { + ajv.addKeyword('even', definition); + + var schema = { "even": true }; + var validate = ajv.compile(schema); + + shouldBeValid(validate, 2); + shouldBeValid(validate, 'abc'); + shouldBeInvalid(validate, 2.5, numErrors); + shouldBeInvalid(validate, 3, numErrors); + + schema = { "properties": { "data": { "even": { "$data": "1/evenValue" } }, "evenValue": {} } }; - ajv.addKeyword('even', definition); - var validate = ajv.compile(schema); + validate = ajv.compile(schema); shouldBeValid(validate, { data: 2, evenValue: true }); shouldBeInvalid(validate, { data: 2, evenValue: false }); shouldBeValid(validate, { data: 'abc', evenValue: true }); shouldBeValid(validate, { data: 'abc', evenValue: false }); - shouldBeInvalid(validate, { data: 2.5, evenValue: true }, numErrors); + shouldBeInvalid(validate, { data: 2.5, evenValue: true }); shouldBeValid(validate, { data: 2.5, evenValue: false }); - shouldBeInvalid(validate, { data: 3, evenValue: true }, numErrors); + shouldBeInvalid(validate, { data: 3, evenValue: true }); shouldBeValid(validate, { data: 3, evenValue: false }); - // shouldBeInvalid(validate, { data: 2, evenValue: "true" }); + shouldBeInvalid(validate, { data: 2, evenValue: "true" }); }); }