From 4332472479d10dfe27f11641b054c92cab34beee Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Sun, 23 Apr 2023 15:02:11 -0400 Subject: [PATCH] fix(schema): correctly handle uuids with `populate()` Fix #13267 --- lib/schema/uuid.js | 18 ++++++++++-------- lib/schematype.js | 1 + lib/utils.js | 2 ++ test/schema.uuid.test.js | 24 ++++++++++++++++++++++-- 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 956c8faa8a1..8c16b84ef13 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -8,7 +8,6 @@ const MongooseBuffer = require('../types/buffer'); const SchemaType = require('../schematype'); const CastError = SchemaType.CastError; const utils = require('../utils'); -const isBsonType = require('../helpers/isBsonType'); const handleBitwiseOperator = require('./operators/bitwise'); const UUID_FORMAT = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i; @@ -86,7 +85,13 @@ function binaryToString(uuidBin) { function SchemaUUID(key, options) { SchemaType.call(this, key, options, 'UUID'); - this.getters.push(binaryToString); + this.getters.push(function(value) { + // For populated + if (value != null && value.$__ != null) { + return value; + } + return binaryToString(value); + }); } /** @@ -110,7 +115,7 @@ SchemaUUID.prototype.constructor = SchemaUUID; */ SchemaUUID._cast = function(value) { - if (value === null) { + if (value == null) { return value; } @@ -247,11 +252,8 @@ SchemaUUID.prototype.checkRequired = function checkRequired(value) { */ SchemaUUID.prototype.cast = function(value, doc, init) { - if (SchemaType._isRef(this, value, doc, init)) { - if (isBsonType(value, 'UUID')) { - return value; - } - + if (utils.isNonBuiltinObject(value) && + SchemaType._isRef(this, value, doc, init)) { return this._castRef(value, doc, init); } diff --git a/lib/schematype.js b/lib/schematype.js index eef7452740e..1005f4f9d67 100644 --- a/lib/schematype.js +++ b/lib/schematype.js @@ -1522,6 +1522,7 @@ SchemaType.prototype._castRef = function _castRef(value, doc, init) { const path = doc.$__fullPath(this.path, true); const owner = doc.ownerDocument(); const pop = owner.$populated(path, true); + let ret = value; if (!doc.$__.populated || !doc.$__.populated[path] || diff --git a/lib/utils.js b/lib/utils.js index 0762520697e..1a013d610d9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -4,6 +4,7 @@ * Module dependencies. */ +const UUID = require('bson').UUID; const ms = require('ms'); const mpath = require('mpath'); const ObjectId = require('./types/objectid'); @@ -395,6 +396,7 @@ exports.isNonBuiltinObject = function isNonBuiltinObject(val) { return typeof val === 'object' && !exports.isNativeObject(val) && !exports.isMongooseType(val) && + !(val instanceof UUID) && val != null; }; diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index 8292c233d35..cb35899fb6f 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -3,9 +3,9 @@ const start = require('./common'); const util = require('./util'); -const bson = require('bson'); - const assert = require('assert'); +const bson = require('bson'); +const { randomUUID } = require('crypto'); const mongoose = start.mongoose; const Schema = mongoose.Schema; @@ -129,6 +129,26 @@ describe('SchemaUUID', function() { assert.equal(organization, undefined); }); + it('works with populate (gh-13267)', async function() { + const userSchema = new mongoose.Schema({ + _id: { type: 'UUID', default: () => randomUUID() }, + name: String, + createdBy: { + type: 'UUID', + ref: 'User' + } + }); + const User = db.model('User', userSchema); + + const u1 = await User.create({ name: 'admin' }); + const { _id } = await User.create({ name: 'created', createdBy: u1._id }); + + const pop = await User.findById(_id).populate('createdBy'); + assert.equal(pop.createdBy.name, 'admin'); + + await pop.save(); + }); + // the following are TODOs based on SchemaUUID.prototype.$conditionalHandlers which are not tested yet it('should work with $bits* operators'); it('should work with $all operator');