Skip to content

Commit

Permalink
refactor(NODE-3402): implement MongoAPIError and its children (#2891)
Browse files Browse the repository at this point in the history
  • Loading branch information
W-A-James committed Jul 23, 2021
1 parent c67daea commit 967c193
Show file tree
Hide file tree
Showing 67 changed files with 522 additions and 309 deletions.
31 changes: 17 additions & 14 deletions src/bulk/common.ts
Expand Up @@ -5,7 +5,8 @@ import {
AnyError,
MONGODB_ERROR_CODES,
MongoServerError,
MongoDriverError
MongoDriverError,
MongoInvalidArgumentError
} from '../error';
import {
applyRetryableWrites,
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1083,51 +1084,51 @@ 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,
op.replaceOne.replacement,
{ ...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);
}
}

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,
Expand All @@ -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,
Expand All @@ -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'
);
}
Expand Down Expand Up @@ -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);
}

Expand Down
7 changes: 4 additions & 3 deletions src/bulk/ordered.ts
Expand Up @@ -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 {
Expand All @@ -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}`
);

Expand Down Expand Up @@ -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);
Expand Down
7 changes: 4 additions & 3 deletions src/bulk/unordered.ts
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}`
);
}
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/change_stream.ts
Expand Up @@ -259,8 +259,9 @@ export class ChangeStream<TSchema extends Document = Document> 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'
);
}

Expand Down Expand Up @@ -364,6 +365,7 @@ export class ChangeStream<TSchema extends Document = Document> 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);
}
Expand Down
1 change: 1 addition & 0 deletions src/cmap/auth/auth_provider.ts
Expand Up @@ -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'));
}
}
17 changes: 13 additions & 4 deletions 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';
Expand All @@ -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,
Expand All @@ -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);

Expand Down Expand Up @@ -66,7 +75,7 @@ function makeKerberosClient(authContext: AuthContext, callback: Callback<Kerbero
const { credentials } = authContext;
if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) {
return callback(
new MongoDriverError('Connection must have host and port and credentials defined.')
new MongoInvalidArgumentError('Connection must have host and port and credentials defined.')
);
}

Expand Down
7 changes: 5 additions & 2 deletions src/cmap/auth/mongo_credentials.ts
@@ -1,7 +1,7 @@
// Resolves the default auth mechanism according to

import type { Document } from '../../bson';
import { MongoDriverError } from '../../error';
import { MongoDriverError, MongoMissingCredentialsError } from '../../error';
import { AuthMechanism } from './defaultAuthProviders';

// https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst
Expand Down Expand Up @@ -122,7 +122,7 @@ export class MongoCredentials {
this.mechanism === AuthMechanism.MONGODB_SCRAM_SHA256) &&
!this.username
) {
throw new MongoDriverError(`Username required for mechanism '${this.mechanism}'`);
throw new MongoMissingCredentialsError(`Username required for mechanism '${this.mechanism}'`);
}

if (
Expand All @@ -131,13 +131,15 @@ export class MongoCredentials {
this.mechanism === AuthMechanism.MONGODB_X509
) {
if (this.source != null && this.source !== '$external') {
// TODO(NODE-3483): Replace this with a MongoAuthValidationError
throw new MongoDriverError(
`Invalid source '${this.source}' for mechanism '${this.mechanism}' specified.`
);
}
}

if (this.mechanism === AuthMechanism.MONGODB_PLAIN && this.source == null) {
// TODO(NODE-3483): Replace this with a MongoAuthValidationError
throw new MongoDriverError('PLAIN Authentication Mechanism needs an auth source');
}

Expand All @@ -146,6 +148,7 @@ export class MongoCredentials {
Reflect.set(this, 'password', undefined);
return;
}
// TODO(NODE-3483): Replace this with a MongoAuthValidationError
throw new MongoDriverError(`Password not allowed for mechanism MONGODB-X509`);
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/cmap/auth/mongocr.ts
@@ -1,13 +1,13 @@
import * as crypto from 'crypto';
import { AuthProvider, AuthContext } from './auth_provider';
import { Callback, ns } from '../../utils';
import { MongoDriverError } from '../../error';
import { MongoMissingCredentialsError } from '../../error';

export class MongoCR 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;
Expand All @@ -24,7 +24,7 @@ export class MongoCR extends AuthProvider {
let md5 = crypto.createHash('md5');

// Generate keys used for authentication
md5.update(username + ':mongo:' + password, 'utf8');
md5.update(`${username}:mongo:${password}`, 'utf8');
const hash_password = md5.digest('hex');

// Final key
Expand Down
16 changes: 12 additions & 4 deletions src/cmap/auth/mongodb_aws.ts
Expand Up @@ -4,7 +4,11 @@ import * as url from 'url';
import * as BSON from '../../bson';
import { AuthProvider, AuthContext } from './auth_provider';
import { MongoCredentials } from './mongo_credentials';
import { MongoDriverError } from '../../error';
import {
MongoDriverError,
MongoMissingCredentialsError,
MongoCompatibilityError
} from '../../error';
import { maxWireVersion, Callback, ns } from '../../utils';
import type { BSONSerializeOptions } from '../../bson';

Expand Down Expand Up @@ -32,7 +36,7 @@ export class MongoDBAWS 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.'));
}

if ('kModuleError' in aws4) {
Expand All @@ -42,7 +46,9 @@ export class MongoDBAWS extends AuthProvider {

if (maxWireVersion(connection) < 9) {
callback(
new MongoDriverError('MONGODB-AWS authentication requires MongoDB version 4.4 or later')
new MongoCompatibilityError(
'MONGODB-AWS authentication requires MongoDB version 4.4 or later'
)
);
return;
}
Expand Down Expand Up @@ -149,7 +155,9 @@ interface AWSCredentials {
function makeTempCredentials(credentials: MongoCredentials, callback: Callback<MongoCredentials>) {
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;
}

Expand Down
4 changes: 2 additions & 2 deletions 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;
Expand Down

0 comments on commit 967c193

Please sign in to comment.