Skip to content

Commit

Permalink
Merge pull request #15215 from Automattic/vkarpov15/gh-15202
Browse files Browse the repository at this point in the history
fix(schema): improve reason for UUID cast error, currently a TypeError
  • Loading branch information
vkarpov15 authored Jan 31, 2025
2 parents 25bd7d8 + 1e8db47 commit 5f0c105
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 70 deletions.
78 changes: 78 additions & 0 deletions lib/cast/uuid.js
Original file line number Diff line number Diff line change
@@ -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;
}
73 changes: 3 additions & 70 deletions lib/schema/uuid.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions test/schema.uuid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 5f0c105

Please # to comment.