Skip to content

Commit

Permalink
fix(model): ensure consistent ordering of validation errors in `inser…
Browse files Browse the repository at this point in the history
…tMany()` with `ordered: false` and `rawResult: true`

Re: #12791
  • Loading branch information
vkarpov15 committed Jan 3, 2023
1 parent 2aee7c1 commit a90f97c
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 2 deletions.
18 changes: 17 additions & 1 deletion lib/model.js
Expand Up @@ -3411,7 +3411,8 @@ Model.$__insertMany = function(arr, options, callback) {
}

const validationErrors = [];
const toExecute = arr.map(doc =>
const validationErrorsToOriginalOrder = new Map();
const toExecute = arr.map((doc, index) =>
callback => {
if (!(doc instanceof _this)) {
try {
Expand All @@ -3437,6 +3438,7 @@ Model.$__insertMany = function(arr, options, callback) {
// failed. It's up to the next function to filter out all failed models
if (ordered === false) {
validationErrors.push(error);
validationErrorsToOriginalOrder.set(error, index);
return callback(null, null);
}
return callback(error);
Expand All @@ -3454,10 +3456,24 @@ Model.$__insertMany = function(arr, options, callback) {
const docAttributes = docs.filter(function(doc) {
return doc != null;
});

// Make sure validation errors are in the same order as the
// original documents, so if both doc1 and doc2 both fail validation,
// `Model.insertMany([doc1, doc2])` will always have doc1's validation
// error before doc2's. Re: gh-12791.
if (validationErrors.length > 0) {
validationErrors.sort((err1, err2) => {
return validationErrorsToOriginalOrder.get(err1) - validationErrorsToOriginalOrder.get(err2);
});
}

// Quickly escape while there aren't any valid docAttributes
if (docAttributes.length === 0) {
if (rawResult) {
const res = {
acknowledged: true,
insertedCount: 0,
insertedIds: {},
mongoose: {
validationErrors: validationErrors
}
Expand Down
34 changes: 33 additions & 1 deletion test/model.test.js
Expand Up @@ -4753,7 +4753,7 @@ describe('Model', function() {
});
});

it('insertMany() validation error with ordered true when all documents are invalid', function(done) {
it('insertMany() validation error with ordered true when all documents are invalid (', function(done) {
const schema = new Schema({
name: { type: String, required: true }
});
Expand Down Expand Up @@ -4791,6 +4791,38 @@ describe('Model', function() {
});
});

it('insertMany() validation error with ordered false and rawResult for checking which documents failed (gh-12791)', async function() {
const schema = new Schema({
name: { type: String, required: true },
year: { type: Number, required: true }
});
const Movie = db.model('Movie', schema);

const id1 = new mongoose.Types.ObjectId();
const id2 = new mongoose.Types.ObjectId();
const id3 = new mongoose.Types.ObjectId();
const arr = [
{ _id: id1, foo: 'The Phantom Menace', year: 1999 },
{ _id: id2, name: 'The Force Awakens', bar: 2015 },
{ _id: id3, name: 'The Empire Strikes Back', year: 1980 }
];
const opts = { ordered: false, rawResult: true };
const res = await Movie.insertMany(arr, opts);
// {
// acknowledged: true,
// insertedCount: 1,
// insertedIds: { '0': new ObjectId("63b34b062cfe38622738e510") },
// mongoose: { validationErrors: [ [Error], [Error] ] }
// }
assert.equal(res.insertedCount, 1);
assert.equal(res.insertedIds[0].toHexString(), id3.toHexString());
assert.equal(res.mongoose.validationErrors.length, 2);
assert.ok(res.mongoose.validationErrors[0].errors['name']);
assert.ok(!res.mongoose.validationErrors[0].errors['year']);
assert.ok(res.mongoose.validationErrors[1].errors['year']);
assert.ok(!res.mongoose.validationErrors[1].errors['name']);
});

it('insertMany() populate option (gh-9720)', async function() {
const schema = new Schema({
name: { type: String, required: true }
Expand Down

0 comments on commit a90f97c

Please sign in to comment.