/*! * Module dependencies. */ var Mixed = require('../schema/mixed'); var ValidationError = require('../error/validation'); var parallel = require('async/parallel'); var flatten = require('./common').flatten; var modifiedPaths = require('./common').modifiedPaths; /** * Applies validators and defaults to update and findOneAndUpdate operations, * specifically passing a null doc as `this` to validators and defaults * * @param {Query} query * @param {Schema} schema * @param {Object} castedDoc * @param {Object} options * @method runValidatorsOnUpdate * @api private */ module.exports = function(query, schema, castedDoc, options) { var _keys; var keys = Object.keys(castedDoc || {}); var updatedKeys = {}; var updatedValues = {}; var arrayAtomicUpdates = {}; var numKeys = keys.length; var hasDollarUpdate = false; var modified = {}; var currentUpdate; var key; for (var i = 0; i < numKeys; ++i) { if (keys[i].charAt(0) === '$') { hasDollarUpdate = true; if (keys[i] === '$push' || keys[i] === '$addToSet') { _keys = Object.keys(castedDoc[keys[i]]); for (var ii = 0; ii < _keys.length; ++ii) { currentUpdate = castedDoc[keys[i]][_keys[ii]]; if (currentUpdate && currentUpdate.$each) { arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []). concat(currentUpdate.$each); } else { arrayAtomicUpdates[_keys[ii]] = (arrayAtomicUpdates[_keys[ii]] || []). concat([currentUpdate]); } } continue; } modifiedPaths(castedDoc[keys[i]], '', modified); var flat = flatten(castedDoc[keys[i]]); var paths = Object.keys(flat); var numPaths = paths.length; for (var j = 0; j < numPaths; ++j) { var updatedPath = paths[j].replace('.$.', '.0.'); updatedPath = updatedPath.replace(/\.\$$/, '.0'); key = keys[i]; if (key === '$set' || key === '$setOnInsert' || key === '$pull' || key === '$pullAll') { updatedValues[updatedPath] = flat[paths[j]]; } else if (key === '$unset') { updatedValues[updatedPath] = undefined; } updatedKeys[updatedPath] = true; } } } if (!hasDollarUpdate) { modifiedPaths(castedDoc, '', modified); updatedValues = flatten(castedDoc); updatedKeys = Object.keys(updatedValues); } var updates = Object.keys(updatedValues); var numUpdates = updates.length; var validatorsToExecute = []; var validationErrors = []; function iter(i, v) { var schemaPath = schema._getSchema(updates[i]); if (schemaPath) { // gh-4305: `_getSchema()` will report all sub-fields of a 'Mixed' path // as 'Mixed', so avoid double validating them. if (schemaPath instanceof Mixed && schemaPath.$fullPath !== updates[i]) { return; } validatorsToExecute.push(function(callback) { schemaPath.doValidate( v, function(err) { if (err) { err.path = updates[i]; validationErrors.push(err); } callback(null); }, options && options.context === 'query' ? query : null, {updateValidator: true}); }); } } for (i = 0; i < numUpdates; ++i) { iter(i, updatedValues[updates[i]]); } var arrayUpdates = Object.keys(arrayAtomicUpdates); var numArrayUpdates = arrayUpdates.length; for (i = 0; i < numArrayUpdates; ++i) { (function(i) { var schemaPath = schema._getSchema(arrayUpdates[i]); if (schemaPath && schemaPath.$isMongooseDocumentArray) { validatorsToExecute.push(function(callback) { schemaPath.doValidate( arrayAtomicUpdates[arrayUpdates[i]], function(err) { if (err) { err.path = arrayUpdates[i]; validationErrors.push(err); } callback(null); }, options && options.context === 'query' ? query : null); }); } else { schemaPath = schema._getSchema(arrayUpdates[i] + '.0'); for (var j = 0; j < arrayAtomicUpdates[arrayUpdates[i]].length; ++j) { (function(j) { validatorsToExecute.push(function(callback) { schemaPath.doValidate( arrayAtomicUpdates[arrayUpdates[i]][j], function(err) { if (err) { err.path = arrayUpdates[i]; validationErrors.push(err); } callback(null); }, options && options.context === 'query' ? query : null, { updateValidator: true }); }); })(j); } } })(i); } return function(callback) { parallel(validatorsToExecute, function() { if (validationErrors.length) { var err = new ValidationError(null); for (var i = 0; i < validationErrors.length; ++i) { err.addError(validationErrors[i].path, validationErrors[i]); } return callback(err); } callback(null); }); }; };