diff --git a/README.md b/README.md index a55aa53..296ddc6 100644 --- a/README.md +++ b/README.md @@ -991,6 +991,7 @@ Defaults: $data: false, allErrors: false, verbose: false, + $comment: false, // NEW in Ajv version 6.0 jsonPointers: false, uniqueItems: true, unicode: true, @@ -1032,6 +1033,10 @@ Defaults: - _$data_: support [$data references](#data-reference). Draft 6 meta-schema that is added by default will be extended to allow them. If you want to use another meta-schema you need to use $dataMetaSchema method to add support for $data reference. See [API](#api). - _allErrors_: check all rules collecting all errors. Default is to return after the first error. - _verbose_: include the reference to the part of the schema (`schema` and `parentSchema`) and validated data in errors (false by default). +- _$comment_ (NEW in Ajv version 6.0): log or pass the value of `$comment` keyword to a function. Option values: + - `false` (default): ignore $comment keyword. + - `true`: log the keyword value to console. + - function: pass the keyword value, its schema path and root schema to the specified function - _jsonPointers_: set `dataPath` property of errors using [JSON Pointers](https://tools.ietf.org/html/rfc6901) instead of JavaScript property access notation. - _uniqueItems_: validate `uniqueItems` keyword (true by default). - _unicode_: calculate correct length of strings with unicode pairs (true by default). Pass `false` to use `.length` of strings that is faster, but gives "incorrect" lengths of strings with unicode pairs - each unicode pair is counted as two characters. diff --git a/lib/compile/rules.js b/lib/compile/rules.js index 0f53db7..5050882 100644 --- a/lib/compile/rules.js +++ b/lib/compile/rules.js @@ -18,10 +18,11 @@ module.exports = function rules() { { rules: [ '$ref', 'const', 'enum', 'not', 'anyOf', 'oneOf', 'allOf', 'if' ] } ]; - var ALL = [ 'type' ]; + var ALL = [ 'type', '$comment' ]; var KEYWORDS = [ - '$schema', '$id', 'id', '$comment', - 'title', 'description', 'default', 'definitions', + '$schema', '$id', 'id', + 'title', 'description', + 'default', 'definitions', 'additionalItems', 'then', 'else' ]; var TYPES = [ 'number', 'integer', 'string', 'array', 'object', 'boolean', 'null' ]; @@ -49,6 +50,11 @@ module.exports = function rules() { return rule; }); + RULES.all.$comment = { + keyword: '$comment', + code: ruleModules.$comment + }; + if (group.type) RULES.types[group.type] = group; }); diff --git a/lib/dot/comment.jst b/lib/dot/comment.jst new file mode 100644 index 0000000..f959150 --- /dev/null +++ b/lib/dot/comment.jst @@ -0,0 +1,9 @@ +{{# def.definitions }} +{{# def.setupKeyword }} + +{{ var $comment = it.util.toQuotedString($schema); }} +{{? it.opts.$comment === true }} + console.log({{=$comment}}); +{{?? typeof it.opts.$comment == 'function' }} + self._opts.$comment({{=$comment}}, {{=it.util.toQuotedString($errSchemaPath)}}, validate.root.schema); +{{?}} diff --git a/lib/dot/validate.jst b/lib/dot/validate.jst index 3a508c6..43a5edc 100644 --- a/lib/dot/validate.jst +++ b/lib/dot/validate.jst @@ -127,6 +127,10 @@ {{?}} {{?}} +{{? it.schema.$comment && it.opts.$comment }} + {{= it.RULES.all.$comment.code(it, '$comment') }} +{{?}} + {{? $typeSchema }} {{? it.opts.coerceTypes }} {{ var $coerceToTypes = it.util.coerceToTypes(it.opts.coerceTypes, $typeSchema); }} diff --git a/lib/dotjs/index.js b/lib/dotjs/index.js index b5531ed..2fb1b00 100644 --- a/lib/dotjs/index.js +++ b/lib/dotjs/index.js @@ -5,6 +5,7 @@ module.exports = { '$ref': require('./ref'), allOf: require('./allOf'), anyOf: require('./anyOf'), + '$comment': require('./comment'), const: require('./const'), contains: require('./contains'), dependencies: require('./dependencies'), diff --git a/spec/.eslintrc.yml b/spec/.eslintrc.yml index d2d4eda..f9c66d5 100644 --- a/spec/.eslintrc.yml +++ b/spec/.eslintrc.yml @@ -8,3 +8,4 @@ globals: it: false before: false beforeEach: false + afterEach: false diff --git a/spec/options.spec.js b/spec/options.spec.js index ac75180..220516e 100644 --- a/spec/options.spec.js +++ b/spec/options.spec.js @@ -1219,4 +1219,92 @@ describe('Ajv Options', function () { }); }); }); + + + describe('$comment', function() { + describe('= true', function() { + var logCalls, consoleLog; + + beforeEach(function () { + consoleLog = console.log; + console.log = log; + }); + + afterEach(function () { + console.log = consoleLog; + }); + + function log() { + logCalls.push(Array.prototype.slice.call(arguments)); + } + + it('should log the text from $comment keyword', function() { + var schema = { + properties: { + foo: {$comment: 'property foo'}, + bar: {$comment: 'property bar', type: 'integer'} + } + }; + + var ajv = new Ajv({$comment: true}); + var fullAjv = new Ajv({allErrors: true, $comment: true}); + + [ajv, fullAjv].forEach(function (_ajv) { + var validate = _ajv.compile(schema); + + test({}, true, []); + test({foo: 1}, true, [['property foo']]); + test({foo: 1, bar: 2}, true, [['property foo'], ['property bar']]); + test({foo: 1, bar: 'baz'}, false, [['property foo'], ['property bar']]); + + function test(data, valid, expectedLogCalls) { + logCalls = []; + validate(data) .should.equal(valid); + logCalls .should.eql(expectedLogCalls); + } + }); + + console.log = consoleLog; + }); + }); + + describe('function hook', function() { + var hookCalls; + + function hook() { + hookCalls.push(Array.prototype.slice.call(arguments)); + } + + it('should pass the text from $comment keyword to the hook', function() { + var schema = { + properties: { + foo: {$comment: 'property foo'}, + bar: {$comment: 'property bar', type: 'integer'} + } + }; + + var ajv = new Ajv({$comment: hook}); + var fullAjv = new Ajv({allErrors: true, $comment: hook}); + + [ajv, fullAjv].forEach(function (_ajv) { + var validate = _ajv.compile(schema); + + test({}, true, []); + test({foo: 1}, true, [['property foo', '#/properties/foo/$comment', schema]]); + test({foo: 1, bar: 2}, true, + [['property foo', '#/properties/foo/$comment', schema], + ['property bar', '#/properties/bar/$comment', schema]]); + test({foo: 1, bar: 'baz'}, false, + [['property foo', '#/properties/foo/$comment', schema], + ['property bar', '#/properties/bar/$comment', schema]]); + + function test(data, valid, expectedHookCalls) { + hookCalls = []; + validate(data) .should.equal(valid); + hookCalls .should.eql(expectedHookCalls); + } + }); + }); + }); + }); }); diff --git a/spec/tests/rules/comment.json b/spec/tests/rules/comment.json new file mode 100644 index 0000000..5be030e --- /dev/null +++ b/spec/tests/rules/comment.json @@ -0,0 +1,40 @@ +[ + { + "description": "$comment keyword", + "schema": { + "$comment": "test" + }, + "tests": [ + { + "description": "any value is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "$comment keyword in subschemas", + "schema": { + "type": "object", + "properties": { + "foo": { + "$comment": "test" + } + } + }, + "tests": [ + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "any value of property foo is valid object is valid", + "data": { + "foo": 1 + }, + "valid": true + } + ] + } +]