Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/migrating_to_9.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Migrating from 8.x to 9.x

<style>
ul > li {
padding: 4px 0px;
}
</style>

There are several backwards-breaking changes you should be aware of when migrating from Mongoose 8.x to Mongoose 9.x.

If you're still on Mongoose 7.x or earlier, please read the [Mongoose 7.x to 8.x migration guide](migrating_to_8.html) and upgrade to Mongoose 8.x first before upgrading to Mongoose 9.

## `Schema.prototype.doValidate()` now returns a promise

`Schema.prototype.doValidate()` now returns a promise that rejects with a validation error if one occurred.
In Mongoose 8.x, `doValidate()` took a callback and did not return a promise.

```javascript
// Mongoose 8.x function signature
function doValidate(value, cb, scope, options) {}

// Mongoose 8.x example usage
schema.doValidate(value, function(error) {
if (error) {
// Handle validation error
}
}, scope, options);

// Mongoose 9.x function signature
async function doValidate(value, scope, options) {}

// Mongoose 9.x example usage
try {
await schema.doValidate(value, scope, options);
} catch (error) {
// Handle validation error
}
```
38 changes: 19 additions & 19 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -2880,9 +2880,9 @@ function _pushNestedArrayPaths(val, paths, path) {
* ignore
*/

Document.prototype._execDocumentPreHooks = function _execDocumentPreHooks(opName) {
Document.prototype._execDocumentPreHooks = function _execDocumentPreHooks(opName, ...args) {
return new Promise((resolve, reject) => {
this.constructor._middleware.execPre(opName, this, [], (error) => {
this.constructor._middleware.execPre(opName, this, [...args], (error) => {
if (error != null) {
reject(error);
return;
Expand Down Expand Up @@ -2912,7 +2912,12 @@ Document.prototype._execDocumentPostHooks = function _execDocumentPostHooks(opNa
*/

Document.prototype.$__validate = async function $__validate(pathsToValidate, options) {
await this._execDocumentPreHooks('validate');
try {
await this._execDocumentPreHooks('validate');
} catch (error) {
await this._execDocumentPostHooks('validate', error);
return;
}

if (this.$__.saveOptions && this.$__.saveOptions.pathsToSave && !pathsToValidate) {
pathsToValidate = [...this.$__.saveOptions.pathsToSave];
Expand Down Expand Up @@ -3085,22 +3090,17 @@ Document.prototype.$__validate = async function $__validate(pathsToValidate, opt
_nestedValidate: true
};

await new Promise((resolve) => {
schemaType.doValidate(val, function doValidateCallback(err) {
if (err) {
const isSubdoc = schemaType.$isSingleNested ||
schemaType.$isArraySubdocument ||
schemaType.$isMongooseDocumentArray;
if (isSubdoc && err instanceof ValidationError) {
return resolve();
}
_this.invalidate(path, err, undefined, true);
resolve();
} else {
resolve();
}
}, scope, doValidateOptions);
});
try {
await schemaType.doValidate(val, scope, doValidateOptions);
} catch (err) {
const isSubdoc = schemaType.$isSingleNested ||
schemaType.$isArraySubdocument ||
schemaType.$isMongooseDocumentArray;
if (isSubdoc && err instanceof ValidationError) {
return;
}
_this.invalidate(path, err, undefined, true);
}
}
};

Expand Down
5 changes: 3 additions & 2 deletions lib/helpers/model/applyHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ module.exports = applyHooks;

applyHooks.middlewareFunctions = [
'deleteOne',
'save',
'remove',
'save',
'updateOne',
'validate',
'init'
];

Expand Down Expand Up @@ -119,7 +120,7 @@ function applyHooks(model, schema, options) {

model._middleware = middleware;

const internalMethodsToWrap = options && options.isChildSchema ? ['save', 'deleteOne'] : ['save'];
const internalMethodsToWrap = options && options.isChildSchema ? ['deleteOne'] : [];
for (const method of internalMethodsToWrap) {
const toWrap = `$__${method}`;
const wrapped = middleware.
Expand Down
162 changes: 54 additions & 108 deletions lib/helpers/updateValidators.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const modifiedPaths = require('./common').modifiedPaths;
* @api private
*/

module.exports = function(query, schema, castedDoc, options, callback) {
module.exports = async function updateValidators(query, schema, castedDoc, options) {
const keys = Object.keys(castedDoc || {});
let updatedKeys = {};
let updatedValues = {};
Expand All @@ -32,9 +32,8 @@ module.exports = function(query, schema, castedDoc, options, callback) {
const modified = {};
let currentUpdate;
let key;
let i;

for (i = 0; i < numKeys; ++i) {
for (let i = 0; i < numKeys; ++i) {
if (keys[i].startsWith('$')) {
hasDollarUpdate = true;
if (keys[i] === '$push' || keys[i] === '$addToSet') {
Expand Down Expand Up @@ -89,161 +88,108 @@ module.exports = function(query, schema, castedDoc, options, callback) {
const alreadyValidated = [];

const context = query;
function iter(i, v) {
for (let i = 0; i < numUpdates; ++i) {
const v = updatedValues[updates[i]];
const schemaPath = schema._getSchema(updates[i]);
if (schemaPath == null) {
return;
continue;
}
if (schemaPath.instance === 'Mixed' && schemaPath.path !== updates[i]) {
return;
continue;
}

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 });
});
validatorsToExecute.push(
schemaPath.doValidate(v, context, { updateValidator: true }).catch(err => {
err.path = updates[i] + '.$in.' + i;
validationErrors.push(err);
})
);
});
} else {
if (isPull[updates[i]] &&
schemaPath.$isMongooseArray) {
return;
continue;
}

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);
validatorsToExecute.push(
schemaPath.doValidate(v, context, { updateValidator: true }).catch(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);
}
for (const path of alreadyValidated) {
if (updates[i].startsWith(path + '.')) {
continue;
}

schemaPath.doValidate(v, function(err) {
}
validatorsToExecute.push(
schemaPath.doValidate(v, context, { updateValidator: true }).catch(err => {
if (schemaPath.schema != null &&
schemaPath.schema.options.storeSubdocValidationError === false &&
err instanceof ValidationError) {
return callback(null);
return;
}

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) {
validatorsToExecute.push(
schemaPath.doValidate(
arrayAtomicUpdates[arrayUpdate],
getValidationCallback(arrayUpdate, validationErrors, callback),
options && options.context === 'query' ? query : null);
});
options && options.context === 'query' ? query : null
).catch(err => {
err.path = arrayUpdate;
validationErrors.push(err);
})
);
} else {
schemaPath = schema._getSchema(arrayUpdate + '.0');
for (const atomicUpdate of arrayAtomicUpdates[arrayUpdate]) {
validatorsToExecute.push(function(callback) {
validatorsToExecute.push(
schemaPath.doValidate(
atomicUpdate,
getValidationCallback(arrayUpdate, validationErrors, callback),
options && options.context === 'query' ? query : null,
{ updateValidator: true });
});
{ updateValidator: true }
).catch(err => {
err.path = arrayUpdate;
validationErrors.push(err);
})
);
}
}
}

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);
}
});
}
};
await Promise.all(validatorsToExecute);
if (validationErrors.length) {
const err = new ValidationError(null);

function _done(callback) {
if (validationErrors.length) {
const err = new ValidationError(null);

for (const validationError of validationErrors) {
err.addError(validationError.path, validationError);
}

return callback(err);
for (const validationError of validationErrors) {
err.addError(validationError.path, validationError);
}
callback(null);
}

function getValidationCallback(arrayUpdate, validationErrors, callback) {
return function(err) {
if (err) {
err.path = arrayUpdate;
validationErrors.push(err);
}
callback(null);
};
throw err;
}
};

Loading
Loading