deno.land / x / mongoose@6.7.5 / lib / helpers / query / cast$expr.js
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283'use strict';
const CastError = require('../../error/cast');const StrictModeError = require('../../error/strict');const castNumber = require('../../cast/number');
const booleanComparison = new Set(['$and', '$or']);const comparisonOperator = new Set(['$cmp', '$eq', '$lt', '$lte', '$gt', '$gte']);const arithmeticOperatorArray = new Set([ // avoid casting '$add' or '$subtract', because expressions can be either number or date, // and we don't have a good way of inferring which arguments should be numbers and which should // be dates. '$multiply', '$divide', '$log', '$mod', '$trunc', '$avg', '$max', '$min', '$stdDevPop', '$stdDevSamp', '$sum']);const arithmeticOperatorNumber = new Set([ '$abs', '$exp', '$ceil', '$floor', '$ln', '$log10', '$round', '$sqrt', '$sin', '$cos', '$tan', '$asin', '$acos', '$atan', '$atan2', '$asinh', '$acosh', '$atanh', '$sinh', '$cosh', '$tanh', '$degreesToRadians', '$radiansToDegrees']);const arrayElementOperators = new Set([ '$arrayElemAt', '$first', '$last']);const dateOperators = new Set([ '$year', '$month', '$week', '$dayOfMonth', '$dayOfYear', '$hour', '$minute', '$second', '$isoDayOfWeek', '$isoWeekYear', '$isoWeek', '$millisecond']);const expressionOperator = new Set(['$not']);
module.exports = function cast$expr(val, schema, strictQuery) { if (typeof val !== 'object' || val === null) { throw new Error('`$expr` must be an object'); }
return _castExpression(val, schema, strictQuery);};
function _castExpression(val, schema, strictQuery) { // Preserve the value if it represents a path or if it's null if (isPath(val) || val === null) { return val; }
if (val.$cond != null) { if (Array.isArray(val.$cond)) { val.$cond = val.$cond.map(expr => _castExpression(expr, schema, strictQuery)); } else { val.$cond.if = _castExpression(val.$cond.if, schema, strictQuery); val.$cond.then = _castExpression(val.$cond.then, schema, strictQuery); val.$cond.else = _castExpression(val.$cond.else, schema, strictQuery); } } else if (val.$ifNull != null) { val.$ifNull.map(v => _castExpression(v, schema, strictQuery)); } else if (val.$switch != null) { val.branches.map(v => _castExpression(v, schema, strictQuery)); val.default = _castExpression(val.default, schema, strictQuery); }
const keys = Object.keys(val); for (const key of keys) { if (booleanComparison.has(key)) { val[key] = val[key].map(v => _castExpression(v, schema, strictQuery)); } else if (comparisonOperator.has(key)) { val[key] = castComparison(val[key], schema, strictQuery); } else if (arithmeticOperatorArray.has(key)) { val[key] = castArithmetic(val[key], schema, strictQuery); } else if (arithmeticOperatorNumber.has(key)) { val[key] = castNumberOperator(val[key], schema, strictQuery); } else if (expressionOperator.has(key)) { val[key] = _castExpression(val[key], schema, strictQuery); } }
if (val.$in) { val.$in = castIn(val.$in, schema, strictQuery); } if (val.$size) { val.$size = castNumberOperator(val.$size, schema, strictQuery); }
_omitUndefined(val);
return val;}
function _omitUndefined(val) { const keys = Object.keys(val); for (let i = 0, len = keys.length; i < len; ++i) { (val[keys[i]] === void 0) && delete val[keys[i]]; }}
// { $op: <number> }function castNumberOperator(val) { if (!isLiteral(val)) { return val; }
try { return castNumber(val); } catch (err) { throw new CastError('Number', val); }}
function castIn(val, schema, strictQuery) { const path = val[1]; if (!isPath(path)) { return val; } const search = val[0];
const schematype = schema.path(path.slice(1)); if (schematype === null) { if (strictQuery === false) { return val; } else if (strictQuery === 'throw') { throw new StrictModeError('$in'); }
return void 0; }
if (!schematype.$isMongooseArray) { throw new Error('Path must be an array for $in'); }
return [ schematype.$isMongooseDocumentArray ? schematype.$embeddedSchemaType.cast(search) : schematype.caster.cast(search), path ];}
// { $op: [<number>, <number>] }function castArithmetic(val) { if (!Array.isArray(val)) { if (!isLiteral(val)) { return val; } try { return castNumber(val); } catch (err) { throw new CastError('Number', val); } }
return val.map(v => { if (!isLiteral(v)) { return v; } try { return castNumber(v); } catch (err) { throw new CastError('Number', v); } });}
// { $op: [expression, expression] }function castComparison(val, schema, strictQuery) { if (!Array.isArray(val) || val.length !== 2) { throw new Error('Comparison operator must be an array of length 2'); }
val[0] = _castExpression(val[0], schema, strictQuery); const lhs = val[0];
if (isLiteral(val[1])) { let path = null; let schematype = null; let caster = null; if (isPath(lhs)) { path = lhs.slice(1); schematype = schema.path(path); } else if (typeof lhs === 'object' && lhs != null) { for (const key of Object.keys(lhs)) { if (dateOperators.has(key) && isPath(lhs[key])) { path = lhs[key].slice(1) + '.' + key; caster = castNumber; } else if (arrayElementOperators.has(key) && isPath(lhs[key])) { path = lhs[key].slice(1) + '.' + key; schematype = schema.path(lhs[key].slice(1)); if (schematype != null) { if (schematype.$isMongooseDocumentArray) { schematype = schematype.$embeddedSchemaType; } else if (schematype.$isMongooseArray) { schematype = schematype.caster; } } } } }
const is$literal = typeof val[1] === 'object' && val[1] != null && val[1].$literal != null; if (schematype != null) { if (is$literal) { val[1] = { $literal: schematype.cast(val[1].$literal) }; } else { val[1] = schematype.cast(val[1]); } } else if (caster != null) { if (is$literal) { try { val[1] = { $literal: caster(val[1].$literal) }; } catch (err) { throw new CastError(caster.name.replace(/^cast/, ''), val[1], path + '.$literal'); } } else { try { val[1] = caster(val[1]); } catch (err) { throw new CastError(caster.name.replace(/^cast/, ''), val[1], path); } } } else if (path != null && strictQuery === true) { return void 0; } else if (path != null && strictQuery === 'throw') { throw new StrictModeError(path); } } else { val[1] = _castExpression(val[1]); }
return val;}
function isPath(val) { return typeof val === 'string' && val[0] === '$';}
function isLiteral(val) { if (typeof val === 'string' && val[0] === '$') { return false; } if (typeof val === 'object' && val !== null && Object.keys(val).find(key => key[0] === '$')) { // The `$literal` expression can make an object a literal // https://docs.mongodb.com/manual/reference/operator/aggregation/literal/#mongodb-expression-exp.-literal return val.$literal != null; } return true;}
Version Info