Skip to content

Commit b06aa1b

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

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed

src/deserializer.ts

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
return Temporal.PlainDate.from(value);
41+
}
42+
return value;
43+
};
44+
45+
// Example boxed revivers map.
46+
export const BOXED_REVIVERS = new Map<number, BSONReviver>();
47+
BOXED_REVIVERS.set(BSON_DATA_INT, boxedInt32Reviver);
48+
BOXED_REVIVERS.set(BSON_DATA_STRING, stringReviver);
49+
Object.freeze(BOXED_REVIVERS);
50+
51+
// Example native revivers map.
52+
export const NATIVE_REVIVERS = new Map<number, BSONReviver>();
53+
NATIVE_REVIVERS.set(BSON_DATA_INT, nativeInt32Reviver);
54+
NATIVE_REVIVERS.set(BSON_DATA_STRING, stringReviver);
55+
Object.freeze(NATIVE_REVIVERS);
56+
57+
/**
58+
* Deserialize the provided buffer.
59+
* @param buffer - The buffer.
60+
* @param reviver - The optional reviver.
61+
*/
62+
export function deserialize(buffer: Uint8Array, options?: DeserializeOptions): Document {
63+
return new RevivingBSONDeserializer(options).deserialize(buffer);
64+
}
65+
66+
export class MissingReviverError extends BSONError {
67+
get name(): 'MissingReviverError' {
68+
return 'MissingReviverError';
69+
}
70+
}
71+
72+
export class RevivingBSONDeserializer implements BSONDeserializer {
73+
revivers: Map<number, BSONReviver>;
74+
75+
constructor(options?: DeserializeOptions) {
76+
if (options?.reviverMap) {
77+
this.revivers = options.reviverMap;
78+
} else {
79+
this.revivers = buildRevivers(options);
80+
}
81+
}
82+
83+
deserialize(buffer: Uint8Array): Document {
84+
const document: Document = {};
85+
// Go through the buffer and deserialize the elements to BSON boxed types.
86+
// If a reviver map is provided, use that to deserialize each element,
87+
// otherwise use the internal reviver for each type.
88+
for (const element of parseToElements(buffer)) {
89+
// For each of the BSONElements create the key, get the reviver for the specific type
90+
// and call it with the key, the buffer, and the offset of the value in the buffer.
91+
// Set the return value of the revivier on the document.
92+
const [type, nameOffset, nameLength] = element;
93+
const key = ByteUtils.toUTF8(buffer, nameOffset, nameOffset + nameLength, false);
94+
const reviver = this.revivers.get(type);
95+
if (!reviver) {
96+
throw new MissingReviverError(`No reviver found for type ${type}.`);
97+
}
98+
document[key] = reviver(key, buffer, element);
99+
}
100+
return document;
101+
}
102+
}
103+
104+
// Build the reviver map based on the user provided BSON options.
105+
export function buildRevivers(options?: DeserializeOptions): Map<number, BSONReviver> {
106+
const reviverMap = new Map(BOXED_REVIVERS);
107+
// Example map population based on options.
108+
if (options?.promoteValues) {
109+
reviverMap.set(BSON_DATA_INT, nativeInt32Reviver);
110+
}
111+
return reviverMap;
112+
}
113+
114+
export class BSONString extends BSONValue {
115+
get _bsontype(): 'BSONString' {
116+
return 'BSONString';
117+
}
118+
119+
value: string;
120+
121+
constructor(value: string) {
122+
super();
123+
this.value = value;
124+
}
125+
126+
static fromElement(bytes: Uint8Array, element: BSONElement): BSONString {
127+
const [offset, length] = element;
128+
return new BSONString(ByteUtils.toUTF8(bytes, offset, length, false));
129+
}
130+
131+
toNative(): string {
132+
return this.value;
133+
}
134+
135+
inspect(): string {
136+
return `new BSONString(${this.value})`;
137+
}
138+
139+
/** @internal */
140+
toExtendedJSON(): string {
141+
return this.value;
142+
}
143+
}

src/parser/deserializer.ts

+7
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';
@@ -60,6 +61,8 @@ export interface DeserializeOptions {
6061
*/
6162
index?: number;
6263

64+
returnBoxedTypes?: boolean;
65+
6366
raw?: boolean;
6467
/** Allows for opt-out utf-8 validation for all keys or
6568
* specified keys. Must be all true or all false.
@@ -77,6 +80,10 @@ export interface DeserializeOptions {
7780
* ```
7881
*/
7982
validation?: { utf8: boolean | Record<string, true> | Record<string, false> };
83+
/**
84+
* A custom map of BSON revivers to override the defaults.
85+
*/
86+
reviverMap?: Map<number, BSONReviver>;
8087
}
8188

8289
// Internal long versions

0 commit comments

Comments
 (0)