Skip to content

Commit 8257582

Browse files
committed
chore: design for bson options
1 parent d22dee9 commit 8257582

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

src/deserializer.ts

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { type DeserializeOptions, Int32, type Document, BSONValue, BSONError } from './bson';
2+
import { BSON_DATA_INT, BSON_DATA_STRING } from './constants';
3+
import { type BSONElement, parseToElements } from './parser/on_demand/parse_to_elements';
4+
import { ByteUtils } from './utils/byte_utils';
5+
import { NumberUtils } from './utils/number_utils';
6+
7+
/**
8+
* Type for reviver functions.
9+
*/
10+
export type BSONReviver = (key: string, bytes: Uint8Array, element: BSONElement) => unknown;
11+
12+
/**
13+
* Deserializer interface.
14+
*/
15+
export interface BSONDeserializer {
16+
deserialize(buffer: Uint8Array, reviver?: BSONReviver): Document;
17+
}
18+
19+
// Reviver that returns Int32 boxed type.
20+
export const boxedInt32Reviver = (key: string, bytes: Uint8Array, element: BSONElement) => {
21+
const [offset] = element;
22+
return new Int32(NumberUtils.getInt32LE(bytes, offset));
23+
};
24+
25+
// Reviver that returns numbers.
26+
export const nativeInt32Reviver = (key: string, bytes: Uint8Array, element: BSONElement) => {
27+
const [offset] = element;
28+
return NumberUtils.getInt32LE(bytes, offset);
29+
};
30+
31+
// Reviver that returns strings.
32+
export const stringReviver = (key: string, bytes: Uint8Array, element: BSONElement) => {
33+
return BSONString.fromElement(bytes, element).toNative();
34+
};
35+
36+
// Reviver that returns strings or temporals based on the key.
37+
export const customStringReviver = (key: string, bytes: Uint8Array, element: BSONElement) => {
38+
const value = BSONString.fromElement(bytes, element).toNative();
39+
if (key === 'date') {
40+
// Temporal not supported by Node.js at this time.
41+
// return Temporal.PlainDate.from(value);
42+
return value;
43+
}
44+
return value;
45+
};
46+
47+
// Example boxed revivers map.
48+
export const BOXED_REVIVERS = new Map<number, BSONReviver>();
49+
BOXED_REVIVERS.set(BSON_DATA_INT, boxedInt32Reviver);
50+
BOXED_REVIVERS.set(BSON_DATA_STRING, stringReviver);
51+
Object.freeze(BOXED_REVIVERS);
52+
53+
// Example native revivers map.
54+
export const NATIVE_REVIVERS = new Map<number, BSONReviver>();
55+
NATIVE_REVIVERS.set(BSON_DATA_INT, nativeInt32Reviver);
56+
NATIVE_REVIVERS.set(BSON_DATA_STRING, stringReviver);
57+
Object.freeze(NATIVE_REVIVERS);
58+
59+
/**
60+
* Deserialize the provided buffer.
61+
* @param buffer - The buffer.
62+
* @param reviver - The optional reviver.
63+
*/
64+
export function deserialize(buffer: Uint8Array, options?: DeserializeOptions): Document {
65+
return new RevivingBSONDeserializer(options).deserialize(buffer);
66+
}
67+
68+
export class MissingReviverError extends BSONError {
69+
get name(): 'MissingReviverError' {
70+
return 'MissingReviverError';
71+
}
72+
}
73+
74+
export class RevivingBSONDeserializer implements BSONDeserializer {
75+
revivers: Map<number, BSONReviver>;
76+
77+
constructor(options?: DeserializeOptions) {
78+
if (options?.reviverMap) {
79+
this.revivers = options.reviverMap;
80+
} else {
81+
this.revivers = buildRevivers(options);
82+
}
83+
}
84+
85+
deserialize(buffer: Uint8Array): Document {
86+
const document: Document = {};
87+
// Go through the buffer and deserialize the elements to BSON boxed types.
88+
// If a reviver map is provided, use that to deserialize each element,
89+
// otherwise use the internal reviver for each type.
90+
for (const element of parseToElements(buffer)) {
91+
// For each of the BSONElements create the key, get the reviver for the specific type
92+
// and call it with the key, the buffer, and the offset of the value in the buffer.
93+
// Set the return value of the revivier on the document.
94+
const [type, nameOffset, nameLength] = element;
95+
const key = ByteUtils.toUTF8(buffer, nameOffset, nameOffset + nameLength, false);
96+
const reviver = this.revivers.get(type);
97+
if (!reviver) {
98+
throw new MissingReviverError(`No reviver found for type ${type}.`);
99+
}
100+
document[key] = reviver(key, buffer, element);
101+
}
102+
return document;
103+
}
104+
}
105+
106+
// Build the reviver map based on the user provided BSON options.
107+
export function buildRevivers(options?: DeserializeOptions): Map<number, BSONReviver> {
108+
const reviverMap = new Map(BOXED_REVIVERS);
109+
// Example map population based on options.
110+
if (options?.promoteValues) {
111+
reviverMap.set(BSON_DATA_INT, nativeInt32Reviver);
112+
}
113+
return reviverMap;
114+
}
115+
116+
export class BSONString extends BSONValue {
117+
get _bsontype(): 'BSONString' {
118+
return 'BSONString';
119+
}
120+
121+
value: string;
122+
123+
constructor(value: string) {
124+
super();
125+
this.value = value;
126+
}
127+
128+
static fromElement(bytes: Uint8Array, element: BSONElement): BSONString {
129+
const [offset, length] = element;
130+
return new BSONString(ByteUtils.toUTF8(bytes, offset, length, false));
131+
}
132+
133+
toNative(): string {
134+
return this.value;
135+
}
136+
137+
inspect(): string {
138+
return `new BSONString(${this.value})`;
139+
}
140+
141+
/** @internal */
142+
toExtendedJSON(): string {
143+
return this.value;
144+
}
145+
}

src/parser/deserializer.ts

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Code } from '../code';
44
import * as constants from '../constants';
55
import { DBRef, type DBRefLike, isDBRefLike } from '../db_ref';
66
import { Decimal128 } from '../decimal128';
7+
import { type BSONReviver } from '../deserializer';
78
import { Double } from '../double';
89
import { BSONError } from '../error';
910
import { Int32 } from '../int_32';
@@ -77,6 +78,10 @@ export interface DeserializeOptions {
7778
* ```
7879
*/
7980
validation?: { utf8: boolean | Record<string, true> | Record<string, false> };
81+
/**
82+
* A custom map of BSON revivers to override the defaults.
83+
*/
84+
reviverMap?: Map<number, BSONReviver>;
8085
}
8186

8287
// Internal long versions

0 commit comments

Comments
 (0)