diff --git a/src/bson.ts b/src/bson.ts index 924235aab..6f89776a4 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -15,7 +15,11 @@ import { ObjectId } from './objectid'; import { BSONError, BSONTypeError } from './error'; import { calculateObjectSize as internalCalculateObjectSize } from './parser/calculate_size'; // Parts of the parser -import { deserialize as internalDeserialize, DeserializeOptions } from './parser/deserializer'; +import { + deserialize as internalDeserialize, + DeserializeOptions, + DefaultDeserializeOptions +} from './parser/deserializer'; import { serializeInto as internalSerialize, SerializeOptions } from './parser/serializer'; import { BSONRegExp } from './regexp'; import { BSONSymbol } from './symbol'; @@ -77,7 +81,7 @@ export { TimestampOverrides } from './timestamp'; export { UUIDExtended } from './uuid'; -export { SerializeOptions, DeserializeOptions }; +export { SerializeOptions, DeserializeOptions, DefaultDeserializeOptions }; export { Code, Map, diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 3ba054997..7e39236b1 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -1,6 +1,6 @@ import { Buffer } from 'buffer'; import { Binary } from '../binary'; -import type { Document } from '../bson'; +import { Document, UUID } from '../bson'; import { Code } from '../code'; import * as constants from '../constants'; import { DBRef, DBRefLike, isDBRefLike } from '../db_ref'; @@ -17,6 +17,11 @@ import { BSONSymbol } from '../symbol'; import { Timestamp } from '../timestamp'; import { validateUtf8 } from '../validate_utf8'; +/** @public */ +export const DefaultDeserializeOptions = { + convertUUIDs: false +}; + /** @public */ export interface DeserializeOptions { /** evaluate functions in the BSON document scoped to the object deserialized. */ @@ -45,6 +50,8 @@ export interface DeserializeOptions { index?: number; raw?: boolean; + /** Convert Binary values of subtype 4 to UUID */ + convertUUIDs?: boolean; } // Internal long versions @@ -120,6 +127,8 @@ function deserializeObject( const promoteLongs = options['promoteLongs'] == null ? true : options['promoteLongs']; const promoteValues = options['promoteValues'] == null ? true : options['promoteValues']; + const convertUUIDs = !!(DefaultDeserializeOptions.convertUUIDs || options.convertUUIDs); + // Set the start index const startIndex = index; @@ -342,10 +351,12 @@ function deserializeObject( throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); } - if (promoteBuffers && promoteValues) { - value = buffer.slice(index, index + binarySize); - } else { - value = new Binary(buffer.slice(index, index + binarySize), subType); + value = buffer.slice(index, index + binarySize); + + if (convertUUIDs && subType === Binary.SUBTYPE_UUID) { + value = new UUID(value); + } else if (!(promoteBuffers && promoteValues)) { + value = new Binary(value, subType); } } else { const _buffer = Buffer.alloc(binarySize); diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index db9e015a1..2967994eb 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -841,6 +841,8 @@ export function serializeInto( index = serializeInt32(buffer, key, value, index, true); } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { index = serializeMinMax(buffer, key, value, index, true); + } else if (value['_bsontype'] === 'UUID') { + index = serializeBinary(buffer, key, value.toBinary(), index); } else if (typeof value['_bsontype'] !== 'undefined') { throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); } @@ -942,6 +944,8 @@ export function serializeInto( index = serializeInt32(buffer, key, value, index); } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); + } else if (value['_bsontype'] === 'UUID') { + index = serializeBinary(buffer, key, value.toBinary(), index); } else if (typeof value['_bsontype'] !== 'undefined') { throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); } @@ -1048,6 +1052,8 @@ export function serializeInto( index = serializeInt32(buffer, key, value, index); } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); + } else if (value['_bsontype'] === 'UUID') { + index = serializeBinary(buffer, key, value.toBinary(), index); } else if (typeof value['_bsontype'] !== 'undefined') { throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); } diff --git a/test/node/uuid_tests.js b/test/node/uuid_tests.js index 6598abbe7..ab608152c 100644 --- a/test/node/uuid_tests.js +++ b/test/node/uuid_tests.js @@ -1,9 +1,16 @@ 'use strict'; const { Buffer } = require('buffer'); -const { Binary, UUID } = require('../register-bson'); +const { + Binary, + UUID, + serialize, + deserialize, + DefaultDeserializeOptions +} = require('../register-bson'); const { inspect } = require('util'); const { validate: uuidStringValidate, version: uuidStringVersion } = require('uuid'); +const { afterEach } = require('mocha'); // Test values const UPPERCASE_DASH_SEPARATED_UUID_STRING = 'AAAAAAAA-AAAA-4AAA-AAAA-AAAAAAAAAAAA'; @@ -160,4 +167,46 @@ describe('UUID', () => { const uuid = new UUID(UPPERCASE_DASH_SEPARATED_UUID_STRING); expect(inspect(uuid)).to.equal(`new UUID("${LOWERCASE_DASH_SEPARATED_UUID_STRING}")`); }); + + describe('when (de)serializing', () => { + describe('without specifying any `convertUUIDs` option', () => { + it('should (de)serialize as Binary/4', () => { + const uuidString = 'bd2d74fe-bad8-430c-aeac-b01d073a1eb6'; + + const { value } = deserialize(serialize({ value: new UUID(uuidString) })); + expect(value).to.be.instanceOf(Binary); + expect(value.toUUID().toHexString()).to.equal(uuidString); + }); + }); + + describe('with DeserializationOptions.convertUUIDs set', () => { + it('should (de)serialize as actual UUID', () => { + const uuidString = 'bd2d74fe-bad8-430c-aeac-b01d073a1eb6'; + + const { value } = deserialize(serialize({ value: new UUID(uuidString) }), { + convertUUIDs: true + }); + expect(value).to.be.instanceOf(UUID); + expect(value.toHexString()).to.equal(uuidString); + }); + }); + + describe('with `DefaultDeserializationOption.convertUUIDs` set', () => { + beforeEach(() => { + DefaultDeserializeOptions.convertUUIDs = true; + }); + + afterEach(() => { + DefaultDeserializeOptions.convertUUIDs = false; + }); + + it('should (de)serialize as actual UUID', () => { + const uuidString = 'bd2d74fe-bad8-430c-aeac-b01d073a1eb6'; + + const { value } = deserialize(serialize({ value: new UUID(uuidString) })); + expect(value).to.be.instanceOf(UUID); + expect(value.toHexString()).to.equal(uuidString); + }); + }); + }); });