"use strict"; const errors = require("../errors"); const getValueType = require("../util/get-value-type"); let indexRegex = /^[0-9]+$/; module.exports = function createGuardedArray(array, guard, _, parent) { function generateArraySignatureError() { let wantedSignature = { _guardedCollectionType: "array", _itemType: guard._rule }; return new errors.ValidationError(`Expected an array or ${getValueType(wantedSignature)}, got ${getValueType(array)} instead`); } if (array._guardedCollectionType === "array") { if (guard === array._itemType) { return array; } else { throw generateArraySignatureError(); } } else if (Array.isArray(array)) { for (let value of array) { check(value); } /* We shallow-clone the array here, to keep users from accidentally mutating the source array outside the purview of the guards specified here. */ let targetArray = array.slice(); function check(value) { let guardResult = guard.call(parent, value); if (guardResult === true) { return true; } else { throw guardResult; } } let guardedForEach = guardedIterationMethod.bind(undefined, "forEach"); let guardedFilter = guardedIterationMethod.bind(undefined, "filter"); let guardedMap = guardedIterationMethod.bind(undefined, "map"); let guardedSome = guardedIterationMethod.bind(undefined, "some"); let guardedEvery = guardedIterationMethod.bind(undefined, "every"); let guardedFind = guardedIterationMethod.bind(undefined, "find"); let proxyAPI = { _guardedCollectionType: "array", _itemType: guard, push: guardedPush, reduce: guardedReduce, reduceRight: guardedReduceRight, filter: guardedFilter, map: guardedMap, forEach: guardedForEach, some: guardedSome, every: guardedEvery, find: guardedFind, unshift: guardedUnshift, fill: guardedFill, splice: guardedSplice }; let proxy = new Proxy(targetArray, { get: function (target, property) { if (proxyAPI[property] != null) { return proxyAPI[property]; } else { return target[property]; } }, set: function (target, property, value) { if (indexRegex.test(property)) { check(value); } target[property] = value; return true; } }); function guardedUnshift(...items) { for (let item of items) { check(item); } return targetArray.unshift(...items); } function guardedPush(value) { if (check(value) === true) { return targetArray.push(value); } } function guardedFill(value) { if (check(value) === true) { return targetArray.fill(value); } } function guardedSplice(start, deleteCount, ...items) { for (let item of items) { check(item); } return targetArray.splice(start, deleteCount, ...items); } /* We provide the proxy instead of the original array as the 'array' argument for iteration functions, to prevent direct access to the underlying array; otherwise the user could accidentally mutate the underlying array from within a forEach callback, bypassing the validation requirements. */ function guardedIterationMethod(method, callback, thisArg) { return targetArray[method]((value, index, _array) => { return callback(value, index, proxy); }, thisArg); } function guardedReduce(callback, initialValue) { return targetArray.reduce((accumulator, value, index, _array) => { return callback(accumulator, value, index, proxy); }, initialValue); } function guardedReduceRight(callback, initialValue) { return targetArray.reduceRight((accumulator, value, index, _array) => { return callback(accumulator, value, index, proxy); }, initialValue); } return proxy; } else { throw generateArraySignatureError(); } };