Skip to content
Closed
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
23 changes: 18 additions & 5 deletions lib/helpers/model/castBulkWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne,
model.schema.options.versionKey
);

updateOne['filter'] = cast(model.schema, updateOne['filter'], {
updateOne['filter'] = _castFilter(model, updateOne['filter'], {
strict: strict,
upsert: updateOne.upsert
});
Expand Down Expand Up @@ -210,7 +210,7 @@ module.exports.castUpdateMany = function castUpdateMany(originalModel, updateMan
model.schema.options.versionKey
);

updateMany['filter'] = cast(model.schema, updateMany['filter'], {
updateMany['filter'] = _castFilter(model, updateMany['filter'], {
strict: strict,
upsert: updateMany.upsert
});
Expand All @@ -230,7 +230,7 @@ module.exports.castReplaceOne = async function castReplaceOne(originalModel, rep
const strict = options.strict ?? model.schema.options.strict;

_addDiscriminatorToObject(schema, replaceOne['filter']);
replaceOne['filter'] = cast(model.schema, replaceOne['filter'], {
replaceOne['filter'] = _castFilter(model, replaceOne['filter'], {
strict: strict,
upsert: replaceOne.upsert
});
Expand Down Expand Up @@ -264,7 +264,7 @@ module.exports.castDeleteOne = function castDeleteOne(originalModel, deleteOne)

_addDiscriminatorToObject(schema, deleteOne['filter']);

deleteOne['filter'] = cast(model.schema, deleteOne['filter']);
deleteOne['filter'] = _castFilter(model, deleteOne['filter']);
};

module.exports.castDeleteMany = function castDeleteMany(originalModel, deleteMany) {
Expand All @@ -273,7 +273,7 @@ module.exports.castDeleteMany = function castDeleteMany(originalModel, deleteMan

_addDiscriminatorToObject(schema, deleteMany['filter']);

deleteMany['filter'] = cast(model.schema, deleteMany['filter']);
deleteMany['filter'] = _castFilter(model, deleteMany['filter']);
};

module.exports.cast = {
Expand All @@ -285,6 +285,19 @@ module.exports.cast = {
deleteMany: module.exports.castDeleteMany
};

function _castFilter(model, filter, options) {
try {
return cast(model.schema, filter, options);
} catch (err) {
// Set the model on cast errors so `{MODEL}` gets replaced, like it is for
// query filter casting (gh-8300)
if (typeof err.setModel === 'function') {
err.setModel(model);
}
throw err;
}
}

function _addDiscriminatorToObject(schema, obj) {
if (schema == null) {
return;
Expand Down
8 changes: 8 additions & 0 deletions lib/helpers/query/castUpdate.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,14 @@ function walkUpdatePath(schema, obj, op, options, context, filter, prefix) {
*/

function _appendError(error, query, key, aggregatedError) {
// Set the model on cast errors so `{MODEL}` gets replaced, like it is for
// query filter casting and document validation (gh-8300). `query` is a
// Query, or the model itself when casting a `bulkWrite()` operation.
const model = query?.modelName != null ? query : query?.model;
Comment on lines 501 to +505
if (model != null && typeof error.setModel === 'function') {
error.setModel(model);
}

if (typeof query !== 'object' || !query.options.multipleCastError) {
throw error;
}
Expand Down
33 changes: 33 additions & 0 deletions test/schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2827,6 +2827,39 @@ describe('schema', function() {
'"twenty" is not a valid number for model gh8300_castObject'
);
});

it('replaces {MODEL} with model name in update and bulkWrite() cast errors', async function() {
const schema = Schema({
age: {
type: Number,
cast: '{VALUE} is not a valid number for model {MODEL}'
}
});
const Test = db.model('gh8300_update', schema);
const message = '"twenty" is not a valid number for model gh8300_update';

const isCastError = err => err.name === 'CastError' && err.message === message;

await assert.rejects(() => Test.updateOne({}, { age: 'twenty' }), isCastError);
await assert.rejects(() => Test.updateMany({}, { $set: { age: 'twenty' } }), isCastError);
await assert.rejects(() => Test.findOneAndUpdate({}, { age: 'twenty' }), isCastError);

await assert.rejects(
() => Test.updateOne({}, { age: 'twenty' }, { multipleCastError: true }),
err => err.name === 'ValidationError' &&
err.errors['age'].message === message &&
err.message === 'Validation failed: age: ' + message
);

await assert.rejects(
() => Test.bulkWrite([{ updateOne: { filter: {}, update: { age: 'twenty' } } }]),
isCastError
);
await assert.rejects(
() => Test.bulkWrite([{ deleteOne: { filter: { age: 'twenty' } } }]),
isCastError
);
});
});

it('copies `.add()`-ed paths when calling `.add()` with a schema argument (gh-8429)', function() {
Expand Down
Loading