From 49cf145e4c922ee0a64357edb9a1b3cea3e4cd3c Mon Sep 17 00:00:00 2001 From: "Donald.Armstrong2" Date: Thu, 1 Mar 2018 21:39:37 -0600 Subject: [PATCH 1/2] Update to latest fast-json-patch * fix the warning 'jsonpatch.apply is deprecated, please use `applyPatch` for applying patch sequences, or `applyOperation` to apply individual operations.' * Update to 2.0.6 version of fast-json-patch and address the deprecation warning * Update tests to include patch array of operations * Update docs with an example reason to use patch with a link to the json-patch spec --- README.md | 2 +- keywords/add_keyword.js | 4 ++-- keywords/merge.js | 2 +- keywords/patch.js | 4 ++-- package.json | 2 +- spec/patch.spec.js | 18 ++++++++++++------ 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4cf3019..cd23981 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ The same schema extension using `$patch` keyword: `$patch` is implemented as a [custom macro keyword](https://github.com/epoberezkin/ajv/blob/master/CUSTOM.md#define-keyword-with-macro-function) using [fast-json-patch](https://github.com/Starcounter-Jack/JSON-Patch) package. -In the majority of cases `$merge` format is easier to understand and to maintain. `$patch` can be used for extensions and changes that cannot be expressed using `$merge`. +In the majority of cases `$merge` format is easier to understand and to maintain. `$patch` can be used for extensions and changes that cannot be expressed using `$merge`, e.g. [Adding an array value](https://tools.ietf.org/html/rfc6902#page-18). `with` property in keywords can also be a reference to a part of some schema, in which case the resolved value will be used rather than the actual object with property `$ref`. diff --git a/keywords/add_keyword.js b/keywords/add_keyword.js index 4bd542b..f49213b 100644 --- a/keywords/add_keyword.js +++ b/keywords/add_keyword.js @@ -2,14 +2,14 @@ var url = require('url'); -module.exports = function (ajv, keyword, jsonPatch, patchSchema) { +module.exports = function (ajv, keyword, jsonPatch, func, patchSchema) { ajv.addKeyword(keyword, { macro: function (schema, parentSchema, it) { var source = schema.source; var patch = schema.with; if (source.$ref) source = JSON.parse(JSON.stringify(getSchema(source.$ref))); if (patch.$ref) patch = getSchema(patch.$ref); - jsonPatch.apply(source, patch, true); + jsonPatch[func].call(null, source, patch, true); return source; function getSchema($ref) { diff --git a/keywords/merge.js b/keywords/merge.js index 05a8cc4..c62f611 100644 --- a/keywords/merge.js +++ b/keywords/merge.js @@ -4,5 +4,5 @@ var addKeyword = require('./add_keyword'); var jsonMergePatch = require('json-merge-patch'); module.exports = function(ajv) { - addKeyword(ajv, '$merge', jsonMergePatch, { "type": "object" }); + addKeyword(ajv, '$merge', jsonMergePatch, 'apply', { "type": "object" }); }; diff --git a/keywords/patch.js b/keywords/patch.js index 87e1a39..481d525 100644 --- a/keywords/patch.js +++ b/keywords/patch.js @@ -1,10 +1,10 @@ 'use strict'; var addKeyword = require('./add_keyword'); -var jsonPatch = require('fast-json-patch/src/json-patch'); +var jsonPatch = require('fast-json-patch'); module.exports = function(ajv) { - addKeyword(ajv, '$patch', jsonPatch, { + addKeyword(ajv, '$patch', jsonPatch, 'applyPatch', { "type": "array", "items": { "type": "object", diff --git a/package.json b/package.json index 3bf2f1b..d09d577 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "homepage": "https://github.com/epoberezkin/ajv-merge-patch#readme", "dependencies": { - "fast-json-patch": "^1.0.0", + "fast-json-patch": "^2.0.6", "json-merge-patch": "^0.2.3" }, "devDependencies": { diff --git a/spec/patch.spec.js b/spec/patch.spec.js index 11ea424..b2bf508 100644 --- a/spec/patch.spec.js +++ b/spec/patch.spec.js @@ -23,10 +23,12 @@ describe('keyword $patch', function() { "source": { "type": "object", "properties": { "p": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ "p" ] }, "with": [ - { "op": "add", "path": "/properties/q", "value": { "type": "number" } } + { "op": "add", "path": "/properties/q", "value": { "type": "number" } }, + { "op": "add", "path": "/required/-", "value": "q" } ] } }; @@ -44,7 +46,8 @@ describe('keyword $patch', function() { "id": "obj.json#", "type": "object", "properties": { "p": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ "p" ] }; ajv.addSchema(sourceSchema); @@ -53,7 +56,8 @@ describe('keyword $patch', function() { "$patch": { "source": { "$ref": "obj.json#" }, "with": [ - { "op": "add", "path": "/properties/q", "value": { "type": "number" } } + { "op": "add", "path": "/properties/q", "value": { "type": "number" } }, + { "op": "add", "path": "/required/-", "value": "q" } ] } }; @@ -73,13 +77,15 @@ describe('keyword $patch', function() { "source": { "type": "object", "properties": { "p": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ "p" ] } }, "$patch": { "source": { "$ref": "#/definitions/source" }, "with": [ - { "op": "add", "path": "/properties/q", "value": { "type": "number" } } + { "op": "add", "path": "/properties/q", "value": { "type": "number" } }, + { "op": "add", "path": "/required/-", "value": "q" } ] } }; From 5708ba9f0ec032a5a89e6ad447290bbb5fcc6504 Mon Sep 17 00:00:00 2001 From: "Donald.Armstrong2" Date: Sun, 11 Mar 2018 20:17:06 -0500 Subject: [PATCH 2/2] Changes infoemed by review by epoberezkin * remove extra parameter from add_keyword; instead change the invoke targets to pass function * make the tests all require 'q' property; confirm it in test_validate. --- keywords/add_keyword.js | 4 ++-- keywords/merge.js | 2 +- keywords/patch.js | 2 +- spec/async.spec.js | 9 ++++++--- spec/merge.spec.js | 18 ++++++++++++------ spec/test_validate.js | 14 ++++++++++++++ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/keywords/add_keyword.js b/keywords/add_keyword.js index f49213b..c3bad06 100644 --- a/keywords/add_keyword.js +++ b/keywords/add_keyword.js @@ -2,14 +2,14 @@ var url = require('url'); -module.exports = function (ajv, keyword, jsonPatch, func, patchSchema) { +module.exports = function (ajv, keyword, jsonPatch, patchSchema) { ajv.addKeyword(keyword, { macro: function (schema, parentSchema, it) { var source = schema.source; var patch = schema.with; if (source.$ref) source = JSON.parse(JSON.stringify(getSchema(source.$ref))); if (patch.$ref) patch = getSchema(patch.$ref); - jsonPatch[func].call(null, source, patch, true); + jsonPatch.call(null, source, patch, true); return source; function getSchema($ref) { diff --git a/keywords/merge.js b/keywords/merge.js index c62f611..4106577 100644 --- a/keywords/merge.js +++ b/keywords/merge.js @@ -4,5 +4,5 @@ var addKeyword = require('./add_keyword'); var jsonMergePatch = require('json-merge-patch'); module.exports = function(ajv) { - addKeyword(ajv, '$merge', jsonMergePatch, 'apply', { "type": "object" }); + addKeyword(ajv, '$merge', jsonMergePatch.apply, { "type": "object" }); }; diff --git a/keywords/patch.js b/keywords/patch.js index 481d525..b992e1e 100644 --- a/keywords/patch.js +++ b/keywords/patch.js @@ -4,7 +4,7 @@ var addKeyword = require('./add_keyword'); var jsonPatch = require('fast-json-patch'); module.exports = function(ajv) { - addKeyword(ajv, '$patch', jsonPatch, 'applyPatch', { + addKeyword(ajv, '$patch', jsonPatch.applyPatch, { "type": "array", "items": { "type": "object", diff --git a/spec/async.spec.js b/spec/async.spec.js index 2465a85..5cff31d 100644 --- a/spec/async.spec.js +++ b/spec/async.spec.js @@ -20,7 +20,8 @@ describe('async schema loading', function() { "$merge": { "source": { "$ref": "obj.json#" }, "with": { - "properties": { "q": { "type": "number" } } + "properties": { "q": { "type": "number" } }, + "required": [ "q" ] } } }; @@ -35,7 +36,8 @@ describe('async schema loading', function() { "$patch": { "source": { "$ref": "obj.json#" }, "with": [ - { "op": "add", "path": "/properties/q", "value": { "type": "number" } } + { "op": "add", "path": "/properties/q", "value": { "type": "number" } }, + { "op": "add", "path": "/required/-", "value": "q" } ] } }; @@ -59,7 +61,8 @@ describe('async schema loading', function() { "id": "obj.json#", "type": "object", "properties": { "p": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ "p" ] }; return Promise.resolve(schema); } diff --git a/spec/merge.spec.js b/spec/merge.spec.js index c9a573b..738663d 100644 --- a/spec/merge.spec.js +++ b/spec/merge.spec.js @@ -26,7 +26,8 @@ describe('keyword $merge', function() { "additionalProperties": false }, "with": { - "properties": { "q": { "type": "number" } } + "properties": { "q": { "type": "number" } }, + "required": [ "q" ] } } }; @@ -53,7 +54,8 @@ describe('keyword $merge', function() { "$merge": { "source": { "$ref": "obj.json#" }, "with": { - "properties": { "q": { "type": "number" } } + "properties": { "q": { "type": "number" } }, + "required": [ "q" ] } } }; @@ -79,7 +81,8 @@ describe('keyword $merge', function() { "$merge": { "source": { "$ref": "#/definitions/source" }, "with": { - "properties": { "q": { "type": "number" } } + "properties": { "q": { "type": "number" } }, + "required": [ "q" ] } } }; @@ -96,13 +99,15 @@ describe('keyword $merge', function() { var sourceSchema = { "type": "object", "properties": { "p": { "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ "p" ] }; var patchSchema = { "type": "object", "properties": { "q": { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required": [ "q" ] }; ajv.addSchema(sourceSchema, "obj1.json#"); @@ -138,7 +143,8 @@ describe('keyword $merge', function() { "patch":{ "type": "object", "properties": { "q": { "type": "number" } }, - "additionalProperties": false + "additionalProperties": false, + "required" : [ "q" ] } }, "$merge": { diff --git a/spec/test_validate.js b/spec/test_validate.js index 3a688f5..a15445b 100644 --- a/spec/test_validate.js +++ b/spec/test_validate.js @@ -4,6 +4,8 @@ var assert = require('assert'); module.exports = function (validate, keyword) { assert.strictEqual(validate({ p: 'abc', q: 1 }), true); + + // property q should be a number assert.strictEqual(validate({ p: 'foo', q: 'bar' }), false); var errs = validate.errors; assert.equal(errs.length, 2); @@ -13,4 +15,16 @@ module.exports = function (validate, keyword) { assert.equal(errs[1].keyword, keyword); assert.equal(errs[1].dataPath, ''); assert.equal(errs[1].schemaPath, '#/' + keyword); + + // an object without q should fail + assert.strictEqual(validate({ p: 'foo' }), false); + errs = validate.errors; + assert.equal(errs.length, 2); + assert.equal(errs[0].keyword, 'required'); + assert.equal(errs[0].dataPath, ''); + assert.equal(errs[0].schemaPath, '#/required'); + assert.deepEqual(errs[0].params, { missingProperty: 'q' }); + assert.equal(errs[1].keyword, keyword); + assert.equal(errs[1].dataPath, ''); + assert.equal(errs[1].schemaPath, '#/' + keyword); };