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