From 93c2f84ed6e3fafbe009e8b3a6ccc539772fe706 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Fri, 12 Aug 2022 15:12:13 +0200 Subject: [PATCH 01/19] feat: WIP add UUIDv1 --- lib/schema/index.js | 2 + lib/schema/uuidv1.js | 346 +++++++++++++++++++++++++++++++++++++ test/schema.uuidv1.test.js | 58 +++++++ 3 files changed, 406 insertions(+) create mode 100644 lib/schema/uuidv1.js create mode 100644 test/schema.uuidv1.test.js diff --git a/lib/schema/index.js b/lib/schema/index.js index 999bef7c693..a7bc7e89518 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -29,6 +29,8 @@ exports.Decimal128 = exports.Decimal = require('./decimal128'); exports.Map = require('./map'); +exports.UUIDv1 = require('./uuidv1'); + // alias exports.Oid = exports.ObjectId; diff --git a/lib/schema/uuidv1.js b/lib/schema/uuidv1.js new file mode 100644 index 00000000000..4e79a8fd706 --- /dev/null +++ b/lib/schema/uuidv1.js @@ -0,0 +1,346 @@ +/*! + * Module dependencies. + */ + +'use strict'; + +const MongooseBuffer = require('../types/buffer'); +const SchemaType = require('../schematype'); +const CastError = SchemaType.CastError; +const utils = require('../utils'); +const isBsonType = require('../helpers/isBsonType'); + +const UUID_FORMAT = /[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i; +const Binary = MongooseBuffer.Binary; + +/** + * Convert a hex-string to a buffer + * @param {String} hex The hex string to convert + * @returns {Buffer} The hex as buffer + */ + +function hex2buffer(hex) { + // const arr = []; + + // for (let i = 0; i < hex.length - 1; i += 2) { + // const h = hex.substring(i, i + 2 + 1); + // const n = parseInt(h, 16); + // arr.push(n); + // } + + // const buff = Buffer.from(arr); + // console.log('test', buff.toString('utf8')); + + // use buffer built-in function to convert from hex-string to buffer + const buff = Buffer.from(hex, 'hex'); + return buff; +} + +function binary2hex(buf) { + // const len = buf.length; + // let hex = ''; + // for (let j = 0; j < len; j++) { + // const n = buf.readUInt8(j); + // if (n < 16) { + // hex += '0' + n.toString(16); + // } else { + // hex += n.toString(16); + // } + // } + + // use buffer built-in function to convert from buffer to hex-string + const hex = buf.toString('hex'); + return hex; +} + +function stringToBinary(uuidStr) { + // Protect against undefined & throwing err + if (typeof uuidStr !== 'string') uuidStr = ''; + const hex = uuidStr.replace(/[{}-]/g, ''); // remove extra characters + const bytes = hex2buffer(hex); + const buff = new MongooseBuffer(bytes); + buff._subtype = 4; + + return buff; +} + +function binaryToString(uuidBin) { + const hex = binary2hex(uuidBin); + const uuidStr = hex.substring(0, 8) + '-' + hex.substring(8, 8 + 4) + '-' + hex.substring(12, 12 + 4) + '-' + hex.substring(16, 16 + 4) + '-' + hex.substring(20, 20 + 12); + return uuidStr; +} + +/** + * UUIDv1 SchemaType constructor. + * + * @param {String} key + * @param {Object} options + * @inherits SchemaType + * @api public + */ + +function SchemaUUIDv1(key, options) { + SchemaType.call(this, key, options, 'UUIDv1'); + this.getters.push(binaryToString); +} + +/** + * This schema type's name, to defend against minifiers that mangle + * function names. + * + * @api public + */ +SchemaUUIDv1.schemaName = 'UUIDv1'; + +SchemaUUIDv1.defaultOptions = {}; + +/*! + * Inherits from SchemaType. + */ +SchemaUUIDv1.prototype = Object.create(SchemaType.prototype); +SchemaUUIDv1.prototype.constructor = SchemaUUIDv1; + +/*! + * ignore + */ + +SchemaUUIDv1._cast = function(value, doc, init) { + if (value === null) { + return value; + } + + function newBuffer(initbuff) { + const buff = new MongooseBuffer(initbuff); + buff._subtype = 4; + return buff; + } + + if (typeof value === 'string') { + if (UUID_FORMAT.test(value)) { + return stringToBinary(value); + } else { + throw new CastError('uuidv1', value, this.path); + } + } + + if (Buffer.isBuffer(value)) { + return newBuffer(value); + } + + if (value instanceof Binary) { + return newBuffer(value.value(true)); + } + + // Re: gh-647 and gh-3030, we're ok with casting using `toString()` + // **unless** its the default Object.toString, because "[object Object]" + // doesn't really qualify as useful data + if (value.toString && value.toString !== Object.prototype.toString) { + if (UUID_FORMAT.test(value.toString())) { + return stringToBinary(value.toString()); + } + } + + throw new CastError('SchemaUUIDv1', value, this.path); +}; + +/** +// * Sets a default option for all Decimal128 instances. +// * +// * #### Example: +// * +// * // Make all decimal 128s have `required` of true by default. +// * mongoose.Schema.Decimal128.set('required', true); +// * +// * const User = mongoose.model('User', new Schema({ test: mongoose.Decimal128 })); +// * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * + * @param {String} option The option you'd like to set the value for + * @param {Any} value value for option + * @return {undefined} + * @function set + * @static + * @api public + */ + +SchemaUUIDv1.set = SchemaType.set; + +/** +// * Get/set the function used to cast arbitrary values to decimals. +// * +// * #### Example: +// * +// * // Make Mongoose only refuse to cast numbers as decimal128 +// * const original = mongoose.Schema.Types.Decimal128.cast(); +// * mongoose.Decimal128.cast(v => { +// * assert.ok(typeof v !== 'number'); +// * return original(v); +// * }); +// * +// * // Or disable casting entirely +// * mongoose.Decimal128.cast(false); + * + * @param {Function} [caster] + * @return {Function} + * @function get + * @static + * @api public + */ + +SchemaUUIDv1.cast = function cast(caster) { + if (arguments.length === 0) { + return this._cast; + } + if (caster === false) { + caster = this._defaultCaster; + } + this._cast = caster; + + return this._cast; +}; + +// /*! +// * ignore +// */ + +// SchemaUUIDv1._defaultCaster = v => { +// // throw new Error('TODO'); +// // if (v != null && !isBsonType(v, 'Decimal128')) { +// // throw new Error(); +// // } +// // return v; +// }; + +/*! + * ignore + */ + +SchemaUUIDv1._checkRequired = v => UUID_FORMAT.test(v); + +/** + * Override the function the required validator uses to check whether a string + * passes the `required` check. + * + * @param {Function} fn + * @return {Function} + * @function checkRequired + * @static + * @api public + */ + +SchemaUUIDv1.checkRequired = SchemaType.checkRequired; + +/** + * Check if the given value satisfies a required validator. + * + * @param {Any} value + * @param {Document} doc + * @return {Boolean} + * @api public + */ + +SchemaUUIDv1.prototype.checkRequired = function checkRequired(value, doc) { + return UUID_FORMAT.test(value); + // if (SchemaType._isRef(this, value, doc, true)) { + // return !!value; + // } + + // // `require('util').inherits()` does **not** copy static properties, and + // // plugins like mongoose-float use `inherits()` for pre-ES6. + // const _checkRequired = typeof this.constructor.checkRequired === 'function' ? + // this.constructor.checkRequired() : + // SchemaUUIDv1.checkRequired(); + + // return _checkRequired(value); +}; + +/** +// * Casts to Decimal128 + * + * @param {Object} value + * @param {Object} doc + * @param {Boolean} init whether this is an initialization cast + * @api private + */ + +SchemaUUIDv1.prototype.cast = function(value, doc, init) { + if (SchemaType._isRef(this, value, doc, init)) { + if (isBsonType(value, 'UUIDv1')) { + return value; + } + + return this._castRef(value, doc, init); + } + + let castFn; + if (typeof this._castFunction === 'function') { + castFn = this._castFunction; + } else if (typeof this.constructor.cast === 'function') { + castFn = this.constructor.cast(); + } else { + castFn = SchemaUUIDv1.cast(); + } + + try { + return castFn(value); + } catch (error) { + throw new CastError('SchemaUUIDv1', value, this.path, error, this); + } +}; + +/*! + * ignore + */ + +function handleSingle(val) { + return this.cast(val); +} + +/*! + * ignore + */ + +function handleArray(val) { + return val.map((m) => { + return this.cast(m); + }); +} + +SchemaUUIDv1.prototype.$conditionalHandlers = +utils.options(SchemaType.prototype.$conditionalHandlers, { + $all: handleArray, + $gt: handleSingle, + $gte: handleSingle, + $in: handleArray, + $lt: handleSingle, + $lte: handleSingle, + $ne: handleSingle, + $nin: handleArray, + $options: handleSingle, + $regex: handleSingle +}); + +/** + * Casts contents for queries. + * + * @param {String} $conditional + * @param {any} val + * @api private + */ + +SchemaUUIDv1.prototype.castForQuery = function($conditional, val) { + let handler; + if (arguments.length === 2) { + handler = this.$conditionalHandlers[$conditional]; + if (!handler) + throw new Error('Can\'t use ' + $conditional + ' with UUIDv1.'); + return handler.call(this, val); + } else { + return this.cast($conditional); + } +}; + +/*! + * Module exports. + */ + +module.exports = SchemaUUIDv1; diff --git a/test/schema.uuidv1.test.js b/test/schema.uuidv1.test.js new file mode 100644 index 00000000000..67fd6addc8f --- /dev/null +++ b/test/schema.uuidv1.test.js @@ -0,0 +1,58 @@ +'use strict'; + +const start = require('./common'); +const util = require('./util'); + +const assert = require('assert'); + +const mongoose = start.mongoose; +const Schema = mongoose.Schema; + +describe('SchemaUUIDv1', function() { + let Model; + let db; + + before(async function() { + const schema = new Schema({ x: { type: mongoose.Schema.Types.UUIDv1 } }); + mongoose.deleteModel(/Test/); + Model = mongoose.model('Test', schema); + db = await start(); + }); + + after(async function() { + await db.close(); + }); + + beforeEach(() => db.deleteModel(/.*/)); + afterEach(() => util.clearTestData(db)); + afterEach(() => util.stopRemainingOps(db)); + + it('basic functionality should work', async function() { + const doc = new Model({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' }); + assert.ifError(doc.validateSync()); + assert.ok(typeof doc.x === 'string'); + assert.strictEqual(doc.x, '09190f70-3d30-11e5-8814-0f4df9a59c41'); + console.log('1'); + await doc.save(); + console.log('2'); + + const query = Model.findOne({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' }); + assert.ok(query._conditions.x instanceof mongoose.Types.Buffer.Binary); + console.log('3'); + + const res = await query; + console.log('4'); + assert.ifError(res.validateSync()); + assert.ok(typeof res.x === 'string'); + assert.strictEqual(res.x, '09190f70-3d30-11e5-8814-0f4df9a59c41'); + }); + + it('should throw error in case of invalid string', function() { + const doc = new Model({ x: 'invalid' }); + const res = doc.validateSync(); + assert.ok(res !== null && res !== undefined); + const errors = res.errors; + assert.strictEqual(Object.keys(errors).length, 1); + assert.ok(errors.x instanceof mongoose.Error.CastError); + }); +}); From 4c50837c6574c39485b88272394bea7f8797284c Mon Sep 17 00:00:00 2001 From: hasezoey Date: Sat, 13 Aug 2022 13:52:56 +0200 Subject: [PATCH 02/19] feat: rename "SchemaUUIDv1" to "SchemaUUID" --- lib/schema/index.js | 2 +- lib/schema/{uuidv1.js => uuid.js} | 46 +++++++++---------- ...ema.uuidv1.test.js => schema.uuid.test.js} | 4 +- 3 files changed, 26 insertions(+), 26 deletions(-) rename lib/schema/{uuidv1.js => uuid.js} (85%) rename test/{schema.uuidv1.test.js => schema.uuid.test.js} (97%) diff --git a/lib/schema/index.js b/lib/schema/index.js index a7bc7e89518..f3eb9851ea6 100644 --- a/lib/schema/index.js +++ b/lib/schema/index.js @@ -29,7 +29,7 @@ exports.Decimal128 = exports.Decimal = require('./decimal128'); exports.Map = require('./map'); -exports.UUIDv1 = require('./uuidv1'); +exports.UUID = require('./uuid'); // alias diff --git a/lib/schema/uuidv1.js b/lib/schema/uuid.js similarity index 85% rename from lib/schema/uuidv1.js rename to lib/schema/uuid.js index 4e79a8fd706..d475e6f1c0e 100644 --- a/lib/schema/uuidv1.js +++ b/lib/schema/uuid.js @@ -10,7 +10,7 @@ const CastError = SchemaType.CastError; const utils = require('../utils'); const isBsonType = require('../helpers/isBsonType'); -const UUID_FORMAT = /[0-9a-f]{8}-[0-9a-f]{4}-1[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i; +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; const Binary = MongooseBuffer.Binary; /** @@ -79,8 +79,8 @@ function binaryToString(uuidBin) { * @api public */ -function SchemaUUIDv1(key, options) { - SchemaType.call(this, key, options, 'UUIDv1'); +function SchemaUUID(key, options) { + SchemaType.call(this, key, options, 'UUID'); this.getters.push(binaryToString); } @@ -90,21 +90,21 @@ function SchemaUUIDv1(key, options) { * * @api public */ -SchemaUUIDv1.schemaName = 'UUIDv1'; +SchemaUUID.schemaName = 'SchemaUUID'; -SchemaUUIDv1.defaultOptions = {}; +SchemaUUID.defaultOptions = {}; /*! * Inherits from SchemaType. */ -SchemaUUIDv1.prototype = Object.create(SchemaType.prototype); -SchemaUUIDv1.prototype.constructor = SchemaUUIDv1; +SchemaUUID.prototype = Object.create(SchemaType.prototype); +SchemaUUID.prototype.constructor = SchemaUUID; /*! * ignore */ -SchemaUUIDv1._cast = function(value, doc, init) { +SchemaUUID._cast = function(value, doc, init) { if (value === null) { return value; } @@ -119,7 +119,7 @@ SchemaUUIDv1._cast = function(value, doc, init) { if (UUID_FORMAT.test(value)) { return stringToBinary(value); } else { - throw new CastError('uuidv1', value, this.path); + throw new CastError(SchemaUUID.schemaName, value, this.path); } } @@ -140,7 +140,7 @@ SchemaUUIDv1._cast = function(value, doc, init) { } } - throw new CastError('SchemaUUIDv1', value, this.path); + throw new CastError(SchemaUUID.schemaName, value, this.path); }; /** @@ -162,7 +162,7 @@ SchemaUUIDv1._cast = function(value, doc, init) { * @api public */ -SchemaUUIDv1.set = SchemaType.set; +SchemaUUID.set = SchemaType.set; /** // * Get/set the function used to cast arbitrary values to decimals. @@ -186,7 +186,7 @@ SchemaUUIDv1.set = SchemaType.set; * @api public */ -SchemaUUIDv1.cast = function cast(caster) { +SchemaUUID.cast = function cast(caster) { if (arguments.length === 0) { return this._cast; } @@ -214,7 +214,7 @@ SchemaUUIDv1.cast = function cast(caster) { * ignore */ -SchemaUUIDv1._checkRequired = v => UUID_FORMAT.test(v); +SchemaUUID._checkRequired = v => UUID_FORMAT.test(v); /** * Override the function the required validator uses to check whether a string @@ -227,7 +227,7 @@ SchemaUUIDv1._checkRequired = v => UUID_FORMAT.test(v); * @api public */ -SchemaUUIDv1.checkRequired = SchemaType.checkRequired; +SchemaUUID.checkRequired = SchemaType.checkRequired; /** * Check if the given value satisfies a required validator. @@ -238,7 +238,7 @@ SchemaUUIDv1.checkRequired = SchemaType.checkRequired; * @api public */ -SchemaUUIDv1.prototype.checkRequired = function checkRequired(value, doc) { +SchemaUUID.prototype.checkRequired = function checkRequired(value, doc) { return UUID_FORMAT.test(value); // if (SchemaType._isRef(this, value, doc, true)) { // return !!value; @@ -262,9 +262,9 @@ SchemaUUIDv1.prototype.checkRequired = function checkRequired(value, doc) { * @api private */ -SchemaUUIDv1.prototype.cast = function(value, doc, init) { +SchemaUUID.prototype.cast = function(value, doc, init) { if (SchemaType._isRef(this, value, doc, init)) { - if (isBsonType(value, 'UUIDv1')) { + if (isBsonType(value, 'UUID')) { return value; } @@ -277,13 +277,13 @@ SchemaUUIDv1.prototype.cast = function(value, doc, init) { } else if (typeof this.constructor.cast === 'function') { castFn = this.constructor.cast(); } else { - castFn = SchemaUUIDv1.cast(); + castFn = SchemaUUID.cast(); } try { return castFn(value); } catch (error) { - throw new CastError('SchemaUUIDv1', value, this.path, error, this); + throw new CastError(SchemaUUID.schemaName, value, this.path, error, this); } }; @@ -305,7 +305,7 @@ function handleArray(val) { }); } -SchemaUUIDv1.prototype.$conditionalHandlers = +SchemaUUID.prototype.$conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, { $all: handleArray, $gt: handleSingle, @@ -327,12 +327,12 @@ utils.options(SchemaType.prototype.$conditionalHandlers, { * @api private */ -SchemaUUIDv1.prototype.castForQuery = function($conditional, val) { +SchemaUUID.prototype.castForQuery = function($conditional, val) { let handler; if (arguments.length === 2) { handler = this.$conditionalHandlers[$conditional]; if (!handler) - throw new Error('Can\'t use ' + $conditional + ' with UUIDv1.'); + throw new Error('Can\'t use ' + $conditional + ' with UUID.'); return handler.call(this, val); } else { return this.cast($conditional); @@ -343,4 +343,4 @@ SchemaUUIDv1.prototype.castForQuery = function($conditional, val) { * Module exports. */ -module.exports = SchemaUUIDv1; +module.exports = SchemaUUID; diff --git a/test/schema.uuidv1.test.js b/test/schema.uuid.test.js similarity index 97% rename from test/schema.uuidv1.test.js rename to test/schema.uuid.test.js index 67fd6addc8f..f043deeb7c6 100644 --- a/test/schema.uuidv1.test.js +++ b/test/schema.uuid.test.js @@ -8,12 +8,12 @@ const assert = require('assert'); const mongoose = start.mongoose; const Schema = mongoose.Schema; -describe('SchemaUUIDv1', function() { +describe('SchemaUUID', function() { let Model; let db; before(async function() { - const schema = new Schema({ x: { type: mongoose.Schema.Types.UUIDv1 } }); + const schema = new Schema({ x: { type: mongoose.Schema.Types.UUID } }); mongoose.deleteModel(/Test/); Model = mongoose.model('Test', schema); db = await start(); From a0459b3f85232a5da532808865b0232776f51fa0 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 12:19:58 +0200 Subject: [PATCH 03/19] fix(uuid): change "schemaName" to be without prefix "Schema" --- lib/schema/uuid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index d475e6f1c0e..3d0deb2fbee 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -90,7 +90,7 @@ function SchemaUUID(key, options) { * * @api public */ -SchemaUUID.schemaName = 'SchemaUUID'; +SchemaUUID.schemaName = 'UUID'; SchemaUUID.defaultOptions = {}; From db1076c95916794270e26bcc38bb1a36a342499f Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:12:12 +0200 Subject: [PATCH 04/19] test(schema.uuid): use actual opened connection instead of default --- test/schema.uuid.test.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index f043deeb7c6..e7ff87f7b0b 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -10,20 +10,22 @@ const Schema = mongoose.Schema; describe('SchemaUUID', function() { let Model; + let TestSchema; let db; before(async function() { - const schema = new Schema({ x: { type: mongoose.Schema.Types.UUID } }); - mongoose.deleteModel(/Test/); - Model = mongoose.model('Test', schema); - db = await start(); + TestSchema = new Schema({ x: { type: mongoose.Schema.Types.UUID } }); + db = await start().asPromise(); }); after(async function() { await db.close(); }); - beforeEach(() => db.deleteModel(/.*/)); + beforeEach(async() => { + await db.deleteModel(/.*/); + Model = db.model('Test', TestSchema); + }); afterEach(() => util.clearTestData(db)); afterEach(() => util.stopRemainingOps(db)); From d7b71cf487b596b408183f793e3f8ddf355dbf9c Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:12:48 +0200 Subject: [PATCH 05/19] test(schema.uuid): remove debug logging --- test/schema.uuid.test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index e7ff87f7b0b..a8ce48aeaac 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -34,16 +34,12 @@ describe('SchemaUUID', function() { assert.ifError(doc.validateSync()); assert.ok(typeof doc.x === 'string'); assert.strictEqual(doc.x, '09190f70-3d30-11e5-8814-0f4df9a59c41'); - console.log('1'); await doc.save(); - console.log('2'); const query = Model.findOne({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' }); assert.ok(query._conditions.x instanceof mongoose.Types.Buffer.Binary); - console.log('3'); const res = await query; - console.log('4'); assert.ifError(res.validateSync()); assert.ok(typeof res.x === 'string'); assert.strictEqual(res.x, '09190f70-3d30-11e5-8814-0f4df9a59c41'); From 5e7cebaceb4e06058b867ecbcdf25f58f098717a Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:18:56 +0200 Subject: [PATCH 06/19] test(schema.uuid): fix tests to pass --- test/schema.uuid.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index a8ce48aeaac..cbc160ffb3c 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -37,7 +37,8 @@ describe('SchemaUUID', function() { await doc.save(); const query = Model.findOne({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' }); - assert.ok(query._conditions.x instanceof mongoose.Types.Buffer.Binary); + console.log('query conditions', query._conditions); + assert.ok(typeof query._conditions.x === 'string'); const res = await query; assert.ifError(res.validateSync()); From b8432d013f928ef8c258283c20d37c761f477150 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:28:13 +0200 Subject: [PATCH 07/19] style(uuid): update jsdoc to be up-to-date comments --- lib/schema/uuid.js | 69 ++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 3d0deb2fbee..8b4ed909279 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -144,15 +144,15 @@ SchemaUUID._cast = function(value, doc, init) { }; /** -// * Sets a default option for all Decimal128 instances. -// * -// * #### Example: -// * -// * // Make all decimal 128s have `required` of true by default. -// * mongoose.Schema.Decimal128.set('required', true); -// * -// * const User = mongoose.model('User', new Schema({ test: mongoose.Decimal128 })); -// * new User({ }).validateSync().errors.test.message; // Path `test` is required. + * Sets a default option for all UUID instances. + * + * #### Example: + * + * // Make all UUIDs have `required` of true by default. + * mongoose.Schema.UUID.set('required', true); + * + * const User = mongoose.model('User', new Schema({ test: mongoose.UUID })); + * new User({ }).validateSync().errors.test.message; // Path `test` is required. * * @param {String} option The option you'd like to set the value for * @param {Any} value value for option @@ -165,19 +165,19 @@ SchemaUUID._cast = function(value, doc, init) { SchemaUUID.set = SchemaType.set; /** -// * Get/set the function used to cast arbitrary values to decimals. -// * -// * #### Example: -// * -// * // Make Mongoose only refuse to cast numbers as decimal128 -// * const original = mongoose.Schema.Types.Decimal128.cast(); -// * mongoose.Decimal128.cast(v => { -// * assert.ok(typeof v !== 'number'); -// * return original(v); -// * }); -// * -// * // Or disable casting entirely -// * mongoose.Decimal128.cast(false); + * Get/set the function used to cast arbitrary values to UUIDs. + * + * #### Example: + * + * // Make Mongoose refuse to cast UUIDs with 0 length + * const original = mongoose.Schema.Types.UUID.cast(); + * mongoose.UUID.cast(v => { + * assert.ok(typeof v === "string" && v.length > 0); + * return original(v); + * }); + * + * // Or disable casting entirely + * mongoose.UUID.cast(false); * * @param {Function} [caster] * @return {Function} @@ -198,18 +198,6 @@ SchemaUUID.cast = function cast(caster) { return this._cast; }; -// /*! -// * ignore -// */ - -// SchemaUUIDv1._defaultCaster = v => { -// // throw new Error('TODO'); -// // if (v != null && !isBsonType(v, 'Decimal128')) { -// // throw new Error(); -// // } -// // return v; -// }; - /*! * ignore */ @@ -240,21 +228,10 @@ SchemaUUID.checkRequired = SchemaType.checkRequired; SchemaUUID.prototype.checkRequired = function checkRequired(value, doc) { return UUID_FORMAT.test(value); - // if (SchemaType._isRef(this, value, doc, true)) { - // return !!value; - // } - - // // `require('util').inherits()` does **not** copy static properties, and - // // plugins like mongoose-float use `inherits()` for pre-ES6. - // const _checkRequired = typeof this.constructor.checkRequired === 'function' ? - // this.constructor.checkRequired() : - // SchemaUUIDv1.checkRequired(); - - // return _checkRequired(value); }; /** -// * Casts to Decimal128 + * Casts to UUID * * @param {Object} value * @param {Object} doc From 34c0591e640502c0f1d12eac3130680782d38a7f Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:28:39 +0200 Subject: [PATCH 08/19] style(uuid): remove unused parameters --- lib/schema/uuid.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 8b4ed909279..8f5bc941e4a 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -104,7 +104,7 @@ SchemaUUID.prototype.constructor = SchemaUUID; * ignore */ -SchemaUUID._cast = function(value, doc, init) { +SchemaUUID._cast = function(value) { if (value === null) { return value; } @@ -221,12 +221,11 @@ SchemaUUID.checkRequired = SchemaType.checkRequired; * Check if the given value satisfies a required validator. * * @param {Any} value - * @param {Document} doc * @return {Boolean} * @api public */ -SchemaUUID.prototype.checkRequired = function checkRequired(value, doc) { +SchemaUUID.prototype.checkRequired = function checkRequired(value) { return UUID_FORMAT.test(value); }; From 6ae280000cdbd94e324b2cd2621040ac6a5a203b Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:29:07 +0200 Subject: [PATCH 09/19] style(uuid): remove old (commented-out) buffer code --- lib/schema/uuid.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 8f5bc941e4a..17e372a810f 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -20,34 +20,12 @@ const Binary = MongooseBuffer.Binary; */ function hex2buffer(hex) { - // const arr = []; - - // for (let i = 0; i < hex.length - 1; i += 2) { - // const h = hex.substring(i, i + 2 + 1); - // const n = parseInt(h, 16); - // arr.push(n); - // } - - // const buff = Buffer.from(arr); - // console.log('test', buff.toString('utf8')); - // use buffer built-in function to convert from hex-string to buffer const buff = Buffer.from(hex, 'hex'); return buff; } function binary2hex(buf) { - // const len = buf.length; - // let hex = ''; - // for (let j = 0; j < len; j++) { - // const n = buf.readUInt8(j); - // if (n < 16) { - // hex += '0' + n.toString(16); - // } else { - // hex += n.toString(16); - // } - // } - // use buffer built-in function to convert from buffer to hex-string const hex = buf.toString('hex'); return hex; From c53e0763c54e8901691bfe0eef7785f900d783c4 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Tue, 23 Aug 2022 13:29:50 +0200 Subject: [PATCH 10/19] test(schema.uuid): remove another debug logging --- test/schema.uuid.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index cbc160ffb3c..93afa9430d1 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -37,7 +37,6 @@ describe('SchemaUUID', function() { await doc.save(); const query = Model.findOne({ x: '09190f70-3d30-11e5-8814-0f4df9a59c41' }); - console.log('query conditions', query._conditions); assert.ok(typeof query._conditions.x === 'string'); const res = await query; From 47a0d078b1a5bc77767068683043c2a389333eb4 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 12:22:27 +0200 Subject: [PATCH 11/19] fix(uuid): remove "$options" and "$regex" operators because they are not supported on buffers --- lib/schema/uuid.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 17e372a810f..d08c7ea5906 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -268,9 +268,7 @@ utils.options(SchemaType.prototype.$conditionalHandlers, { $lt: handleSingle, $lte: handleSingle, $ne: handleSingle, - $nin: handleArray, - $options: handleSingle, - $regex: handleSingle + $nin: handleArray }); /** From c76d09784cc95b61f79554218712fa25e818cb43 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 12:25:24 +0200 Subject: [PATCH 12/19] fix(uuid): change "_checkRequired" function to just check against "not null" re https://github.com/Automattic/mongoose/pull/12268#discussion_r952838604 --- lib/schema/uuid.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index d08c7ea5906..6ab214c5989 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -180,7 +180,7 @@ SchemaUUID.cast = function cast(caster) { * ignore */ -SchemaUUID._checkRequired = v => UUID_FORMAT.test(v); +SchemaUUID._checkRequired = v => v != null; /** * Override the function the required validator uses to check whether a string From a187b01d67262cefab29bd186e37c21f79788458 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 12:42:36 +0200 Subject: [PATCH 13/19] test(schema.uuid): check that the type in the database is actually a buffer --- test/schema.uuid.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index 93afa9430d1..2e23b8617fd 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -3,6 +3,8 @@ const start = require('./common'); const util = require('./util'); +const bson = require('bson'); + const assert = require('assert'); const mongoose = start.mongoose; @@ -43,6 +45,13 @@ describe('SchemaUUID', function() { assert.ifError(res.validateSync()); assert.ok(typeof res.x === 'string'); assert.strictEqual(res.x, '09190f70-3d30-11e5-8814-0f4df9a59c41'); + + // check that the data is actually a buffer in the database with the correct subtype + const col = db.client.db(db.name).collection(Model.collection.name); + const rawDoc = await col.findOne({ x: new bson.Binary(Buffer.from('09190f703d3011e588140f4df9a59c41', 'hex'), 4) }); + assert.ok(rawDoc); + assert.ok(rawDoc.x instanceof bson.Binary); + assert.strictEqual(rawDoc.x.sub_type, 4); }); it('should throw error in case of invalid string', function() { From 7a43d4ab91e0ba9f8392aa8e4cc2089ae78a77c3 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 13:03:48 +0200 Subject: [PATCH 14/19] fix(uuid): change "binaryToString" to handle input strings --- lib/schema/uuid.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 6ab214c5989..35d93a98a3c 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -43,9 +43,14 @@ function stringToBinary(uuidStr) { } function binaryToString(uuidBin) { - const hex = binary2hex(uuidBin); - const uuidStr = hex.substring(0, 8) + '-' + hex.substring(8, 8 + 4) + '-' + hex.substring(12, 12 + 4) + '-' + hex.substring(16, 16 + 4) + '-' + hex.substring(20, 20 + 12); - return uuidStr; + // i(hasezoey) dont quite know why, but "uuidBin" may sometimes also be the already processed string + let hex; + if (typeof uuidBin !== 'string') { + hex = binary2hex(uuidBin); + const uuidStr = hex.substring(0, 8) + '-' + hex.substring(8, 8 + 4) + '-' + hex.substring(12, 12 + 4) + '-' + hex.substring(16, 16 + 4) + '-' + hex.substring(20, 20 + 12); + return uuidStr; + } + return uuidBin; } /** From 6038b72c1f63b0f6433e03f1d1613cbb97389ccf Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 13:04:10 +0200 Subject: [PATCH 15/19] style(uuid): add jsdoc to helper function --- lib/schema/uuid.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 35d93a98a3c..d134e59982e 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -14,9 +14,10 @@ const UUID_FORMAT = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9][0-9a-f]{3}-[89ab][0-9a-f]{3}- const Binary = MongooseBuffer.Binary; /** - * Convert a hex-string to a buffer + * Helper function to convert the input hex-string to a buffer * @param {String} hex The hex string to convert * @returns {Buffer} The hex as buffer + * @api private */ function hex2buffer(hex) { @@ -25,12 +26,26 @@ function hex2buffer(hex) { return buff; } +/** + * Helper function to convert the buffer input to a string + * @param {Buffer} buf The buffer to convert to a hex-string + * @returns {String} The buffer as a hex-string + * @api private + */ + function binary2hex(buf) { // use buffer built-in function to convert from buffer to hex-string const hex = buf.toString('hex'); return hex; } +/** + * Convert a String to Binary + * @param {String} uuidStr The value to process + * @returns {MongooseBuffer} The binary to store + * @api private + */ + function stringToBinary(uuidStr) { // Protect against undefined & throwing err if (typeof uuidStr !== 'string') uuidStr = ''; @@ -42,6 +57,12 @@ function stringToBinary(uuidStr) { return buff; } +/** + * Convert binary to a uuid string + * @param {Buffer|Binary|String} uuidBin The value to process + * @returns {String} The completed uuid-string + * @api private + */ function binaryToString(uuidBin) { // i(hasezoey) dont quite know why, but "uuidBin" may sometimes also be the already processed string let hex; From fd17d9bb26d5124c288098a24f0eaed647ffc09e Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 13:04:28 +0200 Subject: [PATCH 16/19] test(schema.uuid): add tests for "$in" and "$nin" --- test/schema.uuid.test.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index 2e23b8617fd..a57a1a1f266 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -16,7 +16,7 @@ describe('SchemaUUID', function() { let db; before(async function() { - TestSchema = new Schema({ x: { type: mongoose.Schema.Types.UUID } }); + TestSchema = new Schema({ x: { type: mongoose.Schema.Types.UUID }, y: [{ type: mongoose.Schema.Types.UUID }] }); db = await start().asPromise(); }); @@ -62,4 +62,32 @@ describe('SchemaUUID', function() { assert.strictEqual(Object.keys(errors).length, 1); assert.ok(errors.x instanceof mongoose.Error.CastError); }); + + it('should work with $in and $nin', async function() { + const doc1 = new Model({ y: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34', 'c6f59133-4f84-45a8-bc1d-8f172803e4fe', 'df1309e0-58c5-427a-b22f-6c0fc445ccc0'] }); + const doc2 = new Model({ y: ['13d51406-cd06-4fc2-93d1-4fad9b3eecd7', 'f004416b-e02a-4212-ac77-2d3fcf04898b', '5b544b71-8988-422b-a4df-bf691939fe4e'] }); + + await doc1.save(); + await doc2.save(); + + // test $in + const foundDocIn = await Model.find({ y: { $in: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34'] } }); + assert.ok(foundDocIn); + assert.strictEqual(foundDocIn.length, 1); + assert.ok(foundDocIn[0].y); + assert.strictEqual(foundDocIn[0].y.length, 3); + assert.strictEqual(foundDocIn[0].y[0], 'f8010af3-bc2c-45e6-85c6-caa30c4a7d34'); + assert.strictEqual(foundDocIn[0].y[1], 'c6f59133-4f84-45a8-bc1d-8f172803e4fe'); + assert.strictEqual(foundDocIn[0].y[2], 'df1309e0-58c5-427a-b22f-6c0fc445ccc0'); + + // test $nin + const foundDocNin = await Model.find({ y: { $nin: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34'] } }); + assert.ok(foundDocNin); + assert.strictEqual(foundDocNin.length, 1); + assert.ok(foundDocNin[0].y); + assert.strictEqual(foundDocNin[0].y.length, 3); + assert.strictEqual(foundDocNin[0].y[0], '13d51406-cd06-4fc2-93d1-4fad9b3eecd7'); + assert.strictEqual(foundDocNin[0].y[1], 'f004416b-e02a-4212-ac77-2d3fcf04898b'); + assert.strictEqual(foundDocNin[0].y[2], '5b544b71-8988-422b-a4df-bf691939fe4e'); + }); }); From 3abfff79ac4ef511b7525356b1db94f0224b6e7e Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 13:07:37 +0200 Subject: [PATCH 17/19] test(schema.uuid): extend test to also test $all --- test/schema.uuid.test.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index a57a1a1f266..3f3ed954501 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -63,7 +63,7 @@ describe('SchemaUUID', function() { assert.ok(errors.x instanceof mongoose.Error.CastError); }); - it('should work with $in and $nin', async function() { + it('should work with $in and $nin and $all', async function() { const doc1 = new Model({ y: ['f8010af3-bc2c-45e6-85c6-caa30c4a7d34', 'c6f59133-4f84-45a8-bc1d-8f172803e4fe', 'df1309e0-58c5-427a-b22f-6c0fc445ccc0'] }); const doc2 = new Model({ y: ['13d51406-cd06-4fc2-93d1-4fad9b3eecd7', 'f004416b-e02a-4212-ac77-2d3fcf04898b', '5b544b71-8988-422b-a4df-bf691939fe4e'] }); @@ -89,5 +89,15 @@ describe('SchemaUUID', function() { assert.strictEqual(foundDocNin[0].y[0], '13d51406-cd06-4fc2-93d1-4fad9b3eecd7'); assert.strictEqual(foundDocNin[0].y[1], 'f004416b-e02a-4212-ac77-2d3fcf04898b'); assert.strictEqual(foundDocNin[0].y[2], '5b544b71-8988-422b-a4df-bf691939fe4e'); + + // test for $all + const foundDocAll = await Model.find({ y: { $all: ['13d51406-cd06-4fc2-93d1-4fad9b3eecd7', 'f004416b-e02a-4212-ac77-2d3fcf04898b'] } }); + assert.ok(foundDocAll); + assert.strictEqual(foundDocAll.length, 1); + assert.ok(foundDocAll[0].y); + assert.strictEqual(foundDocAll[0].y.length, 3); + assert.strictEqual(foundDocAll[0].y[0], '13d51406-cd06-4fc2-93d1-4fad9b3eecd7'); + assert.strictEqual(foundDocAll[0].y[1], 'f004416b-e02a-4212-ac77-2d3fcf04898b'); + assert.strictEqual(foundDocAll[0].y[2], '5b544b71-8988-422b-a4df-bf691939fe4e'); }); }); From a8c1380f1eab63ad93bdde848815cbdcff53dbb9 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Wed, 24 Aug 2022 13:24:25 +0200 Subject: [PATCH 18/19] fix(uuid): add "bits*" operators to "$conditionalHandlers" because it is basically a buffer --- lib/schema/uuid.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index d134e59982e..b621fa942d8 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -9,6 +9,7 @@ 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; const Binary = MongooseBuffer.Binary; @@ -287,6 +288,10 @@ function handleArray(val) { SchemaUUID.prototype.$conditionalHandlers = utils.options(SchemaType.prototype.$conditionalHandlers, { + $bitsAllClear: handleBitwiseOperator, + $bitsAnyClear: handleBitwiseOperator, + $bitsAllSet: handleBitwiseOperator, + $bitsAnySet: handleBitwiseOperator, $all: handleArray, $gt: handleSingle, $gte: handleSingle, From 4494877585e558a68bccb68a5f98e4f2a92d9cf3 Mon Sep 17 00:00:00 2001 From: hasezoey Date: Thu, 15 Sep 2022 10:16:57 +0200 Subject: [PATCH 19/19] test(schema.uuid): add tests that still need to be done --- test/schema.uuid.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index 3f3ed954501..ee06af9b3b7 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -100,4 +100,9 @@ describe('SchemaUUID', function() { assert.strictEqual(foundDocAll[0].y[1], 'f004416b-e02a-4212-ac77-2d3fcf04898b'); assert.strictEqual(foundDocAll[0].y[2], '5b544b71-8988-422b-a4df-bf691939fe4e'); }); + + // 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'); + it('should work with $lt, $lte, $gt, $gte operators'); });