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
			| 
											3 years ago
										 | '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); | ||
|  |     }; | ||
|  |   } | ||
|  | }; | ||
|  | 
 |