UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

258 lines (257 loc) 8.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validatePredicateOperation = exports.validateOperation = exports.validateOperations = void 0; const validate_1 = require("@jsonjoy.com/json-pointer/lib/validate"); const hasOwnProperty_1 = require("@jsonjoy.com/util/lib/hasOwnProperty"); const validateOperations = (ops, allowMatchesOp = false) => { if (!Array.isArray(ops)) throw new Error('Not a array.'); if (!ops.length) throw new Error('Empty operation patch.'); for (let i = 0; i < ops.length; i++) { const op = ops[i]; try { (0, exports.validateOperation)(op, allowMatchesOp); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`Error in operation [index = ${i}] (${message}).`); } } }; exports.validateOperations = validateOperations; const validateOperation = (op, allowMatchesOp) => { if (!op || typeof op !== 'object') throw new Error('OP_INVALID'); const path = op.path; if (typeof path !== 'string') throw new Error('OP_PATH_INVALID'); (0, validate_1.validateJsonPointer)(path); switch (op.op) { case 'add': validateOperationAdd(op); break; case 'remove': validateOperationRemove(op); break; case 'replace': validateOperationReplace(op); break; case 'copy': validateOperationCopy(op); break; case 'move': validateOperationMove(op); break; case 'flip': break; case 'inc': validateOperationInc(op); break; case 'str_ins': validateOperationInsertText(op); break; case 'str_del': validateOperationRemoveText(op); break; case 'extend': validateOperationExtend(op); break; case 'merge': validateOperationMerge(op); break; case 'split': validateOperationSplit(op); break; default: (0, exports.validatePredicateOperation)(op, allowMatchesOp); } }; exports.validateOperation = validateOperation; const validatePredicateOperation = (op, allowMatchesOp) => { if (!op || typeof op !== 'object') throw new Error('OP_INVALID'); (0, validate_1.validateJsonPointer)(op.path); switch (op.op) { case 'test': validateOperationTest(op); break; case 'test_type': validateOperationTestType(op); break; case 'test_string': validateOperationTestString(op); break; case 'test_string_len': validateOperationTestStringLen(op); break; case 'matches': if (!allowMatchesOp) throw new Error('"matches" operation not allowed.'); validateOperationPredicateWithValueAndCase(op); break; case 'contains': case 'ends': case 'starts': validateOperationPredicateWithValueAndCase(op); break; case 'in': if (!Array.isArray(op.value)) throw new Error('"in" operation "value" must be an array.'); break; case 'more': case 'less': if (typeof op.value !== 'number') throw new Error('Value must be a number.'); break; case 'type': validateValueString(op.value); validateTestType(op.value); break; case 'defined': case 'undefined': break; case 'and': case 'or': case 'not': if (!Array.isArray(op.apply)) throw new Error(`"${op.op}" predicate operators must be an array.`); if (!op.apply.length) throw new Error('Predicate list is empty.'); for (const predicate of op.apply) (0, exports.validatePredicateOperation)(predicate, allowMatchesOp); break; default: throw new Error('OP_UNKNOWN'); } }; exports.validatePredicateOperation = validatePredicateOperation; const validateOperationAdd = (op) => { validateValue(op.value); }; const validateOperationRemove = (op) => { if ((0, hasOwnProperty_1.hasOwnProperty)(op, 'oldValue') && op.oldValue === undefined) throw new Error('Invalid oldValue.'); }; const validateOperationReplace = (op) => { if ((0, hasOwnProperty_1.hasOwnProperty)(op, 'oldValue') && op.oldValue === undefined) throw new Error('Invalid oldValue.'); }; const validateOperationCopy = (op) => { const from = op.from; if (typeof from !== 'string') throw new Error('OP_FROM_INVALID'); (0, validate_1.validateJsonPointer)(from); }; const validateOperationMove = (op) => { const from = op.from; if (typeof from !== 'string') throw new Error('OP_FROM_INVALID'); (0, validate_1.validateJsonPointer)(from); const { path } = op; if (path.indexOf(from + '/') === 0) throw new Error('Cannot move into own children.'); }; const validateOperationTest = (op) => { validateValue(op.value); validateNot(op.not); }; const validateOperationTestType = (op) => { if (!Array.isArray(op.type)) throw new Error('Invalid "type" field.'); if (op.type.length < 1) throw new Error('Empty type list.'); for (const type of op.type) validateTestType(type); }; const validTypes = new Set(['string', 'number', 'boolean', 'object', 'integer', 'array', 'null']); const validateTestType = (type) => { if (!validTypes.has(type)) throw new Error('Invalid type.'); }; const validateOperationTestString = (op) => { validateNot(op.not); validateNonNegativeInteger(op.pos); if (typeof op.str !== 'string') throw new Error('Value must be a string.'); }; const validateOperationTestStringLen = (op) => { validateNot(op.not); validateNonNegativeInteger(op.len); }; const validateOperationInc = (op) => { if (typeof op.inc !== 'number') throw new Error('Invalid "inc" value.'); }; const validateOperationInsertText = (op) => { validateNonNegativeInteger(op.pos); if (typeof op.str !== 'string') throw new Error('Expected a string "text" field.'); }; const validateOperationRemoveText = (op) => { validateNonNegativeInteger(op.pos); if (op.str === undefined && op.len === undefined) throw new Error('Either "text" or "pos" need to be set.'); if (op.str !== undefined) { if (typeof op.str !== 'string') throw new Error('Expected a string "text" field.'); } else { validateNonNegativeInteger(op.len); } }; const validateOperationExtend = (op) => { if (!op.props || typeof op.props !== 'object') throw new Error('Invalid "props" field.'); if (op.deleteNull !== undefined) if (typeof op.deleteNull !== 'boolean') throw new Error('Expected "deleteNull" field to be boolean.'); }; const validateOperationMerge = (op) => { validateInteger(op.pos); if (op.pos < 1) throw new Error('Expected "pos" field to be greater than 0.'); if (op.props) if (typeof op.props !== 'object') throw new Error('Invalid "props" field.'); }; const validateOperationSplit = (op) => { validateInteger(op.pos); if (op.props) if (typeof op.props !== 'object') throw new Error('Invalid "props" field.'); }; const validateOperationPredicateWithValueAndCase = (op) => { validateValueString(op.value); validateIgnoreCase(op.ignore_case); }; const validateValue = (value) => { if (value === undefined) throw new Error('OP_VALUE_MISSING'); }; const validateValueString = (value) => { if (typeof value !== 'string') throw new Error('Expected "value" to be string.'); if (value.length > 20000) throw new Error('Value too long.'); }; const validateIgnoreCase = (ignore) => { if (ignore === undefined) return; if (typeof ignore !== 'boolean') throw new Error('Expected "ignore_case" to be a boolean.'); }; const validateNot = (not) => { if (not !== undefined) if (typeof not !== 'boolean') throw new Error('Invalid "not" modifier.'); }; const validateInteger = (num) => { if (typeof num !== 'number' || num !== Math.round(num)) throw new Error('Not an integer.'); }; const validateNonNegativeInteger = (num) => { validateInteger(num); if (num < 0) throw new Error('Number is negative.'); };