diff --git a/lib/model.js b/lib/model.js index 3929413e76..c29bb5220b 100644 --- a/lib/model.js +++ b/lib/model.js @@ -3076,6 +3076,8 @@ Model.$__insertMany = function(arr, options, callback) { () => { callback(null, doc); }, error => { if (ordered === false) { + // Add index to validation error so users can identify which document failed + error.index = index; validationErrors.push(error); validationErrorsToOriginalOrder.set(error, index); results[index] = error; diff --git a/test/model.insertMany.test.js b/test/model.insertMany.test.js index db8c96b535..87ed26cf01 100644 --- a/test/model.insertMany.test.js +++ b/test/model.insertMany.test.js @@ -429,6 +429,66 @@ describe('insertMany()', function() { assert.ok(!err.mongoose.validationErrors[1].errors['name']); }); + it('insertMany() validation errors include index property with ordered false and rawResult', async function() { + const schema = new Schema({ + title: { type: String, required: true }, + requiredField: { type: String, required: true } + }); + const User = db.model('User', schema); + + const arr = [ + { title: 'title1', requiredField: 'field1' }, + { title: 'title2' }, // Missing requiredField + { requiredField: 'field3' }, // Missing title + { title: 'title4', requiredField: 'field4' } + ]; + const opts = { ordered: false, rawResult: true }; + const res = await User.insertMany(arr, opts); + + assert.equal(res.insertedCount, 2); + assert.equal(res.mongoose.validationErrors.length, 2); + + // Check first validation error (index 1) + const error1 = res.mongoose.validationErrors[0]; + assert.equal(error1.index, 1); + assert.ok(error1.errors['requiredField']); + + // Check second validation error (index 2) + const error2 = res.mongoose.validationErrors[1]; + assert.equal(error2.index, 2); + assert.ok(error2.errors['title']); + }); + + it('insertMany() validation errors include index property with throwOnValidationError', async function() { + const schema = new Schema({ + title: { type: String, required: true }, + requiredField: { type: String, required: true } + }); + const User = db.model('User', schema); + + const arr = [ + { title: 'title1', requiredField: 'field1' }, + { title: 'title2' }, // Missing requiredField + { requiredField: 'field3' }, // Missing title + { title: 'title4', requiredField: 'field4' } + ]; + const opts = { ordered: false, rawResult: true, throwOnValidationError: true }; + const err = await User.insertMany(arr, opts).then(() => null, err => err); + + assert.ok(err); + assert.equal(err.validationErrors.length, 2); + + // Check first validation error (index 1) + const error1 = err.validationErrors[0]; + assert.equal(error1.index, 1); + assert.ok(error1.errors['requiredField']); + + // Check second validation error (index 2) + const error2 = err.validationErrors[1]; + assert.equal(error2.index, 2); + assert.ok(error2.errors['title']); + }); + it('insertMany() populate option (gh-9720)', async function() { const schema = new Schema({ name: { type: String, required: true } diff --git a/types/error.d.ts b/types/error.d.ts index 1de6e62a58..cf34cb69fa 100644 --- a/types/error.d.ts +++ b/types/error.d.ts @@ -91,6 +91,9 @@ declare module 'mongoose' { errors: { [path: string]: ValidatorError | CastError }; addError: (path: string, error: ValidatorError | CastError) => void; + /** Index of the document in insertMany() that failed validation (only set for unordered insertMany) */ + index?: number; + constructor(instance?: MongooseError); }