You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
3.7 KiB
JavaScript

"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();
}
};