Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(NODE-4535): automatically promote UUIDs when deserializing and parsing UUIDs #513

Merged
merged 8 commits into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/binary.ts
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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', () => {
baileympearson marked this conversation as resolved.
Show resolved Hide resolved
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);
});
});
});