blob: 7c27b4955a90469cdd538a9e6a3d8b5551d56338 [file]
function isNonNullObject(value) {
return typeof value === 'object' && value !== null;
}
// throws if the user is using the wrong query field value type
function checkFieldValueType(name, value, isHttp) {
let message = '';
let received = value;
let addReceived = true;
if (['$in', '$nin', '$or', '$and', '$mod', '$nor', '$all'].indexOf(name) !== -1) {
if (!Array.isArray(value)) {
message = 'Query operator ' + name + ' must be an array.';
}
}
if (['$not', '$elemMatch', '$allMatch'].indexOf(name) !== -1) {
if (!(!Array.isArray(value) && isNonNullObject(value))) {
message = 'Query operator ' + name + ' must be an object.';
}
}
if (name === '$mod' && Array.isArray(value)) {
if (value.length !== 2) {
message = 'Query operator $mod must be in the format [divisor, remainder], ' +
'where divisor and remainder are both integers.';
} else {
const divisor = value[0];
const mod = value[1];
if (divisor === 0) {
message = 'Query operator $mod\'s divisor cannot be 0, cannot divide by zero.';
addReceived = false;
}
if (typeof divisor !== 'number' || parseInt(divisor, 10) !== divisor) {
message = 'Query operator $mod\'s divisor is not an integer.';
received = divisor;
}
if (parseInt(mod, 10) !== mod) {
message = 'Query operator $mod\'s remainder is not an integer.';
received = mod;
}
}
}
if (name === '$exists') {
if (typeof value !== 'boolean') {
message = 'Query operator $exists must be a boolean.';
}
}
if (name === '$type') {
const allowed = ['null', 'boolean', 'number', 'string', 'array', 'object'];
const allowedStr = '"' + allowed.slice(0, allowed.length - 1).join('", "') + '", or "' + allowed[allowed.length - 1] + '"';
if (typeof value !== 'string') {
message = 'Query operator $type must be a string. Supported values: ' + allowedStr + '.';
} else if (allowed.indexOf(value) == -1) {
message = 'Query operator $type must be a string. Supported values: ' + allowedStr + '.';
}
}
if (name === '$size') {
if (parseInt(value, 10) !== value) {
message = 'Query operator $size must be a integer.';
}
}
if (name === '$regex') {
if (typeof value !== 'string') {
if (isHttp) {
message = 'Query operator $regex must be a string.';
} else if (!(value instanceof RegExp)) {
message = 'Query operator $regex must be a string or an instance ' +
'of a javascript regular expression.';
}
}
}
if (message) {
if (addReceived) {
const type = received === null
? ' '
: Array.isArray(received)
? ' array'
: ' ' + typeof received;
const receivedStr = isNonNullObject(received)
? JSON.stringify(received, null, '\t')
: received;
message += ' Received' + type + ': ' + receivedStr;
}
throw new Error(message);
}
}
const requireValidation = ['$all', '$allMatch', '$and', '$elemMatch', '$exists', '$in', '$mod', '$nin', '$nor', '$not', '$or', '$regex', '$size', '$type'];
const arrayTypeComparisonOperators = ['$in', '$nin', '$mod', '$all'];
const equalityOperators = ['$eq', '$gt', '$gte', '$lt', '$lte'];
// recursively walks down the a query selector validating any operators
export default function validateSelector(input, isHttp) {
if (Array.isArray(input)) {
for (const entry of input) {
if (isNonNullObject(entry)) {
validateSelector(entry, isHttp);
}
}
}
else {
for (const [key, value] of Object.entries(input)) {
if (requireValidation.indexOf(key) !== -1) {
checkFieldValueType(key, value, isHttp);
}
if (equalityOperators.indexOf(key) !== -1) {
// skip, explicit comparison operators can be anything
continue;
}
if (arrayTypeComparisonOperators.indexOf(key) !== -1) {
// skip, their values are already valid
continue;
}
if (isNonNullObject(value)) {
validateSelector(value, isHttp);
}
}
}
}