Skip to content

Commit

Permalink
Merge pull request #13827 from Automattic/vkarpov15/gh-12668
Browse files Browse the repository at this point in the history
BREAKING CHANGE: make `Model.validate()` use `Model.castObject()` to cast, and return casted copy of object instead of modifying in place
  • Loading branch information
vkarpov15 committed Sep 8, 2023
2 parents 1f79e2c + e7f74c8 commit 49a00ea
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 20 deletions.
32 changes: 15 additions & 17 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -4064,7 +4064,7 @@ Model.aggregate = function aggregate(pipeline, options) {
* @param {Object} obj
* @param {Object|Array|String} pathsOrOptions
* @param {Object} [context]
* @return {Promise|undefined}
* @return {Promise<Object>} casted and validated copy of `obj` if validation succeeded
* @api public
*/

Expand Down Expand Up @@ -4123,8 +4123,19 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
pushNestedArrayPaths(paths, val, path);
}

let remaining = paths.length;
let error = null;
paths = new Set(paths);

try {
obj = this.castObject(obj);
} catch (err) {
error = err;
for (const key of Object.keys(error.errors || {})) {
paths.delete(key);
}
}

let remaining = paths.size;

return new Promise((resolve, reject) => {
for (const path of paths) {
Expand All @@ -4140,20 +4151,7 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
cur = cur[pieces[i]];
}

let val = get(obj, path, void 0);

if (val != null) {
try {
val = schemaType.cast(val);
cur[pieces[pieces.length - 1]] = val;
} catch (err) {
error = error || new ValidationError();
error.addError(path, err);

_checkDone();
continue;
}
}
const val = get(obj, path, void 0);

schemaType.doValidate(val, err => {
if (err) {
Expand All @@ -4169,7 +4167,7 @@ Model.validate = async function validate(obj, pathsOrOptions, context) {
if (error) {
reject(error);
} else {
resolve();
resolve(obj);
}
}
}
Expand Down
29 changes: 26 additions & 3 deletions test/model.validate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('model: validate: ', function() {
assert.deepEqual(Object.keys(err.errors), ['comments.name']);

obj = { age: '42' };
await Model.validate(obj, ['age']);
obj = await Model.validate(obj, ['age']);
assert.strictEqual(obj.age, 42);
});

Expand Down Expand Up @@ -106,7 +106,7 @@ describe('model: validate: ', function() {

const test = { docs: ['6132655f2cdb9d94eaebc09b'] };

const err = await Test.validate(test);
const err = await Test.validate(test).then(() => null, err => err);
assert.ifError(err);
});

Expand All @@ -124,7 +124,7 @@ describe('model: validate: ', function() {
const User = mongoose.model('User', userSchema);

const user = new User({ name: 'test', nameRequired: false });
const err = await User.validate(user).catch(err => err);
const err = await User.validate(user).then(() => null, err => err);

assert.ifError(err);

Expand Down Expand Up @@ -181,6 +181,29 @@ describe('model: validate: ', function() {
await assert.rejects(async() => {
await Test.validate(test, pathsOrOptions);
}, { message: 'Validation failed: name: Validator failed for path `name` with value `1`' });
});

it('runs validation on casted paths even if cast error happened', async function() {
const Model = mongoose.model('Test', new Schema({
invalid1: {
type: String,
validate: () => false
},
myNumber: {
type: Number,
required: true
},
invalid2: {
type: String,
validate: () => false
}
}));

const err = await Model.validate({ invalid1: 'foo', myNumber: 'not a number', invalid2: 'bar' }).
then(() => null, err => err);
assert.ok(err);
assert.deepEqual(Object.keys(err.errors).sort(), ['invalid1', 'invalid2', 'myNumber']);
assert.equal(err.errors['myNumber'].name, 'CastError');
assert.equal(err.errors['invalid1'].name, 'ValidatorError');
});
});

0 comments on commit 49a00ea

Please sign in to comment.