From 1dc7eaea6a61924be66ae5b8a05b74d5dd9c7b1e Mon Sep 17 00:00:00 2001 From: aditi-khare-mongoDB <106987683+aditi-khare-mongoDB@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:33:51 -0400 Subject: [PATCH] feat(NODE-4535): automatically promote UUIDs when deserializing and parsing UUIDs (#513) --- src/binary.ts | 2 +- src/parser/deserializer.ts | 18 +++---- test/node/bson_compliance_test.js | 81 ------------------------------- test/node/compliance/valid.js | 69 -------------------------- test/node/extended_json_tests.js | 10 ++++ test/node/uuid_tests.js | 65 +++---------------------- 6 files changed, 27 insertions(+), 218 deletions(-) delete mode 100644 test/node/compliance/valid.js diff --git a/src/binary.ts b/src/binary.ts index 032a1e02..d86275c9 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -278,7 +278,7 @@ export class Binary { if (!data) { throw new BSONTypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`); } - return new Binary(data, type); + return type === BSON_BINARY_SUBTYPE_UUID_NEW ? new UUID(data) : new Binary(data, type); } /** @internal */ diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 3f103616..faef442f 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -35,8 +35,6 @@ export interface DeserializeOptions { promoteBuffers?: boolean; /** when deserializing will promote BSON values to their Node.js closest equivalent types. */ promoteValues?: boolean; - /** when deserializing will return UUID type, if promoteBuffers is also true then promoteUUIDs will take precedence and a buffer will not be returned */ - promoteUUIDs?: boolean; /** allow to specify if there what fields we wish to return as unserialized raw buffer. */ fieldsAsRaw?: Document; /** return BSON regular expressions as BSONRegExp instances. */ @@ -137,7 +135,6 @@ function deserializeObject( const promoteBuffers = options['promoteBuffers'] == null ? false : options['promoteBuffers']; const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; const promoteValues = options['promoteValues'] == null ? true : options['promoteValues']; - const promoteUUIDs = options.promoteUUIDs == null ? false : options.promoteUUIDs; // Ensures default validation option if none given const validation = options.validation == null ? { utf8: true } : options.validation; @@ -416,12 +413,13 @@ function deserializeObject( throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); } - if (promoteUUIDs && subType === 4) { - value = new Binary(buffer.slice(index, index + binarySize), subType).toUUID(); - } else if (promoteBuffers && promoteValues) { + if (promoteBuffers && promoteValues) { value = buffer.slice(index, index + binarySize); } else { value = new Binary(buffer.slice(index, index + binarySize), subType); + if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW) { + value = value.toUUID(); + } } } else { const _buffer = Buffer.alloc(binarySize); @@ -445,12 +443,12 @@ function deserializeObject( _buffer[i] = buffer[index + i]; } - if (promoteUUIDs && subType === 4) { - value = new Binary(_buffer, subType).toUUID(); - } else if (promoteBuffers && promoteValues) { + if (promoteBuffers && promoteValues) { value = _buffer; + } else if (subType === constants.BSON_BINARY_SUBTYPE_UUID_NEW) { + value = new Binary(buffer.slice(index, index + binarySize), subType).toUUID(); } else { - value = new Binary(_buffer, subType); + value = new Binary(buffer.slice(index, index + binarySize), subType); } } diff --git a/test/node/bson_compliance_test.js b/test/node/bson_compliance_test.js index 9e1761b2..d788b780 100644 --- a/test/node/bson_compliance_test.js +++ b/test/node/bson_compliance_test.js @@ -2,14 +2,6 @@ const Buffer = require('buffer').Buffer; const BSON = require('../register-bson'); -const Code = BSON.Code; -const Binary = BSON.Binary; -const Timestamp = BSON.Timestamp; -const Long = BSON.Long; -const ObjectId = BSON.ObjectId; -const DBRef = BSON.DBRef; -const MinKey = BSON.MinKey; -const MaxKey = BSON.MaxKey; describe('BSON Compliance', function () { /** @@ -36,77 +28,4 @@ describe('BSON Compliance', function () { done(); }); - - /** - * @ignore - */ - it('Pass all valid BSON serialization scenarios ./compliance/valid.json', function (done) { - // Read and parse the json file - const scenarios = require('./compliance/valid'); - - // Translate extended json to correctly typed doc - function translate(doc, object) { - for (let name in doc) { - if ( - typeof doc[name] === 'number' || - typeof doc[name] === 'string' || - typeof doc[name] === 'boolean' - ) { - object[name] = doc[name]; - } else if (Array.isArray(doc[name])) { - object[name] = translate(doc[name], []); - } else if (doc[name]['$numberLong']) { - object[name] = Long.fromString(doc[name]['$numberLong']); - } else if (doc[name]['$undefined']) { - object[name] = null; - } else if (doc[name]['$date']) { - const date = new Date(); - date.setTime(parseInt(doc[name]['$date']['$numberLong'], 10)); - object[name] = date; - } else if (doc[name]['$regexp']) { - object[name] = new RegExp(doc[name]['$regexp'], doc[name]['$options'] || ''); - } else if (doc[name]['$oid']) { - object[name] = new ObjectId(doc[name]['$oid']); - } else if (doc[name]['$binary']) { - object[name] = new Binary(doc[name]['$binary'], doc[name]['$type'] || 1); - } else if (doc[name]['$timestamp']) { - object[name] = Timestamp.fromBits( - parseInt(doc[name]['$timestamp']['t'], 10), - parseInt(doc[name]['$timestamp']['i']) - ); - } else if (doc[name]['$ref']) { - object[name] = new DBRef(doc[name]['$ref'], doc[name]['$id'], doc[name]['$db']); - } else if (doc[name]['$minKey']) { - object[name] = new MinKey(); - } else if (doc[name]['$maxKey']) { - object[name] = new MaxKey(); - } else if (doc[name]['$code']) { - object[name] = new Code(doc[name]['$code'], doc[name]['$scope'] || {}); - } else if (doc[name] != null && typeof doc[name] === 'object') { - object[name] = translate(doc[name], {}); - } - } - - return object; - } - - // Iterate over all the results - scenarios.documents.forEach(function (doc) { - if (doc.skip) return; - // Create a buffer containing the payload - const expectedData = Buffer.from(doc.encoded, 'hex'); - // Get the expectedDocument - const expectedDocument = translate(doc.document, {}); - // Serialize to buffer - const buffer = BSON.serialize(expectedDocument); - // Validate the output - expect(expectedData.toString('hex')).to.equal(buffer.toString('hex')); - // Attempt to deserialize - const object = BSON.deserialize(buffer, { promoteLongs: false }); - // // Validate the object - expect(JSON.stringify(expectedDocument)).to.deep.equal(JSON.stringify(object)); - }); - - done(); - }); }); diff --git a/test/node/compliance/valid.js b/test/node/compliance/valid.js deleted file mode 100644 index d674b739..00000000 --- a/test/node/compliance/valid.js +++ /dev/null @@ -1,69 +0,0 @@ -'use strict'; - -var data = { - description: 'Valid bson documents', - documents: [ - { - encoded: '160000000268656c6c6f0006000000776f726c640000', - document: { - hello: 'world' - } - }, - { - encoded: - 'ab0100000268656c6c6f0006000000776f726c640008626f6f6c65616e000110696e743332000200000012696e743634003868d9f60400000001646f75626c65001f85eb51b81e09400562696e6172790020000000044667414141414a6f5a5778736277414741414141643239796247514141413d3d0964617465008805c3fb4d0100001174696d657374616d700001000000010000000b646174615f7265676578006100730007646174615f6f69640011111111111111111111111103646174615f72656600250000000224726566000b000000636f6c6c656374696f6e000224696400020000003100000a646174615f756e646566696e656400ff646174615f6d696e6b6579007f646174615f6d61786b6579000f636f646500210000000d00000066756e6374696f6e28297b7d000c000000106100010000000003656d626564646564002c0000000f636f646500210000000d00000066756e6374696f6e28297b7d000c00000010610001000000000004617272617900300000000f3000210000000d00000066756e6374696f6e28297b7d000c0000001061000100000000103100010000000000', - document: { - hello: 'world', - boolean: true, - int32: 2, - int64: { - $numberLong: '21321312312' - }, - double: 3.14, - binary: { - $binary: 'FgAAAAJoZWxsbwAGAAAAd29ybGQAAA==', - $type: 4 - }, - date: { - $date: { $numberLong: '1434447971720' } - }, - timestamp: { - $timestamp: { t: '1', i: '1' } - }, - data_regex: { - $regexp: 'a', - $options: 'g' - }, - data_oid: { - $oid: '111111111111111111111111' - }, - data_ref: { - $ref: 'collection', - $id: '1' - }, - data_undefined: { - $undefined: true - }, - data_minkey: { - $minKey: 1 - }, - data_maxkey: { - $maxKey: 1 - }, - code: { - $code: 'function(){}', - $scope: { a: 1 } - }, - embedded: { - code: { - $code: 'function(){}', - $scope: { a: 1 } - } - }, - array: [{ $code: 'function(){}', $scope: { a: 1 } }, 1] - } - } - ] -}; - -module.exports = data; diff --git a/test/node/extended_json_tests.js b/test/node/extended_json_tests.js index 37632bf6..cd70b011 100644 --- a/test/node/extended_json_tests.js +++ b/test/node/extended_json_tests.js @@ -773,5 +773,15 @@ Converting circular structure to EJSON: ); expect(parsedUndashedInput).to.deep.equal(parsedDashedInput); }); + + it('should return UUID object when deserializing UUID subtype', () => { + const exampleUUID = new BSON.UUID('878dac12-01cc-4830-b271-cbc8518e63ad'); + const stringifiedUUID = EJSON.stringify({ uuid: exampleUUID }); + const parsedUUID = EJSON.parse(stringifiedUUID); + const expectedResult = { + uuid: new UUID('878dac12-01cc-4830-b271-cbc8518e63ad') + }; + expect(parsedUUID).to.deep.equal(expectedResult); + }); }); }); diff --git a/test/node/uuid_tests.js b/test/node/uuid_tests.js index cd3cf4d2..37dd9ec2 100644 --- a/test/node/uuid_tests.js +++ b/test/node/uuid_tests.js @@ -193,63 +193,14 @@ describe('UUID', () => { }); describe('deserialize', () => { - const originalUUID = new BSON.UUID(); - const binaryUUID = originalUUID.toBinary(); - const serializedUUID = BSON.serialize({ uuid: originalUUID.toBinary() }); - - it('should promoteUUIDs when flag is true', () => { - const { uuid: promotedUUID } = BSON.deserialize(serializedUUID, { promoteUUIDs: true }); - expect(promotedUUID._bsontype).to.equal('UUID'); - expect(promotedUUID).to.deep.equal(originalUUID); - }); - - it('should not promoteUUIDs when flag is false', () => { - const { uuid: unpromotedUUID } = BSON.deserialize(serializedUUID, { promoteUUIDs: false }); - expect(unpromotedUUID._bsontype).to.equal('Binary'); - expect(unpromotedUUID).to.deep.equal(binaryUUID); - }); - - it('should not promoteUUIDs when flag is omitted', () => { - const { uuid: omittedFlagUUID } = BSON.deserialize(serializedUUID); - expect(omittedFlagUUID._bsontype).to.equal('Binary'); - expect(omittedFlagUUID).to.deep.equal(binaryUUID); - }); - - it('should throw BSONTypeError if _bsontype is not UUID and promoteUUIDs is true', () => { - const binaryVar = new Binary(Buffer.from('abc'), BSON_BINARY_SUBTYPE_UUID_NEW); - const serializedBinary = BSON.serialize({ d: binaryVar }); - expect(() => { - BSON.deserialize(serializedBinary, { promoteUUIDs: true }); - }).to.throw(BSONTypeError); - }); - - describe('promoteBuffers', () => { - const promoteUUIDValues = [true, false, undefined]; - const promoteBufferValues = [true, false, undefined]; - - const testCases = promoteUUIDValues.flatMap(promoteUUIDs => - promoteBufferValues.flatMap(promoteBuffers => ({ - options: { promoteUUIDs, promoteBuffers }, - // promoteBuffers: true returns a Buffer so _bsontype does not exist - outcome: promoteUUIDs ? 'UUID' : promoteBuffers ? undefined : 'Binary' - })) - ); - - for (const { options, outcome } of testCases) { - it(`should deserialize to ${outcome} type when promoteUUIDs is ${options.promoteUUIDs} and promoteBuffers is ${options.promoteBuffers}`, () => { - const { uuid } = BSON.deserialize(serializedUUID, options); - expect(uuid._bsontype).to.equal(outcome); - if (uuid._bsontype === 'UUID') { - expect(uuid.id).to.deep.equal(originalUUID.id); - } else if (uuid._bsontype === 'Binary') { - expect(uuid.buffer).to.deep.equal(originalUUID.id); - } else if (uuid._bsontype === undefined) { - expect(uuid).to.deep.equal(originalUUID.id); - } else { - expect.fail('Unexpected _bsontype'); - } - }); - } + it('should return UUID object when deserializing UUID subtype', () => { + const exampleUUID = new BSON.UUID('878dac12-01cc-4830-b271-cbc8518e63ad'); + const serializedUUID = BSON.serialize({ uuid: exampleUUID }); + const deserializedUUID = BSON.deserialize(serializedUUID); + const expectedResult = { + uuid: new UUID('878dac12-01cc-4830-b271-cbc8518e63ad') + }; + expect(deserializedUUID).to.deep.equal(expectedResult); }); }); });