updateValidators.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /*!
  2. * Module dependencies.
  3. */
  4. var Mixed = require('../schema/mixed');
  5. var ValidationError = require('../error/validation');
  6. var parallel = require('async/parallel');
  7. var flatten = require('./common').flatten;
  8. var modifiedPaths = require('./common').modifiedPaths;
  9. /**
  10. * Applies validators and defaults to update and findOneAndUpdate operations,
  11. * specifically passing a null doc as `this` to validators and defaults
  12. *
  13. * @param {Query} query
  14. * @param {Schema} schema
  15. * @param {Object} castedDoc
  16. * @param {Object} options
  17. * @method runValidatorsOnUpdate
  18. * @api private
  19. */
  20. module.exports = function(query, schema, castedDoc, options) {
  21. var _keys;
  22. var keys = Object.keys(castedDoc || {});
  23. var updatedKeys = {};
  24. var updatedValues = {};
  25. var arrayAtomicUpdates = {};
  26. var numKeys = keys.length;
  27. var hasDollarUpdate = false;
  28. var modified = {};
  29. var currentUpdate;
  30. var key;
  31. for (var i = 0; i < numKeys; ++i) {
  32. if (keys[i].charAt(0) === '$') {
  33. hasDollarUpdate = true;
  34. if (keys[i] === '$push' || keys[i] === '$addToSet') {
  35. _keys = Object.keys(castedDoc[keys[i]]);
  36. for (var ii = 0; ii < _keys.length; ++ii) {
  37. currentUpdate = castedDoc[keys[i]][_keys[ii]];
  38. if (currentUpdate && currentUpdate.$each) {
  39. arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []).
  40. concat(currentUpdate.$each);
  41. } else {
  42. arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []).
  43. concat([currentUpdate]);
  44. }
  45. }
  46. continue;
  47. }
  48. modifiedPaths(castedDoc[keys[i]], '', modified);
  49. var flat = flatten(castedDoc[keys[i]]);
  50. var paths = Object.keys(flat);
  51. var numPaths = paths.length;
  52. for (var j = 0; j < numPaths; ++j) {
  53. var updatedPath = paths[j].replace('.$.', '.0.');
  54. updatedPath = updatedPath.replace(/\.\$$/, '.0');
  55. key = keys[i];
  56. if (key === '$set' || key === '$setOnInsert' ||
  57. key === '$pull' || key === '$pullAll') {
  58. updatedValues[updatedPath] = flat[paths[j]];
  59. } else if (key === '$unset') {
  60. updatedValues[updatedPath] = undefined;
  61. }
  62. updatedKeys[updatedPath] = true;
  63. }
  64. }
  65. }
  66. if (!hasDollarUpdate) {
  67. modifiedPaths(castedDoc, '', modified);
  68. updatedValues = flatten(castedDoc);
  69. updatedKeys = Object.keys(updatedValues);
  70. }
  71. var updates = Object.keys(updatedValues);
  72. var numUpdates = updates.length;
  73. var validatorsToExecute = [];
  74. var validationErrors = [];
  75. function iter(i, v) {
  76. var schemaPath = schema._getSchema(updates[i]);
  77. if (schemaPath) {
  78. // gh-4305: `_getSchema()` will report all sub-fields of a 'Mixed' path
  79. // as 'Mixed', so avoid double validating them.
  80. if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) {
  81. return;
  82. }
  83. validatorsToExecute.push(function(callback) {
  84. schemaPath.doValidate(
  85. v,
  86. function(err) {
  87. if (err) {
  88. err.path = updates[i];
  89. validationErrors.push(err);
  90. }
  91. callback(null);
  92. },
  93. options && options.context === 'query' ? query : null,
  94. {updateValidator: true});
  95. });
  96. }
  97. }
  98. for (i = 0; i < numUpdates; ++i) {
  99. iter(i, updatedValues[updates[i]]);
  100. }
  101. var arrayUpdates = Object.keys(arrayAtomicUpdates);
  102. var numArrayUpdates = arrayUpdates.length;
  103. for (i = 0; i < numArrayUpdates; ++i) {
  104. (function(i) {
  105. var schemaPath = schema._getSchema(arrayUpdates[i]);
  106. if (schemaPath && schemaPath.$isMongooseDocumentArray) {
  107. validatorsToExecute.push(function(callback) {
  108. schemaPath.doValidate(
  109. arrayAtomicUpdates[arrayUpdates[i]],
  110. function(err) {
  111. if (err) {
  112. err.path = arrayUpdates[i];
  113. validationErrors.push(err);
  114. }
  115. callback(null);
  116. },
  117. options && options.context === 'query' ? query : null);
  118. });
  119. } else {
  120. schemaPath = schema._getSchema(arrayUpdates[i] + '.0');
  121. for (var j = 0; j < arrayAtomicUpdates[arrayUpdates[i]].length; ++j) {
  122. (function(j) {
  123. validatorsToExecute.push(function(callback) {
  124. schemaPath.doValidate(
  125. arrayAtomicUpdates[arrayUpdates[i]][j],
  126. function(err) {
  127. if (err) {
  128. err.path = arrayUpdates[i];
  129. validationErrors.push(err);
  130. }
  131. callback(null);
  132. },
  133. options && options.context === 'query' ? query : null,
  134. { updateValidator: true });
  135. });
  136. })(j);
  137. }
  138. }
  139. })(i);
  140. }
  141. return function(callback) {
  142. parallel(validatorsToExecute, function() {
  143. if (validationErrors.length) {
  144. var err = new ValidationError(null);
  145. for (var i = 0; i < validationErrors.length; ++i) {
  146. err.addError(validationErrors[i].path, validationErrors[i]);
  147. }
  148. return callback(err);
  149. }
  150. callback(null);
  151. });
  152. };
  153. };