/*!
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var utils = require('./utils');
|
|
var Types = require('./schema/index');
|
|
|
|
var ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
|
|
|
|
/**
|
|
* Handles internal casting for queries
|
|
*
|
|
* @param {Schema} schema
|
|
* @param {Object} obj Object to cast
|
|
* @api private
|
|
*/
|
|
module.exports = function cast(schema, obj) {
|
|
var paths = Object.keys(obj),
|
|
i = paths.length,
|
|
any$conditionals,
|
|
schematype,
|
|
nested,
|
|
path,
|
|
type,
|
|
val;
|
|
|
|
while (i--) {
|
|
path = paths[i];
|
|
val = obj[path];
|
|
|
|
if (path === '$or' || path === '$nor' || path === '$and') {
|
|
var k = val.length;
|
|
|
|
while (k--) {
|
|
val[k] = cast(schema, val[k]);
|
|
}
|
|
} else if (path === '$where') {
|
|
type = typeof val;
|
|
|
|
if (type !== 'string' && type !== 'function') {
|
|
throw new Error('Must have a string or function for $where');
|
|
}
|
|
|
|
if (type === 'function') {
|
|
obj[path] = val.toString();
|
|
}
|
|
|
|
continue;
|
|
} else if (path === '$elemMatch') {
|
|
val = cast(schema, val);
|
|
} else {
|
|
if (!schema) {
|
|
// no casting for Mixed types
|
|
continue;
|
|
}
|
|
|
|
schematype = schema.path(path);
|
|
|
|
if (!schematype) {
|
|
// Handle potential embedded array queries
|
|
var split = path.split('.'),
|
|
j = split.length,
|
|
pathFirstHalf,
|
|
pathLastHalf,
|
|
remainingConds;
|
|
|
|
// Find the part of the var path that is a path of the Schema
|
|
while (j--) {
|
|
pathFirstHalf = split.slice(0, j).join('.');
|
|
schematype = schema.path(pathFirstHalf);
|
|
if (schematype) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If a substring of the input path resolves to an actual real path...
|
|
if (schematype) {
|
|
// Apply the casting; similar code for $elemMatch in schema/array.js
|
|
if (schematype.caster && schematype.caster.schema) {
|
|
remainingConds = {};
|
|
pathLastHalf = split.slice(j).join('.');
|
|
remainingConds[pathLastHalf] = val;
|
|
obj[path] = cast(schematype.caster.schema, remainingConds)[pathLastHalf];
|
|
} else {
|
|
obj[path] = val;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (utils.isObject(val)) {
|
|
// handle geo schemas that use object notation
|
|
// { loc: { long: Number, lat: Number }
|
|
|
|
var geo = '';
|
|
if (val.$near) {
|
|
geo = '$near';
|
|
} else if (val.$nearSphere) {
|
|
geo = '$nearSphere';
|
|
} else if (val.$within) {
|
|
geo = '$within';
|
|
} else if (val.$geoIntersects) {
|
|
geo = '$geoIntersects';
|
|
} else if (val.$geoWithin) {
|
|
geo = '$geoWithin';
|
|
}
|
|
|
|
if (!geo) {
|
|
continue;
|
|
}
|
|
|
|
var numbertype = new Types.Number('__QueryCasting__');
|
|
var value = val[geo];
|
|
|
|
if (val.$maxDistance != null) {
|
|
val.$maxDistance = numbertype.castForQuery(val.$maxDistance);
|
|
}
|
|
if (val.$minDistance != null) {
|
|
val.$minDistance = numbertype.castForQuery(val.$minDistance);
|
|
}
|
|
|
|
if (geo === '$within') {
|
|
var withinType = value.$center
|
|
|| value.$centerSphere
|
|
|| value.$box
|
|
|| value.$polygon;
|
|
|
|
if (!withinType) {
|
|
throw new Error('Bad $within paramater: ' + JSON.stringify(val));
|
|
}
|
|
|
|
value = withinType;
|
|
} else if (geo === '$near' &&
|
|
typeof value.type === 'string' && Array.isArray(value.coordinates)) {
|
|
// geojson; cast the coordinates
|
|
value = value.coordinates;
|
|
} else if ((geo === '$near' || geo === '$nearSphere' || geo === '$geoIntersects') &&
|
|
value.$geometry && typeof value.$geometry.type === 'string' &&
|
|
Array.isArray(value.$geometry.coordinates)) {
|
|
if (value.$maxDistance != null) {
|
|
value.$maxDistance = numbertype.castForQuery(value.$maxDistance);
|
|
}
|
|
if (value.$minDistance != null) {
|
|
value.$minDistance = numbertype.castForQuery(value.$minDistance);
|
|
}
|
|
if (utils.isMongooseObject(value.$geometry)) {
|
|
value.$geometry = value.$geometry.toObject({ virtuals: false });
|
|
}
|
|
value = value.$geometry.coordinates;
|
|
} else if (geo === '$geoWithin') {
|
|
if (!value.$geometry) {
|
|
throw new Error('$geoWithin must specify $geometry');
|
|
}
|
|
if (utils.isMongooseObject(value.$geometry)) {
|
|
value.$geometry = value.$geometry.toObject({ virtuals: false });
|
|
}
|
|
var geoWithinType = value.$geometry.type;
|
|
if (ALLOWED_GEOWITHIN_GEOJSON_TYPES.indexOf(geoWithinType) === -1) {
|
|
throw new Error('Invalid geoJSON type for $geoWithin "' +
|
|
geoWithinType + '", must be "Polygon" or "MultiPolygon"');
|
|
}
|
|
value = value.$geometry.coordinates;
|
|
}
|
|
|
|
_cast(value, numbertype);
|
|
}
|
|
} else if (val === null || val === undefined) {
|
|
obj[path] = null;
|
|
continue;
|
|
} else if (val.constructor.name === 'Object') {
|
|
any$conditionals = Object.keys(val).some(function(k) {
|
|
return k.charAt(0) === '$' && k !== '$id' && k !== '$ref';
|
|
});
|
|
|
|
if (!any$conditionals) {
|
|
obj[path] = schematype.castForQuery(val);
|
|
} else {
|
|
var ks = Object.keys(val),
|
|
$cond;
|
|
|
|
k = ks.length;
|
|
|
|
while (k--) {
|
|
$cond = ks[k];
|
|
nested = val[$cond];
|
|
|
|
if ($cond === '$exists') {
|
|
if (typeof nested !== 'boolean') {
|
|
throw new Error('$exists parameter must be Boolean');
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ($cond === '$type') {
|
|
if (typeof nested !== 'number' && typeof nested !== 'string') {
|
|
throw new Error('$type parameter must be number or string');
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ($cond === '$not') {
|
|
cast(schema, nested);
|
|
} else {
|
|
val[$cond] = schematype.castForQuery($cond, nested);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
obj[path] = schematype.castForQuery(val);
|
|
}
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
};
|
|
|
|
function _cast(val, numbertype) {
|
|
if (Array.isArray(val)) {
|
|
val.forEach(function(item, i) {
|
|
if (Array.isArray(item) || utils.isObject(item)) {
|
|
return _cast(item, numbertype);
|
|
}
|
|
val[i] = numbertype.castForQuery(item);
|
|
});
|
|
} else {
|
|
var nearKeys = Object.keys(val);
|
|
var nearLen = nearKeys.length;
|
|
while (nearLen--) {
|
|
var nkey = nearKeys[nearLen];
|
|
var item = val[nkey];
|
|
if (Array.isArray(item) || utils.isObject(item)) {
|
|
_cast(item, numbertype);
|
|
val[nkey] = item;
|
|
} else {
|
|
val[nkey] = numbertype.castForQuery(item);
|
|
}
|
|
}
|
|
}
|
|
}
|