Skip to content

Binary UUID is not recognize as Bson Type when need to be cast #13267

@ystreibel

Description

@ystreibel

Prerequisites

  • I have written a descriptive issue title

Mongoose version

7.0.3

Node.js version

v16.13.1

MongoDB version

4.2

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

No response

Issue

When mongoose try to populate a document I have this error

TypeError: Cannot read properties of undefined (reading 'options')
    at SchemaUUID._castRef (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/schematype.js:1531:19)
    at SchemaUUID.cast (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/schema/uuid.js:255:17)
    at _init (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/document.js:781:35)
    at init (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/document.js:735:5)
    at EmbeddedDocument.Document.$__init (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/document.js:694:3)
    at EmbeddedDocument.syncWrapper [as $__init] (/Users/username/Projects/applications/mybackend/node_modules/kareem/index.js:331:25)
    at EmbeddedDocument.Document.init (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/document.js:638:8)
    at EmbeddedDocument.Document.$init (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/document.js:654:42)
    at DocumentArrayPath.cast (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/schema/documentarray.js:495:30)
    at _init (/Users/username/Projects/applications/mybackend/node_modules/mongoose/lib/document.js:781:35)

It du to cast function in uuid.js, it doesn't have binary or embeded case.

SchemaUUID.prototype.cast = function(value, doc, init) {
  if (SchemaType._isRef(this, value, doc, init)) {
    if (isBsonType(value, 'UUID')) {
      return value;
    }

    return this._castRef(value, doc, init);
  }

On stackoverflow some people talk about override it as

SchemaUUID.prototype.cast = function (value, doc, init) {

console.log("cast", value, doc, init)

  if (value instanceof mongoose.Types.Buffer.Binary) {
    if (init && doc instanceof mongoose.Types.Embedded) {
      return getter(value);
    }
    return value;
  }

  if (typeof value === 'string') {
    var uuidBuffer = new mongoose.Types.Buffer(uuidParse.parse(value));

    uuidBuffer.subtype(bson.Binary.SUBTYPE_UUID);

    return uuidBuffer.toObject();
  }

  throw new Error('Could not cast ' + value + ' to UUID.');
};

This is my user schema

'use strict';
var { randomUUID } = require('crypto');
var mongoose = require('mongoose');
var mongoosePaginate = require('mongoose-paginate-v2');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');

var UserSchema = new Schema({
                                _id: { type: Schema.Types.UUID, default: () => randomUUID() },
                                username: {type: String, unique: true},
                                datacollect_id: { type: Schema.Types.UUID, required: true},
                                password: {type: String},
                                age: {type: Number},
                                gender: {type: String, enum: ["woman", "man"]},
                                handedness: {type: String, enum: ["left", "right"]},
                                language: { type:  Schema.Types.UUID, ref: 'Language'},
                                isTest: {type: Boolean, required: true, default: false},
                                isMyScriptSSOAccount: {type: Boolean, required: true, default: false},
                                swipeExperience: {type: String, enum: ["never", "first_experience", "regular_user", "expert"]},
                                keyboardExperience: {type: String, enum: ["never", "first_experience", "regular_user", "expert"]},
                                created: {type: Date, default: Date.now, required: true},
                                creationBy: { type:  Schema.Types.UUID, ref: 'User', required: true },
                                expirationDate: {type: Date},
                                updateDate: {type: Date, required: true},
                                enabled: {type: Boolean, required: true},
                                roles: {
                                    type: [String], enum: ["USER", "SCRIPTER", "CLEANER", "COLLECTER", "ADMIN"], required: true
                                },
                                collects: [{
                                    collect: { type:  Schema.Types.UUID, ref: 'Collect', required: true},
                                    sessions: {type: [{
                                        stepsIndex: {type: Number, required: true},
                                        inksCreated: {type: Number, required: true},
                                        inksGoal: {type: Number, required: true},
                                        beginDate: {type: Date, required: true},
                                        endDate: {type: Date},
                                        updateDate: {type: Date, required: true}
                                    }]}
                                }]
                            },
                            {
                                id: false
                            });

UserSchema.set('toObject', {getters: true});

UserSchema.set('toJSON', {getters: true});

UserSchema.plugin(mongoosePaginate);

UserSchema.pre('save', function (next) {
    var user = this;
    if (this.password && (this.isModified('password') || this.isNew)) {
        bcrypt.genSalt(10, function (err, salt) {
            if (err) {
                return next(err);
            }
            bcrypt.hash(user.password, salt, function (err, hash) {
                if (err) {
                    return next(err);
                }
                user.password = hash;
                next();
            });
        });
    } else {
        return next();
    }
});

UserSchema.methods.comparePassword = function (passw, cb) {
    bcrypt.compare(passw, this.password, function (err, isMatch) {
        if (err) {
            return cb(err);
        }
        cb(null, isMatch);
    });
};

module.exports = mongoose.model('User', UserSchema);

This is how I called the populate on user schema

function getUserByToken(payload, done) {
    var id = payload.userId,
        options = [
            {path: 'language'},
            {path: 'creationBy', select: '-password -collects'},
            {path: "collects.collect", populate: [
                    {path: "language"},
                    {path: "creationBy", select: "-password -collects"},
                    {path: "steps.inputMethod.keyboard", model: "Keyboard", populate: {path: "language"}},
                    {path: "steps.labelsSettings.dataFormat"},
                    {path: "steps.labelsSettings.language"}
                ]}
        ];

    User.findOne({_id: id})
        .select("-password")
        .populate(options)
        .then(user => {
            if (user) {
                done(null, user);
            } else {
                done(null, false);
            }
        })
        .catch(err => {
            return done(err, false);
        });
}

Language and collect references are in errors.

Is there a way to improve that ?

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugWe've confirmed this is a bug in Mongoose and will fix it.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions