diff --git a/src/bson.ts b/src/bson.ts index 8ec644bf..962e69e0 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -50,7 +50,7 @@ export { Decimal128 }; export { BSONValue } from './bson_value'; -export { BSONError, BSONVersionError } from './error'; +export { BSONError, BSONVersionError, BSONRuntimeError } from './error'; export { BSONType } from './constants'; export { EJSON } from './extended_json'; diff --git a/src/error.ts b/src/error.ts index b01cde53..f98f0fb7 100644 --- a/src/error.ts +++ b/src/error.ts @@ -2,7 +2,11 @@ import { BSON_MAJOR_VERSION } from './constants'; /** * @public - * `BSONError` objects are thrown when runtime errors occur. + * @category Error + * + * `BSONError` objects are thrown when BSON ecounters an error. + * + * This is the parent class for all the other errors thrown by this library. */ export class BSONError extends Error { /** @@ -46,7 +50,10 @@ export class BSONError extends Error { } } -/** @public */ +/** + * @public + * @category Error + */ export class BSONVersionError extends BSONError { get name(): 'BSONVersionError' { return 'BSONVersionError'; @@ -58,3 +65,21 @@ export class BSONVersionError extends BSONError { ); } } + +/** + * @public + * @category Error + * + * An error generated when BSON functions encounter an unexpected input + * or reaches an unexpected/invalid internal state + * + */ +export class BSONRuntimeError extends BSONError { + get name(): 'BSONRuntimeError' { + return 'BSONRuntimeError'; + } + + constructor(message: string) { + super(message); + } +} diff --git a/src/extended_json.ts b/src/extended_json.ts index d0b60131..fd1f0a30 100644 --- a/src/extended_json.ts +++ b/src/extended_json.ts @@ -11,7 +11,7 @@ import { import { DBRef, isDBRefLike } from './db_ref'; import { Decimal128 } from './decimal128'; import { Double } from './double'; -import { BSONError, BSONVersionError } from './error'; +import { BSONError, BSONRuntimeError, BSONVersionError } from './error'; import { Int32 } from './int_32'; import { Long } from './long'; import { MaxKey } from './max_key'; @@ -125,10 +125,14 @@ function deserializeValue(value: any, options: EJSONOptions = {}) { if (options.legacy) { if (typeof d === 'number') date.setTime(d); else if (typeof d === 'string') date.setTime(Date.parse(d)); + else if (typeof d === 'bigint') date.setTime(Number(d)); + else throw new BSONRuntimeError(`Unrecognized type for EJSON date: ${typeof d}`); } else { if (typeof d === 'string') date.setTime(Date.parse(d)); else if (Long.isLong(d)) date.setTime(d.toNumber()); else if (typeof d === 'number' && options.relaxed) date.setTime(d); + else if (typeof d === 'bigint') date.setTime(Number(d)); + else throw new BSONRuntimeError(`Unrecognized type for EJSON date: ${typeof d}`); } return date; } diff --git a/test/node/error.test.ts b/test/node/error.test.ts index 8e1e7289..ee854368 100644 --- a/test/node/error.test.ts +++ b/test/node/error.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { loadESModuleBSON } from '../load_bson'; -import { __isWeb__, BSONError, BSONVersionError } from '../register-bson'; +import { __isWeb__, BSONError, BSONVersionError, BSONRuntimeError } from '../register-bson'; const instanceOfChecksWork = !__isWeb__; @@ -92,4 +92,14 @@ describe('BSONError', function () { expect(new BSONVersionError()).to.have.property('name', 'BSONVersionError'); }); }); + + describe('class BSONRuntimeError', function () { + it('is a BSONError instance', function () { + expect(BSONError.isBSONError(new BSONRuntimeError('Oopsie'))).to.be.true; + }); + + it('has a name property equal to "BSONRuntimeError"', function () { + expect(new BSONRuntimeError('Woops!')).to.have.property('name', 'BSONRuntimeError'); + }); + }); }); diff --git a/test/node/exports.test.ts b/test/node/exports.test.ts index ac258c20..2f800729 100644 --- a/test/node/exports.test.ts +++ b/test/node/exports.test.ts @@ -26,6 +26,7 @@ const EXPECTED_EXPORTS = [ 'BSONRegExp', 'Decimal128', 'BSONError', + 'BSONRuntimeError', 'setInternalBufferSize', 'serialize', 'serializeWithBufferAndIndex', diff --git a/test/node/extended_json.test.ts b/test/node/extended_json.test.ts index 51e19fd7..c1f14f2d 100644 --- a/test/node/extended_json.test.ts +++ b/test/node/extended_json.test.ts @@ -2,7 +2,7 @@ import * as BSON from '../register-bson'; const EJSON = BSON.EJSON; import * as vm from 'node:vm'; import { expect } from 'chai'; -import { BSONVersionError } from '../../src'; +import { BSONVersionError, BSONRuntimeError } from '../../src'; // BSON types const Binary = BSON.Binary; @@ -440,6 +440,29 @@ describe('Extended JSON', function () { expect(bson).to.deep.equal(doc); }); }); + + context('when using useBigInt64=true', function () { + it('parses $date.$numberLong with millis since epoch', function () { + if (BSON.__noBigInt__) { + this.skip(); + } + const date = new Date(1676315495987); + const doc = { field: date }; + const stringified = EJSON.stringify(doc, { relaxed: false }); + const parsedDoc = EJSON.parse(stringified, { useBigInt64: true, relaxed: false }); + expect(parsedDoc).to.deep.equal(doc); + }); + }); + + context('when deserializing object with invalid $date key', function () { + it('throws a BSONRuntimeError', function () { + const doc = { field: { $date: new ArrayBuffer(10) } }; + const s = EJSON.stringify(doc, { relaxed: false }); + expect(() => { + EJSON.parse(s, { relaxed: false }); + }).to.throw(BSONRuntimeError, /Unrecognized type/i); + }); + }); }); context('when deserializing regex', function () {