Skip to content

Commit

Permalink
feat(NODE-4535): automatically promote UUIDs when deserializing and p…
Browse files Browse the repository at this point in the history
…arsing UUIDs (#513)
  • Loading branch information
aditi-khare-mongoDB committed Aug 17, 2022
1 parent e9afa9d commit 1dc7eae
Show file tree
Hide file tree
Showing 6 changed files with 27 additions and 218 deletions.
2 changes: 1 addition & 1 deletion src/binary.ts
Expand Up @@ -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 */
Expand Down
18 changes: 8 additions & 10 deletions src/parser/deserializer.ts
Expand Up @@ -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. */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}

Expand Down
81 changes: 0 additions & 81 deletions test/node/bson_compliance_test.js
Expand Up @@ -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 () {
/**
Expand All @@ -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();
});
});
69 changes: 0 additions & 69 deletions test/node/compliance/valid.js

This file was deleted.

10 changes: 10 additions & 0 deletions test/node/extended_json_tests.js
Expand Up @@ -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);
});
});
});
65 changes: 8 additions & 57 deletions test/node/uuid_tests.js
Expand Up @@ -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);
});
});
});

0 comments on commit 1dc7eae

Please sign in to comment.