cast.js 5.7 KB

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