|
|
/*! * 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); } } } }
|