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-4874): support EJSON parse for BigInt from $numberLong #552

Merged
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
75a6207
feat(NODE-4874): WIP
W-A-James Jan 5, 2023
7cd7f6c
test(NODE-4874): Start table test
W-A-James Jan 5, 2023
df97552
fix(NODE-4874): Get canonical mode parsing to work
W-A-James Jan 6, 2023
07904f9
style(NODE-4874): Style
W-A-James Jan 6, 2023
d0dd23d
style(NODE-4874): eslint
W-A-James Jan 6, 2023
5120ca5
fix(NODE-4874): Fix handling of flags
W-A-James Jan 6, 2023
d67757d
test(NODE-4874): Finish tests
W-A-James Jan 6, 2023
4f15e42
style(NODE-4874): fixup condition
W-A-James Jan 6, 2023
2f5db05
fix(NODE-4874): Remove dead code
W-A-James Jan 6, 2023
822dff8
fix(NODE-4874): Add fix and test for double bug
W-A-James Jan 11, 2023
5666255
fix(NODE-4874): Implement requested fixes
W-A-James Jan 11, 2023
79f0c81
test(NODE-4874): Add new table tests
W-A-James Jan 11, 2023
260b667
test(NODE-4874): Add test to check that legacy flag doesn't affect ou…
W-A-James Jan 11, 2023
6a5364a
test(NODE-4874): Add another assertion
W-A-James Jan 11, 2023
7eca3ea
style(NODE-4874): Update comment
W-A-James Jan 11, 2023
f6aed3d
test(NODE-4874): Remove 'only' annotation
W-A-James Jan 11, 2023
b0e67aa
fix(NODE-4874): Update Long.fromExtendedJSON
W-A-James Jan 11, 2023
4361871
test(NODE-4874): Add new tests
W-A-James Jan 11, 2023
92ae357
fix(NODE-4874): Add fixes
W-A-James Jan 12, 2023
fff3d32
test(NODE-4874): Update tests
W-A-James Jan 12, 2023
a86a942
style(NODE-4874): Change variable name
W-A-James Jan 12, 2023
d9f539f
fix(NODE-4874): Fix MAX_INT64_STRING_LENGTH constant
W-A-James Jan 13, 2023
0acf248
test(NODE-4874): Test fixups
W-A-James Jan 17, 2023
cdf0334
fix(NODE-4874): Fix fromExtendedJSON checks and errors
W-A-James Jan 17, 2023
027b5df
fix(NODE-4874): Update fromExtendedJSON
W-A-James Jan 17, 2023
a02236f
test(NODE-4874): New tests
W-A-James Jan 17, 2023
31f8bf7
test(NODE-4874): Add tests for current validation behaviour
W-A-James Jan 18, 2023
f22ce03
fix(NODE-4874): Small style fix
W-A-James Jan 18, 2023
8b0a741
test(NODE-4874): Add comments and fix test value
W-A-James Jan 18, 2023
e0049b3
fix(NODE-4874): Make error messages more verbose
W-A-James Jan 18, 2023
47cdb65
fix(NODE-4874): Fix error messages
W-A-James Jan 18, 2023
d502ddb
Merge branch 'main' into NODE-4874/support_ejson_parse_for_BigInt_fro…
W-A-James Jan 18, 2023
f80b707
fix(NODE-4874): merge conflict fix
W-A-James Jan 18, 2023
c90b783
fix(NODE-4874): Update fromExtendedJSON behaviour to be more consiste…
W-A-James Jan 19, 2023
43b39c1
test(NODE-4874): move EJSON.parse tests back under the correct descri…
W-A-James Jan 19, 2023
b99a141
test(NODE-4874): Unnest EJSON.stringify fron EJSON.parse
W-A-James Jan 19, 2023
a0b5cf0
test(NODE-4874): Remove todo
W-A-James Jan 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 17 additions & 4 deletions src/extended_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export type EJSONOptions = {
legacy?: boolean;
/** Enable Extended JSON's `relaxed` mode, which attempts to return native JS types where possible, rather than BSON types */
relaxed?: boolean;
/** Enable native bigint support */
useBigInt64?: boolean;
};

