updateValidators.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*!
  2. * Module dependencies.
  3. */
  4. var async = require('async');
  5. var ValidationError = require('../error/validation.js');
  6. var ObjectId = require('../types/objectid');
  7. /**
  8. * Applies validators and defaults to update and fineOneAndUpdate operations,
  9. * specifically passing a null doc as `this` to validators and defaults
  10. *
  11. * @param {Query} query
  12. * @param {Schema} schema
  13. * @param {Object} castedDoc
  14. * @param {Object} options
  15. * @method runValidatorsOnUpdate
  16. * @api private
  17. */
  18. module.exports = function(query, schema, castedDoc, options) {
  19. var keys = Object.keys(castedDoc || {});
  20. var updatedKeys = {};
  21. var updatedValues = {};
  22. var numKeys = keys.length;
  23. var hasDollarUpdate = false;
  24. var modified = {};
  25. for (var i = 0; i < numKeys; ++i) {
  26. if (keys[i].charAt(0) === '$') {
  27. modifiedPaths(castedDoc[keys[i]], '', modified);
  28. var flat = flatten(castedDoc[keys[i]]);
  29. var paths = Object.keys(flat);
  30. var numPaths = paths.length;
  31. for (var j = 0; j < numPaths; ++j) {
  32. if (keys[i] === '$set' || keys[i] === '$setOnInsert') {
  33. updatedValues[paths[j]] = flat[paths[j]];
  34. } else if (keys[i] === '$unset') {
  35. updatedValues[paths[j]] = undefined;
  36. }
  37. updatedKeys[paths[j]] = true;
  38. }
  39. hasDollarUpdate = true;
  40. }
  41. }
  42. if (!hasDollarUpdate) {
  43. modifiedPaths(castedDoc, '', modified);
  44. updatedValues = flatten(castedDoc);
  45. updatedKeys = Object.keys(updatedValues);
  46. }
  47. if (options && options.upsert) {
  48. paths = Object.keys(query._conditions);
  49. numPaths = keys.length;
  50. for (var i = 0; i < numPaths; ++i) {
  51. var path = paths[i];
  52. var condition = query._conditions[path];
  53. if (condition && typeof condition === 'object') {
  54. var conditionKeys = Object.keys(condition);
  55. var numConditionKeys = conditionKeys.length;
  56. var hasDollarKey = false;
  57. for (var j = 0; j < numConditionKeys; ++j) {
  58. if (conditionKeys[j].charAt(0) === '$') {
  59. hasDollarKey = true;
  60. break;
  61. }
  62. }
  63. if (hasDollarKey) {
  64. continue;
  65. }
  66. }
  67. updatedKeys[path] = true;
  68. modified[path] = true;
  69. }
  70. if (options.setDefaultsOnInsert) {
  71. schema.eachPath(function(path, schemaType) {
  72. if (path === '_id') {
  73. // Ignore _id for now because it causes bugs in 2.4
  74. return;
  75. }
  76. var def = schemaType.getDefault(null, true);
  77. if (!modified[path] && typeof def !== 'undefined') {
  78. castedDoc.$setOnInsert = castedDoc.$setOnInsert || {};
  79. castedDoc.$setOnInsert[path] = def;
  80. updatedValues[path] = def;
  81. }
  82. });
  83. }
  84. }
  85. var updates = Object.keys(updatedValues);
  86. var numUpdates = updates.length;
  87. var validatorsToExecute = [];
  88. var validationErrors = [];
  89. for (var i = 0; i < numUpdates; ++i) {
  90. (function(i) {
  91. if (schema.path(updates[i])) {
  92. validatorsToExecute.push(function(callback) {
  93. schema.path(updates[i]).doValidate(
  94. updatedValues[updates[i]],
  95. function(err) {
  96. if (err) {
  97. validationErrors.push(err);
  98. }
  99. callback(null);
  100. },
  101. null);
  102. });
  103. }
  104. })(i);
  105. }
  106. return function(callback) {
  107. async.parallel(validatorsToExecute, function() {
  108. if (validationErrors.length) {
  109. var err = new ValidationError(null);
  110. for (var i = 0; i < validationErrors.length; ++i) {
  111. err.errors[validationErrors[i].path] = validationErrors[i];
  112. }
  113. return callback(err);
  114. }
  115. callback(null);
  116. });
  117. };
  118. };
  119. function modifiedPaths(update, path, result) {
  120. var keys = Object.keys(update);
  121. var numKeys = keys.length;
  122. result = result || {};
  123. path = path ? path + '.' : '';
  124. for (var i = 0; i < numKeys; ++i) {
  125. var key = keys[i];
  126. var val = update[key];
  127. result[path + key] = true;
  128. if (shouldFlatten(val)) {
  129. modifiedPaths(val, path + key, result);
  130. }
  131. }
  132. return result;
  133. }
  134. function flatten(update, path) {
  135. var keys = Object.keys(update);
  136. var numKeys = keys.length;
  137. var result = {};
  138. path = path ? path + '.' : '';
  139. for (var i = 0; i < numKeys; ++i) {
  140. var key = keys[i];
  141. var val = update[key];
  142. if (shouldFlatten(val)) {
  143. var flat = flatten(val, path + key);
  144. for (var k in flat) {
  145. result[k] = flat[k];
  146. }
  147. } else {
  148. result[path + key] = val;
  149. }
  150. }
  151. return result;
  152. }
  153. function shouldFlatten(val) {
  154. return val &&
  155. typeof val === 'object' &&
  156. !(val instanceof ObjectId);
  157. }