diff --git a/README.md b/README.md
index 0240bed9..273fa356 100644
--- a/README.md
+++ b/README.md
@@ -285,6 +285,29 @@ Deserialize stream data as BSON documents.
**Returns**: Number
- returns the next index in the buffer after deserialization **x** numbers of documents.
+## Error Handling
+
+It is our recommendation to use `BSONError.isBSONError()` checks on errors and to avoid relying on parsing `error.message` and `error.name` strings in your code. We guarantee `BSONError.isBSONError()` checks will pass according to semver guidelines, but errors may be sub-classed or their messages may change at any time, even patch releases, as we see fit to increase the helpfulness of the errors.
+
+Any new errors we add to the driver will directly extend an existing error class and no existing error will be moved to a different parent class outside of a major release.
+This means `BSONError.isBSONError()` will always be able to accurately capture the errors that our BSON library throws.
+
+Hypothetical example: A collection in our Db has an issue with UTF-8 data:
+
+```ts
+let documentCount = 0;
+const cursor = collection.find({}, { utf8Validation: true });
+try {
+ for await (const doc of cursor) documentCount += 1;
+} catch (error) {
+ if (BSONError.isBSONError(error)) {
+ console.log(`Found the troublemaker UTF-8!: ${documentCount} ${error.message}`);
+ return documentCount;
+ }
+ throw error;
+}
+```
+
## FAQ
#### Why does `undefined` get converted to `null`?
diff --git a/docs/upgrade-to-v5.md b/docs/upgrade-to-v5.md
index a1a8cc23..01be3b26 100644
--- a/docs/upgrade-to-v5.md
+++ b/docs/upgrade-to-v5.md
@@ -264,3 +264,27 @@ You can now find compiled bundles of the BSON library in 3 common formats in the
- ES Module - `lib/bson.mjs`
- Immediate Invoked Function Expression (IIFE) - `lib/bson.bundle.js`
- Typically used when trying to import JS on the web CDN style, but the ES Module (`.mjs`) bundle is fully browser compatible and should be preferred if it works in your use case.
+
+### `BSONTypeError` removed and `BSONError` offers filtering functionality with `static isBSONError()`
+
+`BSONTypeError` has been removed because it was not a subclass of BSONError so would not return true for an `instanceof` check against `BSONError`. To learn more about our expectations of error handling see [this section of the mongodb driver's readme](https://github.com/mongodb/node-mongodb-native/tree/main#error-handling).
+
+
+A `BSONError` can be thrown from deep within a library that relies on BSON, having one error super class for the library helps with programmatic filtering of an error's origin.
+Since BSON can be used in environments where instances may originate from across realms, `BSONError` has a static `isBSONError()` method that helps with determining if an object is a `BSONError` instance (much like [Array.isArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)).
+It is our recommendation to use `isBSONError()` checks on errors and to avoid relying on parsing `error.message` and `error.name` strings in your code. We guarantee `isBSONError()` checks will pass according to semver guidelines, but errors may be sub-classed or their messages may change at any time, even patch releases, as we see fit to increase the helpfulness of the errors.
+
+Hypothetical example: A collection in our Db has an issue with UTF-8 data:
+```ts
+let documentCount = 0;
+const cursor = collection.find({}, { utf8Validation: true });
+try {
+ for await (const doc of cursor) documentCount += 1;
+} catch (error) {
+ if (BSONError.isBSONError(error)) {
+ console.log(`Found the troublemaker UTF-8!: ${documentCount} ${error.message}`);
+ return documentCount;
+ }
+ throw error;
+}
+```
diff --git a/src/binary.ts b/src/binary.ts
index 72f2eeb5..0b0113f8 100644
--- a/src/binary.ts
+++ b/src/binary.ts
@@ -1,7 +1,7 @@
import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils';
import { isUint8Array } from './parser/utils';
import type { EJSONOptions } from './extended_json';
-import { BSONError, BSONTypeError } from './error';
+import { BSONError } from './error';
import { BSON_BINARY_SUBTYPE_UUID_NEW, BSON_MAJOR_VERSION } from './constants';
import { ByteUtils } from './utils/byte_utils';
@@ -86,7 +86,7 @@ export class Binary {
!(buffer instanceof ArrayBuffer) &&
!Array.isArray(buffer)
) {
- throw new BSONTypeError(
+ throw new BSONError(
'Binary can only be constructed from string, Buffer, TypedArray, or Array'
);
}
@@ -121,9 +121,9 @@ export class Binary {
put(byteValue: string | number | Uint8Array | number[]): void {
// If it's a string and a has more than one character throw an error
if (typeof byteValue === 'string' && byteValue.length !== 1) {
- throw new BSONTypeError('only accepts single character String');
+ throw new BSONError('only accepts single character String');
} else if (typeof byteValue !== 'number' && byteValue.length !== 1)
- throw new BSONTypeError('only accepts single character Uint8Array or Array');
+ throw new BSONError('only accepts single character Uint8Array or Array');
// Decode the byte value once
let decodedByte: number;
@@ -136,7 +136,7 @@ export class Binary {
}
if (decodedByte < 0 || decodedByte > 255) {
- throw new BSONTypeError('only accepts number in a valid unsigned byte range 0-255');
+ throw new BSONError('only accepts number in a valid unsigned byte range 0-255');
}
if (this.buffer.byteLength > this.position) {
@@ -283,7 +283,7 @@ export class Binary {
data = uuidHexStringToBuffer(doc.$uuid);
}
if (!data) {
- throw new BSONTypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`);
+ throw new BSONError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`);
}
return type === BSON_BINARY_SUBTYPE_UUID_NEW ? new UUID(data) : new Binary(data, type);
}
@@ -337,7 +337,7 @@ export class UUID extends Binary {
} else if (typeof input === 'string') {
bytes = uuidHexStringToBuffer(input);
} else {
- throw new BSONTypeError(
+ throw new BSONError(
'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).'
);
}
diff --git a/src/bson.ts b/src/bson.ts
index 871dac3b..b0684731 100644
--- a/src/bson.ts
+++ b/src/bson.ts
@@ -49,7 +49,7 @@ export {
BSONRegExp,
Decimal128
};
-export { BSONError, BSONTypeError } from './error';
+export { BSONError } from './error';
export { BSONType } from './constants';
export { EJSON } from './extended_json';
diff --git a/src/decimal128.ts b/src/decimal128.ts
index 81384a24..e2f097b6 100644
--- a/src/decimal128.ts
+++ b/src/decimal128.ts
@@ -1,5 +1,5 @@
import { BSON_MAJOR_VERSION } from './constants';
-import { BSONTypeError } from './error';
+import { BSONError } from './error';
import { Long } from './long';
import { isUint8Array } from './parser/utils';
import { ByteUtils } from './utils/byte_utils';
@@ -114,7 +114,7 @@ function lessThan(left: Long, right: Long): boolean {
}
function invalidErr(string: string, message: string) {
- throw new BSONTypeError(`"${string}" is not a valid Decimal128 string - ${message}`);
+ throw new BSONError(`"${string}" is not a valid Decimal128 string - ${message}`);
}
/** @public */
@@ -147,11 +147,11 @@ export class Decimal128 {
this.bytes = Decimal128.fromString(bytes).bytes;
} else if (isUint8Array(bytes)) {
if (bytes.byteLength !== 16) {
- throw new BSONTypeError('Decimal128 must take a Buffer of 16 bytes');
+ throw new BSONError('Decimal128 must take a Buffer of 16 bytes');
}
this.bytes = bytes;
} else {
- throw new BSONTypeError('Decimal128 must take a Buffer or string');
+ throw new BSONError('Decimal128 must take a Buffer or string');
}
}
@@ -206,7 +206,7 @@ export class Decimal128 {
// TODO: implementing a custom parsing for this, or refactoring the regex would yield
// further gains.
if (representation.length >= 7000) {
- throw new BSONTypeError('' + representation + ' not a valid Decimal128 string');
+ throw new BSONError('' + representation + ' not a valid Decimal128 string');
}
// Results
@@ -216,7 +216,7 @@ export class Decimal128 {
// Validate the string
if ((!stringMatch && !infMatch && !nanMatch) || representation.length === 0) {
- throw new BSONTypeError('' + representation + ' not a valid Decimal128 string');
+ throw new BSONError('' + representation + ' not a valid Decimal128 string');
}
if (stringMatch) {
@@ -288,7 +288,7 @@ export class Decimal128 {
}
if (sawRadix && !nDigitsRead)
- throw new BSONTypeError('' + representation + ' not a valid Decimal128 string');
+ throw new BSONError('' + representation + ' not a valid Decimal128 string');
// Read exponent if exists
if (representation[index] === 'e' || representation[index] === 'E') {
diff --git a/src/error.ts b/src/error.ts
index fa5dbb06..88e16b3e 100644
--- a/src/error.ts
+++ b/src/error.ts
@@ -1,21 +1,45 @@
-/** @public */
+/**
+ * @public
+ * `BSONError` objects are thrown when runtime errors occur.
+ */
export class BSONError extends Error {
- constructor(message: string) {
- super(message);
+ /**
+ * @internal
+ * The underlying algorithm for isBSONError may change to improve how strict it is
+ * about determining if an input is a BSONError. But it must remain backwards compatible
+ * with previous minors & patches of the current major version.
+ */
+ protected get bsonError(): true {
+ return true;
}
- get name(): string {
+ override get name(): string {
return 'BSONError';
}
-}
-/** @public */
-export class BSONTypeError extends TypeError {
constructor(message: string) {
super(message);
}
- get name(): string {
- return 'BSONTypeError';
+ /**
+ * @public
+ *
+ * All errors thrown from the BSON library inherit from `BSONError`.
+ * This method can assist with determining if an error originates from the BSON library
+ * even if it does not pass an `instanceof` check against this class' constructor.
+ *
+ * @param value - any javascript value that needs type checking
+ */
+ public static isBSONError(value: unknown): value is BSONError {
+ return (
+ value != null &&
+ typeof value === 'object' &&
+ 'bsonError' in value &&
+ value.bsonError === true &&
+ // Do not access the following properties, just check existence
+ 'name' in value &&
+ 'message' in value &&
+ 'stack' in value
+ );
}
}
diff --git a/src/extended_json.ts b/src/extended_json.ts
index dcfef767..32ee886a 100644
--- a/src/extended_json.ts
+++ b/src/extended_json.ts
@@ -5,7 +5,7 @@ import { BSON_INT32_MAX, BSON_INT32_MIN, BSON_INT64_MAX, BSON_INT64_MIN } from '
import { DBRef, isDBRefLike } from './db_ref';
import { Decimal128 } from './decimal128';
import { Double } from './double';
-import { BSONError, BSONTypeError } from './error';
+import { BSONError } from './error';
import { Int32 } from './int_32';
import { Long } from './long';
import { MaxKey } from './max_key';
@@ -192,7 +192,7 @@ function serializeValue(value: any, options: EJSONSerializeOptions): any {
circularPart.length + (alreadySeen.length + current.length) / 2 - 1
);
- throw new BSONTypeError(
+ throw new BSONError(
'Converting circular structure to EJSON:\n' +
` ${leadingPart}${alreadySeen}${circularPart}${current}\n` +
` ${leadingSpace}\\${dashes}/`
@@ -324,7 +324,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) {
// Copy the object into this library's version of that type.
const mapper = BSON_TYPE_MAPPINGS[doc._bsontype];
if (!mapper) {
- throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + doc._bsontype);
+ throw new BSONError('Unrecognized or invalid _bsontype: ' + doc._bsontype);
}
outDoc = mapper(outDoc);
}
diff --git a/src/long.ts b/src/long.ts
index 77916fc5..b02d9b22 100644
--- a/src/long.ts
+++ b/src/long.ts
@@ -1,4 +1,5 @@
import { BSON_MAJOR_VERSION } from './constants';
+import { BSONError } from './error';
import type { EJSONOptions } from './extended_json';
import type { Timestamp } from './timestamp';
@@ -250,7 +251,7 @@ export class Long {
* @returns The corresponding Long value
*/
static fromString(str: string, unsigned?: boolean, radix?: number): Long {
- if (str.length === 0) throw Error('empty string');
+ if (str.length === 0) throw new BSONError('empty string');
if (str === 'NaN' || str === 'Infinity' || str === '+Infinity' || str === '-Infinity')
return Long.ZERO;
if (typeof unsigned === 'number') {
@@ -260,10 +261,10 @@ export class Long {
unsigned = !!unsigned;
}
radix = radix || 10;
- if (radix < 2 || 36 < radix) throw RangeError('radix');
+ if (radix < 2 || 36 < radix) throw new BSONError('radix');
let p;
- if ((p = str.indexOf('-')) > 0) throw Error('interior hyphen');
+ if ((p = str.indexOf('-')) > 0) throw new BSONError('interior hyphen');
else if (p === 0) {
return Long.fromString(str.substring(1), unsigned, radix).neg();
}
@@ -431,7 +432,7 @@ export class Long {
*/
divide(divisor: string | number | Long | Timestamp): Long {
if (!Long.isLong(divisor)) divisor = Long.fromValue(divisor);
- if (divisor.isZero()) throw Error('division by zero');
+ if (divisor.isZero()) throw new BSONError('division by zero');
// use wasm support if present
if (wasm) {
@@ -959,7 +960,7 @@ export class Long {
*/
toString(radix?: number): string {
radix = radix || 10;
- if (radix < 2 || 36 < radix) throw RangeError('radix');
+ if (radix < 2 || 36 < radix) throw new BSONError('radix');
if (this.isZero()) return '0';
if (this.isNegative()) {
// Unsigned Longs are never negative
diff --git a/src/objectid.ts b/src/objectid.ts
index ac83f9dc..012c1e19 100644
--- a/src/objectid.ts
+++ b/src/objectid.ts
@@ -1,5 +1,5 @@
import { BSON_MAJOR_VERSION } from './constants';
-import { BSONTypeError } from './error';
+import { BSONError } from './error';
import { isUint8Array } from './parser/utils';
import { BSONDataView, ByteUtils } from './utils/byte_utils';
@@ -57,9 +57,7 @@ export class ObjectId {
let workingId;
if (typeof inputId === 'object' && inputId && 'id' in inputId) {
if (typeof inputId.id !== 'string' && !ArrayBuffer.isView(inputId.id)) {
- throw new BSONTypeError(
- 'Argument passed in must have an id that is of type string or Buffer'
- );
+ throw new BSONError('Argument passed in must have an id that is of type string or Buffer');
}
if ('toHexString' in inputId && typeof inputId.toHexString === 'function') {
workingId = ByteUtils.fromHex(inputId.toHexString());
@@ -85,17 +83,17 @@ export class ObjectId {
if (bytes.byteLength === 12) {
this[kId] = bytes;
} else {
- throw new BSONTypeError('Argument passed in must be a string of 12 bytes');
+ throw new BSONError('Argument passed in must be a string of 12 bytes');
}
} else if (workingId.length === 24 && checkForHexRegExp.test(workingId)) {
this[kId] = ByteUtils.fromHex(workingId);
} else {
- throw new BSONTypeError(
+ throw new BSONError(
'Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer'
);
}
} else {
- throw new BSONTypeError('Argument passed in does not match the accepted types');
+ throw new BSONError('Argument passed in does not match the accepted types');
}
// If we are caching the hex string
if (ObjectId.cacheHexString) {
@@ -271,7 +269,7 @@ export class ObjectId {
static createFromHexString(hexString: string): ObjectId {
// Throw an error if it's not a valid setup
if (typeof hexString === 'undefined' || (hexString != null && hexString.length !== 24)) {
- throw new BSONTypeError(
+ throw new BSONError(
'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters'
);
}
diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts
index 04115d49..1ee93a23 100644
--- a/src/parser/serializer.ts
+++ b/src/parser/serializer.ts
@@ -5,7 +5,7 @@ import * as constants from '../constants';
import type { DBRefLike } from '../db_ref';
import type { Decimal128 } from '../decimal128';
import type { Double } from '../double';
-import { BSONError, BSONTypeError } from '../error';
+import { BSONError } from '../error';
import type { Int32 } from '../int_32';
import { Long } from '../long';
import type { MinKey } from '../min_key';
@@ -165,7 +165,7 @@ function serializeRegExp(buffer: Uint8Array, key: string, value: RegExp, index:
index = index + numberOfWrittenBytes;
buffer[index++] = 0;
if (value.source && value.source.match(regexp) != null) {
- throw Error('value ' + value.source + ' must not contain null bytes');
+ throw new BSONError('value ' + value.source + ' must not contain null bytes');
}
// Adjust the index
index = index + ByteUtils.encodeUTF8Into(buffer, value.source, index);
@@ -194,7 +194,7 @@ function serializeBSONRegExp(buffer: Uint8Array, key: string, value: BSONRegExp,
if (value.pattern.match(regexp) != null) {
// The BSON spec doesn't allow keys with null bytes because keys are
// null-terminated.
- throw Error('pattern ' + value.pattern + ' must not contain null bytes');
+ throw new BSONError('pattern ' + value.pattern + ' must not contain null bytes');
}
// Adjust the index
@@ -241,7 +241,7 @@ function serializeObjectId(buffer: Uint8Array, key: string, value: ObjectId, ind
if (isUint8Array(value.id)) {
buffer.set(value.id.subarray(0, 12), index);
} else {
- throw new BSONTypeError('object [' + JSON.stringify(value) + '] is not a valid ObjectId');
+ throw new BSONError('object [' + JSON.stringify(value) + '] is not a valid ObjectId');
}
// Adjust index
@@ -675,7 +675,7 @@ export function serializeInto(
} else if (typeof value === 'number') {
index = serializeNumber(buffer, key, value, index);
} else if (typeof value === 'bigint') {
- throw new BSONTypeError('Unsupported type BigInt, please use Decimal128');
+ throw new BSONError('Unsupported type BigInt, please use Decimal128');
} else if (typeof value === 'boolean') {
index = serializeBoolean(buffer, key, value, index);
} else if (value instanceof Date || isDate(value)) {
@@ -737,7 +737,7 @@ export function serializeInto(
} else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
index = serializeMinMax(buffer, key, value, index);
} else if (typeof value._bsontype !== 'undefined') {
- throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
+ throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
}
}
} else if (object instanceof Map || isMap(object)) {
@@ -763,14 +763,14 @@ export function serializeInto(
if (key.match(regexp) != null) {
// The BSON spec doesn't allow keys with null bytes because keys are
// null-terminated.
- throw Error('key ' + key + ' must not contain null bytes');
+ throw new BSONError('key ' + key + ' must not contain null bytes');
}
if (checkKeys) {
if ('$' === key[0]) {
- throw Error('key ' + key + " must not start with '$'");
+ throw new BSONError('key ' + key + " must not start with '$'");
} else if (~key.indexOf('.')) {
- throw Error('key ' + key + " must not contain '.'");
+ throw new BSONError('key ' + key + " must not contain '.'");
}
}
}
@@ -780,7 +780,7 @@ export function serializeInto(
} else if (type === 'number') {
index = serializeNumber(buffer, key, value, index);
} else if (type === 'bigint' || isBigInt64Array(value) || isBigUInt64Array(value)) {
- throw new BSONTypeError('Unsupported type BigInt, please use Decimal128');
+ throw new BSONError('Unsupported type BigInt, please use Decimal128');
} else if (type === 'boolean') {
index = serializeBoolean(buffer, key, value, index);
} else if (value instanceof Date || isDate(value)) {
@@ -840,7 +840,7 @@ export function serializeInto(
} else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
index = serializeMinMax(buffer, key, value, index);
} else if (typeof value._bsontype !== 'undefined') {
- throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
+ throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
}
}
} else {
@@ -848,7 +848,7 @@ export function serializeInto(
// Provided a custom serialization method
object = object.toBSON();
if (object != null && typeof object !== 'object') {
- throw new BSONTypeError('toBSON function did not return an object');
+ throw new BSONError('toBSON function did not return an object');
}
}
@@ -868,14 +868,14 @@ export function serializeInto(
if (key.match(regexp) != null) {
// The BSON spec doesn't allow keys with null bytes because keys are
// null-terminated.
- throw Error('key ' + key + ' must not contain null bytes');
+ throw new BSONError('key ' + key + ' must not contain null bytes');
}
if (checkKeys) {
if ('$' === key[0]) {
- throw Error('key ' + key + " must not start with '$'");
+ throw new BSONError('key ' + key + " must not start with '$'");
} else if (~key.indexOf('.')) {
- throw Error('key ' + key + " must not contain '.'");
+ throw new BSONError('key ' + key + " must not contain '.'");
}
}
}
@@ -885,7 +885,7 @@ export function serializeInto(
} else if (type === 'number') {
index = serializeNumber(buffer, key, value, index);
} else if (type === 'bigint') {
- throw new BSONTypeError('Unsupported type BigInt, please use Decimal128');
+ throw new BSONError('Unsupported type BigInt, please use Decimal128');
} else if (type === 'boolean') {
index = serializeBoolean(buffer, key, value, index);
} else if (value instanceof Date || isDate(value)) {
@@ -947,7 +947,7 @@ export function serializeInto(
} else if (value._bsontype === 'MinKey' || value._bsontype === 'MaxKey') {
index = serializeMinMax(buffer, key, value, index);
} else if (typeof value._bsontype !== 'undefined') {
- throw new BSONTypeError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
+ throw new BSONError(`Unrecognized or invalid _bsontype: ${String(value._bsontype)}`);
}
}
}
diff --git a/src/regexp.ts b/src/regexp.ts
index bffb0bf9..c40de2b0 100644
--- a/src/regexp.ts
+++ b/src/regexp.ts
@@ -1,5 +1,5 @@
import { BSON_MAJOR_VERSION } from './constants';
-import { BSONError, BSONTypeError } from './error';
+import { BSONError } from './error';
import type { EJSONOptions } from './extended_json';
function alphabetize(str: string): string {
@@ -103,7 +103,7 @@ export class BSONRegExp {
BSONRegExp.parseOptions(doc.$regularExpression.options)
);
}
- throw new BSONTypeError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`);
+ throw new BSONError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`);
}
/** @internal */
diff --git a/src/uuid_utils.ts b/src/uuid_utils.ts
index 859d5cfd..37f0dcfd 100644
--- a/src/uuid_utils.ts
+++ b/src/uuid_utils.ts
@@ -1,4 +1,4 @@
-import { BSONTypeError } from './error';
+import { BSONError } from './error';
import { ByteUtils } from './utils/byte_utils';
// Validation regex for v4 uuid (validates with or without dashes)
@@ -10,7 +10,7 @@ export const uuidValidateString = (str: string): boolean =>
export const uuidHexStringToBuffer = (hexString: string): Uint8Array => {
if (!uuidValidateString(hexString)) {
- throw new BSONTypeError(
+ throw new BSONError(
'UUID string representations must be a 32 or 36 character hex string (dashes excluded/included). Format: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" or "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".'
);
}
diff --git a/test/node/error.test.ts b/test/node/error.test.ts
new file mode 100644
index 00000000..126e7f2b
--- /dev/null
+++ b/test/node/error.test.ts
@@ -0,0 +1,85 @@
+import { expect } from 'chai';
+import { loadESModuleBSON } from '../load_bson';
+
+import { __isWeb__, BSONError } from '../register-bson';
+
+const instanceOfChecksWork = !__isWeb__;
+
+describe('BSONError', function () {
+ describe('isBSONError()', () => {
+ it('returns true for BSONError instances', () => {
+ const error = new BSONError('ah!');
+ if (instanceOfChecksWork) expect(error).to.be.instanceOf(BSONError);
+ expect(BSONError.isBSONError(error)).to.be.true;
+ });
+
+ it('returns true for BSONError created from another vm', async () => {
+ const {
+ exports: { BSONError: BSONErrorFromAnotherVM }
+ } = await loadESModuleBSON();
+ const error = new BSONErrorFromAnotherVM('ah!');
+
+ // Instanceof here is false but the helper still returns true
+ if (instanceOfChecksWork) expect(error).to.not.be.instanceOf(BSONError);
+ expect(BSONError.isBSONError(error)).to.be.true;
+ });
+
+ it('returns true for for any object that meets the required API bsonError:true and name,message,stack properties', () => {
+ expect(
+ BSONError.isBSONError({
+ bsonError: true,
+ name: 'BSONError',
+ message: 'ah!',
+ stack: 'at line X'
+ })
+ ).to.be.true;
+ // The types can be wrong for name,message,stack.
+ // isBSONError does not access the stack getter to avoid triggering generating it
+ expect(BSONError.isBSONError({ bsonError: true, name: Symbol(), message: false, stack: 2 }))
+ .to.be.true;
+ });
+
+ it('returns false for objects that are almost shaped like BSONError', () => {
+ expect(BSONError.isBSONError({ bsonError: true })).to.be.false;
+ expect(BSONError.isBSONError({ bsonError: false })).to.be.false;
+ expect(BSONError.isBSONError({ bsonError: true, name: 'BSONError' })).to.be.false;
+ expect(BSONError.isBSONError({ bsonError: true, name: 'BSONError', message: 'ah!' })).to.be
+ .false;
+ expect(
+ BSONError.isBSONError({
+ bsonError: false,
+ name: 'BSONError',
+ message: 'ah!',
+ stack: 'at line X'
+ })
+ ).to.be.false;
+ });
+
+ it('returns false for nullish and non-object inputs', async () => {
+ expect(BSONError.isBSONError(null)).to.be.false;
+ expect(BSONError.isBSONError(undefined)).to.be.false;
+ expect(BSONError.isBSONError(3)).to.be.false;
+ expect(BSONError.isBSONError(true)).to.be.false;
+ expect(BSONError.isBSONError(new Function())).to.be.false;
+ });
+ });
+
+ it('should evaluate true on instanceof BSONError and Error', function () {
+ const bsonErr = new BSONError('ah!');
+ if (instanceOfChecksWork) {
+ expect(bsonErr instanceof BSONError).to.be.true;
+ expect(bsonErr instanceof Error).to.be.true;
+ expect(bsonErr).to.be.instanceOf(BSONError);
+ expect(bsonErr).to.be.instanceOf(Error);
+ } else {
+ expect(bsonErr).to.have.property('name', 'BSONError');
+ expect(Object.getPrototypeOf(BSONError.prototype)).to.have.property('name', 'Error');
+ }
+ });
+
+ it('should correctly set BSONError name and message properties', function () {
+ const bsonErr = new BSONError('This is a BSONError message');
+ expect(bsonErr.name).equals('BSONError');
+ expect(bsonErr.message).equals('This is a BSONError message');
+ });
+});
diff --git a/test/node/error_test.js b/test/node/error_test.js
deleted file mode 100644
index 2ba82add..00000000
--- a/test/node/error_test.js
+++ /dev/null
@@ -1,48 +0,0 @@
-'use strict';
-
-const { __isWeb__ } = require('../register-bson');
-const BSON = require('../register-bson');
-const BSONTypeError = BSON.BSONTypeError;
-const BSONError = BSON.BSONError;
-
-describe('BSONTypeError', function () {
- it('should evaluate true on instanceof BSONTypeError and TypeError', function () {
- const bsonTypeErr = new BSONTypeError();
- if (__isWeb__) {
- expect(bsonTypeErr).to.have.property('name', 'BSONTypeError');
- expect(Object.getPrototypeOf(BSONTypeError.prototype)).to.have.property('name', 'TypeError');
- } else {
- expect(bsonTypeErr instanceof BSONTypeError).to.be.true;
- expect(bsonTypeErr).to.be.instanceOf(BSONTypeError);
- expect(bsonTypeErr instanceof TypeError).to.be.true;
- expect(bsonTypeErr).to.be.instanceOf(TypeError);
- }
- });
-
- it('should correctly set BSONTypeError name and message properties', function () {
- const bsonTypeErr = new BSONTypeError('This is a BSONTypeError message');
- expect(bsonTypeErr.name).equals('BSONTypeError');
- expect(bsonTypeErr.message).equals('This is a BSONTypeError message');
- });
-});
-
-describe('BSONError', function () {
- it('should evaluate true on instanceof BSONError and Error', function () {
- const bsonErr = new BSONError();
- if (__isWeb__) {
- expect(bsonErr).to.have.property('name', 'BSONError');
- expect(Object.getPrototypeOf(BSONError.prototype)).to.have.property('name', 'Error');
- } else {
- expect(bsonErr instanceof BSONError).to.be.true;
- expect(bsonErr instanceof Error).to.be.true;
- expect(bsonErr).to.be.instanceOf(BSONError);
- expect(bsonErr).to.be.instanceOf(Error);
- }
- });
-
- it('should correctly set BSONError name and message properties', function () {
- const bsonErr = new BSONError('This is a BSONError message');
- expect(bsonErr.name).equals('BSONError');
- expect(bsonErr.message).equals('This is a BSONError message');
- });
-});
diff --git a/test/node/exports.test.ts b/test/node/exports.test.ts
index 204f327e..004bc616 100644
--- a/test/node/exports.test.ts
+++ b/test/node/exports.test.ts
@@ -23,7 +23,6 @@ const EXPECTED_EXPORTS = [
'BSONRegExp',
'Decimal128',
'BSONError',
- 'BSONTypeError',
'setInternalBufferSize',
'serialize',
'serializeWithBufferAndIndex',