diff --git a/src/bulk/common.ts b/src/bulk/common.ts index 290cbd4e36..a61834d491 100644 --- a/src/bulk/common.ts +++ b/src/bulk/common.ts @@ -5,7 +5,8 @@ import { AnyError, MONGODB_ERROR_CODES, MongoServerError, - MongoDriverError + MongoDriverError, + MongoInvalidArgumentError } from '../error'; import { applyRetryableWrites, @@ -753,7 +754,7 @@ export class FindOperators { /** Add a single update operation to the bulk operation */ updateOne(updateDocument: Document): BulkOperationBase { if (!hasAtomicOperators(updateDocument)) { - throw new MongoDriverError('Update document requires atomic operators'); + throw new MongoInvalidArgumentError('Update document requires atomic operators'); } const currentOp = buildCurrentOp(this.bulkOperation); @@ -766,7 +767,7 @@ export class FindOperators { /** Add a replace one operation to the bulk operation */ replaceOne(replacement: Document): BulkOperationBase { if (hasAtomicOperators(replacement)) { - throw new MongoDriverError('Replacement document must not use atomic operators'); + throw new MongoInvalidArgumentError('Replacement document must not use atomic operators'); } const currentOp = buildCurrentOp(this.bulkOperation); @@ -1049,7 +1050,7 @@ export abstract class BulkOperationBase { */ find(selector: Document): FindOperators { if (!selector) { - throw new MongoDriverError('Bulk find operation must specify a selector'); + throw new MongoInvalidArgumentError('Bulk find operation must specify a selector'); } // Save a current selector @@ -1083,7 +1084,7 @@ export abstract class BulkOperationBase { if ('replaceOne' in op || 'updateOne' in op || 'updateMany' in op) { if ('replaceOne' in op) { if ('q' in op.replaceOne) { - throw new MongoDriverError('Raw operations are not allowed'); + throw new MongoInvalidArgumentError('Raw operations are not allowed'); } const updateStatement = makeUpdateStatement( op.replaceOne.filter, @@ -1091,35 +1092,35 @@ export abstract class BulkOperationBase { { ...op.replaceOne, multi: false } ); if (hasAtomicOperators(updateStatement.u)) { - throw new MongoDriverError('Replacement document must not use atomic operators'); + throw new MongoInvalidArgumentError('Replacement document must not use atomic operators'); } return this.addToOperationsList(BatchType.UPDATE, updateStatement); } if ('updateOne' in op) { if ('q' in op.updateOne) { - throw new MongoDriverError('Raw operations are not allowed'); + throw new MongoInvalidArgumentError('Raw operations are not allowed'); } const updateStatement = makeUpdateStatement(op.updateOne.filter, op.updateOne.update, { ...op.updateOne, multi: false }); if (!hasAtomicOperators(updateStatement.u)) { - throw new MongoDriverError('Update document requires atomic operators'); + throw new MongoInvalidArgumentError('Update document requires atomic operators'); } return this.addToOperationsList(BatchType.UPDATE, updateStatement); } if ('updateMany' in op) { if ('q' in op.updateMany) { - throw new MongoDriverError('Raw operations are not allowed'); + throw new MongoInvalidArgumentError('Raw operations are not allowed'); } const updateStatement = makeUpdateStatement(op.updateMany.filter, op.updateMany.update, { ...op.updateMany, multi: true }); if (!hasAtomicOperators(updateStatement.u)) { - throw new MongoDriverError('Update document requires atomic operators'); + throw new MongoInvalidArgumentError('Update document requires atomic operators'); } return this.addToOperationsList(BatchType.UPDATE, updateStatement); } @@ -1127,7 +1128,7 @@ export abstract class BulkOperationBase { if ('deleteOne' in op) { if ('q' in op.deleteOne) { - throw new MongoDriverError('Raw operations are not allowed'); + throw new MongoInvalidArgumentError('Raw operations are not allowed'); } return this.addToOperationsList( BatchType.DELETE, @@ -1137,7 +1138,7 @@ export abstract class BulkOperationBase { if ('deleteMany' in op) { if ('q' in op.deleteMany) { - throw new MongoDriverError('Raw operations are not allowed'); + throw new MongoInvalidArgumentError('Raw operations are not allowed'); } return this.addToOperationsList( BatchType.DELETE, @@ -1146,7 +1147,7 @@ export abstract class BulkOperationBase { } // otherwise an unknown operation was provided - throw new MongoDriverError( + throw new MongoInvalidArgumentError( 'bulkWrite only supports insertOne, updateOne, updateMany, deleteOne, deleteMany' ); } @@ -1198,7 +1199,9 @@ export abstract class BulkOperationBase { } // If we have no operations in the bulk raise an error if (this.s.batches.length === 0) { - const emptyBatchError = new MongoDriverError('Invalid BulkOperation, Batch cannot be empty'); + const emptyBatchError = new MongoInvalidArgumentError( + 'Invalid BulkOperation, Batch cannot be empty' + ); return handleEarlyError(emptyBatchError, callback); } diff --git a/src/bulk/ordered.ts b/src/bulk/ordered.ts index 187fa77c2f..ce99650c28 100644 --- a/src/bulk/ordered.ts +++ b/src/bulk/ordered.ts @@ -4,7 +4,7 @@ import type { Document } from '../bson'; import type { Collection } from '../collection'; import type { UpdateStatement } from '../operations/update'; import type { DeleteStatement } from '../operations/delete'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError } from '../error'; /** @public */ export class OrderedBulkOperation extends BulkOperationBase { @@ -26,7 +26,8 @@ export class OrderedBulkOperation extends BulkOperationBase { // Throw error if the doc is bigger than the max BSON size if (bsonSize >= this.s.maxBsonObjectSize) - throw new MongoDriverError( + // TODO(NODE-3483): Change this to MongoBSONError + throw new MongoInvalidArgumentError( `Document is larger than the maximum size ${this.s.maxBsonObjectSize}` ); @@ -68,7 +69,7 @@ export class OrderedBulkOperation extends BulkOperationBase { // We have an array of documents if (Array.isArray(document)) { - throw new MongoDriverError('Operation passed in cannot be an Array'); + throw new MongoInvalidArgumentError('Operation passed in cannot be an Array'); } this.s.currentBatch.originalIndexes.push(this.s.currentIndex); diff --git a/src/bulk/unordered.ts b/src/bulk/unordered.ts index 0fcb3ef511..e241e50be6 100644 --- a/src/bulk/unordered.ts +++ b/src/bulk/unordered.ts @@ -5,7 +5,7 @@ import type { Document } from '../bson'; import type { Collection } from '../collection'; import type { UpdateStatement } from '../operations/update'; import type { DeleteStatement } from '../operations/delete'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError } from '../error'; /** @public */ export class UnorderedBulkOperation extends BulkOperationBase { @@ -36,7 +36,8 @@ export class UnorderedBulkOperation extends BulkOperationBase { // Throw error if the doc is bigger than the max BSON size if (bsonSize >= this.s.maxBsonObjectSize) { - throw new MongoDriverError( + // TODO(NODE-3483): Change this to MongoBSONError + throw new MongoInvalidArgumentError( `Document is larger than the maximum size ${this.s.maxBsonObjectSize}` ); } @@ -79,7 +80,7 @@ export class UnorderedBulkOperation extends BulkOperationBase { // We have an array of documents if (Array.isArray(document)) { - throw new MongoDriverError('Operation passed in cannot be an Array'); + throw new MongoInvalidArgumentError('Operation passed in cannot be an Array'); } this.s.currentBatch.operations.push(document); diff --git a/src/change_stream.ts b/src/change_stream.ts index 9e4ea69452..b260b4d80a 100644 --- a/src/change_stream.ts +++ b/src/change_stream.ts @@ -259,8 +259,9 @@ export class ChangeStream extends TypedEven } else if (parent instanceof MongoClient) { this.type = CHANGE_DOMAIN_TYPES.CLUSTER; } else { + // TODO(NODE-3404): Replace this with MongoChangeStreamError throw new MongoDriverError( - 'parent provided to ChangeStream constructor is not an instance of Collection, Db, or MongoClient' + 'Parent provided to ChangeStream constructor must an instance of Collection, Db, or MongoClient' ); } @@ -364,6 +365,7 @@ export class ChangeStream extends TypedEven */ stream(options?: CursorStreamOptions): Readable { this.streamOptions = options; + // TODO(NODE-3404): Replace this with MongoChangeStreamError if (!this.cursor) throw new MongoDriverError(NO_CURSOR_ERROR); return this.cursor.stream(options); } diff --git a/src/cmap/auth/auth_provider.ts b/src/cmap/auth/auth_provider.ts index 8aebf2ac62..5db4189b97 100644 --- a/src/cmap/auth/auth_provider.ts +++ b/src/cmap/auth/auth_provider.ts @@ -54,6 +54,7 @@ export class AuthProvider { * @param callback - The callback to return the result from the authentication */ auth(context: AuthContext, callback: Callback): void { + // TODO(NODE-3485): Replace this with MongoMethodOverrideError callback(new MongoDriverError('`auth` method must be overridden by subclass')); } } diff --git a/src/cmap/auth/gssapi.ts b/src/cmap/auth/gssapi.ts index af96409695..67531c6385 100644 --- a/src/cmap/auth/gssapi.ts +++ b/src/cmap/auth/gssapi.ts @@ -1,5 +1,11 @@ import { AuthProvider, AuthContext } from './auth_provider'; -import { MongoDriverError, MongoError } from '../../error'; +import { + MongoDriverError, + MongoInvalidArgumentError, + MongoMissingCredentialsError, + MongoError, + MongoMissingDependencyError +} from '../../error'; import { Kerberos, KerberosClient } from '../../deps'; import { Callback, ns } from '../../utils'; import type { Document } from '../../bson'; @@ -15,7 +21,10 @@ import * as dns from 'dns'; export class GSSAPI extends AuthProvider { auth(authContext: AuthContext, callback: Callback): void { const { connection, credentials } = authContext; - if (credentials == null) return callback(new MongoDriverError('credentials required')); + if (credentials == null) + return callback( + new MongoMissingCredentialsError('Credentials required for GSSAPI authentication') + ); const { username } = credentials; function externalCommand( command: Document, @@ -25,7 +34,7 @@ export class GSSAPI extends AuthProvider { } makeKerberosClient(authContext, (err, client) => { if (err) return callback(err); - if (client == null) return callback(new MongoDriverError('gssapi client missing')); + if (client == null) return callback(new MongoMissingDependencyError('GSSAPI client missing')); client.step('', (err, payload) => { if (err) return callback(err); @@ -66,7 +75,7 @@ function makeKerberosClient(authContext: AuthContext, callback: Callback) { function done(creds: AWSCredentials) { if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) { - callback(new MongoDriverError('Could not obtain temporary MONGODB-AWS credentials')); + callback( + new MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials') + ); return; } diff --git a/src/cmap/auth/plain.ts b/src/cmap/auth/plain.ts index d34839c014..afbca63d20 100644 --- a/src/cmap/auth/plain.ts +++ b/src/cmap/auth/plain.ts @@ -1,13 +1,13 @@ import { Binary } from '../../bson'; import { AuthProvider, AuthContext } from './auth_provider'; -import { MongoDriverError } from '../../error'; +import { MongoMissingCredentialsError } from '../../error'; import { Callback, ns } from '../../utils'; export class Plain extends AuthProvider { auth(authContext: AuthContext, callback: Callback): void { const { connection, credentials } = authContext; if (!credentials) { - return callback(new MongoDriverError('AuthContext must provide credentials.')); + return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } const username = credentials.username; const password = credentials.password; diff --git a/src/cmap/auth/scram.ts b/src/cmap/auth/scram.ts index c19ed572f5..a309d038c7 100644 --- a/src/cmap/auth/scram.ts +++ b/src/cmap/auth/scram.ts @@ -1,6 +1,12 @@ import * as crypto from 'crypto'; import { Binary, Document } from '../../bson'; -import { AnyError, MongoDriverError, MongoServerError } from '../../error'; +import { + AnyError, + MongoDriverError, + MongoServerError, + MongoInvalidArgumentError, + MongoMissingCredentialsError +} from '../../error'; import { AuthProvider, AuthContext } from './auth_provider'; import { Callback, ns, emitWarning } from '../../utils'; import type { MongoCredentials } from './mongo_credentials'; @@ -22,7 +28,7 @@ class ScramSHA extends AuthProvider { const cryptoMethod = this.cryptoMethod; const credentials = authContext.credentials; if (!credentials) { - return callback(new MongoDriverError('AuthContext must provide credentials.')); + return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } if (cryptoMethod === 'sha256' && saslprep == null) { emitWarning('Warning: no saslprep library specified. Passwords will not be sanitized'); @@ -103,10 +109,12 @@ function makeFirstMessage( function executeScram(cryptoMethod: CryptoMethod, authContext: AuthContext, callback: Callback) { const { connection, credentials } = authContext; if (!credentials) { - return callback(new MongoDriverError('AuthContext must provide credentials.')); + return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } if (!authContext.nonce) { - return callback(new MongoDriverError('AuthContext must contain a valid nonce property')); + return callback( + new MongoInvalidArgumentError('AuthContext must contain a valid nonce property') + ); } const nonce = authContext.nonce; const db = credentials.source; @@ -131,10 +139,10 @@ function continueScramConversation( const connection = authContext.connection; const credentials = authContext.credentials; if (!credentials) { - return callback(new MongoDriverError('AuthContext must provide credentials.')); + return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } if (!authContext.nonce) { - return callback(new MongoDriverError('Unable to continue SCRAM without valid nonce')); + return callback(new MongoInvalidArgumentError('Unable to continue SCRAM without valid nonce')); } const nonce = authContext.nonce; @@ -240,15 +248,15 @@ function parsePayload(payload: string) { function passwordDigest(username: string, password: string) { if (typeof username !== 'string') { - throw new MongoDriverError('username must be a string'); + throw new MongoInvalidArgumentError('Username must be a string'); } if (typeof password !== 'string') { - throw new MongoDriverError('password must be a string'); + throw new MongoInvalidArgumentError('Password must be a string'); } if (password.length === 0) { - throw new MongoDriverError('password cannot be empty'); + throw new MongoInvalidArgumentError('Password cannot be empty'); } const md5 = crypto.createHash('md5'); diff --git a/src/cmap/auth/x509.ts b/src/cmap/auth/x509.ts index f425a069e2..73f7688035 100644 --- a/src/cmap/auth/x509.ts +++ b/src/cmap/auth/x509.ts @@ -1,5 +1,5 @@ import { AuthProvider, AuthContext } from './auth_provider'; -import { MongoDriverError } from '../../error'; +import { MongoMissingCredentialsError } from '../../error'; import type { Document } from '../../bson'; import { Callback, ns } from '../../utils'; import type { MongoCredentials } from './mongo_credentials'; @@ -9,7 +9,7 @@ export class X509 extends AuthProvider { prepare(handshakeDoc: HandshakeDocument, authContext: AuthContext, callback: Callback): void { const { credentials } = authContext; if (!credentials) { - return callback(new MongoDriverError('AuthContext must provide credentials.')); + return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } Object.assign(handshakeDoc, { speculativeAuthenticate: x509AuthenticateCommand(credentials) @@ -22,7 +22,7 @@ export class X509 extends AuthProvider { const connection = authContext.connection; const credentials = authContext.credentials; if (!credentials) { - return callback(new MongoDriverError('AuthContext must provide credentials.')); + return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } const response = authContext.response; diff --git a/src/cmap/commands.ts b/src/cmap/commands.ts index 5b9eb7e923..140fea90cd 100644 --- a/src/cmap/commands.ts +++ b/src/cmap/commands.ts @@ -5,7 +5,7 @@ import { OP_QUERY, OP_GETMORE, OP_KILL_CURSORS, OP_MSG } from './wire_protocol/c import type { Long, Document, BSONSerializeOptions } from '../bson'; import type { ClientSession } from '../sessions'; import type { CommandOptions } from './connection'; -import { MongoDriverError } from '../error'; +import { MongoDriverError, MongoInvalidArgumentError } from '../error'; // Incrementing request id let _requestId = 0; @@ -77,12 +77,15 @@ export class Query { constructor(ns: string, query: Document, options: OpQueryOptions) { // Basic options needed to be passed in - if (ns == null) throw new MongoDriverError('ns must be specified for query'); - if (query == null) throw new MongoDriverError('query must be specified for query'); + // TODO(NODE-3483): Replace with MongoCommandError + if (ns == null) throw new MongoDriverError('Namespace must be specified for query'); + // TODO(NODE-3483): Replace with MongoCommandError + if (query == null) throw new MongoDriverError('A query document must be specified for query'); // Validate that we are not passing 0x00 in the collection name if (ns.indexOf('\x00') !== -1) { - throw new MongoDriverError('namespace cannot contain a null character'); + // TODO(NODE-3483): Replace with MongoCommandError + throw new MongoDriverError('Namespace cannot contain a null character'); } // Basic options @@ -664,7 +667,8 @@ export class Msg { constructor(ns: string, command: Document, options: OpQueryOptions) { // Basic options needed to be passed in - if (command == null) throw new MongoDriverError('query must be specified for query'); + if (command == null) + throw new MongoInvalidArgumentError('Query document must be specified for query'); // Basic options this.ns = ns; @@ -852,6 +856,8 @@ export class BinMsg { this.index += bsonSize; } else if (payloadType === 1) { // It was decided that no driver makes use of payload type 1 + + // TODO(NODE-3483): Replace with MongoDeprecationError throw new MongoDriverError('OP_MSG Payload Type 1 detected unsupported protocol'); } } diff --git a/src/cmap/connect.ts b/src/cmap/connect.ts index b33ea78180..e9b2c21f6b 100644 --- a/src/cmap/connect.ts +++ b/src/cmap/connect.ts @@ -6,7 +6,9 @@ import { MongoNetworkTimeoutError, AnyError, MongoDriverError, - MongoServerError + MongoCompatibilityError, + MongoServerError, + MongoInvalidArgumentError } from '../error'; import { AUTH_PROVIDERS, AuthMechanism } from './auth/defaultAuthProviders'; import { AuthContext } from './auth/auth_provider'; @@ -58,13 +60,13 @@ function checkSupportedServer(ismaster: Document, options: ConnectionOptions) { const message = `Server at ${options.hostAddress} reports minimum wire version ${JSON.stringify( ismaster.minWireVersion )}, but this version of the Node.js Driver requires at most ${MAX_SUPPORTED_WIRE_VERSION} (MongoDB ${MAX_SUPPORTED_SERVER_VERSION})`; - return new MongoDriverError(message); + return new MongoCompatibilityError(message); } const message = `Server at ${options.hostAddress} reports maximum wire version ${ JSON.stringify(ismaster.maxWireVersion) ?? 0 }, but this version of the Node.js Driver requires at least ${MIN_SUPPORTED_WIRE_VERSION} (MongoDB ${MIN_SUPPORTED_SERVER_VERSION})`; - return new MongoDriverError(message); + return new MongoCompatibilityError(message); } function performInitialHandshake( @@ -85,7 +87,9 @@ function performInitialHandshake( !(credentials.mechanism === AuthMechanism.MONGODB_DEFAULT) && !AUTH_PROVIDERS.get(credentials.mechanism) ) { - callback(new MongoDriverError(`authMechanism '${credentials.mechanism}' not supported`)); + callback( + new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`) + ); return; } } @@ -143,7 +147,9 @@ function performInitialHandshake( const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism); if (!provider) { return callback( - new MongoDriverError(`No AuthProvider for ${resolvedCredentials.mechanism} defined.`) + new MongoInvalidArgumentError( + `No AuthProvider for ${resolvedCredentials.mechanism} defined.` + ) ); } provider.auth(authContext, err => { @@ -189,7 +195,9 @@ function prepareHandshakeDocument(authContext: AuthContext, callback: Callback = {}; for (const name of LEGAL_TCP_SOCKET_OPTIONS) { diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 9ddc021190..d5ef9f6945 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -19,6 +19,8 @@ import { import { AnyError, MongoDriverError, + MongoMissingDependencyError, + MongoCompatibilityError, MongoError, MongoNetworkError, MongoNetworkTimeoutError, @@ -342,7 +344,8 @@ export class Connection extends TypedEventEmitter { callback: Callback ): void { if (typeof ns.db === 'undefined' || typeof ns === 'string') { - throw new MongoDriverError('ns cannot be a string'); + // TODO(NODE-3483): Replace this with a MongoCommandError + throw new MongoDriverError('Namespace cannot be a string'); } const readPreference = getReadPreference(cmd, options); @@ -531,6 +534,7 @@ export class Connection extends TypedEventEmitter { const fullResult = typeof options.fullResult === 'boolean' ? options.fullResult : false; const wireVersion = maxWireVersion(this); if (!cursorId) { + // TODO(NODE-3483): Replace this with a MongoCommandError callback(new MongoDriverError('Invalid internal cursor state, no known cursor id')); return; } @@ -585,7 +589,8 @@ export class Connection extends TypedEventEmitter { callback: Callback ): void { if (!cursorIds || !Array.isArray(cursorIds)) { - throw new MongoDriverError('Invalid list of cursor ids provided: ' + cursorIds); + // TODO(NODE-3483): Replace this with a MongoCommandError + throw new MongoDriverError(`Invalid list of cursor ids provided: ${cursorIds}`); } if (maxWireVersion(this) < 4) { @@ -648,7 +653,7 @@ export class CryptoConnection extends Connection { command(ns: MongoDBNamespace, cmd: Document, options: CommandOptions, callback: Callback): void { const autoEncrypter = this[kAutoEncrypter]; if (!autoEncrypter) { - return callback(new MongoDriverError('No AutoEncrypter available for encryption')); + return callback(new MongoMissingDependencyError('No AutoEncrypter available for encryption')); } const serverWireVersion = maxWireVersion(this); @@ -658,7 +663,9 @@ export class CryptoConnection extends Connection { } if (serverWireVersion < 8) { - callback(new MongoDriverError('Auto-encryption requires a minimum MongoDB version of 4.2')); + callback( + new MongoCompatibilityError('Auto-encryption requires a minimum MongoDB version of 4.2') + ); return; } diff --git a/src/cmap/connection_pool.ts b/src/cmap/connection_pool.ts index ac5f894d3e..3961f479bc 100644 --- a/src/cmap/connection_pool.ts +++ b/src/cmap/connection_pool.ts @@ -3,7 +3,7 @@ import { Logger } from '../logger'; import { APM_EVENTS, Connection, ConnectionEvents, ConnectionOptions } from './connection'; import { connect } from './connect'; import { eachAsync, makeCounter, Callback } from '../utils'; -import { MongoDriverError, MongoError } from '../error'; +import { MongoDriverError, MongoError, MongoInvalidArgumentError } from '../error'; import { PoolClosedError, WaitQueueTimeoutError } from './errors'; import { ConnectionPoolCreatedEvent, @@ -174,7 +174,7 @@ export class ConnectionPool extends TypedEventEmitter { }); if (this.options.minPoolSize > this.options.maxPoolSize) { - throw new MongoDriverError( + throw new MongoInvalidArgumentError( 'Connection pool minimum size must not be greater than maximum pool size' ); } diff --git a/src/cmap/wire_protocol/shared.ts b/src/cmap/wire_protocol/shared.ts index ffab8d047e..0fd624df80 100644 --- a/src/cmap/wire_protocol/shared.ts +++ b/src/cmap/wire_protocol/shared.ts @@ -1,6 +1,6 @@ import { ServerType } from '../../sdam/common'; import { TopologyDescription } from '../../sdam/topology_description'; -import { MongoDriverError } from '../../error'; +import { MongoInvalidArgumentError } from '../../error'; import { ReadPreference } from '../../read_preference'; import type { Document } from '../../bson'; import type { OpQueryOptions } from '../commands'; @@ -28,7 +28,9 @@ export function getReadPreference(cmd: Document, options?: ReadPreferenceOption) } if (!(readPreference instanceof ReadPreference)) { - throw new MongoDriverError('read preference must be a ReadPreference instance'); + throw new MongoInvalidArgumentError( + 'Option "readPreference" must be a ReadPreference instance' + ); } return readPreference; diff --git a/src/collection.ts b/src/collection.ts index 3b83e81f44..f9ec68a439 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -8,7 +8,7 @@ import { getTopology } from './utils'; import { Document, BSONSerializeOptions, resolveBSONOptions } from './bson'; -import { MongoDriverError } from './error'; +import { MongoInvalidArgumentError } from './error'; import { UnorderedBulkOperation } from './bulk/unordered'; import { OrderedBulkOperation } from './bulk/ordered'; import { ChangeStream, ChangeStreamOptions } from './change_stream'; @@ -390,7 +390,7 @@ export class Collection { options = options || { ordered: true }; if (!Array.isArray(operations)) { - throw new MongoDriverError('operations must be an array of documents'); + throw new MongoInvalidArgumentError('Argument "operations" must be an array of documents'); } return executeOperation( @@ -700,7 +700,9 @@ export class Collection { callback?: Callback ): Promise | void { if (callback !== undefined && typeof callback !== 'function') { - throw new MongoDriverError('Third parameter to `findOne()` must be a callback or undefined'); + throw new MongoInvalidArgumentError( + 'Third parameter to `findOne()` must be a callback or undefined' + ); } if (typeof filter === 'function') @@ -730,10 +732,12 @@ export class Collection { find(filter: Filter, options?: FindOptions): FindCursor; find(filter?: Filter, options?: FindOptions): FindCursor { if (arguments.length > 2) { - throw new MongoDriverError('Third parameter to `collection.find()` must be undefined'); + throw new MongoInvalidArgumentError( + 'Method "collection.find()" accepts at most two arguments' + ); } if (typeof options === 'function') { - throw new MongoDriverError('`options` parameter must not be function'); + throw new MongoInvalidArgumentError('Argument "options" must not be function'); } return new FindCursor( @@ -1369,13 +1373,17 @@ export class Collection { options?: AggregateOptions ): AggregationCursor { if (arguments.length > 2) { - throw new MongoDriverError('Third parameter to `collection.aggregate()` must be undefined'); + throw new MongoInvalidArgumentError( + 'Method "collection.aggregate()" accepts at most two arguments' + ); } if (!Array.isArray(pipeline)) { - throw new MongoDriverError('`pipeline` parameter must be an array of aggregation stages'); + throw new MongoInvalidArgumentError( + 'Argument "pipeline" must be an array of aggregation stages' + ); } if (typeof options === 'function') { - throw new MongoDriverError('`options` parameter must not be function'); + throw new MongoInvalidArgumentError('Argument "options" must not be function'); } return new AggregationCursor( @@ -1445,8 +1453,8 @@ export class Collection { // Out must always be defined (make sure we don't break weirdly on pre 1.8+ servers) // TODO NODE-3339: Figure out if this is still necessary given we no longer officially support pre-1.8 if (options?.out == null) { - throw new MongoDriverError( - 'the out option parameter must be defined, see mongodb docs for possible values' + throw new MongoInvalidArgumentError( + 'Option "out" must be defined, see mongodb docs for possible values' ); } diff --git a/src/cursor/abstract_cursor.ts b/src/cursor/abstract_cursor.ts index 9593749866..1be6e37114 100644 --- a/src/cursor/abstract_cursor.ts +++ b/src/cursor/abstract_cursor.ts @@ -1,7 +1,7 @@ import { Callback, maybePromise, MongoDBNamespace, ns } from '../utils'; import { Long, Document, BSONSerializeOptions, pluckBSONSerializeOptions } from '../bson'; import { ClientSession } from '../sessions'; -import { MongoDriverError } from '../error'; +import { MongoDriverError, MongoInvalidArgumentError } from '../error'; import { ReadPreference, ReadPreferenceLike } from '../read_preference'; import type { Server } from '../sdam/server'; import type { Topology } from '../sdam/topology'; @@ -324,7 +324,7 @@ export abstract class AbstractCursor< callback?: Callback ): Promise | void { if (typeof iterator !== 'function') { - throw new MongoDriverError('Missing required parameter `iterator`'); + throw new MongoInvalidArgumentError('Argument "iterator" must be a function'); } return maybePromise(callback, done => { const transform = this[kTransform]; @@ -467,11 +467,11 @@ export abstract class AbstractCursor< addCursorFlag(flag: CursorFlag, value: boolean): this { assertUninitialized(this); if (!CURSOR_FLAGS.includes(flag)) { - throw new MongoDriverError(`flag ${flag} is not one of ${CURSOR_FLAGS}`); + throw new MongoInvalidArgumentError(`Flag ${flag} is not one of ${CURSOR_FLAGS}`); } if (typeof value !== 'boolean') { - throw new MongoDriverError(`flag ${flag} must be a boolean value`); + throw new MongoInvalidArgumentError(`Flag ${flag} must be a boolean value`); } this[kOptions][flag] = value; @@ -522,7 +522,7 @@ export abstract class AbstractCursor< } else if (typeof readPreference === 'string') { this[kOptions].readPreference = ReadPreference.fromString(readPreference); } else { - throw new MongoDriverError('Invalid read preference: ' + readPreference); + throw new MongoInvalidArgumentError(`Invalid read preference: ${readPreference}`); } return this; @@ -551,7 +551,7 @@ export abstract class AbstractCursor< maxTimeMS(value: number): this { assertUninitialized(this); if (typeof value !== 'number') { - throw new MongoDriverError('maxTimeMS must be a number'); + throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number'); } this[kOptions].maxTimeMS = value; @@ -570,7 +570,7 @@ export abstract class AbstractCursor< } if (typeof value !== 'number') { - throw new MongoDriverError('batchSize requires an integer'); + throw new MongoInvalidArgumentError('Operation "batchSize" requires an integer'); } this[kOptions].batchSize = value; diff --git a/src/cursor/find_cursor.ts b/src/cursor/find_cursor.ts index 672ad38b1f..4f48815f9b 100644 --- a/src/cursor/find_cursor.ts +++ b/src/cursor/find_cursor.ts @@ -1,5 +1,5 @@ import type { Document } from '../bson'; -import { MongoDriverError } from '../error'; +import { MongoDriverError, MongoInvalidArgumentError } from '../error'; import type { ExplainVerbosityLike } from '../explain'; import { CountOperation, CountOptions } from '../operations/count'; import { executeOperation, ExecutionResult } from '../operations/execute_operation'; @@ -129,7 +129,7 @@ export class FindCursor extends AbstractCursor { callback?: Callback ): Promise | void { if (typeof options === 'boolean') { - throw new MongoDriverError('Invalid first parameter to count'); + throw new MongoInvalidArgumentError('Invalid first parameter to count'); } if (typeof options === 'function') (callback = options), (options = {}); @@ -241,7 +241,7 @@ export class FindCursor extends AbstractCursor { addQueryModifier(name: string, value: string | boolean | number | Document): this { assertUninitialized(this); if (name[0] !== '$') { - throw new MongoDriverError(`${name} is not a valid query modifier`); + throw new MongoInvalidArgumentError(`${name} is not a valid query modifier`); } // Strip of the $ @@ -290,7 +290,7 @@ export class FindCursor extends AbstractCursor { break; default: - throw new MongoDriverError(`invalid query modifier: ${name}`); + throw new MongoInvalidArgumentError(`Invalid query modifier: ${name}`); } return this; @@ -315,7 +315,7 @@ export class FindCursor extends AbstractCursor { maxAwaitTimeMS(value: number): this { assertUninitialized(this); if (typeof value !== 'number') { - throw new MongoDriverError('maxAwaitTimeMS must be a number'); + throw new MongoInvalidArgumentError('Argument for maxAwaitTimeMS must be a number'); } this[kBuiltOptions].maxAwaitTimeMS = value; @@ -330,7 +330,7 @@ export class FindCursor extends AbstractCursor { maxTimeMS(value: number): this { assertUninitialized(this); if (typeof value !== 'number') { - throw new MongoDriverError('maxTimeMS must be a number'); + throw new MongoInvalidArgumentError('Argument for maxTimeMS must be a number'); } this[kBuiltOptions].maxTimeMS = value; @@ -387,7 +387,7 @@ export class FindCursor extends AbstractCursor { allowDiskUse(): this { assertUninitialized(this); if (!this[kBuiltOptions].sort) { - throw new MongoDriverError('allowDiskUse requires a sort specification'); + throw new MongoInvalidArgumentError('Option "allowDiskUse" requires a sort specification'); } this[kBuiltOptions].allowDiskUse = true; return this; @@ -416,7 +416,7 @@ export class FindCursor extends AbstractCursor { } if (typeof value !== 'number') { - throw new MongoDriverError('limit requires an integer'); + throw new MongoInvalidArgumentError('Operation "limit" requires an integer'); } this[kBuiltOptions].limit = value; @@ -435,7 +435,7 @@ export class FindCursor extends AbstractCursor { } if (typeof value !== 'number') { - throw new MongoDriverError('skip requires an integer'); + throw new MongoInvalidArgumentError('Operation "skip" requires an integer'); } this[kBuiltOptions].skip = value; diff --git a/src/db.ts b/src/db.ts index e343047e61..bf197fd8e9 100644 --- a/src/db.ts +++ b/src/db.ts @@ -9,7 +9,7 @@ import { import { AggregationCursor } from './cursor/aggregation_cursor'; import { Document, BSONSerializeOptions, resolveBSONOptions } from './bson'; import { ReadPreference, ReadPreferenceLike } from './read_preference'; -import { MongoDriverError } from './error'; +import { MongoDriverError, MongoInvalidArgumentError } from './error'; import { Collection, CollectionOptions } from './collection'; import { ChangeStream, ChangeStreamOptions } from './change_stream'; import * as CONSTANTS from './constants'; @@ -291,13 +291,13 @@ export class Db { */ aggregate(pipeline: Document[] = [], options?: AggregateOptions): AggregationCursor { if (arguments.length > 2) { - throw new MongoDriverError('Third parameter to `db.aggregate()` must be undefined'); + throw new MongoInvalidArgumentError('Method "db.aggregate()" accepts at most two arguments'); } if (typeof pipeline === 'function') { - throw new MongoDriverError('`pipeline` parameter must not be function'); + throw new MongoInvalidArgumentError('Argument "pipeline" must not be function'); } if (typeof options === 'function') { - throw new MongoDriverError('`options` parameter must not be function'); + throw new MongoInvalidArgumentError('Argument "options" must not be function'); } return new AggregationCursor( @@ -328,6 +328,7 @@ export class Db { if (!options) { options = {}; } else if (typeof options === 'function') { + // TODO(NODE-3485): Replace this with MongoDeprecationError throw new MongoDriverError('The callback form of this helper has been removed.'); } const finalOptions = resolveOptions(this, options); @@ -740,14 +741,15 @@ export class Db { // Validate the database name function validateDatabaseName(databaseName: string) { if (typeof databaseName !== 'string') - throw new MongoDriverError('database name must be a string'); + throw new MongoInvalidArgumentError('Database name must be a string'); if (databaseName.length === 0) - throw new MongoDriverError('database name cannot be the empty string'); + throw new MongoInvalidArgumentError('Database name cannot be the empty string'); if (databaseName === '$external') return; const invalidChars = [' ', '.', '$', '/', '\\']; for (let i = 0; i < invalidChars.length; i++) { if (databaseName.indexOf(invalidChars[i]) !== -1) + // TODO(NODE-3405): Change this out for a child of MongoParseError throw new MongoDriverError( `database names cannot contain the character '${invalidChars[i]}'` ); diff --git a/src/deps.ts b/src/deps.ts index 35858bae2f..01edbd5b02 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -1,4 +1,4 @@ -import { MongoDriverError } from './error'; +import { MongoMissingDependencyError } from './error'; import type { MongoClient } from './mongo_client'; import type { deserialize, Document, serialize } from './bson'; import type { Callback } from './utils'; @@ -20,8 +20,8 @@ function makeErrorModule(error: any) { export let Kerberos: | typeof import('kerberos') - | { kModuleError: MongoDriverError } = makeErrorModule( - new MongoDriverError( + | { kModuleError: MongoMissingDependencyError } = makeErrorModule( + new MongoMissingDependencyError( 'Optional module `kerberos` not found. Please install it to enable kerberos authentication' ) ); @@ -41,8 +41,10 @@ export interface KerberosClient { unwrap: (challenge: string, callback?: Callback) => Promise | void; } -export let Snappy: typeof import('snappy') | { kModuleError: MongoDriverError } = makeErrorModule( - new MongoDriverError( +export let Snappy: + | typeof import('snappy') + | { kModuleError: MongoMissingDependencyError } = makeErrorModule( + new MongoMissingDependencyError( 'Optional module `snappy` not found. Please install it to enable snappy compression' ) ); @@ -54,8 +56,8 @@ try { export let saslprep: | typeof import('saslprep') - | { kModuleError: MongoDriverError } = makeErrorModule( - new MongoDriverError( + | { kModuleError: MongoMissingDependencyError } = makeErrorModule( + new MongoMissingDependencyError( 'Optional module `saslprep` not found.' + ' Please install it to enable Stringprep Profile for User Names and Passwords' ) @@ -66,8 +68,10 @@ try { saslprep = require('saslprep'); } catch {} // eslint-disable-line -export let aws4: typeof import('aws4') | { kModuleError: MongoDriverError } = makeErrorModule( - new MongoDriverError( +export let aws4: + | typeof import('aws4') + | { kModuleError: MongoMissingDependencyError } = makeErrorModule( + new MongoMissingDependencyError( 'Optional module `aws4` not found. Please install it to enable AWS authentication' ) ); diff --git a/src/encrypter.ts b/src/encrypter.ts index 8b5cd83ea7..18cd7a1279 100644 --- a/src/encrypter.ts +++ b/src/encrypter.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import { MongoClient, MongoClientOptions } from './mongo_client'; import type { AutoEncrypter, AutoEncryptionOptions } from './deps'; -import { MongoDriverError } from './error'; +import { MongoInvalidArgumentError, MongoMissingDependencyError } from './error'; import { deserialize, serialize } from './bson'; import type { Callback } from './utils'; import { MONGO_CLIENT_EVENTS } from './operations/connect'; @@ -26,7 +26,7 @@ export class Encrypter { constructor(client: MongoClient, uri: string, options: MongoClientOptions) { if (typeof options.autoEncryption !== 'object') { - throw new MongoDriverError('Options autoEncryption must be specified'); + throw new MongoInvalidArgumentError('Option "autoEncryption" must be specified'); } this.bypassAutoEncryption = !!options.autoEncryption.bypassAutoEncryption; @@ -108,19 +108,12 @@ export class Encrypter { // Ensure you always wrap an optional require in the try block NODE-3199 mongodbClientEncryption = require('mongodb-client-encryption'); } catch (err) { - throw new MongoDriverError( + throw new MongoMissingDependencyError( 'Auto-encryption requested, but the module is not installed. ' + 'Please add `mongodb-client-encryption` as a dependency of your project' ); } - if (typeof mongodbClientEncryption?.extension !== 'function') { - throw new MongoDriverError( - 'loaded version of `mongodb-client-encryption` does not have property `extension`. ' + - 'Please make sure you are loading the correct version of `mongodb-client-encryption`' - ); - } - AutoEncrypterClass = mongodbClientEncryption.extension(require('../lib/index')).AutoEncrypter; } } diff --git a/src/error.ts b/src/error.ts index c5112d54a9..1a04516d74 100644 --- a/src/error.ts +++ b/src/error.ts @@ -598,6 +598,95 @@ export class MongoParseError extends MongoDriverError { } } +/** + * An error generated when the driver API is used incorrectly + * + * @privateRemarks + * Should **never** be directly instantiated + * + * @public + * @category Error + */ + +export class MongoAPIError extends MongoDriverError { + protected constructor(message: string) { + super(message); + } + + get name(): string { + return 'MongoAPIError'; + } +} + +/** + * An error generated when the user supplies malformed or unexpected arguments + * or when a required argument or field is not provided. + * + * + * @public + * @category Error + */ +export class MongoInvalidArgumentError extends MongoAPIError { + constructor(message: string) { + super(message); + } + + get name(): string { + return 'MongoInvalidArgumentError'; + } +} + +/** + * An error generated when a feature that is not enabled or allowed for the current server + * configuration is used + * + * + * @public + * @category Error + */ +export class MongoCompatibilityError extends MongoAPIError { + constructor(message: string) { + super(message); + } + + get name(): string { + return 'MongoCompatibilityError'; + } +} + +/** + * An error generated when the user fails to provide authentication credentials before attempting + * to connect to a mongo server instance. + * + * + * @public + * @category Error + */ +export class MongoMissingCredentialsError extends MongoAPIError { + constructor(message: string) { + super(message); + } + + get name(): string { + return 'MongoMissingCredentialsError'; + } +} + +/** + * An error generated when a required module or dependency is not present in the local environment + * + * @public + * @category Error + */ +export class MongoMissingDependencyError extends MongoAPIError { + constructor(message: string) { + super(message); + } + + get name(): string { + return 'MongoMissingDependencyError'; + } +} /** * An error signifying a general system issue * @public diff --git a/src/explain.ts b/src/explain.ts index 87fafc70cc..9d2c6d34cd 100644 --- a/src/explain.ts +++ b/src/explain.ts @@ -1,4 +1,4 @@ -import { MongoDriverError } from './error'; +import { MongoInvalidArgumentError } from './error'; /** @public */ export const ExplainVerbosity = Object.freeze({ @@ -47,6 +47,6 @@ export class Explain { return new Explain(explain); } - throw new MongoDriverError('explain must be a string or a boolean'); + throw new MongoInvalidArgumentError('Field "explain" must be a string or a boolean'); } } diff --git a/src/gridfs/download.ts b/src/gridfs/download.ts index c8a6e40918..639e65d41d 100644 --- a/src/gridfs/download.ts +++ b/src/gridfs/download.ts @@ -1,5 +1,5 @@ import { Readable } from 'stream'; -import { AnyError, MongoDriverError } from '../error'; +import { AnyError, MongoDriverError, MongoInvalidArgumentError } from '../error'; import type { Document } from '../bson'; import type { FindOptions } from '../operations/find'; import type { Sort } from '../sort'; @@ -236,12 +236,12 @@ function doRead(stream: GridFSBucketReadStream): void { const expectedLength = Math.min(stream.s.file.chunkSize, bytesRemaining); let errmsg: string; if (doc.n > expectedN) { - errmsg = 'ChunkIsMissing: Got unexpected n: ' + doc.n + ', expected: ' + expectedN; + errmsg = `ChunkIsMissing: Got unexpected n: ${doc.n}, expected: ${expectedN}`; return __handleError(stream, new MongoDriverError(errmsg)); } if (doc.n < expectedN) { - errmsg = 'ExtraChunk: Got unexpected n: ' + doc.n + ', expected: ' + expectedN; + errmsg = `ExtraChunk: Got unexpected n: ${doc.n}, expected: ${expectedN}`; return __handleError(stream, new MongoDriverError(errmsg)); } @@ -249,7 +249,7 @@ function doRead(stream: GridFSBucketReadStream): void { if (buf.byteLength !== expectedLength) { if (bytesRemaining <= 0) { - errmsg = 'ExtraChunk: Got unexpected n: ' + doc.n; + errmsg = `ExtraChunk: Got unexpected n: ${doc.n}`; return __handleError(stream, new MongoDriverError(errmsg)); } @@ -312,7 +312,7 @@ function init(stream: GridFSBucketReadStream): void { const identifier = stream.s.filter._id ? stream.s.filter._id.toString() : stream.s.filter.filename; - const errmsg = 'FileNotFound: file ' + identifier + ' was not found'; + const errmsg = `FileNotFound: file ${identifier} was not found`; const err = new MongoDriverError(errmsg); err.code = 'ENOENT'; // TODO: NODE-3338 set property as part of constructor return __handleError(stream, err); @@ -391,26 +391,16 @@ function handleStartOption( ): number { if (options && options.start != null) { if (options.start > doc.length) { - throw new MongoDriverError( - 'Stream start (' + - options.start + - ') must not be ' + - 'more than the length of the file (' + - doc.length + - ')' + throw new MongoInvalidArgumentError( + `Stream start (${options.start}) must not be more than the length of the file (${doc.length})` ); } if (options.start < 0) { - throw new MongoDriverError('Stream start (' + options.start + ') must not be ' + 'negative'); + throw new MongoInvalidArgumentError(`Stream start (${options.start}) must not be negative`); } if (options.end != null && options.end < options.start) { - throw new MongoDriverError( - 'Stream start (' + - options.start + - ') must not be ' + - 'greater than stream end (' + - options.end + - ')' + throw new MongoInvalidArgumentError( + `Stream start (${options.start}) must not be greater than stream end (${options.end})` ); } @@ -419,7 +409,7 @@ function handleStartOption( return options.start - stream.s.bytesRead; } - throw new MongoDriverError('No start option defined'); + throw new MongoInvalidArgumentError('Start option must be defined'); } function handleEndOption( @@ -430,17 +420,12 @@ function handleEndOption( ) { if (options && options.end != null) { if (options.end > doc.length) { - throw new MongoDriverError( - 'Stream end (' + - options.end + - ') must not be ' + - 'more than the length of the file (' + - doc.length + - ')' + throw new MongoInvalidArgumentError( + `Stream end (${options.end}) must not be more than the length of the file (${doc.length})` ); } if (options.start == null || options.start < 0) { - throw new MongoDriverError('Stream end (' + options.end + ') must not be ' + 'negative'); + throw new MongoInvalidArgumentError(`Stream end (${options.end}) must not be negative`); } const start = options.start != null ? Math.floor(options.start / doc.chunkSize) : 0; @@ -451,7 +436,7 @@ function handleEndOption( return Math.ceil(options.end / doc.chunkSize) * doc.chunkSize - options.end; } - throw new MongoDriverError('No end option defined'); + throw new MongoInvalidArgumentError('End option must be defined'); } function __handleError(stream: GridFSBucketReadStream, error?: AnyError): void { diff --git a/src/gridfs/index.ts b/src/gridfs/index.ts index 3ab3cbc873..0865cb0dd5 100644 --- a/src/gridfs/index.ts +++ b/src/gridfs/index.ts @@ -227,8 +227,9 @@ function _delete(bucket: GridFSBucket, id: ObjectId, callback: Callback): // Delete orphaned chunks before returning FileNotFound if (!res?.deletedCount) { - const errmsg = 'FileNotFound: no file with id ' + id + ' found'; - return callback(new MongoDriverError(errmsg)); + // TODO(NODE-3483): Replace with more appropriate error + // Consider creating new error MongoGridFSFileNotFoundError + return callback(new MongoDriverError(`File not found for id ${id}`)); } return callback(); diff --git a/src/index.ts b/src/index.ts index d09c7f80f8..757ce75024 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,11 @@ export { MongoError, MongoServerError, MongoDriverError, + MongoAPIError, + MongoCompatibilityError, + MongoInvalidArgumentError, + MongoMissingCredentialsError, + MongoMissingDependencyError, MongoNetworkError, MongoNetworkTimeoutError, MongoSystemError, diff --git a/src/logger.ts b/src/logger.ts index 29bb3c16bf..e51540215e 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,6 @@ import { format } from 'util'; -import { MongoDriverError } from './error'; +import { enumToString } from './utils'; +import { MongoInvalidArgumentError } from './error'; // Filters for classes const classFilters: any = {}; @@ -222,7 +223,7 @@ export class Logger { */ static setCurrentLogger(logger: LoggerFunction): void { if (typeof logger !== 'function') { - throw new MongoDriverError('current logger must be a function'); + throw new MongoInvalidArgumentError('Current logger must be a function'); } currentLogger = logger; @@ -253,7 +254,9 @@ export class Logger { newLevel !== LoggerLevel.DEBUG && newLevel !== LoggerLevel.WARN ) { - throw new MongoDriverError(`${newLevel} is an illegal logging level`); + throw new MongoInvalidArgumentError( + `Argument "newLevel" should be one of ${enumToString(LoggerLevel)}` + ); } level = newLevel; diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 57653cbe48..b78d47b33f 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -1,7 +1,7 @@ import { Db, DbOptions } from './db'; import { ChangeStream, ChangeStreamOptions } from './change_stream'; import type { ReadPreference, ReadPreferenceMode } from './read_preference'; -import { AnyError, MongoDriverError } from './error'; +import { AnyError, MongoDriverError, MongoInvalidArgumentError } from './error'; import type { W, WriteConcern } from './write_concern'; import { maybePromise, @@ -403,7 +403,7 @@ export class MongoClient extends TypedEventEmitter { connect(callback: Callback): void; connect(callback?: Callback): Promise | void { if (callback && typeof callback !== 'function') { - throw new MongoDriverError('`connect` only accepts a callback'); + throw new MongoInvalidArgumentError('Method `connect` only accepts a callback'); } return maybePromise(callback, cb => { @@ -549,7 +549,7 @@ export class MongoClient extends TypedEventEmitter { } if (callback == null) { - throw new MongoDriverError('Missing required callback parameter'); + throw new MongoInvalidArgumentError('Missing required callback parameter'); } const session = this.startSession(options); diff --git a/src/operations/add_user.ts b/src/operations/add_user.ts index ed51140dff..4dd56acc5d 100644 --- a/src/operations/add_user.ts +++ b/src/operations/add_user.ts @@ -1,7 +1,7 @@ import * as crypto from 'crypto'; import { Aspect, defineAspects } from './operation'; import { CommandOperation, CommandOperationOptions } from './command'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError } from '../error'; import { Callback, emitWarningOnce, getTopology } from '../utils'; import type { Document } from '../bson'; import type { Server } from '../sdam/server'; @@ -54,9 +54,8 @@ export class AddUserOperation extends CommandOperation { // Error out if digestPassword set if (options.digestPassword != null) { return callback( - new MongoDriverError( - 'The digestPassword option is not supported via add_user. ' + - "Please use db.command('createUser', ...) instead for this option." + new MongoInvalidArgumentError( + 'Option "digestPassword" not supported via addUser, use db.command(...) instead' ) ); } @@ -83,7 +82,7 @@ export class AddUserOperation extends CommandOperation { // Use node md5 generator const md5 = crypto.createHash('md5'); // Generate keys used for authentication - md5.update(username + ':mongo:' + password); + md5.update(`${username}:mongo:${password}`); userPassword = md5.digest('hex'); } diff --git a/src/operations/aggregate.ts b/src/operations/aggregate.ts index 73b83821c8..443201f99e 100644 --- a/src/operations/aggregate.ts +++ b/src/operations/aggregate.ts @@ -5,7 +5,7 @@ import { CollationOptions } from './command'; import { ReadPreference } from '../read_preference'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError } from '../error'; import { maxWireVersion } from '../utils'; import { Aspect, defineAspects, Hint } from './operation'; import type { Callback } from '../utils'; @@ -75,11 +75,13 @@ export class AggregateOperation extends CommandOperation { } if (this.explain && this.writeConcern) { - throw new MongoDriverError('"explain" cannot be used on an aggregate call with writeConcern'); + throw new MongoInvalidArgumentError( + 'Option "explain" cannot be used on an aggregate call with writeConcern' + ); } if (options?.cursor != null && typeof options.cursor !== 'object') { - throw new MongoDriverError('cursor options must be an object'); + throw new MongoInvalidArgumentError('Cursor options must be an object'); } } diff --git a/src/operations/command.ts b/src/operations/command.ts index 9945ba0aa6..0b11e444cd 100644 --- a/src/operations/command.ts +++ b/src/operations/command.ts @@ -4,7 +4,7 @@ import { WriteConcern, WriteConcernOptions } from '../write_concern'; import { maxWireVersion, MongoDBNamespace, Callback, decorateWithExplain } from '../utils'; import type { ReadPreference } from '../read_preference'; import { ClientSession, commandSupportsReadConcern } from '../sessions'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError, MongoCompatibilityError } from '../error'; import type { Logger } from '../logger'; import type { Server } from '../sdam/server'; import type { BSONSerializeOptions, Document } from '../bson'; @@ -98,7 +98,7 @@ export abstract class CommandOperation extends AbstractOperation { if (this.hasAspect(Aspect.EXPLAINABLE)) { this.explain = Explain.fromOptions(options); } else if (options?.explain !== undefined) { - throw new MongoDriverError(`explain is not supported on this command`); + throw new MongoInvalidArgumentError(`Option "explain" is not supported on this command`); } } @@ -131,7 +131,7 @@ export abstract class CommandOperation extends AbstractOperation { if (options.collation && serverWireVersion < SUPPORTS_WRITE_CONCERN_AND_COLLATION) { callback( - new MongoDriverError( + new MongoCompatibilityError( `Server ${server.name}, which reports wire version ${serverWireVersion}, does not support collation` ) ); diff --git a/src/operations/connect.ts b/src/operations/connect.ts index 739a3d632a..474e0a233f 100644 --- a/src/operations/connect.ts +++ b/src/operations/connect.ts @@ -1,4 +1,4 @@ -import { MongoDriverError } from '../error'; +import { MongoDriverError, MongoInvalidArgumentError } from '../error'; import { Topology, TOPOLOGY_EVENTS } from '../sdam/topology'; import { resolveSRVRecord } from '../connection_string'; import type { Callback } from '../utils'; @@ -21,7 +21,7 @@ export function connect( callback: Callback ): void { if (!callback) { - throw new MongoDriverError('no callback function provided'); + throw new MongoInvalidArgumentError('Callback function must be provided'); } // If a connection already been established, we can terminate early diff --git a/src/operations/delete.ts b/src/operations/delete.ts index 664562741d..83124aea13 100644 --- a/src/operations/delete.ts +++ b/src/operations/delete.ts @@ -5,7 +5,7 @@ import type { Document } from '../bson'; import type { Server } from '../sdam/server'; import type { Collection } from '../collection'; import type { ClientSession } from '../sessions'; -import { MongoDriverError, MongoServerError } from '../error'; +import { MongoServerError, MongoCompatibilityError } from '../error'; import type { WriteConcernOptions } from '../write_concern'; /** @public */ @@ -82,21 +82,23 @@ export class DeleteOperation extends CommandOperation { if (options.explain !== undefined && maxWireVersion(server) < 3) { return callback - ? callback(new MongoDriverError(`server ${server.name} does not support explain on delete`)) + ? callback( + new MongoCompatibilityError(`Server ${server.name} does not support explain on delete`) + ) : undefined; } const unacknowledgedWrite = this.writeConcern && this.writeConcern.w === 0; if (unacknowledgedWrite || maxWireVersion(server) < 5) { if (this.statements.find((o: Document) => o.hint)) { - callback(new MongoDriverError(`servers < 3.4 do not support hint on delete`)); + callback(new MongoCompatibilityError(`Servers < 3.4 do not support hint on delete`)); return; } } const statementWithCollation = this.statements.find(statement => !!statement.collation); if (statementWithCollation && collationNotSupported(server, statementWithCollation)) { - callback(new MongoDriverError(`server ${server.name} does not support collation`)); + callback(new MongoCompatibilityError(`Server ${server.name} does not support collation`)); return; } diff --git a/src/operations/distinct.ts b/src/operations/distinct.ts index 247e41115a..2591b9858b 100644 --- a/src/operations/distinct.ts +++ b/src/operations/distinct.ts @@ -4,7 +4,7 @@ import { decorateWithCollation, decorateWithReadConcern, Callback, maxWireVersio import type { Document } from '../bson'; import type { Server } from '../sdam/server'; import type { Collection } from '../collection'; -import { MongoDriverError } from '../error'; +import { MongoCompatibilityError } from '../error'; import type { ClientSession } from '../sessions'; /** @public */ @@ -68,7 +68,9 @@ export class DistinctOperation extends CommandOperation { } if (this.explain && maxWireVersion(server) < 4) { - callback(new MongoDriverError(`server ${server.name} does not support explain on distinct`)); + callback( + new MongoCompatibilityError(`Server ${server.name} does not support explain on distinct`) + ); return; } diff --git a/src/operations/execute_operation.ts b/src/operations/execute_operation.ts index c6baa30c07..2d1b3d8959 100644 --- a/src/operations/execute_operation.ts +++ b/src/operations/execute_operation.ts @@ -4,6 +4,7 @@ import { isRetryableError, MONGODB_ERROR_CODES, MongoDriverError, + MongoCompatibilityError, MongoServerError } from '../error'; import { Aspect, AbstractOperation } from './operation'; @@ -64,6 +65,7 @@ export function executeOperation< TResult = ResultTypeFromOperation >(topology: Topology, operation: T, callback?: Callback): Promise | void { if (!(operation instanceof AbstractOperation)) { + // TODO(NODE-3483) throw new MongoDriverError('This method requires a valid operation instance'); } @@ -85,6 +87,7 @@ export function executeOperation< owner = Symbol(); session = topology.startSession({ owner, explicit: false }); } else if (session.hasEnded) { + // TODO(NODE-3405): Change this out for MongoExpiredSessionError return cb(new MongoDriverError('Use of expired sessions is not permitted')); } else if (session.snapshotEnabled && !topology.capabilities.supportsSnapshotReads) { return cb(new MongoDriverError('Snapshot reads require MongoDB 5.0 or later')); @@ -92,7 +95,7 @@ export function executeOperation< } else if (session) { // If the user passed an explicit session and we are still, after server selection, // trying to run against a topology that doesn't support sessions we error out. - return cb(new MongoDriverError('Current topology does not support sessions')); + return cb(new MongoCompatibilityError('Current topology does not support sessions')); } try { @@ -128,6 +131,7 @@ function executeWithServerSelection( if (inTransaction && !readPreference.equals(ReadPreference.primary)) { callback( + // TODO(NODE-3405): Change this out for MongoTransactionError new MongoDriverError( `Read preference in a transaction must be primary, not: ${readPreference.mode}` ) @@ -189,6 +193,7 @@ function executeWithServerSelection( session.inTransaction() ) { callback( + // TODO(NODE-3405): Change this out for MongoTransactionError new MongoDriverError( `Read preference in a transaction must be primary, not: ${readPreference.mode}` ) diff --git a/src/operations/find.ts b/src/operations/find.ts index 2cc21ae75f..aa7d8a1230 100644 --- a/src/operations/find.ts +++ b/src/operations/find.ts @@ -6,7 +6,7 @@ import { normalizeHintField, decorateWithExplain } from '../utils'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError, MongoCompatibilityError } from '../error'; import type { Document } from '../bson'; import type { Server } from '../sdam/server'; import type { Collection } from '../collection'; @@ -84,15 +84,15 @@ export class FindOperation extends CommandOperation { this.ns = ns; if (typeof filter !== 'object' || Array.isArray(filter)) { - throw new MongoDriverError('Query filter must be a plain object or ObjectId'); + throw new MongoInvalidArgumentError('Query filter must be a plain object or ObjectId'); } // If the filter is a buffer, validate that is a valid BSON document if (Buffer.isBuffer(filter)) { const objectSize = filter[0] | (filter[1] << 8) | (filter[2] << 16) | (filter[3] << 24); if (objectSize !== filter.length) { - throw new MongoDriverError( - `query filter raw message size does not match message header size [${filter.length}] != [${objectSize}]` + throw new MongoInvalidArgumentError( + `Query filter raw message size does not match message header size [${filter.length}] != [${objectSize}]` ); } } @@ -107,13 +107,15 @@ export class FindOperation extends CommandOperation { const serverWireVersion = maxWireVersion(server); const options = this.options; if (typeof options.allowDiskUse !== 'undefined' && serverWireVersion < 4) { - callback(new MongoDriverError('The `allowDiskUse` option is not supported on MongoDB < 3.2')); + callback( + new MongoCompatibilityError('Option "allowDiskUse" is not supported on MongoDB < 3.2') + ); return; } if (options.collation && serverWireVersion < SUPPORTS_WRITE_CONCERN_AND_COLLATION) { callback( - new MongoDriverError( + new MongoCompatibilityError( `Server ${server.name}, which reports wire version ${serverWireVersion}, does not support collation` ) ); @@ -124,8 +126,8 @@ export class FindOperation extends CommandOperation { if (serverWireVersion < 4) { if (this.readConcern && this.readConcern.level !== 'local') { callback( - new MongoDriverError( - `server find command does not support a readConcern level of ${this.readConcern.level}` + new MongoCompatibilityError( + `Server find command does not support a readConcern level of ${this.readConcern.level}` ) ); diff --git a/src/operations/find_and_modify.ts b/src/operations/find_and_modify.ts index dcc9cb9e98..eb04588c81 100644 --- a/src/operations/find_and_modify.ts +++ b/src/operations/find_and_modify.ts @@ -1,6 +1,6 @@ import { ReadPreference } from '../read_preference'; import { maxWireVersion, decorateWithCollation, hasAtomicOperators, Callback } from '../utils'; -import { MongoDriverError } from '../error'; +import { MongoInvalidArgumentError, MongoCompatibilityError } from '../error'; import { CommandOperation, CommandOperationOptions } from './command'; import { defineAspects, Aspect } from './operation'; import type { Document } from '../bson'; @@ -172,7 +172,7 @@ class FindAndModifyOperation extends CommandOperation { const unacknowledgedWrite = this.writeConcern?.w === 0; if (unacknowledgedWrite || maxWireVersion(server) < 8) { callback( - new MongoDriverError( + new MongoCompatibilityError( 'The current topology does not support a hint on findAndModify commands' ) ); @@ -185,7 +185,9 @@ class FindAndModifyOperation extends CommandOperation { if (this.explain && maxWireVersion(server) < 4) { callback( - new MongoDriverError(`server ${server.name} does not support explain on findAndModify`) + new MongoCompatibilityError( + `Server ${server.name} does not support explain on findAndModify` + ) ); return; } @@ -203,7 +205,7 @@ export class FindOneAndDeleteOperation extends FindAndModifyOperation { constructor(collection: Collection, filter: Document, options: FindOneAndDeleteOptions) { // Basic validation if (filter == null || typeof filter !== 'object') { - throw new MongoDriverError('Filter parameter must be an object'); + throw new MongoInvalidArgumentError('Argument "filter" must be an object'); } super(collection, filter, options); @@ -220,15 +222,15 @@ export class FindOneAndReplaceOperation extends FindAndModifyOperation { options: FindOneAndReplaceOptions ) { if (filter == null || typeof filter !== 'object') { - throw new MongoDriverError('Filter parameter must be an object'); + throw new MongoInvalidArgumentError('Argument "filter" must be an object'); } if (replacement == null || typeof replacement !== 'object') { - throw new MongoDriverError('Replacement parameter must be an object'); + throw new MongoInvalidArgumentError('Argument "replacement" must be an object'); } if (hasAtomicOperators(replacement)) { - throw new MongoDriverError('Replacement document must not contain atomic operators'); + throw new MongoInvalidArgumentError('Replacement document must not contain atomic operators'); } super(collection, filter, options); @@ -246,15 +248,15 @@ export class FindOneAndUpdateOperation extends FindAndModifyOperation { options: FindOneAndUpdateOptions ) { if (filter == null || typeof filter !== 'object') { - throw new MongoDriverError('Filter parameter must be an object'); + throw new MongoInvalidArgumentError('Argument "filter" must be an object'); } if (update == null || typeof update !== 'object') { - throw new MongoDriverError('Update parameter must be an object'); + throw new MongoInvalidArgumentError('Argument "update" must be an object'); } if (!hasAtomicOperators(update)) { - throw new MongoDriverError('Update document requires atomic operators'); + throw new MongoInvalidArgumentError('Update document requires atomic operators'); } super(collection, filter, options); diff --git a/src/operations/indexes.ts b/src/operations/indexes.ts index 30a05494e2..ed93aef564 100644 --- a/src/operations/indexes.ts +++ b/src/operations/indexes.ts @@ -1,6 +1,6 @@ import { indexInformation, IndexInformationOptions } from './common_functions'; import { AbstractOperation, Aspect, defineAspects } from './operation'; -import { MONGODB_ERROR_CODES, MongoDriverError, MongoServerError } from '../error'; +import { MONGODB_ERROR_CODES, MongoServerError, MongoCompatibilityError } from '../error'; import { maxWireVersion, parseIndexOptions, @@ -215,7 +215,7 @@ export class CreateIndexesOperation< // Did the user pass in a collation, check if our write server supports it if (indexes[i].collation && serverWireVersion < 5) { callback( - new MongoDriverError( + new MongoCompatibilityError( `Server ${server.name}, which reports wire version ${serverWireVersion}, ` + 'does not support collation' ) @@ -240,8 +240,8 @@ export class CreateIndexesOperation< if (options.commitQuorum != null) { if (serverWireVersion < 9) { callback( - new MongoDriverError( - '`commitQuorum` option for `createIndexes` not supported on servers < 4.4' + new MongoCompatibilityError( + 'Option `commitQuorum` for `createIndexes` not supported on servers < 4.4' ) ); return; diff --git a/src/operations/insert.ts b/src/operations/insert.ts index 787e6faa98..65ee2faf70 100644 --- a/src/operations/insert.ts +++ b/src/operations/insert.ts @@ -1,4 +1,4 @@ -import { MongoDriverError, MongoServerError } from '../error'; +import { MongoServerError, MongoInvalidArgumentError } from '../error'; import { defineAspects, Aspect, AbstractOperation } from './operation'; import { CommandOperation, CommandOperationOptions } from './command'; import { prepareDocs } from './common_functions'; @@ -100,7 +100,7 @@ export class InsertManyOperation extends AbstractOperation { super(options); if (!Array.isArray(docs)) { - throw new MongoDriverError('docs parameter must be an array of documents'); + throw new MongoInvalidArgumentError('Argument "docs" must be an array of documents'); } this.options = options; diff --git a/src/operations/list_collections.ts b/src/operations/list_collections.ts index 0481a552e3..8512adf683 100644 --- a/src/operations/list_collections.ts +++ b/src/operations/list_collections.ts @@ -46,10 +46,7 @@ export class ListCollectionsOperation extends CommandOperation { const databaseName = this.db.s.namespace.db; // If we have legacy mode and have not provided a full db name filter it - if ( - typeof filter.name === 'string' && - !new RegExp('^' + databaseName + '\\.').test(filter.name) - ) { + if (typeof filter.name === 'string' && !new RegExp(`^${databaseName}\\.`).test(filter.name)) { filter = Object.assign({}, filter); filter.name = this.db.s.namespace.withCollection(filter.name).toString(); } diff --git a/src/operations/map_reduce.ts b/src/operations/map_reduce.ts index 6f8540e736..3e84aeb0e1 100644 --- a/src/operations/map_reduce.ts +++ b/src/operations/map_reduce.ts @@ -12,7 +12,7 @@ import { CommandOperation, CommandOperationOptions } from './command'; import type { Server } from '../sdam/server'; import type { Collection } from '../collection'; import type { Sort } from '../sort'; -import { MongoDriverError, MongoServerError } from '../error'; +import { MongoServerError, MongoCompatibilityError } from '../error'; import type { ObjectId } from '../bson'; import { Aspect, defineAspects } from './operation'; import type { ClientSession } from '../sessions'; @@ -165,7 +165,9 @@ export class MapReduceOperation extends CommandOperation } if (this.explain && maxWireVersion(server) < 9) { - callback(new MongoDriverError(`server ${server.name} does not support explain on mapReduce`)); + callback( + new MongoCompatibilityError(`Server ${server.name} does not support explain on mapReduce`) + ); return; } diff --git a/src/operations/profiling_level.ts b/src/operations/profiling_level.ts index 983427267e..66fedcd2de 100644 --- a/src/operations/profiling_level.ts +++ b/src/operations/profiling_level.ts @@ -24,8 +24,10 @@ export class ProfilingLevelOperation extends CommandOperation { if (was === 0) return callback(undefined, 'off'); if (was === 1) return callback(undefined, 'slow_only'); if (was === 2) return callback(undefined, 'all'); - return callback(new MongoDriverError('Error: illegal profiling level value ' + was)); + // TODO(NODE-3483) + return callback(new MongoDriverError(`Illegal profiling level value ${was}`)); } else { + // TODO(NODE-3483): Consider MongoUnexpectedServerResponseError err != null ? callback(err) : callback(new MongoDriverError('Error with profile command')); } }); diff --git a/src/operations/set_profiling_level.ts b/src/operations/set_profiling_level.ts index ade1e6fe5d..12d5fade4f 100644 --- a/src/operations/set_profiling_level.ts +++ b/src/operations/set_profiling_level.ts @@ -1,9 +1,10 @@ import { CommandOperation, CommandOperationOptions } from './command'; import type { Callback } from '../utils'; +import { enumToString } from '../utils'; import type { Server } from '../sdam/server'; import type { Db } from '../db'; import type { ClientSession } from '../sessions'; -import { MongoDriverError } from '../error'; +import { MongoDriverError, MongoInvalidArgumentError } from '../error'; const levelValues = new Set(['off', 'slow_only', 'all']); /** @public */ @@ -50,9 +51,14 @@ export class SetProfilingLevelOperation extends CommandOperation const level = this.level; if (!levelValues.has(level)) { - return callback(new MongoDriverError('Error: illegal profiling level value ' + level)); + return callback( + new MongoInvalidArgumentError( + `Profiling level must be one of "${enumToString(ProfilingLevel)}"` + ) + ); } + // TODO(NODE-3483): Determine error to put here super.executeCommand(server, session, { profile: this.profile }, (err, doc) => { if (err == null && doc.ok === 1) return callback(undefined, level); return err != null diff --git a/src/operations/update.ts b/src/operations/update.ts index 1cecf4ea6d..b30c45716a 100644 --- a/src/operations/update.ts +++ b/src/operations/update.ts @@ -11,7 +11,7 @@ import type { Server } from '../sdam/server'; import type { Collection } from '../collection'; import type { ObjectId, Document } from '../bson'; import type { ClientSession } from '../sessions'; -import { MongoDriverError, MongoServerError } from '../error'; +import { MongoServerError, MongoInvalidArgumentError, MongoCompatibilityError } from '../error'; /** @public */ export interface UpdateOptions extends CommandOperationOptions { @@ -108,25 +108,29 @@ export class UpdateOperation extends CommandOperation { collationNotSupported(server, options) || (statementWithCollation && collationNotSupported(server, statementWithCollation)) ) { - callback(new MongoDriverError(`server ${server.name} does not support collation`)); + callback(new MongoCompatibilityError(`Server ${server.name} does not support collation`)); return; } const unacknowledgedWrite = this.writeConcern && this.writeConcern.w === 0; if (unacknowledgedWrite || maxWireVersion(server) < 5) { if (this.statements.find((o: Document) => o.hint)) { - callback(new MongoDriverError(`servers < 3.4 do not support hint on update`)); + callback(new MongoCompatibilityError(`Servers < 3.4 do not support hint on update`)); return; } } if (this.explain && maxWireVersion(server) < 3) { - callback(new MongoDriverError(`server ${server.name} does not support explain on update`)); + callback( + new MongoCompatibilityError(`Server ${server.name} does not support explain on update`) + ); return; } if (this.statements.some(statement => !!statement.arrayFilters) && maxWireVersion(server) < 6) { - callback(new MongoDriverError('arrayFilters are only supported on MongoDB 3.6+')); + callback( + new MongoCompatibilityError('Option "arrayFilters" is only supported on MongoDB 3.6+') + ); return; } @@ -144,7 +148,7 @@ export class UpdateOneOperation extends UpdateOperation { ); if (!hasAtomicOperators(update)) { - throw new MongoDriverError('Update document requires atomic operators'); + throw new MongoInvalidArgumentError('Update document requires atomic operators'); } } @@ -181,7 +185,7 @@ export class UpdateManyOperation extends UpdateOperation { ); if (!hasAtomicOperators(update)) { - throw new MongoDriverError('Update document requires atomic operators'); + throw new MongoInvalidArgumentError('Update document requires atomic operators'); } } @@ -235,7 +239,7 @@ export class ReplaceOneOperation extends UpdateOperation { ); if (hasAtomicOperators(replacement)) { - throw new MongoDriverError('Replacement document must not contain atomic operators'); + throw new MongoInvalidArgumentError('Replacement document must not contain atomic operators'); } } @@ -268,11 +272,11 @@ export function makeUpdateStatement( options: UpdateOptions & { multi?: boolean } ): UpdateStatement { if (filter == null || typeof filter !== 'object') { - throw new MongoDriverError('selector must be a valid JavaScript object'); + throw new MongoInvalidArgumentError('Selector must be a valid JavaScript object'); } if (update == null || typeof update !== 'object') { - throw new MongoDriverError('document must be a valid JavaScript object'); + throw new MongoInvalidArgumentError('Document must be a valid JavaScript object'); } const op: UpdateStatement = { q: filter, u: update }; diff --git a/src/operations/validate_collection.ts b/src/operations/validate_collection.ts index 6b43668540..46318c587d 100644 --- a/src/operations/validate_collection.ts +++ b/src/operations/validate_collection.ts @@ -40,13 +40,14 @@ export class ValidateCollectionOperation extends CommandOperation { super.executeCommand(server, session, this.command, (err, doc) => { if (err != null) return callback(err); + // TODO(NODE-3483): Replace these with MongoUnexpectedServerResponseError if (doc.ok === 0) return callback(new MongoDriverError('Error with validate command')); - if (doc.result != null && doc.result.constructor !== String) + if (doc.result != null && typeof doc.result !== 'string') return callback(new MongoDriverError('Error with validation data')); if (doc.result != null && doc.result.match(/exception|corrupt/) != null) - return callback(new MongoDriverError('Error: invalid collection ' + collectionName)); + return callback(new MongoDriverError(`Invalid collection ${collectionName}`)); if (doc.valid != null && !doc.valid) - return callback(new MongoDriverError('Error: invalid collection ' + collectionName)); + return callback(new MongoDriverError(`Invalid collection ${collectionName}`)); return callback(undefined, doc); }); diff --git a/src/promise_provider.ts b/src/promise_provider.ts index 08d9b9231d..a6aeb548eb 100644 --- a/src/promise_provider.ts +++ b/src/promise_provider.ts @@ -1,4 +1,4 @@ -import { MongoDriverError } from './error'; +import { MongoInvalidArgumentError } from './error'; /** @internal */ const kPromise = Symbol('promise'); @@ -19,7 +19,7 @@ export class PromiseProvider { /** Validates the passed in promise library */ static validate(lib: unknown): lib is PromiseConstructor { if (typeof lib !== 'function') - throw new MongoDriverError(`Promise must be a function, got ${lib}`); + throw new MongoInvalidArgumentError(`Promise must be a function, got ${lib}`); return !!lib; } diff --git a/src/read_preference.ts b/src/read_preference.ts index 1f2c8e0b2e..a0bfbd7504 100644 --- a/src/read_preference.ts +++ b/src/read_preference.ts @@ -1,7 +1,7 @@ import type { TagSet } from './sdam/server_description'; import type { Document } from './bson'; import type { ClientSession } from './sessions'; -import { MongoDriverError } from './error'; +import { MongoInvalidArgumentError } from './error'; /** @public */ export type ReadPreferenceLike = ReadPreference | ReadPreferenceMode; @@ -84,13 +84,13 @@ export class ReadPreference { */ constructor(mode: ReadPreferenceMode, tags?: TagSet[], options?: ReadPreferenceOptions) { if (!ReadPreference.isValid(mode)) { - throw new MongoDriverError(`Invalid read preference mode ${JSON.stringify(mode)}`); + throw new MongoInvalidArgumentError(`Invalid read preference mode ${JSON.stringify(mode)}`); } if (options === undefined && typeof tags === 'object' && !Array.isArray(tags)) { options = tags; tags = undefined; } else if (tags && !Array.isArray(tags)) { - throw new MongoDriverError('ReadPreference tags must be an array'); + throw new MongoInvalidArgumentError('ReadPreference tags must be an array'); } this.mode = mode; @@ -102,7 +102,7 @@ export class ReadPreference { options = options ?? {}; if (options.maxStalenessSeconds != null) { if (options.maxStalenessSeconds <= 0) { - throw new MongoDriverError('maxStalenessSeconds must be a positive integer'); + throw new MongoInvalidArgumentError('maxStalenessSeconds must be a positive integer'); } this.maxStalenessSeconds = options.maxStalenessSeconds; @@ -114,17 +114,19 @@ export class ReadPreference { if (this.mode === ReadPreference.PRIMARY) { if (this.tags && Array.isArray(this.tags) && this.tags.length > 0) { - throw new MongoDriverError('Primary read preference cannot be combined with tags'); + throw new MongoInvalidArgumentError('Primary read preference cannot be combined with tags'); } if (this.maxStalenessSeconds) { - throw new MongoDriverError( + throw new MongoInvalidArgumentError( 'Primary read preference cannot be combined with maxStalenessSeconds' ); } if (this.hedge) { - throw new MongoDriverError('Primary read preference cannot be combined with hedge'); + throw new MongoInvalidArgumentError( + 'Primary read preference cannot be combined with hedge' + ); } } } @@ -193,7 +195,7 @@ export class ReadPreference { }); } } else if (!(r instanceof ReadPreference)) { - throw new MongoDriverError('Invalid read preference: ' + r); + throw new MongoInvalidArgumentError(`Invalid read preference: ${r}`); } return options; diff --git a/src/sdam/server.ts b/src/sdam/server.ts index 76acdcd461..71d8a266e0 100644 --- a/src/sdam/server.ts +++ b/src/sdam/server.ts @@ -33,7 +33,9 @@ import { isRetryableWriteError, isNodeShuttingDownError, isNetworkErrorBeforeHandshake, - MongoDriverError + MongoDriverError, + MongoCompatibilityError, + MongoInvalidArgumentError } from '../error'; import { Connection, @@ -260,15 +262,16 @@ export class Server extends TypedEventEmitter { } if (callback == null) { - throw new MongoDriverError('callback must be provided'); + throw new MongoInvalidArgumentError('Callback must be provided'); } if (ns.db == null || typeof ns === 'string') { - throw new MongoDriverError('ns must not be a string'); + throw new MongoInvalidArgumentError('Namespace must not be a string'); } if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) { - callback(new MongoDriverError('server is closed')); + // TODO(NODE-3405): Change this out for MongoServerClosedError + callback(new MongoDriverError('Server is closed')); return; } @@ -277,7 +280,7 @@ export class Server extends TypedEventEmitter { // error if collation not supported if (collationNotSupported(this, cmd)) { - callback(new MongoDriverError(`server ${this.name} does not support collation`)); + callback(new MongoCompatibilityError(`Server ${this.name} does not support collation`)); return; } diff --git a/src/sdam/server_selection.ts b/src/sdam/server_selection.ts index 9f85c7686d..0b8883b489 100644 --- a/src/sdam/server_selection.ts +++ b/src/sdam/server_selection.ts @@ -1,6 +1,6 @@ import { ServerType, TopologyType } from './common'; import { ReadPreference } from '../read_preference'; -import { MongoDriverError } from '../error'; +import { MongoCompatibilityError, MongoInvalidArgumentError } from '../error'; import type { TopologyDescription } from './topology_description'; import type { ServerDescription, TagSet } from './server_description'; @@ -50,14 +50,14 @@ function maxStalenessReducer( const maxStalenessVariance = (topologyDescription.heartbeatFrequencyMS + IDLE_WRITE_PERIOD) / 1000; if (maxStaleness < maxStalenessVariance) { - throw new MongoDriverError( - `maxStalenessSeconds must be at least ${maxStalenessVariance} seconds` + throw new MongoInvalidArgumentError( + `Option "maxStalenessSeconds" must be at least ${maxStalenessVariance} seconds` ); } if (maxStaleness < SMALLEST_MAX_STALENESS_SECONDS) { - throw new MongoDriverError( - `maxStalenessSeconds must be at least ${SMALLEST_MAX_STALENESS_SECONDS} seconds` + throw new MongoInvalidArgumentError( + `Option "maxStalenessSeconds" must be at least ${SMALLEST_MAX_STALENESS_SECONDS} seconds` ); } @@ -214,7 +214,7 @@ function knownFilter(server: ServerDescription): boolean { */ export function readPreferenceServerSelector(readPreference: ReadPreference): ServerSelector { if (!readPreference.isValid()) { - throw new MongoDriverError('Invalid read preference specified'); + throw new MongoInvalidArgumentError('Invalid read preference specified'); } return ( @@ -227,7 +227,7 @@ export function readPreferenceServerSelector(readPreference: ReadPreference): Se readPreference.minWireVersion && readPreference.minWireVersion > commonWireVersion ) { - throw new MongoDriverError( + throw new MongoCompatibilityError( `Minimum wire version '${readPreference.minWireVersion}' required, but found '${commonWireVersion}'` ); } diff --git a/src/sdam/srv_polling.ts b/src/sdam/srv_polling.ts index 40973c8205..e588c33e4b 100644 --- a/src/sdam/srv_polling.ts +++ b/src/sdam/srv_polling.ts @@ -67,7 +67,8 @@ export class SrvPoller extends TypedEventEmitter { super(); if (!options || !options.srvHost) { - throw new MongoDriverError('options for SrvPoller must exist and include srvHost'); + // TODO(NODE-3483) + throw new MongoDriverError('Options for SrvPoller must exist and include srvHost'); } this.srvHost = options.srvHost; diff --git a/src/sdam/topology.ts b/src/sdam/topology.ts index 5f6171acf0..756d57d21b 100644 --- a/src/sdam/topology.ts +++ b/src/sdam/topology.ts @@ -11,7 +11,7 @@ import { } from '../sessions'; import { SrvPoller, SrvPollingEvent } from './srv_polling'; import { CMAP_EVENTS, ConnectionPoolEvents } from '../cmap/connection_pool'; -import { MongoServerSelectionError, MongoDriverError } from '../error'; +import { MongoServerSelectionError, MongoCompatibilityError, MongoDriverError } from '../error'; import { readPreferenceServerSelector, ServerSelector } from './server_selection'; import { makeStateMachine, @@ -274,6 +274,7 @@ export class Topology extends TypedEventEmitter { } else if (seed instanceof HostAddress) { seedlist.push(seed); } else { + // FIXME(NODE-3484): May need to be a MongoParseError throw new MongoDriverError(`Topology cannot be constructed from ${JSON.stringify(seed)}`); } } @@ -692,7 +693,7 @@ export class Topology extends TypedEventEmitter { // first update the TopologyDescription this.s.description = this.s.description.update(serverDescription); if (this.s.description.compatibilityError) { - this.emit(Topology.ERROR, new MongoDriverError(this.s.description.compatibilityError)); + this.emit(Topology.ERROR, new MongoCompatibilityError(this.s.description.compatibilityError)); return; } diff --git a/src/sdam/topology_description.ts b/src/sdam/topology_description.ts index 4e9abdbf1a..fab332fed4 100644 --- a/src/sdam/topology_description.ts +++ b/src/sdam/topology_description.ts @@ -3,7 +3,7 @@ import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants'; import { TopologyType, ServerType } from './common'; import type { ObjectId, Document } from '../bson'; import type { SrvPollingEvent } from './srv_polling'; -import { MongoDriverError, MongoError } from '../error'; +import { MongoError, MongoDriverError } from '../error'; // constants related to compatibility checks const MIN_SUPPORTED_SERVER_VERSION = WIRE_CONSTANTS.MIN_SUPPORTED_SERVER_VERSION; @@ -431,7 +431,8 @@ function updateRsWithPrimaryFromMember( setName?: string ): TopologyType { if (setName == null) { - throw new MongoDriverError('setName is required'); + // TODO(NODE-3483): should be an appropriate runtime error + throw new MongoDriverError('Argument "setName" is required if connected to a replica set'); } if ( diff --git a/src/sessions.ts b/src/sessions.ts index fb59f75952..08eba0ec4d 100644 --- a/src/sessions.ts +++ b/src/sessions.ts @@ -6,7 +6,9 @@ import { resolveClusterTime, ClusterTime } from './sdam/common'; import { isSharded } from './cmap/wire_protocol/shared'; import { MongoError, + MongoInvalidArgumentError, isRetryableError, + MongoCompatibilityError, MongoNetworkError, MongoWriteConcernError, MONGODB_ERROR_CODES, @@ -125,10 +127,12 @@ export class ClientSession extends TypedEventEmitter { super(); if (topology == null) { + // TODO(NODE-3483) throw new MongoDriverError('ClientSession requires a topology'); } if (sessionPool == null || !(sessionPool instanceof ServerSessionPool)) { + // TODO(NODE-3483) throw new MongoDriverError('ClientSession requires a ServerSessionPool'); } @@ -137,7 +141,7 @@ export class ClientSession extends TypedEventEmitter { if (options.snapshot === true) { this[kSnapshotEnabled] = true; if (options.causalConsistency === true) { - throw new MongoDriverError( + throw new MongoInvalidArgumentError( 'Properties "causalConsistency" and "snapshot" are mutually exclusive' ); } @@ -296,7 +300,7 @@ export class ClientSession extends TypedEventEmitter { topologyMaxWireVersion != null && topologyMaxWireVersion < minWireVersionForShardedTransactions ) { - throw new MongoDriverError( + throw new MongoCompatibilityError( 'Transactions are not supported on sharded clusters in MongoDB < 4.2.' ); } @@ -461,7 +465,9 @@ function attemptTransaction( if (!isPromiseLike(promise)) { session.abortTransaction(); - throw new MongoDriverError('Function provided to `withTransaction` must return a Promise'); + throw new MongoInvalidArgumentError( + 'Function provided to `withTransaction` must return a Promise' + ); } return promise.then( diff --git a/src/sort.ts b/src/sort.ts index 7046b1b30b..baf63cf62d 100644 --- a/src/sort.ts +++ b/src/sort.ts @@ -1,4 +1,4 @@ -import { MongoDriverError } from './error'; +import { MongoInvalidArgumentError } from './error'; /** @public */ export type SortDirection = @@ -45,7 +45,7 @@ function prepareDirection(direction: any = 1): SortDirectionForCmd { case '-1': return -1; default: - throw new MongoDriverError(`Invalid sort direction: ${JSON.stringify(direction)}`); + throw new MongoInvalidArgumentError(`Invalid sort direction: ${JSON.stringify(direction)}`); } } @@ -118,7 +118,9 @@ export function formatSort( if (sort == null) return undefined; if (typeof sort === 'string') return new Map([[sort, prepareDirection(direction)]]); if (typeof sort !== 'object') { - throw new MongoDriverError(`Invalid sort format: ${JSON.stringify(sort)}`); + throw new MongoInvalidArgumentError( + `Invalid sort format: ${JSON.stringify(sort)} Sort must be a valid object` + ); } if (!Array.isArray(sort)) { return isMap(sort) ? mapToMap(sort) : Object.keys(sort).length ? objectToMap(sort) : undefined; diff --git a/src/utils.ts b/src/utils.ts index 562818e0db..8da2f6885d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,13 @@ import * as os from 'os'; import * as crypto from 'crypto'; import { PromiseProvider } from './promise_provider'; -import { AnyError, MongoParseError, MongoDriverError } from './error'; +import { + AnyError, + MongoParseError, + MongoDriverError, + MongoCompatibilityError, + MongoInvalidArgumentError +} from './error'; import { WriteConcern, WriteConcernOptions, W } from './write_concern'; import type { Server } from './sdam/server'; import type { Topology } from './sdam/topology'; @@ -54,27 +60,27 @@ export function getSingleProperty( */ export function checkCollectionName(collectionName: string): void { if ('string' !== typeof collectionName) { - throw new MongoDriverError('collection name must be a String'); + throw new MongoInvalidArgumentError('Collection name must be a String'); } if (!collectionName || collectionName.indexOf('..') !== -1) { - throw new MongoDriverError('collection names cannot be empty'); + throw new MongoInvalidArgumentError('Collection names cannot be empty'); } if ( collectionName.indexOf('$') !== -1 && collectionName.match(/((^\$cmd)|(oplog\.\$main))/) == null ) { - throw new MongoDriverError("collection names must not contain '$'"); + throw new MongoInvalidArgumentError("Collection names must not contain '$'"); } if (collectionName.match(/^\.|\.$/) != null) { - throw new MongoDriverError("collection names must not start or end with '.'"); + throw new MongoInvalidArgumentError("Collection names must not start or end with '.'"); } // Validate that we are not passing 0x00 in the collection name if (collectionName.indexOf('\x00') !== -1) { - throw new MongoDriverError('collection names cannot contain a null character'); + throw new MongoInvalidArgumentError('Collection names cannot contain a null character'); } } @@ -228,6 +234,7 @@ export function executeLegacyOperation( const Promise = PromiseProvider.get(); if (!Array.isArray(args)) { + // TODO(NODE-3483) throw new MongoDriverError('This method requires an array of arguments to apply'); } @@ -248,6 +255,7 @@ export function executeLegacyOperation( const optionsIndex = args.length - 2; args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session }); } else if (opOptions.session && opOptions.session.hasEnded) { + // TODO(NODE-3405): Replace this with MongoExpiredSessionError throw new MongoDriverError('Use of expired sessions is not permitted'); } } @@ -289,7 +297,8 @@ export function executeLegacyOperation( // Return a Promise if (args[args.length - 1] != null) { - throw new MongoDriverError('final argument to `executeLegacyOperation` must be a callback'); + // TODO(NODE-3483) + throw new MongoDriverError('Final argument to `executeLegacyOperation` must be a callback'); } return new Promise((resolve, reject) => { @@ -399,7 +408,7 @@ export function decorateWithCollation( if (capabilities && capabilities.commandsTakeCollation) { command.collation = options.collation; } else { - throw new MongoDriverError(`Current topology does not support collation`); + throw new MongoCompatibilityError(`Current topology does not support collation`); } } } @@ -574,6 +583,7 @@ export class MongoDBNamespace { static fromString(namespace?: string): MongoDBNamespace { if (!namespace) { + // TODO(NODE-3483): Replace with MongoNamespaceError throw new MongoDriverError(`Cannot parse namespace from "${namespace}"`); } @@ -921,7 +931,7 @@ export function now(): number { /** @internal */ export function calculateDurationInMs(started: number): number { if (typeof started !== 'number') { - throw new MongoDriverError('numeric value required to calculate duration'); + throw new MongoInvalidArgumentError('Numeric value required to calculate duration'); } const elapsed = now() - started; @@ -1222,7 +1232,7 @@ export class BufferPool { /** Reads the requested number of bytes, optionally consuming them */ read(size: number, consume = true): Buffer { if (typeof size !== 'number' || size < 0) { - throw new MongoDriverError('Parameter size must be a non-negative number'); + throw new MongoInvalidArgumentError('Argument "size" must be a non-negative number'); } if (size > this[kLength]) { @@ -1324,7 +1334,7 @@ export class HostAddress { throw new MongoParseError('Invalid port (zero) with hostname'); } } else { - throw new MongoDriverError('Either socketPath or host must be defined.'); + throw new MongoInvalidArgumentError('Either socketPath or host must be defined.'); } Object.freeze(this); } @@ -1385,3 +1395,10 @@ export function emitWarningOnce(message: string): void { return emitWarning(message); } } + +/** + * Takes a JS object and joins the values into a string separated by ', ' + */ +export function enumToString(en: Record): string { + return Object.values(en).join(', '); +} diff --git a/test/functional/collations.test.js b/test/functional/collations.test.js index db2bddc0c5..fd237d3e79 100644 --- a/test/functional/collations.test.js +++ b/test/functional/collations.test.js @@ -455,7 +455,7 @@ describe('Collation', function () { .catch(err => { expect(err).to.exist; expect(err.message).to.equal( - `server ${testContext.server.uri()} does not support collation` + `Server ${testContext.server.uri()} does not support collation` ); return client.close(); diff --git a/test/functional/collection.test.js b/test/functional/collection.test.js index fd00b06bfb..47183dfffd 100644 --- a/test/functional/collection.test.js +++ b/test/functional/collection.test.js @@ -174,16 +174,16 @@ describe('Collection', function () { }); it('should fail due to illegal listCollections', function (done) { - expect(() => db.collection(5)).to.throw('collection name must be a String'); - expect(() => db.collection('')).to.throw('collection names cannot be empty'); - expect(() => db.collection('te$t')).to.throw("collection names must not contain '$'"); + expect(() => db.collection(5)).to.throw('Collection name must be a String'); + expect(() => db.collection('')).to.throw('Collection names cannot be empty'); + expect(() => db.collection('te$t')).to.throw("Collection names must not contain '$'"); expect(() => db.collection('.test')).to.throw( - "collection names must not start or end with '.'" + "Collection names must not start or end with '.'" ); expect(() => db.collection('test.')).to.throw( - "collection names must not start or end with '.'" + "Collection names must not start or end with '.'" ); - expect(() => db.collection('test..t')).to.throw('collection names cannot be empty'); + expect(() => db.collection('test..t')).to.throw('Collection names cannot be empty'); done(); }); @@ -269,9 +269,9 @@ describe('Collection', function () { it('should throw error due to illegal update', function (done) { db.createCollection('shouldThrowErrorDueToIllegalUpdate', {}, (err, coll) => { - expect(() => coll.update({}, null)).to.throw(/document must be a valid JavaScript object/); + expect(() => coll.update({}, null)).to.throw(/Document must be a valid JavaScript object/); expect(() => coll.update(null, null)).to.throw( - /selector must be a valid JavaScript object/ + /Selector must be a valid JavaScript object/ ); done(); diff --git a/test/functional/cursor.test.js b/test/functional/cursor.test.js index af56b72315..c73fe49706 100644 --- a/test/functional/cursor.test.js +++ b/test/functional/cursor.test.js @@ -602,7 +602,7 @@ describe('Cursor', function () { try { cursor.limit('not-an-integer'); } catch (err) { - test.equal('limit requires an integer', err.message); + test.equal('Operation "limit" requires an integer', err.message); } done(); @@ -781,7 +781,7 @@ describe('Cursor', function () { try { collection.find().skip('not-an-integer'); } catch (err) { - test.equal('skip requires an integer', err.message); + test.equal('Operation "skip" requires an integer', err.message); } const cursor = collection.find(); @@ -836,7 +836,7 @@ describe('Cursor', function () { cursor.batchSize('not-an-integer'); test.ok(false); } catch (err) { - test.equal('batchSize requires an integer', err.message); + test.equal('Operation "batchSize" requires an integer', err.message); } cursor = collection.find(); @@ -4345,7 +4345,7 @@ describe('Cursor', function () { const db = client.db('test'); const collection = db.collection('test_sort_allow_disk_use'); expect(() => collection.find({}).allowDiskUse()).to.throw( - /allowDiskUse requires a sort specification/ + /Option "allowDiskUse" requires a sort specification/ ); done(); }) diff --git a/test/functional/db.test.js b/test/functional/db.test.js index 9ba9b28e32..3fbf3301dd 100644 --- a/test/functional/db.test.js +++ b/test/functional/db.test.js @@ -15,8 +15,8 @@ describe('Db', function () { }, test: withClient((client, done) => { - expect(() => new Db(client, 5)).to.throw('database name must be a string'); - expect(() => new Db(client, '')).to.throw('database name cannot be the empty string'); + expect(() => new Db(client, 5)).to.throw('Database name must be a string'); + expect(() => new Db(client, '')).to.throw('Database name cannot be the empty string'); expect(() => new Db(client, 'te$t')).to.throw( "database names cannot contain the character '$'" ); diff --git a/test/functional/index.test.js b/test/functional/index.test.js index eec7b82adf..16ac892257 100644 --- a/test/functional/index.test.js +++ b/test/functional/index.test.js @@ -1257,7 +1257,7 @@ describe('Indexes', function () { testCommand(db, collection, (err, result) => { expect(err).to.exist; expect(err.message).to.equal( - '`commitQuorum` option for `createIndexes` not supported on servers < 4.4' + 'Option `commitQuorum` for `createIndexes` not supported on servers < 4.4' ); expect(result).to.not.exist; done(); diff --git a/test/functional/operation_example.test.js b/test/functional/operation_example.test.js index 24c2155535..e021d5cca2 100644 --- a/test/functional/operation_example.test.js +++ b/test/functional/operation_example.test.js @@ -2771,7 +2771,7 @@ describe('Operation Examples', function () { collection1.rename(5, function (err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal('collection name must be a String', err.message); + test.equal('Collection name must be a String', err.message); } // Attemp to rename a collection to an empty string @@ -2779,7 +2779,7 @@ describe('Operation Examples', function () { collection1.rename('', function (err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal('collection names cannot be empty', err.message); + test.equal('Collection names cannot be empty', err.message); } // Attemp to rename a collection to an illegal name including the character $ @@ -2787,7 +2787,7 @@ describe('Operation Examples', function () { collection1.rename('te$t', function (err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not contain '$'", err.message); + test.equal("Collection names must not contain '$'", err.message); } // Attemp to rename a collection to an illegal name starting with the character . @@ -2795,7 +2795,7 @@ describe('Operation Examples', function () { collection1.rename('.test', function (err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not start or end with '.'", err.message); + test.equal("Collection names must not start or end with '.'", err.message); } // Attemp to rename a collection to an illegal name ending with the character . @@ -2803,14 +2803,14 @@ describe('Operation Examples', function () { collection1.rename('test.', function (err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not start or end with '.'", err.message); + test.equal("Collection names must not start or end with '.'", err.message); } // Attemp to rename a collection to an illegal name with an empty middle name try { collection1.rename('tes..t', function (err, collection) {}); // eslint-disable-line } catch (err) { - test.equal('collection names cannot be empty', err.message); + test.equal('Collection names cannot be empty', err.message); } // Insert a couple of documents diff --git a/test/functional/operation_generators_example.test.js b/test/functional/operation_generators_example.test.js index 4e70da6683..2c9934a406 100644 --- a/test/functional/operation_generators_example.test.js +++ b/test/functional/operation_generators_example.test.js @@ -2055,7 +2055,7 @@ describe('Operation (Generators)', function () { collection1.rename(5, function(err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal('collection name must be a String', err.message); + test.equal('Collection name must be a String', err.message); } // Attemp to rename a collection to an empty string @@ -2063,7 +2063,7 @@ describe('Operation (Generators)', function () { collection1.rename('', function(err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal('collection names cannot be empty', err.message); + test.equal('Collection names cannot be empty', err.message); } // Attemp to rename a collection to an illegal name including the character $ @@ -2071,7 +2071,7 @@ describe('Operation (Generators)', function () { collection1.rename('te$t', function(err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not contain '$'", err.message); + test.equal("Collection names must not contain '$'", err.message); } // Attemp to rename a collection to an illegal name starting with the character . @@ -2079,7 +2079,7 @@ describe('Operation (Generators)', function () { collection1.rename('.test', function(err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not start or end with '.'", err.message); + test.equal("Collection names must not start or end with '.'", err.message); } // Attemp to rename a collection to an illegal name ending with the character . @@ -2087,14 +2087,14 @@ describe('Operation (Generators)', function () { collection1.rename('test.', function(err, collection) {}); // eslint-disable-line } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not start or end with '.'", err.message); + test.equal("Collection names must not start or end with '.'", err.message); } // Attemp to rename a collection to an illegal name with an empty middle name try { collection1.rename('tes..t', function(err, collection) {}); // eslint-disable-line } catch (err) { - test.equal('collection names cannot be empty', err.message); + test.equal('Collection names cannot be empty', err.message); } // Insert a couple of documents diff --git a/test/functional/operation_promises_example.test.js b/test/functional/operation_promises_example.test.js index f3a621d435..21b7808367 100644 --- a/test/functional/operation_promises_example.test.js +++ b/test/functional/operation_promises_example.test.js @@ -2,6 +2,8 @@ var f = require('util').format; var test = require('./shared').assert; var setupDatabase = require('./shared').setupDatabase; +const { enumToString } = require('../../src/utils'); +const { ProfilingLevel } = require('../../src/operations/set_profiling_level'); const { Code, ReturnDocument } = require('../../src'); const { expect } = require('chai'); @@ -1097,7 +1099,7 @@ describe('Operation (Promises)', function () { // Insert some documents to perform map reduce over return collection - .insertMany([{ user_id: 1 }, { user_id: 2 }], { writeConcern: { w: 1 }}) + .insertMany([{ user_id: 1 }, { user_id: 2 }], { writeConcern: { w: 1 } }) .then(function () { // Map function var map = function () { @@ -2092,7 +2094,7 @@ describe('Operation (Promises)', function () { collection1.rename(5, function (err, collection) {}); } catch (err) { test.ok(err instanceof Error); - test.equal('collection name must be a String', err.message); + test.equal('Collection name must be a String', err.message); } // Attemp to rename a collection to an empty string @@ -2100,7 +2102,7 @@ describe('Operation (Promises)', function () { collection1.rename('', function (err, collection) {}); } catch (err) { test.ok(err instanceof Error); - test.equal('collection names cannot be empty', err.message); + test.equal('Collection names cannot be empty', err.message); } // Attemp to rename a collection to an illegal name including the character $ @@ -2108,7 +2110,7 @@ describe('Operation (Promises)', function () { collection1.rename('te$t', function (err, collection) {}); } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not contain '$'", err.message); + test.equal("Collection names must not contain '$'", err.message); } // Attemp to rename a collection to an illegal name starting with the character . @@ -2116,7 +2118,7 @@ describe('Operation (Promises)', function () { collection1.rename('.test', function (err, collection) {}); } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not start or end with '.'", err.message); + test.equal("Collection names must not start or end with '.'", err.message); } // Attemp to rename a collection to an illegal name ending with the character . @@ -2124,14 +2126,14 @@ describe('Operation (Promises)', function () { collection1.rename('test.', function (err, collection) {}); } catch (err) { test.ok(err instanceof Error); - test.equal("collection names must not start or end with '.'", err.message); + test.equal("Collection names must not start or end with '.'", err.message); } // Attemp to rename a collection to an illegal name with an empty middle name try { collection1.rename('tes..t', function (err, collection) {}); } catch (err) { - test.equal('collection names cannot be empty', err.message); + test.equal('Collection names cannot be empty', err.message); } // Insert a couple of documents @@ -3459,7 +3461,10 @@ describe('Operation (Promises)', function () { }) .catch(function (err) { test.ok(err instanceof Error); - test.equal('Error: illegal profiling level value medium', err.message); + test.equal( + `Profiling level must be one of "${enumToString(ProfilingLevel)}"`, + err.message + ); return client.close(); }); }); diff --git a/test/functional/promises_collection.test.js b/test/functional/promises_collection.test.js index 510c252003..844d2e9c59 100644 --- a/test/functional/promises_collection.test.js +++ b/test/functional/promises_collection.test.js @@ -239,7 +239,7 @@ describe('Promises (Collection)', function () { const db = client.db(configuration.db); expect(() => { db.collection('insertMany_Promise_error').insertMany({ a: 1 }); - }).to.throw(/docs parameter must be an array of documents/); + }).to.throw(/Argument "docs" must be an array of documents/); done(); }); diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js index 83152dc50d..6b6925a097 100644 --- a/test/unit/utils.test.js +++ b/test/unit/utils.test.js @@ -211,12 +211,12 @@ describe('utils', function () { context('read', function () { it('should throw an error if a negative size is requested', function () { const buffer = new BufferPool(); - expect(() => buffer.read(-1)).to.throw(/Parameter size must be a non-negative number/); + expect(() => buffer.read(-1)).to.throw(/Argument "size" must be a non-negative number/); }); it('should throw an error if a non-number size is requested', function () { const buffer = new BufferPool(); - expect(() => buffer.read('256')).to.throw(/Parameter size must be a non-negative number/); + expect(() => buffer.read('256')).to.throw(/Argument "size" must be a non-negative number/); }); it('exact size', function () {