thoughts/node_modules/mongoose/lib/cast.js
node_modules/mongoose/lib/cast.jsRaw
1/*!
2 * Module dependencies.
3 */
4
5var utils = require('./utils');
6var Types = require('./schema/index');
7
8var ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
9
10/**
11 * Handles internal casting for queries
12 *
13 * @param {Schema} schema
14 * @param {Object} obj Object to cast
15 * @api private
16 */
17module.exports = function cast(schema, obj) {
18 var paths = Object.keys(obj),
19 i = paths.length,
20 any$conditionals,
21 schematype,
22 nested,
23 path,
24 type,
25 val;
26
27 while (i--) {
28 path = paths[i];
29 val = obj[path];
30
31 if (path === '$or' || path === '$nor' || path === '$and') {
32 var k = val.length;
33
34 while (k--) {
35 val[k] = cast(schema, val[k]);
36 }
37 } else if (path === '$where') {
38 type = typeof val;
39
40 if (type !== 'string' && type !== 'function') {
41 throw new Error('Must have a string or function for $where');
42 }
43
44 if (type === 'function') {
45 obj[path] = val.toString();
46 }
47
48 continue;
49 } else if (path === '$elemMatch') {
50 val = cast(schema, val);
51 } else {
52 if (!schema) {
53 // no casting for Mixed types
54 continue;
55 }
56
57 schematype = schema.path(path);
58
59 if (!schematype) {
60 // Handle potential embedded array queries
61 var split = path.split('.'),
62 j = split.length,
63 pathFirstHalf,
64 pathLastHalf,
65 remainingConds;
66
67 // Find the part of the var path that is a path of the Schema
68 while (j--) {
69 pathFirstHalf = split.slice(0, j).join('.');
70 schematype = schema.path(pathFirstHalf);
71 if (schematype) {
72 break;
73 }
74 }
75
76 // If a substring of the input path resolves to an actual real path...
77 if (schematype) {
78 // Apply the casting; similar code for $elemMatch in schema/array.js
79 if (schematype.caster && schematype.caster.schema) {
80 remainingConds = {};
81 pathLastHalf = split.slice(j).join('.');
82 remainingConds[pathLastHalf] = val;
83 obj[path] = cast(schematype.caster.schema, remainingConds)[pathLastHalf];
84 } else {
85 obj[path] = val;
86 }
87 continue;
88 }
89
90 if (utils.isObject(val)) {
91 // handle geo schemas that use object notation
92 // { loc: { long: Number, lat: Number }
93
94 var geo = '';
95 if (val.$near) {
96 geo = '$near';
97 } else if (val.$nearSphere) {
98 geo = '$nearSphere';
99 } else if (val.$within) {
100 geo = '$within';
101 } else if (val.$geoIntersects) {
102 geo = '$geoIntersects';
103 } else if (val.$geoWithin) {
104 geo = '$geoWithin';
105 }
106
107 if (!geo) {
108 continue;
109 }
110
111 var numbertype = new Types.Number('__QueryCasting__');
112 var value = val[geo];
113
114 if (val.$maxDistance != null) {
115 val.$maxDistance = numbertype.castForQuery(val.$maxDistance);
116 }
117 if (val.$minDistance != null) {
118 val.$minDistance = numbertype.castForQuery(val.$minDistance);
119 }
120
121 if (geo === '$within') {
122 var withinType = value.$center
123 || value.$centerSphere
124 || value.$box
125 || value.$polygon;
126
127 if (!withinType) {
128 throw new Error('Bad $within paramater: ' + JSON.stringify(val));
129 }
130
131 value = withinType;
132 } else if (geo === '$near' &&
133 typeof value.type === 'string' && Array.isArray(value.coordinates)) {
134 // geojson; cast the coordinates
135 value = value.coordinates;
136 } else if ((geo === '$near' || geo === '$nearSphere' || geo === '$geoIntersects') &&
137 value.$geometry && typeof value.$geometry.type === 'string' &&
138 Array.isArray(value.$geometry.coordinates)) {
139 if (value.$maxDistance != null) {
140 value.$maxDistance = numbertype.castForQuery(value.$maxDistance);
141 }
142 if (value.$minDistance != null) {
143 value.$minDistance = numbertype.castForQuery(value.$minDistance);
144 }
145 if (utils.isMongooseObject(value.$geometry)) {
146 value.$geometry = value.$geometry.toObject({ virtuals: false });
147 }
148 value = value.$geometry.coordinates;
149 } else if (geo === '$geoWithin') {
150 if (!value.$geometry) {
151 throw new Error('$geoWithin must specify $geometry');
152 }
153 if (utils.isMongooseObject(value.$geometry)) {
154 value.$geometry = value.$geometry.toObject({ virtuals: false });
155 }
156 var geoWithinType = value.$geometry.type;
157 if (ALLOWED_GEOWITHIN_GEOJSON_TYPES.indexOf(geoWithinType) === -1) {
158 throw new Error('Invalid geoJSON type for $geoWithin "' +
159 geoWithinType + '", must be "Polygon" or "MultiPolygon"');
160 }
161 value = value.$geometry.coordinates;
162 }
163
164 _cast(value, numbertype);
165 }
166 } else if (val === null || val === undefined) {
167 obj[path] = null;
168 continue;
169 } else if (val.constructor.name === 'Object') {
170 any$conditionals = Object.keys(val).some(function(k) {
171 return k.charAt(0) === '$' && k !== '$id' && k !== '$ref';
172 });
173
174 if (!any$conditionals) {
175 obj[path] = schematype.castForQuery(val);
176 } else {
177 var ks = Object.keys(val),
178 $cond;
179
180 k = ks.length;
181
182 while (k--) {
183 $cond = ks[k];
184 nested = val[$cond];
185
186 if ($cond === '$exists') {
187 if (typeof nested !== 'boolean') {
188 throw new Error('$exists parameter must be Boolean');
189 }
190 continue;
191 }
192
193 if ($cond === '$type') {
194 if (typeof nested !== 'number' && typeof nested !== 'string') {
195 throw new Error('$type parameter must be number or string');
196 }
197 continue;
198 }
199
200 if ($cond === '$not') {
201 cast(schema, nested);
202 } else {
203 val[$cond] = schematype.castForQuery($cond, nested);
204 }
205 }
206 }
207 } else {
208 obj[path] = schematype.castForQuery(val);
209 }
210 }
211 }
212
213 return obj;
214};
215
216function _cast(val, numbertype) {
217 if (Array.isArray(val)) {
218 val.forEach(function(item, i) {
219 if (Array.isArray(item) || utils.isObject(item)) {
220 return _cast(item, numbertype);
221 }
222 val[i] = numbertype.castForQuery(item);
223 });
224 } else {
225 var nearKeys = Object.keys(val);
226 var nearLen = nearKeys.length;
227 while (nearLen--) {
228 var nkey = nearKeys[nearLen];
229 var item = val[nkey];
230 if (Array.isArray(item) || utils.isObject(item)) {
231 _cast(item, numbertype);
232 val[nkey] = item;
233 } else {
234 val[nkey] = numbertype.castForQuery(item);
235 }
236 }
237 }
238}