You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							258 lines
						
					
					
						
							7.6 KiB
						
					
					
				
			
		
		
	
	
							258 lines
						
					
					
						
							7.6 KiB
						
					
					
				'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) {
 | 
						|
  let _keys;
 | 
						|
  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') {
 | 
						|
        _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 = options && options.context === 'query' ? query : null;
 | 
						|
  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) {
 | 
						|
              err.path = updates[i];
 | 
						|
              validationErrors.push(err);
 | 
						|
              return callback(null);
 | 
						|
            }
 | 
						|
 | 
						|
            v.validate(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);
 | 
						|
                }
 | 
						|
              }
 | 
						|
              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);
 | 
						|
    };
 | 
						|
  }
 | 
						|
};
 | 
						|
 |