123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- 'use strict';
- /*!
- * Module dependencies.
- */
- const ValidationError = require('../error/validation');
- const cleanPositionalOperators = require('./schema/cleanPositionalOperators');
- const flatten = require('./common').flatten;
- const 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, callback) {
- const keys = Object.keys(castedDoc || {});
- let updatedKeys = {};
- let updatedValues = {};
- const isPull = {};
- const arrayAtomicUpdates = {};
- const numKeys = keys.length;
- let hasDollarUpdate = false;
- const modified = {};
- let currentUpdate;
- let key;
- let i;
- for (i = 0; i < numKeys; ++i) {
- if (keys[i].startsWith('$')) {
- hasDollarUpdate = true;
- if (keys[i] === '$push' || keys[i] === '$addToSet') {
- const _keys = Object.keys(castedDoc[keys[i]]);
- for (let 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);
- const flat = flatten(castedDoc[keys[i]], null, null, schema);
- const paths = Object.keys(flat);
- const numPaths = paths.length;
- for (let j = 0; j < numPaths; ++j) {
- const updatedPath = cleanPositionalOperators(paths[j]);
- key = keys[i];
- // With `$pull` we might flatten `$in`. Skip stuff nested under `$in`
- // for the rest of the logic, it will get handled later.
- if (updatedPath.includes('$')) {
- continue;
- }
- if (key === '$set' || key === '$setOnInsert' ||
- key === '$pull' || key === '$pullAll') {
- updatedValues[updatedPath] = flat[paths[j]];
- isPull[updatedPath] = key === '$pull' || key === '$pullAll';
- } else if (key === '$unset') {
- updatedValues[updatedPath] = undefined;
- }
- updatedKeys[updatedPath] = true;
- }
- }
- }
- if (!hasDollarUpdate) {
- modifiedPaths(castedDoc, '', modified);
- updatedValues = flatten(castedDoc, null, null, schema);
- updatedKeys = Object.keys(updatedValues);
- }
- const updates = Object.keys(updatedValues);
- const numUpdates = updates.length;
- const validatorsToExecute = [];
- const validationErrors = [];
- const alreadyValidated = [];
- const context = query;
- function iter(i, v) {
- const schemaPath = schema._getSchema(updates[i]);
- if (schemaPath == null) {
- return;
- }
- if (schemaPath.instance === 'Mixed' && schemaPath.path !== updates[i]) {
- return;
- }
- if (v && Array.isArray(v.$in)) {
- v.$in.forEach((v, i) => {
- validatorsToExecute.push(function(callback) {
- schemaPath.doValidate(
- v,
- function(err) {
- if (err) {
- err.path = updates[i] + '.$in.' + i;
- validationErrors.push(err);
- }
- callback(null);
- },
- context,
- { updateValidator: true });
- });
- });
- } else {
- if (isPull[updates[i]] &&
- schemaPath.$isMongooseArray) {
- return;
- }
- if (schemaPath.$isMongooseDocumentArrayElement && v != null && v.$__ != null) {
- alreadyValidated.push(updates[i]);
- validatorsToExecute.push(function(callback) {
- schemaPath.doValidate(v, function(err) {
- if (err) {
- if (err.errors) {
- for (const key of Object.keys(err.errors)) {
- const _err = err.errors[key];
- _err.path = updates[i] + '.' + key;
- validationErrors.push(_err);
- }
- } else {
- err.path = updates[i];
- validationErrors.push(err);
- }
- }
- return callback(null);
- }, context, { updateValidator: true });
- });
- } else {
- validatorsToExecute.push(function(callback) {
- for (const path of alreadyValidated) {
- if (updates[i].startsWith(path + '.')) {
- return callback(null);
- }
- }
- schemaPath.doValidate(v, function(err) {
- if (schemaPath.schema != null &&
- schemaPath.schema.options.storeSubdocValidationError === false &&
- err instanceof ValidationError) {
- return callback(null);
- }
- if (err) {
- err.path = updates[i];
- validationErrors.push(err);
- }
- callback(null);
- }, context, { updateValidator: true });
- });
- }
- }
- }
- for (i = 0; i < numUpdates; ++i) {
- iter(i, updatedValues[updates[i]]);
- }
- const arrayUpdates = Object.keys(arrayAtomicUpdates);
- for (const arrayUpdate of arrayUpdates) {
- let schemaPath = schema._getSchema(arrayUpdate);
- if (schemaPath && schemaPath.$isMongooseDocumentArray) {
- validatorsToExecute.push(function(callback) {
- schemaPath.doValidate(
- arrayAtomicUpdates[arrayUpdate],
- getValidationCallback(arrayUpdate, validationErrors, callback),
- options && options.context === 'query' ? query : null);
- });
- } else {
- schemaPath = schema._getSchema(arrayUpdate + '.0');
- for (const atomicUpdate of arrayAtomicUpdates[arrayUpdate]) {
- validatorsToExecute.push(function(callback) {
- schemaPath.doValidate(
- atomicUpdate,
- getValidationCallback(arrayUpdate, validationErrors, callback),
- options && options.context === 'query' ? query : null,
- { updateValidator: true });
- });
- }
- }
- }
- if (callback != null) {
- let numValidators = validatorsToExecute.length;
- if (numValidators === 0) {
- return _done(callback);
- }
- for (const validator of validatorsToExecute) {
- validator(function() {
- if (--numValidators <= 0) {
- _done(callback);
- }
- });
- }
- return;
- }
- return function(callback) {
- let numValidators = validatorsToExecute.length;
- if (numValidators === 0) {
- return _done(callback);
- }
- for (const validator of validatorsToExecute) {
- validator(function() {
- if (--numValidators <= 0) {
- _done(callback);
- }
- });
- }
- };
- function _done(callback) {
- if (validationErrors.length) {
- const err = new ValidationError(null);
- for (const validationError of validationErrors) {
- err.addError(validationError.path, validationError);
- }
- return callback(err);
- }
- callback(null);
- }
- function getValidationCallback(arrayUpdate, validationErrors, callback) {
- return function(err) {
- if (err) {
- err.path = arrayUpdate;
- validationErrors.push(err);
- }
- callback(null);
- };
- }
- };
|