/** @internal */
Expand Down Expand Up @@ -76,17 +78,23 @@ const keysToCodecs = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function deserializeValue(value: any, options: EJSONOptions = {}) {
if (typeof value === 'number') {
// TODO(NODE-4377): EJSON js number handling diverges from BSON
const in32BitRange = value <= BSON_INT32_MAX && value >= BSON_INT32_MIN;
const in64BitRange = value <= BSON_INT64_MAX && value >= BSON_INT64_MIN;
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved

if (options.relaxed || options.legacy) {
return value;
}

if (Number.isInteger(value) && !Object.is(value, -0)) {
// interpret as being of the smallest BSON integer type that can represent the number exactly
if (value >= BSON_INT32_MIN && value <= BSON_INT32_MAX) {
if (in32BitRange) {
return new Int32(value);
}
if (value >= BSON_INT64_MIN && value <= BSON_INT64_MAX) {
// TODO(NODE-4377): EJSON js number handling diverges from BSON
if (in64BitRange) {
if (options.useBigInt64) {
return BigInt(value);
}
return Long.fromNumber(value);
}
}
Expand Down Expand Up @@ -378,13 +386,18 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) {
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function parse(text: string, options?: EJSONOptions): any {
const ejsonOptions = {
useBigInt64: options?.useBigInt64 ?? false,
relaxed: options?.relaxed ?? true,
legacy: options?.legacy ?? false
};
return JSON.parse(text, (key, value) => {
if (key.indexOf('\x00') !== -1) {
throw new BSONError(
`BSON Document field names cannot contain null bytes, found: ${JSON.stringify(key)}`
);
}
return deserializeValue(value, { relaxed: true, legacy: false, ...options });
return deserializeValue(value, ejsonOptions);
});
}

Expand Down
36 changes: 33 additions & 3 deletions src/long.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const INT_CACHE: { [key: number]: Long } = {};
/** A cache of the Long representations of small unsigned integer values. */
const UINT_CACHE: { [key: number]: Long } = {};

const MAX_INT64_STRING_LENGTH = 20;

const DECIMAL_REG_EX = /^(\+?0|(\+|-)?[1-9][0-9]*)$/;

/** @public */
export interface LongExtended {
$numberLong: string;
Expand Down Expand Up @@ -1023,9 +1027,35 @@ export class Long extends BSONValue {
if (options && options.relaxed) return this.toNumber();
return { $numberLong: this.toString() };
}
static fromExtendedJSON(doc: { $numberLong: string }, options?: EJSONOptions): number | Long {
const result = Long.fromString(doc.$numberLong);
return options && options.relaxed ? result.toNumber() : result;
static fromExtendedJSON(
doc: { $numberLong: string },
options?: EJSONOptions
): number | Long | bigint {
const { useBigInt64 = false, relaxed = true } = { ...options };

if (doc.$numberLong.length > MAX_INT64_STRING_LENGTH) {
throw new BSONError('$numberLong string is too long');
}

if (!DECIMAL_REG_EX.test(doc.$numberLong)) {
throw new BSONError(`$numberLong string "${doc.$numberLong}" is in an invalid format`);
}

if (useBigInt64) {
const INT64_MAX = BigInt('0x7fffffffffffffff');
const INT64_MIN = -BigInt('0x8000000000000000');
const bigIntResult = BigInt(doc.$numberLong);
if (bigIntResult > INT64_MAX || bigIntResult < INT64_MIN) {
throw new BSONError(`EJSON numberLong must be in int64 range; got ${bigIntResult}`);
}
return bigIntResult;
}

const longResult = Long.fromString(doc.$numberLong);
if (relaxed) {
return longResult.toNumber();
}
return longResult;
}

/** @internal */
Expand Down