diff --git a/package.json b/package.json index 21adf40c1..aad454ba9 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "cross-fetch": "0.0.8", "deep-extend": "^0.4.1", "encode-3986": "^1.0.0", - "fast-json-patch": "1.1.8", + "fast-json-patch": "^1.2.2", "isomorphic-form-data": "0.0.1", "js-yaml": "^3.8.1", "lodash": "^4.16.2", @@ -77,4 +77,4 @@ "utf8-bytes": "0.0.1", "utfstring": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/specmap/index.js b/src/specmap/index.js index fcf1431e8..c18c03247 100644 --- a/src/specmap/index.js +++ b/src/specmap/index.js @@ -212,8 +212,10 @@ class SpecMap { } updateMutations(patch) { - if (lib.applyPatch(this.state, patch, {allowMetaPatches: this.allowMetaPatches})) { + const result = lib.applyPatch(this.state, patch, {allowMetaPatches: this.allowMetaPatches}) + if (result) { this.mutations.push(patch) + this.state = result } } diff --git a/src/specmap/lib/index.js b/src/specmap/lib/index.js index cbd865f79..26d23a241 100644 --- a/src/specmap/lib/index.js +++ b/src/specmap/lib/index.js @@ -37,15 +37,14 @@ function applyPatch(obj, patch, opts) { }) if (patch.op === 'merge') { - const valPatch = _get(patch.path) - jsonPatch.apply(obj, [valPatch]) - Object.assign(valPatch.value, patch.value) + const newValue = getInByJsonPath(obj, patch.path) + Object.assign(newValue, patch.value) + jsonPatch.applyPatch(obj, [replace(patch.path, newValue)]) } else if (patch.op === 'mergeDeep') { - const valPatch = _get(patch.path) - jsonPatch.apply(obj, [valPatch]) - const origValPatchValue = Object.assign({}, valPatch.value) - deepExtend(valPatch.value, patch.value) + const currentValue = getInByJsonPath(obj, patch.path) + const origValPatchValue = Object.assign({}, currentValue) + deepExtend(currentValue, patch.value) // deepExtend doesn't merge arrays, so we will do it manually for (const prop in patch.value) { @@ -53,20 +52,47 @@ function applyPatch(obj, patch, opts) { const propVal = patch.value[prop] if (Array.isArray(propVal)) { const existing = origValPatchValue[prop] || [] - valPatch.value[prop] = existing.concat(propVal) + currentValue[prop] = existing.concat(propVal) } } } } + else if (patch.op === 'add' && patch.path === '' && isObject(patch.value)) { + // { op: 'add', path: '', value: { a: 1, b: 2 }} + // has no effect: json patch refuses to do anything. + // so let's break that patch down into a set of patches, + // one for each key in the intended root value. + + const patches = Object.keys(patch.value) + .reduce((arr, key) => { + arr.push({ + op: 'add', + path: `/${normalizeJSONPath(key)}`, + value: patch.value[key] + }) + return arr + }, []) + + jsonPatch.applyPatch(obj, patches) + } + else if (patch.op === 'replace' && patch.path === '') { + let value = patch.value + + if (opts.allowMetaPatches && patch.meta && isAdditiveMutation(patch) && + (Array.isArray(patch.value) || isObject(patch.value))) { + value = Object.assign({}, value, patch.meta) + } + obj = value + } else { - jsonPatch.apply(obj, [patch]) + jsonPatch.applyPatch(obj, [patch]) // Attach metadata to the resulting value. if (opts.allowMetaPatches && patch.meta && isAdditiveMutation(patch) && (Array.isArray(patch.value) || isObject(patch.value))) { - const valPatch = _get(patch.path) - jsonPatch.apply(obj, [valPatch]) - Object.assign(valPatch.value, patch.meta) + const currentValue = getInByJsonPath(obj, patch.path) + const newValue = Object.assign({}, currentValue, patch.meta) + jsonPatch.applyPatch(obj, [replace(patch.path, newValue)]) } } @@ -302,3 +328,13 @@ function isContextPatch(patch) { function isPatch(patch) { return patch && typeof patch === 'object' } + +function getInByJsonPath(obj, jsonPath) { + try { + return jsonPatch.getValueByPointer(obj, jsonPath) + } + catch (e) { + console.error(e) // eslint-disable-line no-console + return {} + } +} diff --git a/test/resolver.js b/test/resolver.js index c86dab181..19129ea08 100644 --- a/test/resolver.js +++ b/test/resolver.js @@ -447,7 +447,6 @@ describe('resolver', () => { }, definitions: { Category: { - $$ref: '#/definitions/Category', // FIXME: benign, but this should not be present type: 'object', properties: { id: { @@ -460,14 +459,13 @@ describe('resolver', () => { } }, Pet: { - $$ref: '#/definitions/Pet', type: 'object', required: [ 'category' ], properties: { category: { - $$ref: '#/definitions/Category', // FIXME: benign, but this should not be present + $$ref: '#/definitions/Category', type: 'object', properties: { id: { diff --git a/test/specmap/all-of.js b/test/specmap/all-of.js index 8c5293c6f..60de3e514 100644 --- a/test/specmap/all-of.js +++ b/test/specmap/all-of.js @@ -128,7 +128,6 @@ describe('allOf', function () { errors: [], spec: { Pet: { - $$ref: '#/Pet', type: 'object', properties: { name: { @@ -137,7 +136,6 @@ describe('allOf', function () { } }, Cat: { - $$ref: '#/Cat', properties: { meow: { type: 'string' diff --git a/test/specmap/index.js b/test/specmap/index.js index d8cf9afd9..2d341fc98 100644 --- a/test/specmap/index.js +++ b/test/specmap/index.js @@ -549,6 +549,7 @@ describe('specmap', function () { }) .then((res) => { expect(res.spec.one.$$ref).toEqual('#/two') + expect(res.spec.two.$$ref).toEqual(undefined) }) })