Skip to content

Commit e3c447d

Browse files
committed
chore: error name and unit tests
1 parent e46e198 commit e3c447d

File tree

9 files changed

+75
-27
lines changed

9 files changed

+75
-27
lines changed

src/client-side-encryption/state_machine.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export class StateMachine {
319319
*/
320320
async kmsRequest(
321321
request: MongoCryptKMSRequest,
322-
options: { timeoutContext?: TimeoutContext } & Abortable
322+
options?: { timeoutContext?: TimeoutContext } & Abortable
323323
): Promise<void> {
324324
const parsedUrl = request.endpoint.split(':');
325325
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
@@ -428,7 +428,7 @@ export class StateMachine {
428428
resolve
429429
} = promiseWithResolvers<void>();
430430

431-
abortListener = addAbortListener(options.signal, error => {
431+
abortListener = addAbortListener(options?.signal, error => {
432432
destroySockets();
433433
rejectOnTlsSocketError(error);
434434
});
@@ -447,7 +447,7 @@ export class StateMachine {
447447
resolve();
448448
}
449449
});
450-
await (options.timeoutContext?.csotEnabled()
450+
await (options?.timeoutContext?.csotEnabled()
451451
? Promise.all([
452452
willResolveKmsRequest,
453453
Timeout.expires(options.timeoutContext?.remainingTimeMS)
@@ -464,7 +464,7 @@ export class StateMachine {
464464
}
465465
}
466466

467-
*requests(context: MongoCryptContext, options: { timeoutContext?: TimeoutContext } & Abortable) {
467+
*requests(context: MongoCryptContext, options?: { timeoutContext?: TimeoutContext } & Abortable) {
468468
for (
469469
let request = context.nextKMSRequest();
470470
request != null;
@@ -531,16 +531,16 @@ export class StateMachine {
531531
client: MongoClient,
532532
ns: string,
533533
filter: Document,
534-
options: { timeoutContext?: TimeoutContext } & Abortable
534+
options?: { timeoutContext?: TimeoutContext } & Abortable
535535
): Promise<Uint8Array | null> {
536536
const { db } = MongoDBCollectionNamespace.fromString(ns);
537537

538538
const cursor = client.db(db).listCollections(filter, {
539539
promoteLongs: false,
540540
promoteValues: false,
541541
timeoutContext:
542-
options.timeoutContext && new CursorTimeoutContext(options.timeoutContext, Symbol()),
543-
signal: options.signal
542+
options?.timeoutContext && new CursorTimeoutContext(options?.timeoutContext, Symbol()),
543+
signal: options?.signal
544544
});
545545

546546
// There is always exactly zero or one matching documents, so this should always exhaust the cursor
@@ -564,7 +564,7 @@ export class StateMachine {
564564
client: MongoClient,
565565
ns: string,
566566
command: Uint8Array,
567-
options: { timeoutContext?: TimeoutContext } & Abortable
567+
options?: { timeoutContext?: TimeoutContext } & Abortable
568568
): Promise<Uint8Array> {
569569
const { db } = MongoDBCollectionNamespace.fromString(ns);
570570
const bsonOptions = { promoteLongs: false, promoteValues: false };
@@ -578,10 +578,10 @@ export class StateMachine {
578578
signal: undefined
579579
};
580580

581-
if (options.timeoutContext?.csotEnabled()) {
581+
if (options?.timeoutContext?.csotEnabled()) {
582582
commandOptions.timeoutMS = options.timeoutContext.remainingTimeMS;
583583
}
584-
if (options.signal) {
584+
if (options?.signal) {
585585
commandOptions.signal = options.signal;
586586
}
587587

@@ -605,7 +605,7 @@ export class StateMachine {
605605
client: MongoClient,
606606
keyVaultNamespace: string,
607607
filter: Uint8Array,
608-
options: { timeoutContext?: TimeoutContext } & Abortable
608+
options?: { timeoutContext?: TimeoutContext } & Abortable
609609
): Promise<Array<DataKey>> {
610610
const { db: dbName, collection: collectionName } =
611611
MongoDBCollectionNamespace.fromString(keyVaultNamespace);
@@ -618,10 +618,10 @@ export class StateMachine {
618618
signal: undefined
619619
};
620620

621-
if (options.timeoutContext != null) {
621+
if (options?.timeoutContext != null) {
622622
commandOptions.timeoutContext = new CursorTimeoutContext(options.timeoutContext, Symbol());
623623
}
624-
if (options.signal != null) {
624+
if (options?.signal != null) {
625625
commandOptions.signal = options.signal;
626626
}
627627

src/error.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,12 +199,28 @@ export class MongoError extends Error {
199199
/**
200200
* An error thrown when a signal is aborted
201201
*
202+
* A MongoAbortError has the name "AbortError" to match the name
203+
* given to a DOMException thrown from web APIs that support AbortSignals.
204+
*
205+
* @example
206+
* ```js
207+
* try {
208+
* const res = await fetch('...', { signal });
209+
* await collection.insertOne(await res.json(), { signal });
210+
* catch (error) {
211+
* if (error.name === 'AbortError') {
212+
* // error is MongoAbortError or DOMException,
213+
* // both represent the signal being aborted
214+
* }
215+
* }
216+
* ```
217+
*
202218
* @public
203219
* @category Error
204220
*/
205-
export class MongoAbortedError extends MongoError {
206-
override get name(): string {
207-
return 'MongoAbortedError';
221+
export class MongoAbortError extends MongoError {
222+
override get name(): 'AbortError' {
223+
return 'AbortError';
208224
}
209225
}
210226

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export {
4545
export { ClientEncryption } from './client-side-encryption/client_encryption';
4646
export { ChangeStreamCursor } from './cursor/change_stream_cursor';
4747
export {
48+
MongoAbortError,
4849
MongoAPIError,
4950
MongoAWSError,
5051
MongoAzureError,

src/mongo_types.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,30 @@ export class CancellationToken extends TypedEventEmitter<{ cancel(): void }> {}
478478
export type Abortable = {
479479
/**
480480
* When provided the corresponding `AbortController` can be used to cancel an asynchronous action.
481+
*
482+
* The driver will convert the abort event into a promise rejection with an error that has the name `'AbortError'`.
483+
*
484+
* The cause of the error will be set to `signal.reason`
485+
*
486+
* @example
487+
* ```js
488+
* const controller = new AbortController();
489+
* const { signal } = controller;
490+
* req,on('close', () => controller.abort(new Error('Request aborted by user')));
491+
*
492+
* try {
493+
* const res = await fetch('...', { signal });
494+
* await collection.insertOne(await res.json(), { signal });
495+
* catch (error) {
496+
* if (error.name === 'AbortError') {
497+
* // error is MongoAbortError or DOMException,
498+
* // both represent the signal being aborted
499+
* error.cause === signal.reason; // true
500+
* }
501+
* }
502+
* ```
503+
*
504+
* @see MongoAbortError
481505
*/
482506
signal?: AbortSignal | undefined;
483507
};

src/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { FindCursor } from './cursor/find_cursor';
1818
import type { Db } from './db';
1919
import {
2020
type AnyError,
21-
MongoAbortedError,
21+
MongoAbortError,
2222
MongoAPIError,
2323
MongoCompatibilityError,
2424
MongoInvalidArgumentError,
@@ -1484,7 +1484,7 @@ export function addAbortListener(
14841484
if (signal == null) return;
14851485

14861486
const convertReasonToError = () =>
1487-
listener(new MongoAbortedError('Operation was aborted', { cause: signal.reason }));
1487+
listener(new MongoAbortError('Operation was aborted', { cause: signal.reason }));
14881488

14891489
signal.addEventListener('abort', convertReasonToError);
14901490

@@ -1493,6 +1493,6 @@ export function addAbortListener(
14931493

14941494
export function throwIfAborted(signal?: { aborted?: boolean; reason?: any }): void {
14951495
if (signal?.aborted) {
1496-
throw new MongoAbortedError('Operation was aborted', { cause: signal.reason });
1496+
throw new MongoAbortError('Operation was aborted', { cause: signal.reason });
14971497
}
14981498
}

test/integration/node-specific/abort_signal.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
type Collection,
77
type Db,
88
FindCursor,
9-
MongoAbortedError,
9+
MongoAbortError,
1010
type MongoClient,
1111
ReadPreference,
1212
setDifference
@@ -155,7 +155,7 @@ describe('AbortSignal support', () => {
155155
for (const [cursorAPI, { value: args }] of getAllOwnProps(cursorAPIs)) {
156156
it(`rejects ${cursorAPI.toString()} with MongoAbortedError and the cause is signal.reason`, async () => {
157157
const result = await captureCursorError(cursor, cursorAPI, args);
158-
expect(result).to.be.instanceOf(MongoAbortedError);
158+
expect(result).to.be.instanceOf(MongoAbortError);
159159
expect(result.cause).to.equal(signal.reason);
160160
});
161161
}
@@ -190,7 +190,7 @@ describe('AbortSignal support', () => {
190190
controller.abort('The operation was aborted');
191191

192192
const error = await captureCursorError(cursor, cursorAPI, args);
193-
expect(error).to.be.instanceOf(MongoAbortedError);
193+
expect(error).to.be.instanceOf(MongoAbortError);
194194
});
195195
}
196196
});
@@ -235,7 +235,7 @@ describe('AbortSignal support', () => {
235235
const end = performance.now();
236236
expect(end - start).to.be.lessThan(1000); // should be way less than 30s server selection timeout
237237

238-
expect(result).to.be.instanceOf(MongoAbortedError);
238+
expect(result).to.be.instanceOf(MongoAbortError);
239239
expect(result.cause).to.equal(signal.reason);
240240
});
241241
}

test/unit/client-side-encryption/state_machine.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ describe('StateMachine', function () {
493493
});
494494

495495
await stateMachine
496-
.fetchKeys(client, 'keyVault', BSON.serialize({ a: 1 }), context)
496+
.fetchKeys(client, 'keyVault', BSON.serialize({ a: 1 }), { timeoutContext: context })
497497
.catch(e => squashError(e));
498498

499499
const { timeoutContext } = findSpy.getCalls()[0].args[1] as FindOptions;
@@ -535,7 +535,7 @@ describe('StateMachine', function () {
535535
});
536536
await sleep(300);
537537
await stateMachine
538-
.markCommand(client, 'keyVault', BSON.serialize({ a: 1 }), timeoutContext)
538+
.markCommand(client, 'keyVault', BSON.serialize({ a: 1 }), { timeoutContext })
539539
.catch(e => squashError(e));
540540
expect(dbCommandSpy.getCalls()[0].args[1].timeoutMS).to.not.be.undefined;
541541
expect(dbCommandSpy.getCalls()[0].args[1].timeoutMS).to.be.lessThanOrEqual(205);
@@ -576,7 +576,9 @@ describe('StateMachine', function () {
576576
});
577577
await sleep(300);
578578
await stateMachine
579-
.fetchCollectionInfo(client, 'keyVault', BSON.serialize({ a: 1 }), context)
579+
.fetchCollectionInfo(client, 'keyVault', BSON.serialize({ a: 1 }), {
580+
timeoutContext: context
581+
})
580582
.catch(e => squashError(e));
581583
const [_filter, { timeoutContext }] = listCollectionsSpy.getCalls()[0].args;
582584
expect(timeoutContext).to.exist;

test/unit/error.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,11 @@ describe('MongoErrors', () => {
7777
expect(errorNameDescriptor).to.have.property('set').that.does.not.exist;
7878
expect(errorNameDescriptor).to.not.have.property('value');
7979
expect(errorNameDescriptor).to.have.property('get');
80-
expect(errorNameDescriptor.get.call(undefined)).to.equal(errorName);
80+
if (errorName === 'MongoAbortError') {
81+
expect(errorNameDescriptor.get.call(undefined)).to.equal('AbortError');
82+
} else {
83+
expect(errorNameDescriptor.get.call(undefined)).to.equal(errorName);
84+
}
8185
});
8286
}
8387
});

test/unit/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const EXPECTED_EXPORTS = [
6060
'Long',
6161
'MaxKey',
6262
'MinKey',
63+
'MongoAbortError',
6364
'MongoAPIError',
6465
'MongoAWSError',
6566
'MongoAzureError',

0 commit comments

Comments
 (0)