From 0b725bbc310a2c08b7c6337193fb24d2413ef448 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sat, 9 Jan 2016 01:07:33 +0000 Subject: [PATCH] "default" keyword in "properties" subschemas, #42 --- lib/ajv.js | 9 ++++-- lib/compile/index.js | 47 +++++++++++++++++++------------ lib/compile/util.js | 8 ++++-- lib/dot/properties.jst | 63 ++++++++++++++++++++++++++---------------- lib/dot/required.jst | 33 ++++++++++++++++++---- spec/ajv_options.js | 2 +- spec/options.spec.js | 43 ++++++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 52 deletions(-) diff --git a/lib/ajv.js b/lib/ajv.js index fd5946ae8..e72be6a9f 100644 --- a/lib/ajv.js +++ b/lib/ajv.js @@ -233,13 +233,18 @@ function Ajv(opts) { } schemaObj.compiling = true; - var currentRA = self.opts.removeAdditional; - if (currentRA && schemaObj.meta) self.opts.removeAdditional = false; + var currentRA = self.opts.removeAdditional + , currentUD = self.opts.useDefaults; + if (schemaObj.meta) { + if (currentRA) self.opts.removeAdditional = false; + if (currentUD) self.opts.useDefaults = false; + } var v; try { v = compileSchema.call(self, schemaObj.schema, root, schemaObj.localRefs); } finally { schemaObj.compiling = false; if (currentRA) self.opts.removeAdditional = currentRA; + if (currentUD) self.opts.useDefaults = currentUD; } schemaObj.validate = v; diff --git a/lib/compile/index.js b/lib/compile/index.js index eb4121560..00185f425 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -19,6 +19,8 @@ function compile(schema, root, localRefs, baseId) { , refs = {} , patterns = [] , patternsHash = {} + , defaults = [] + , defaultsHash = {} , customRules = [] , customRulesHash = {}; @@ -50,14 +52,16 @@ function compile(schema, root, localRefs, baseId) { resolve: resolve, resolveRef: resolveRef, usePattern: usePattern, + useDefault: useDefault, useCustomRule: useCustomRule, opts: self.opts, formats: formats, self: self }); - validateCode = refsCode(refVal) + patternsCode(patterns) - + customRulesCode(customRules) + validateCode; + validateCode = vars(refVal, refValCode) + vars(patterns, patternCode) + + vars(defaults, defaultCode) + vars(customRules, customRuleCode) + + validateCode; if (self.opts.beautify) { var opts = self.opts.beautify === true ? { indent_size: 2 } : self.opts.beautify; @@ -146,6 +150,25 @@ function compile(schema, root, localRefs, baseId) { return 'pattern' + index; } + function useDefault(value) { + switch (typeof value) { + case 'boolean': + case 'number': + return '' + value; + case 'string': + return util.toQuotedString(value); + case 'object': + if (value === null) return 'null'; + var valueStr = stableStringify(value); + var index = defaultsHash[valueStr]; + if (index === undefined) { + index = defaultsHash[valueStr] = defaults.length; + defaults[index] = value; + } + return 'default' + index; + } + } + function useCustomRule(rule, schema, parentSchema, it) { var compile = rule.definition.compile , inline = rule.definition.inline @@ -173,37 +196,27 @@ function compile(schema, root, localRefs, baseId) { } -function patternsCode(patterns) { - return _arrCode(patterns, patternCode); -} - - function patternCode(i, patterns) { return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');'; } -function refsCode(refVal) { - return _arrCode(refVal, refCode); +function defaultCode(i) { + return 'var default' + i + ' = defaults[' + i + '];'; } -function refCode(i, refVal) { +function refValCode(i, refVal) { return refVal[i] ? 'var refVal' + i + ' = refVal[' + i + '];' : ''; } -function customRulesCode(customRules) { - return _arrCode(customRules, customRuleCode); -} - - -function customRuleCode(i, rule) { +function customRuleCode(i) { return 'var customRule' + i + ' = customRules[' + i + '];'; } -function _arrCode(arr, statement) { +function vars(arr, statement) { if (!arr.length) return ''; var code = ''; for (var i=0; i= it.opts.loopRequired; + }} {{? $breakOnError }} var missing{{=$lvl}}; - {{? $isData || $required.length >= it.opts.loopRequired }} + {{? $loopRequired }} {{# def.setupLoop }} - var {{=$valid}}; + var {{=$valid}} = true; {{?$isData}}{{# def.check$dataIsArray }}{{?}} for (var {{=$i}} = 0; {{=$i}} < schema{{=$lvl}}.length; {{=$i}}++) { - {{=$valid}} = {{=$data}}[schema{{=$lvl}}[{{=$i}}]] !== undefined; + {{# def.checkDefaults }} + {{=$valid}} = {{=$data}}[property{{=$lvl}}] !== undefined; if (!{{=$valid}}) break; } @@ -64,7 +86,7 @@ } else { {{?}} {{??}} - {{? $isData || $required.length >= it.opts.loopRequired }} + {{? $loopRequired }} {{# def.setupLoop }} {{? $isData }} if (schema{{=$lvl}} && !Array.isArray(schema{{=$lvl}})) { @@ -73,6 +95,7 @@ {{?}} for (var {{=$i}} = 0; {{=$i}} < schema{{=$lvl}}.length; {{=$i}}++) { + {{# def.checkDefaults }} if ({{=$data}}[schema{{=$lvl}}[{{=$i}}]] === undefined) { {{# def.addError:'required' }} } diff --git a/spec/ajv_options.js b/spec/ajv_options.js index 5c1227e40..b8af604a1 100644 --- a/spec/ajv_options.js +++ b/spec/ajv_options.js @@ -9,7 +9,7 @@ var options = fullTest verbose: true, format: 'full', inlineRefs: false, - jsonPointers: true, + jsonPointers: true } : { allErrors: true }; diff --git a/spec/options.spec.js b/spec/options.spec.js index d5fcfb526..a8a3cf107 100644 --- a/spec/options.spec.js +++ b/spec/options.spec.js @@ -2,6 +2,7 @@ var Ajv = require('./ajv') + , getAjvInstances = require('./ajv_instances') , should = require('./chai').should() @@ -331,4 +332,46 @@ describe('Ajv Options', function () { } }); }); + + + describe('useDefaults', function() { + it('should replace undefined property with default value', function() { + var instances = getAjvInstances({ + allErrors: true, + loopRequired: 3 + }, { useDefaults: true }); + + instances.forEach(test); + + + function test(ajv) { + var schema = { + properties: { + foo: { type: 'string', default: 'abc' }, + bar: { type: 'number', default: 1 }, + baz: { type: 'boolean', default: false }, + nil: { type: 'null', default: null }, + obj: { type: 'object', default: {} }, + arr: { type: 'array', default: [] } + }, + required: ['foo', 'bar', 'baz', 'nil', 'obj', 'arr'] + }; + + var validate = ajv.compile(schema); + + var data = {}; + try { + validate(data) .should.equal(true); + } catch(e) { + console.log('failed with:', ajv.opts); + throw e; + } + data. should.eql({ foo: 'abc', bar: 1, baz: false, nil: null, obj: {}, arr:[] }); + + var data = { foo: 'foo', bar: 2, obj: { test: true } }; + validate(data) .should.equal(true); + data. should.eql({ foo: 'foo', bar: 2, baz: false, nil: null, obj: { test: true }, arr:[] }); + } + }); + }); });