You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
7.0 KiB

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