Skip to content

Commit b1b2a0e

Browse files
author
Thomas Reggi
authored
feat: add extended json parsing for $uuid
NODE-2805
1 parent a48676b commit b1b2a0e

File tree

4 files changed

+87
-8
lines changed

4 files changed

+87
-8
lines changed

src/binary.ts

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Buffer } from 'buffer';
22
import { ensureBuffer } from './ensure_buffer';
33
import type { EJSONOptions } from './extended_json';
4+
import { parseUUID, UUIDExtended } from './uuid';
45

56
type BinarySequence = Uint8Array | Buffer | number[];
67

@@ -223,20 +224,25 @@ export class Binary {
223224

224225
/** @internal */
225226
static fromExtendedJSON(
226-
doc: BinaryExtendedLegacy | BinaryExtended,
227+
doc: BinaryExtendedLegacy | BinaryExtended | UUIDExtended,
227228
options?: EJSONOptions
228229
): Binary {
229230
options = options || {};
230231
let data: Buffer | undefined;
231232
let type;
232-
if (options.legacy && typeof doc.$binary === 'string' && '$type' in doc) {
233-
type = doc.$type ? parseInt(doc.$type, 16) : 0;
234-
data = Buffer.from(doc.$binary, 'base64');
235-
} else {
236-
if (typeof doc.$binary !== 'string') {
237-
type = doc.$binary.subType ? parseInt(doc.$binary.subType, 16) : 0;
238-
data = Buffer.from(doc.$binary.base64, 'base64');
233+
if ('$binary' in doc) {
234+
if (options.legacy && typeof doc.$binary === 'string' && '$type' in doc) {
235+
type = doc.$type ? parseInt(doc.$type, 16) : 0;
236+
data = Buffer.from(doc.$binary, 'base64');
237+
} else {
238+
if (typeof doc.$binary !== 'string') {
239+
type = doc.$binary.subType ? parseInt(doc.$binary.subType, 16) : 0;
240+
data = Buffer.from(doc.$binary.base64, 'base64');
241+
}
239242
}
243+
} else if ('$uuid' in doc) {
244+
type = 4;
245+
data = Buffer.from(parseUUID(doc.$uuid));
240246
}
241247
if (!data) {
242248
throw new TypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`);

src/extended_json.ts

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export interface EJSONOptions {
5252
const keysToCodecs = {
5353
$oid: ObjectId,
5454
$binary: Binary,
55+
$uuid: Binary,
5556
$symbol: BSONSymbol,
5657
$numberInt: Int32,
5758
$numberDecimal: Decimal128,

src/uuid.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* UUID regular expression pattern copied from `uuid` npm module.
3+
* @see https://github.com/uuidjs/uuid/blob/master/src/regex.js
4+
*/
5+
const UUID_RX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
6+
7+
export interface UUIDExtended {
8+
$uuid: string;
9+
}
10+
11+
/**
12+
* Parser function copied from `uuid` npm module.
13+
* @see https://github.com/uuidjs/uuid/blob/master/src/parse.js
14+
* @internal
15+
*/
16+
export function parseUUID(uuid: string): Uint8Array {
17+
if (typeof uuid !== 'string') {
18+
throw new TypeError('Invalid type for UUID, expected string but got ' + typeof uuid);
19+
}
20+
21+
if (!UUID_RX.test(uuid)) {
22+
throw new TypeError('Invalid format for UUID: ' + uuid);
23+
}
24+
25+
let v;
26+
const arr = new Uint8Array(16);
27+
28+
// Parse ########-....-....-....-............
29+
arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24;
30+
arr[1] = (v >>> 16) & 0xff;
31+
arr[2] = (v >>> 8) & 0xff;
32+
arr[3] = v & 0xff;
33+
34+
// Parse ........-####-....-....-............
35+
arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8;
36+
arr[5] = v & 0xff;
37+
38+
// Parse ........-....-####-....-............
39+
arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8;
40+
arr[7] = v & 0xff;
41+
42+
// Parse ........-....-....-####-............
43+
arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8;
44+
arr[9] = v & 0xff;
45+
46+
// Parse ........-....-....-....-############
47+
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
48+
arr[10] = ((v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000) & 0xff;
49+
arr[11] = (v / 0x100000000) & 0xff;
50+
arr[12] = (v >>> 24) & 0xff;
51+
arr[13] = (v >>> 16) & 0xff;
52+
arr[14] = (v >>> 8) & 0xff;
53+
arr[15] = v & 0xff;
54+
55+
return arr;
56+
}

test/node/specs/bson-corpus/binary.json

+16
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
"canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400",
4040
"canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}"
4141
},
42+
{
43+
"description": "subtype 0x04 UUID",
44+
"canonical_bson": "1D000000057800100000000473FFD26444B34C6990E8E7D1DFC035D400",
45+
"canonical_extjson": "{\"x\" : { \"$binary\" : {\"base64\" : \"c//SZESzTGmQ6OfR38A11A==\", \"subType\" : \"04\"}}}",
46+
"degenerate_extjson": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}"
47+
},
4248
{
4349
"description": "subtype 0x05",
4450
"canonical_bson": "1D000000057800100000000573FFD26444B34C6990E8E7D1DFC035D400",
@@ -81,5 +87,15 @@
8187
"description": "subtype 0x02 length negative one",
8288
"bson": "130000000578000600000002FFFFFFFFFFFF00"
8389
}
90+
],
91+
"parseErrors": [
92+
{
93+
"description": "$uuid wrong type",
94+
"string": "{\"x\" : { \"$uuid\" : { \"data\" : \"73ffd264-44b3-4c69-90e8-e7d1dfc035d4\"}}}"
95+
},
96+
{
97+
"description": "$uuid invalid value",
98+
"string": "{\"x\" : { \"$uuid\" : \"73ffd264-44b3-90e8-e7d1dfc035d4\"}}"
99+
}
84100
]
85101
}

0 commit comments

Comments
 (0)