Skip to content

Commit 9608c6a

Browse files
authored
refactor(NODE-3291)!: Standardize error representation in the driver (#2824)
1 parent 0d91da1 commit 9608c6a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+588
-472
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Below are some conventions that aren't enforced by any of our tooling but we non
108108
As a product of using TS we should be using es6 syntax features whenever possible.
109109
- **Errors**
110110
- Error messages should be sentence case, and have no periods at the end.
111-
- Use built-in error types where possible (not just `Error`, but `TypeError`/`RangeError`), also endeavor to create new Mongo-specific error types (e.g. `MongoNetworkError`)
111+
- Use driver-specific error types where possible (not just `Error`, but classes that extend `MongoError`, e.g. `MongoNetworkError`)
112112

113113
## Pull Request Process
114114

src/bulk/common.ts

+30-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { PromiseProvider } from '../promise_provider';
22
import { Long, ObjectId, Document, BSONSerializeOptions, resolveBSONOptions } from '../bson';
3-
import { MongoError, MongoWriteConcernError, AnyError, MONGODB_ERROR_CODES } from '../error';
3+
import {
4+
MongoWriteConcernError,
5+
AnyError,
6+
MONGODB_ERROR_CODES,
7+
MongoServerError,
8+
MongoDriverError
9+
} from '../error';
410
import {
511
applyRetryableWrites,
612
executeLegacyOperation,
@@ -303,7 +309,7 @@ export class BulkWriteResult {
303309
}
304310

305311
return new WriteConcernError(
306-
new MongoError({ errmsg: errmsg, code: MONGODB_ERROR_CODES.WriteConcernFailed })
312+
new MongoServerError({ errmsg: errmsg, code: MONGODB_ERROR_CODES.WriteConcernFailed })
307313
);
308314
}
309315
}
@@ -327,9 +333,9 @@ export class BulkWriteResult {
327333
* @category Error
328334
*/
329335
export class WriteConcernError {
330-
err: MongoError;
336+
err: MongoServerError;
331337

332-
constructor(err: MongoError) {
338+
constructor(err: MongoServerError) {
333339
this.err = err;
334340
}
335341

@@ -649,16 +655,17 @@ function handleMongoWriteConcernError(
649655
) {
650656
mergeBatchResults(batch, bulkResult, undefined, err.result);
651657

658+
// TODO: Remove multiple levels of wrapping (NODE-3337)
652659
const wrappedWriteConcernError = new WriteConcernError(
653-
new MongoError({
660+
new MongoServerError({
654661
errmsg: err.result?.writeConcernError.errmsg,
655662
code: err.result?.writeConcernError.result
656663
})
657664
);
658665

659666
callback(
660667
new MongoBulkWriteError(
661-
new MongoError(wrappedWriteConcernError),
668+
new MongoServerError(wrappedWriteConcernError),
662669
new BulkWriteResult(bulkResult)
663670
)
664671
);
@@ -669,7 +676,7 @@ function handleMongoWriteConcernError(
669676
* @public
670677
* @category Error
671678
*/
672-
export class MongoBulkWriteError extends MongoError {
679+
export class MongoBulkWriteError extends MongoServerError {
673680
result: BulkWriteResult;
674681

675682
/** Creates a new MongoBulkWriteError */
@@ -743,7 +750,7 @@ export class FindOperators {
743750
/** Add a single update operation to the bulk operation */
744751
updateOne(updateDocument: Document): BulkOperationBase {
745752
if (!hasAtomicOperators(updateDocument)) {
746-
throw new TypeError('Update document requires atomic operators');
753+
throw new MongoDriverError('Update document requires atomic operators');
747754
}
748755

749756
const currentOp = buildCurrentOp(this.bulkOperation);
@@ -756,7 +763,7 @@ export class FindOperators {
756763
/** Add a replace one operation to the bulk operation */
757764
replaceOne(replacement: Document): BulkOperationBase {
758765
if (hasAtomicOperators(replacement)) {
759-
throw new TypeError('Replacement document must not use atomic operators');
766+
throw new MongoDriverError('Replacement document must not use atomic operators');
760767
}
761768

762769
const currentOp = buildCurrentOp(this.bulkOperation);
@@ -1039,7 +1046,7 @@ export abstract class BulkOperationBase {
10391046
*/
10401047
find(selector: Document): FindOperators {
10411048
if (!selector) {
1042-
throw TypeError('Bulk find operation must specify a selector');
1049+
throw new MongoDriverError('Bulk find operation must specify a selector');
10431050
}
10441051

10451052
// Save a current selector
@@ -1073,51 +1080,51 @@ export abstract class BulkOperationBase {
10731080
if ('replaceOne' in op || 'updateOne' in op || 'updateMany' in op) {
10741081
if ('replaceOne' in op) {
10751082
if ('q' in op.replaceOne) {
1076-
throw new TypeError('Raw operations are not allowed');
1083+
throw new MongoDriverError('Raw operations are not allowed');
10771084
}
10781085
const updateStatement = makeUpdateStatement(
10791086
op.replaceOne.filter,
10801087
op.replaceOne.replacement,
10811088
{ ...op.replaceOne, multi: false }
10821089
);
10831090
if (hasAtomicOperators(updateStatement.u)) {
1084-
throw new TypeError('Replacement document must not use atomic operators');
1091+
throw new MongoDriverError('Replacement document must not use atomic operators');
10851092
}
10861093
return this.addToOperationsList(BatchType.UPDATE, updateStatement);
10871094
}
10881095

10891096
if ('updateOne' in op) {
10901097
if ('q' in op.updateOne) {
1091-
throw new TypeError('Raw operations are not allowed');
1098+
throw new MongoDriverError('Raw operations are not allowed');
10921099
}
10931100
const updateStatement = makeUpdateStatement(op.updateOne.filter, op.updateOne.update, {
10941101
...op.updateOne,
10951102
multi: false
10961103
});
10971104
if (!hasAtomicOperators(updateStatement.u)) {
1098-
throw new TypeError('Update document requires atomic operators');
1105+
throw new MongoDriverError('Update document requires atomic operators');
10991106
}
11001107
return this.addToOperationsList(BatchType.UPDATE, updateStatement);
11011108
}
11021109

11031110
if ('updateMany' in op) {
11041111
if ('q' in op.updateMany) {
1105-
throw new TypeError('Raw operations are not allowed');
1112+
throw new MongoDriverError('Raw operations are not allowed');
11061113
}
11071114
const updateStatement = makeUpdateStatement(op.updateMany.filter, op.updateMany.update, {
11081115
...op.updateMany,
11091116
multi: true
11101117
});
11111118
if (!hasAtomicOperators(updateStatement.u)) {
1112-
throw new TypeError('Update document requires atomic operators');
1119+
throw new MongoDriverError('Update document requires atomic operators');
11131120
}
11141121
return this.addToOperationsList(BatchType.UPDATE, updateStatement);
11151122
}
11161123
}
11171124

11181125
if ('deleteOne' in op) {
11191126
if ('q' in op.deleteOne) {
1120-
throw new TypeError('Raw operations are not allowed');
1127+
throw new MongoDriverError('Raw operations are not allowed');
11211128
}
11221129
return this.addToOperationsList(
11231130
BatchType.DELETE,
@@ -1127,7 +1134,7 @@ export abstract class BulkOperationBase {
11271134

11281135
if ('deleteMany' in op) {
11291136
if ('q' in op.deleteMany) {
1130-
throw new TypeError('Raw operations are not allowed');
1137+
throw new MongoDriverError('Raw operations are not allowed');
11311138
}
11321139
return this.addToOperationsList(
11331140
BatchType.DELETE,
@@ -1136,7 +1143,7 @@ export abstract class BulkOperationBase {
11361143
}
11371144

11381145
// otherwise an unknown operation was provided
1139-
throw TypeError(
1146+
throw new MongoDriverError(
11401147
'bulkWrite only supports insertOne, updateOne, updateMany, deleteOne, deleteMany'
11411148
);
11421149
}
@@ -1170,7 +1177,7 @@ export abstract class BulkOperationBase {
11701177
options = options ?? {};
11711178

11721179
if (this.s.executed) {
1173-
return handleEarlyError(new MongoError('Batch cannot be re-executed'), callback);
1180+
return handleEarlyError(new MongoDriverError('Batch cannot be re-executed'), callback);
11741181
}
11751182

11761183
const writeConcern = WriteConcern.fromOptions(options);
@@ -1188,7 +1195,7 @@ export abstract class BulkOperationBase {
11881195
}
11891196
// If we have no operations in the bulk raise an error
11901197
if (this.s.batches.length === 0) {
1191-
const emptyBatchError = new TypeError('Invalid BulkOperation, Batch cannot be empty');
1198+
const emptyBatchError = new MongoDriverError('Invalid BulkOperation, Batch cannot be empty');
11921199
return handleEarlyError(emptyBatchError, callback);
11931200
}
11941201

@@ -1211,7 +1218,7 @@ export abstract class BulkOperationBase {
12111218

12121219
callback(
12131220
new MongoBulkWriteError(
1214-
new MongoError({
1221+
new MongoServerError({
12151222
message: msg,
12161223
code: this.s.bulkResult.writeErrors[0].code,
12171224
writeErrors: this.s.bulkResult.writeErrors
@@ -1225,7 +1232,7 @@ export abstract class BulkOperationBase {
12251232

12261233
const writeConcernError = writeResult.getWriteConcernError();
12271234
if (writeConcernError) {
1228-
callback(new MongoBulkWriteError(new MongoError(writeConcernError), writeResult));
1235+
callback(new MongoBulkWriteError(new MongoServerError(writeConcernError), writeResult));
12291236
return true;
12301237
}
12311238
}

src/bulk/ordered.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Document } from '../bson';
44
import type { Collection } from '../collection';
55
import type { UpdateStatement } from '../operations/update';
66
import type { DeleteStatement } from '../operations/delete';
7+
import { MongoDriverError } from '../error';
78

89
/** @public */
910
export class OrderedBulkOperation extends BulkOperationBase {
@@ -25,7 +26,9 @@ export class OrderedBulkOperation extends BulkOperationBase {
2526

2627
// Throw error if the doc is bigger than the max BSON size
2728
if (bsonSize >= this.s.maxBsonObjectSize)
28-
throw new TypeError(`Document is larger than the maximum size ${this.s.maxBsonObjectSize}`);
29+
throw new MongoDriverError(
30+
`Document is larger than the maximum size ${this.s.maxBsonObjectSize}`
31+
);
2932

3033
// Create a new batch object if we don't have a current one
3134
if (this.s.currentBatch == null) {
@@ -65,7 +68,7 @@ export class OrderedBulkOperation extends BulkOperationBase {
6568

6669
// We have an array of documents
6770
if (Array.isArray(document)) {
68-
throw new TypeError('Operation passed in cannot be an Array');
71+
throw new MongoDriverError('Operation passed in cannot be an Array');
6972
}
7073

7174
this.s.currentBatch.originalIndexes.push(this.s.currentIndex);

src/bulk/unordered.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { Document } from '../bson';
55
import type { Collection } from '../collection';
66
import type { UpdateStatement } from '../operations/update';
77
import type { DeleteStatement } from '../operations/delete';
8+
import { MongoDriverError } from '../error';
89

910
/** @public */
1011
export class UnorderedBulkOperation extends BulkOperationBase {
@@ -35,7 +36,9 @@ export class UnorderedBulkOperation extends BulkOperationBase {
3536

3637
// Throw error if the doc is bigger than the max BSON size
3738
if (bsonSize >= this.s.maxBsonObjectSize) {
38-
throw new TypeError(`Document is larger than the maximum size ${this.s.maxBsonObjectSize}`);
39+
throw new MongoDriverError(
40+
`Document is larger than the maximum size ${this.s.maxBsonObjectSize}`
41+
);
3942
}
4043

4144
// Holds the current batch
@@ -76,7 +79,7 @@ export class UnorderedBulkOperation extends BulkOperationBase {
7679

7780
// We have an array of documents
7881
if (Array.isArray(document)) {
79-
throw new TypeError('Operation passed in cannot be an Array');
82+
throw new MongoDriverError('Operation passed in cannot be an Array');
8083
}
8184

8285
this.s.currentBatch.operations.push(document);

src/change_stream.ts

+16-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Denque = require('denque');
2-
import { MongoError, AnyError, isResumableError } from './error';
2+
import { MongoError, AnyError, isResumableError, MongoDriverError } from './error';
33
import { AggregateOperation, AggregateOptions } from './operations/aggregate';
44
import {
55
maxWireVersion,
@@ -46,11 +46,10 @@ const CHANGE_DOMAIN_TYPES = {
4646
CLUSTER: Symbol('Cluster')
4747
};
4848

49-
const NO_RESUME_TOKEN_ERROR = new MongoError(
50-
'A change stream document has been received that lacks a resume token (_id).'
51-
);
52-
const NO_CURSOR_ERROR = new MongoError('ChangeStream has no cursor');
53-
const CHANGESTREAM_CLOSED_ERROR = new MongoError('ChangeStream is closed');
49+
const NO_RESUME_TOKEN_ERROR =
50+
'A change stream document has been received that lacks a resume token (_id).';
51+
const NO_CURSOR_ERROR = 'ChangeStream has no cursor';
52+
const CHANGESTREAM_CLOSED_ERROR = 'ChangeStream is closed';
5453

5554
/** @public */
5655
export interface ResumeOptions {
@@ -254,7 +253,7 @@ export class ChangeStream<TSchema extends Document> extends TypedEventEmitter<Ch
254253
} else if (parent instanceof MongoClient) {
255254
this.type = CHANGE_DOMAIN_TYPES.CLUSTER;
256255
} else {
257-
throw new TypeError(
256+
throw new MongoDriverError(
258257
'parent provided to ChangeStream constructor is not an instance of Collection, Db, or MongoClient'
259258
);
260259
}
@@ -352,11 +351,11 @@ export class ChangeStream<TSchema extends Document> extends TypedEventEmitter<Ch
352351

353352
/**
354353
* Return a modified Readable stream including a possible transform method.
355-
* @throws MongoError if this.cursor is undefined
354+
* @throws MongoDriverError if this.cursor is undefined
356355
*/
357356
stream(options?: CursorStreamOptions): Readable {
358357
this.streamOptions = options;
359-
if (!this.cursor) throw NO_CURSOR_ERROR;
358+
if (!this.cursor) throw new MongoDriverError(NO_CURSOR_ERROR);
360359
return this.cursor.stream(options);
361360
}
362361

@@ -606,7 +605,7 @@ function waitForTopologyConnected(
606605
}
607606

608607
if (calculateDurationInMs(start) > timeout) {
609-
return callback(new MongoError('Timed out waiting for connection'));
608+
return callback(new MongoDriverError('Timed out waiting for connection'));
610609
}
611610

612611
waitForTopologyConnected(topology, options, callback);
@@ -651,17 +650,17 @@ function processNewChange<TSchema>(
651650
callback?: Callback<ChangeStreamDocument<TSchema>>
652651
) {
653652
if (changeStream[kClosed]) {
654-
if (callback) callback(CHANGESTREAM_CLOSED_ERROR);
653+
if (callback) callback(new MongoDriverError(CHANGESTREAM_CLOSED_ERROR));
655654
return;
656655
}
657656

658657
// a null change means the cursor has been notified, implicitly closing the change stream
659658
if (change == null) {
660-
return closeWithError(changeStream, CHANGESTREAM_CLOSED_ERROR, callback);
659+
return closeWithError(changeStream, new MongoDriverError(CHANGESTREAM_CLOSED_ERROR), callback);
661660
}
662661

663662
if (change && !change._id) {
664-
return closeWithError(changeStream, NO_RESUME_TOKEN_ERROR, callback);
663+
return closeWithError(changeStream, new MongoDriverError(NO_RESUME_TOKEN_ERROR), callback);
665664
}
666665

667666
// cache the resume token
@@ -685,7 +684,7 @@ function processError<TSchema>(
685684

686685
// If the change stream has been closed explicitly, do not process error.
687686
if (changeStream[kClosed]) {
688-
if (callback) callback(CHANGESTREAM_CLOSED_ERROR);
687+
if (callback) callback(new MongoDriverError(CHANGESTREAM_CLOSED_ERROR));
689688
return;
690689
}
691690

@@ -745,7 +744,7 @@ function processError<TSchema>(
745744
*/
746745
function getCursor<T>(changeStream: ChangeStream<T>, callback: Callback<ChangeStreamCursor<T>>) {
747746
if (changeStream[kClosed]) {
748-
callback(CHANGESTREAM_CLOSED_ERROR);
747+
callback(new MongoDriverError(CHANGESTREAM_CLOSED_ERROR));
749748
return;
750749
}
751750

@@ -770,11 +769,11 @@ function processResumeQueue<TSchema>(changeStream: ChangeStream<TSchema>, err?:
770769
const request = changeStream[kResumeQueue].pop();
771770
if (!err) {
772771
if (changeStream[kClosed]) {
773-
request(CHANGESTREAM_CLOSED_ERROR);
772+
request(new MongoDriverError(CHANGESTREAM_CLOSED_ERROR));
774773
return;
775774
}
776775
if (!changeStream.cursor) {
777-
request(NO_CURSOR_ERROR);
776+
request(new MongoDriverError(NO_CURSOR_ERROR));
778777
return;
779778
}
780779
}

src/cmap/auth/auth_provider.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Connection, ConnectionOptions } from '../connection';
33
import type { MongoCredentials } from './mongo_credentials';
44
import type { HandshakeDocument } from '../connect';
55
import type { ClientMetadataOptions, Callback } from '../../utils';
6+
import { MongoDriverError } from '../../error';
67

78
export type AuthContextOptions = ConnectionOptions & ClientMetadataOptions;
89

@@ -53,6 +54,6 @@ export class AuthProvider {
5354
* @param callback - The callback to return the result from the authentication
5455
*/
5556
auth(context: AuthContext, callback: Callback): void {
56-
callback(new TypeError('`auth` method must be overridden by subclass'));
57+
callback(new MongoDriverError('`auth` method must be overridden by subclass'));
5758
}
5859
}

0 commit comments

Comments
 (0)