diff --git a/lib/document.js b/lib/document.js index afc9e5ceac..1f0e9d332a 100644 --- a/lib/document.js +++ b/lib/document.js @@ -2770,15 +2770,6 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) { } } - for (const path of paths) { - // Single nested paths (paths embedded under single nested subdocs) will - // be validated on their own when we call `validate()` on the subdoc itself. - // Re: gh-8468 - if (doc.$__schema.singleNestedPaths.hasOwnProperty(path)) { - paths.delete(path); - continue; - } - } if (Array.isArray(pathsToValidate)) { paths = _handlePathsToValidate(paths, pathsToValidate); @@ -2800,7 +2791,10 @@ function _getPathsToValidate(doc, pathsToValidate, pathsToSkip) { _v = _v.toObject({ transform: false }); } const flat = flatten(_v, pathToCheck, flattenOptions, doc.$__schema); - Object.keys(flat).forEach(addToPaths); + // Single nested paths (paths embedded under single nested subdocs) will + // be validated on their own when we call `validate()` on the subdoc itself. + // Re: gh-8468 + Object.keys(flat).filter(path => !doc.$__schema.singleNestedPaths.hasOwnProperty(path)).forEach(addToPaths); } } diff --git a/test/document.test.js b/test/document.test.js index f65479224d..f1563c3006 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -13782,6 +13782,64 @@ describe('document', function() { assert.strictEqual(doc.toObject().labPlots[0].lab.capacityLevelCeil, 4); assert.strictEqual(doc.toJSON().labPlots[0].lab.capacityLevelCeil, 4); }); + + it('calls required with correct context on single nested properties (gh-14788)', async function() { + const requiredCalls = []; + function createCustomSchema() { + return new mongoose.Schema({ + prop1: { + type: String, + required() { + requiredCalls.push(this); + return this.prop1 === undefined; + }, + validate: { + validator(prop1) { + if (this.prop2 !== null && prop1 != null) { + throw new Error('cannot use prop1 if prop2 is defined!'); + } + return true; + } + } + }, + prop2: { + type: String, + required() { + requiredCalls.push(this); + return this.prop2 === undefined; + }, + validate: { + validator(prop2) { + if (this.prop1 === null && prop2 === null) { + throw new Error('cannot be null if prop1 is missing!'); + } + return true; + } + } + } + }, { _id: false }); + } + + const schema = new mongoose.Schema({ + config: { + prop: { + type: createCustomSchema(), + required: true + } + } + }); + + const TestModel = db.model('Test', schema); + const doc = new TestModel({ + config: { + prop: { prop1: null, prop2: 'test-value' } + } + }); + await doc.validate(); + assert.equal(requiredCalls.length, 2); + assert.strictEqual(requiredCalls[0], doc.config.prop); + assert.strictEqual(requiredCalls[1], doc.config.prop); + }); }); describe('Check if instance function that is supplied in schema option is available', function() {