cast.js 8.0 KB

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