UNPKG

rfc6902

Version:

Complete implementation of RFC6902 (patch and diff)

236 lines (229 loc) 9.36 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.apply = exports.InvalidOperationError = exports.test = exports.copy = exports.move = exports.replace = exports.remove = exports.add = exports.TestError = exports.MissingError = void 0; var pointer_1 = require("./pointer"); var util_1 = require("./util"); var diff_1 = require("./diff"); var MissingError = /** @class */ (function (_super) { __extends(MissingError, _super); function MissingError(path) { var _this = _super.call(this, "Value required at path: ".concat(path)) || this; _this.path = path; _this.name = 'MissingError'; return _this; } return MissingError; }(Error)); exports.MissingError = MissingError; var TestError = /** @class */ (function (_super) { __extends(TestError, _super); function TestError(actual, expected) { var _this = _super.call(this, "Test failed: ".concat(actual, " != ").concat(expected)) || this; _this.actual = actual; _this.expected = expected; _this.name = 'TestError'; return _this; } return TestError; }(Error)); exports.TestError = TestError; function _add(object, key, value) { if (Array.isArray(object)) { // `key` must be an index if (key == '-') { object.push(value); } else { var index = parseInt(key, 10); object.splice(index, 0, value); } } else { object[key] = value; } } function _remove(object, key) { if (Array.isArray(object)) { // '-' syntax doesn't make sense when removing var index = parseInt(key, 10); object.splice(index, 1); } else { // not sure what the proper behavior is when path = '' delete object[key]; } } /** > o If the target location specifies an array index, a new value is > inserted into the array at the specified index. > o If the target location specifies an object member that does not > already exist, a new member is added to the object. > o If the target location specifies an object member that does exist, > that member's value is replaced. */ function add(object, operation) { var endpoint = pointer_1.Pointer.fromJSON(operation.path).evaluate(object); // it's not exactly a "MissingError" in the same way that `remove` is -- more like a MissingParent, or something if (endpoint.parent === undefined) { return new MissingError(operation.path); } _add(endpoint.parent, endpoint.key, (0, util_1.clone)(operation.value)); return null; } exports.add = add; /** > The "remove" operation removes the value at the target location. > The target location MUST exist for the operation to be successful. */ function remove(object, operation) { // endpoint has parent, key, and value properties var endpoint = pointer_1.Pointer.fromJSON(operation.path).evaluate(object); if (endpoint.value === undefined) { return new MissingError(operation.path); } // not sure what the proper behavior is when path = '' _remove(endpoint.parent, endpoint.key); return null; } exports.remove = remove; /** > The "replace" operation replaces the value at the target location > with a new value. The operation object MUST contain a "value" member > whose content specifies the replacement value. > The target location MUST exist for the operation to be successful. > This operation is functionally identical to a "remove" operation for > a value, followed immediately by an "add" operation at the same > location with the replacement value. Even more simply, it's like the add operation with an existence check. */ function replace(object, operation) { var endpoint = pointer_1.Pointer.fromJSON(operation.path).evaluate(object); if (endpoint.parent === null) { return new MissingError(operation.path); } // this existence check treats arrays as a special case if (Array.isArray(endpoint.parent)) { if (parseInt(endpoint.key, 10) >= endpoint.parent.length) { return new MissingError(operation.path); } } else if (endpoint.value === undefined) { return new MissingError(operation.path); } endpoint.parent[endpoint.key] = (0, util_1.clone)(operation.value); return null; } exports.replace = replace; /** > The "move" operation removes the value at a specified location and > adds it to the target location. > The operation object MUST contain a "from" member, which is a string > containing a JSON Pointer value that references the location in the > target document to move the value from. > This operation is functionally identical to a "remove" operation on > the "from" location, followed immediately by an "add" operation at > the target location with the value that was just removed. > The "from" location MUST NOT be a proper prefix of the "path" > location; i.e., a location cannot be moved into one of its children. TODO: throw if the check described in the previous paragraph fails. */ function move(object, operation) { var from_endpoint = pointer_1.Pointer.fromJSON(operation.from).evaluate(object); if (from_endpoint.value === undefined) { return new MissingError(operation.from); } var endpoint = pointer_1.Pointer.fromJSON(operation.path).evaluate(object); if (endpoint.parent === undefined) { return new MissingError(operation.path); } _remove(from_endpoint.parent, from_endpoint.key); _add(endpoint.parent, endpoint.key, from_endpoint.value); return null; } exports.move = move; /** > The "copy" operation copies the value at a specified location to the > target location. > The operation object MUST contain a "from" member, which is a string > containing a JSON Pointer value that references the location in the > target document to copy the value from. > The "from" location MUST exist for the operation to be successful. > This operation is functionally identical to an "add" operation at the > target location using the value specified in the "from" member. Alternatively, it's like 'move' without the 'remove'. */ function copy(object, operation) { var from_endpoint = pointer_1.Pointer.fromJSON(operation.from).evaluate(object); if (from_endpoint.value === undefined) { return new MissingError(operation.from); } var endpoint = pointer_1.Pointer.fromJSON(operation.path).evaluate(object); if (endpoint.parent === undefined) { return new MissingError(operation.path); } _add(endpoint.parent, endpoint.key, (0, util_1.clone)(from_endpoint.value)); return null; } exports.copy = copy; /** > The "test" operation tests that a value at the target location is > equal to a specified value. > The operation object MUST contain a "value" member that conveys the > value to be compared to the target location's value. > The target location MUST be equal to the "value" value for the > operation to be considered successful. */ function test(object, operation) { var endpoint = pointer_1.Pointer.fromJSON(operation.path).evaluate(object); // TODO: this diffAny(...).length usage could/should be lazy if ((0, diff_1.diffAny)(endpoint.value, operation.value, new pointer_1.Pointer()).length) { return new TestError(endpoint.value, operation.value); } return null; } exports.test = test; var InvalidOperationError = /** @class */ (function (_super) { __extends(InvalidOperationError, _super); function InvalidOperationError(operation) { var _this = _super.call(this, "Invalid operation: ".concat(operation.op)) || this; _this.operation = operation; _this.name = 'InvalidOperationError'; return _this; } return InvalidOperationError; }(Error)); exports.InvalidOperationError = InvalidOperationError; /** Switch on `operation.op`, applying the corresponding patch function for each case to `object`. */ function apply(object, operation) { // not sure why TypeScript can't infer typesafety of: // {add, remove, replace, move, copy, test}[operation.op](object, operation) // (seems like a bug) switch (operation.op) { case 'add': return add(object, operation); case 'remove': return remove(object, operation); case 'replace': return replace(object, operation); case 'move': return move(object, operation); case 'copy': return copy(object, operation); case 'test': return test(object, operation); } return new InvalidOperationError(operation); } exports.apply = apply;