diff --git a/lib/cast/uuid.js b/lib/cast/uuid.js new file mode 100644 index 0000000000..6e296bf3e2 --- /dev/null +++ b/lib/cast/uuid.js @@ -0,0 +1,78 @@ +'use strict'; + +const MongooseBuffer = require('../types/buffer'); + +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; + +module.exports = function castUUID(value) { + 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 Error(`"${value}" is not a valid UUID string`); + } + } + + 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 Error(`"${value}" cannot be casted to a UUID`); +}; + +module.exports.UUID_FORMAT = UUID_FORMAT; + +/** + * 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) { + // use buffer built-in function to convert from hex-string to buffer + const buff = hex != null && Buffer.from(hex, 'hex'); + return buff; +} + +/** + * 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 = ''; + const hex = uuidStr.replace(/[{}-]/g, ''); // remove extra characters + const bytes = hex2buffer(hex); + const buff = new MongooseBuffer(bytes); + buff._subtype = 4; + + return buff; +} diff --git a/lib/schema/uuid.js b/lib/schema/uuid.js index 1fbfc38654..0b3da55998 100644 --- a/lib/schema/uuid.js +++ b/lib/schema/uuid.js @@ -7,43 +7,13 @@ const MongooseBuffer = require('../types/buffer'); const SchemaType = require('../schemaType'); const CastError = SchemaType.CastError; +const castUUID = require('../cast/uuid'); const utils = require('../utils'); 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 UUID_FORMAT = castUUID.UUID_FORMAT; const Binary = MongooseBuffer.Binary; -/** - * 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) { - // use buffer built-in function to convert from hex-string to buffer - const buff = hex != null && Buffer.from(hex, 'hex'); - return buff; -} - -/** - * 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 = ''; - const hex = uuidStr.replace(/[{}-]/g, ''); // remove extra characters - const bytes = hex2buffer(hex); - const buff = new MongooseBuffer(bytes); - buff._subtype = 4; - - return buff; -} - /** * Convert binary to a uuid string * @param {Buffer|Binary|String} uuidBin The value to process @@ -109,44 +79,7 @@ SchemaUUID.prototype.constructor = SchemaUUID; * ignore */ -SchemaUUID._cast = function(value) { - 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(SchemaUUID.schemaName, 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(SchemaUUID.schemaName, value, this.path); -}; +SchemaUUID._cast = castUUID; /** * Attaches a getter for all UUID instances. diff --git a/test/schema.uuid.test.js b/test/schema.uuid.test.js index 77b7b2300f..e93538f78c 100644 --- a/test/schema.uuid.test.js +++ b/test/schema.uuid.test.js @@ -63,6 +63,8 @@ describe('SchemaUUID', function() { const errors = res.errors; assert.strictEqual(Object.keys(errors).length, 1); assert.ok(errors.x instanceof mongoose.Error.CastError); + + assert.ok(errors.x.reason.message.includes('not a valid UUID string'), errors.x.reason.message); }); it('should work with $in and $nin and $all', async function() {