From a2dfb2e8bc3f8bcab064933716491ae4e7cfd613 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 3 Apr 2024 09:21:09 -0600 Subject: [PATCH 1/5] add more descriptive error info --- src/cmap/auth/gssapi.ts | 2 +- src/cmap/connection.ts | 4 +- src/cmap/wire_protocol/compression.ts | 2 +- src/deps.ts | 88 ++++++++++++++++----------- src/encrypter.ts | 4 +- src/error.ts | 4 +- 6 files changed, 64 insertions(+), 40 deletions(-) diff --git a/src/cmap/auth/gssapi.ts b/src/cmap/auth/gssapi.ts index e4e2658db24..7ace642abeb 100644 --- a/src/cmap/auth/gssapi.ts +++ b/src/cmap/auth/gssapi.ts @@ -36,7 +36,7 @@ async function externalCommand( }>); } -let krb: typeof Kerberos; +let krb: Kerberos; export class GSSAPI extends AuthProvider { override async auth(authContext: AuthContext): Promise { diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 1a4db3401f9..4f9ebce644b 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -13,8 +13,8 @@ import { UNPINNED } from '../constants'; import { + MongoAPIError, MongoCompatibilityError, - MongoMissingDependencyError, MongoNetworkError, MongoNetworkTimeoutError, MongoParseError, @@ -695,7 +695,7 @@ export class CryptoConnection extends Connection { ): Promise { const { autoEncrypter } = this; if (!autoEncrypter) { - throw new MongoMissingDependencyError('No AutoEncrypter available for encryption'); + throw new MongoAPIError('No AutoEncrypter available for encryption'); } const serverWireVersion = maxWireVersion(this); diff --git a/src/cmap/wire_protocol/compression.ts b/src/cmap/wire_protocol/compression.ts index 06c83f29123..31d2bec2510 100644 --- a/src/cmap/wire_protocol/compression.ts +++ b/src/cmap/wire_protocol/compression.ts @@ -45,7 +45,7 @@ const ZSTD_COMPRESSION_LEVEL = 3; const zlibInflate = promisify(zlib.inflate.bind(zlib)); const zlibDeflate = promisify(zlib.deflate.bind(zlib)); -let zstd: typeof ZStandard; +let zstd: ZStandard; let Snappy: SnappyLib | null = null; function loadSnappy() { if (Snappy == null) { diff --git a/src/deps.ts b/src/deps.ts index 7a3c121f689..407be401a2b 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -18,21 +18,23 @@ function makeErrorModule(error: any) { }); } -export let Kerberos: typeof import('kerberos') | { kModuleError: MongoMissingDependencyError } = - makeErrorModule( - new MongoMissingDependencyError( - 'Optional module `kerberos` not found. Please install it to enable kerberos authentication' - ) - ); +export type Kerberos = typeof import('kerberos') | { kModuleError: MongoMissingDependencyError }; -export function getKerberos(): typeof Kerberos | { kModuleError: MongoMissingDependencyError } { +export function getKerberos(): Kerberos { + let kerberos: Kerberos; try { // Ensure you always wrap an optional require in the try block NODE-3199 - Kerberos = require('kerberos'); - return Kerberos; - } catch { - return Kerberos; + kerberos = require('kerberos'); + } catch (error) { + kerberos = makeErrorModule( + new MongoMissingDependencyError( + 'Optional module `kerberos` not found. Please install it to enable kerberos authentication', + 'kerberos', + { cause: error } + ) + ); } + return kerberos; } export interface KerberosClient { @@ -57,20 +59,23 @@ type ZStandardLib = { decompress(buf: Buffer): Promise; }; -export let ZStandard: ZStandardLib | { kModuleError: MongoMissingDependencyError } = - makeErrorModule( - new MongoMissingDependencyError( - 'Optional module `@mongodb-js/zstd` not found. Please install it to enable zstd compression' - ) - ); +export type ZStandard = ZStandardLib | { kModuleError: MongoMissingDependencyError }; -export function getZstdLibrary(): typeof ZStandard | { kModuleError: MongoMissingDependencyError } { +export function getZstdLibrary(): ZStandardLib | { kModuleError: MongoMissingDependencyError } { + let ZStandard: ZStandardLib | { kModuleError: MongoMissingDependencyError }; try { ZStandard = require('@mongodb-js/zstd'); - return ZStandard; - } catch { - return ZStandard; + } catch (error) { + ZStandard = makeErrorModule( + new MongoMissingDependencyError( + 'Optional module `@mongodb-js/zstd` not found. Please install it to enable zstd compression', + 'zstd', + { cause: error } + ) + ); } + + return ZStandard; } /** @@ -100,11 +105,13 @@ export function getAwsCredentialProvider(): // Ensure you always wrap an optional require in the try block NODE-3199 const credentialProvider = require('@aws-sdk/credential-providers'); return credentialProvider; - } catch { + } catch (error) { return makeErrorModule( new MongoMissingDependencyError( 'Optional module `@aws-sdk/credential-providers` not found.' + - ' Please install it to enable getting aws credentials via the official sdk.' + ' Please install it to enable getting aws credentials via the official sdk.', + '@aws-sdk/credential-providers', + { cause: error } ) ); } @@ -120,11 +127,13 @@ export function getGcpMetadata(): GcpMetadata { // Ensure you always wrap an optional require in the try block NODE-3199 const credentialProvider = require('gcp-metadata'); return credentialProvider; - } catch { + } catch (error) { return makeErrorModule( new MongoMissingDependencyError( 'Optional module `gcp-metadata` not found.' + - ' Please install it to enable getting gcp credentials via the official sdk.' + ' Please install it to enable getting gcp credentials via the official sdk.', + 'gcp-metadata', + { cause: error } ) ); } @@ -153,6 +162,7 @@ export function getSnappy(): SnappyLib | { kModuleError: MongoMissingDependencyE } catch (cause) { const kModuleError = new MongoMissingDependencyError( 'Optional module `snappy` not found. Please install it to enable snappy compression', + 'snappy', { cause } ); return { kModuleError }; @@ -187,6 +197,7 @@ export function getSocks(): SocksLib | { kModuleError: MongoMissingDependencyErr } catch (cause) { const kModuleError = new MongoMissingDependencyError( 'Optional module `socks` not found. Please install it to connections over a SOCKS5 proxy', + 'socks', { cause } ); return { kModuleError }; @@ -234,16 +245,24 @@ interface AWS4 { }; } -export let aws4: AWS4 | { kModuleError: MongoMissingDependencyError } = makeErrorModule( - new MongoMissingDependencyError( - 'Optional module `aws4` not found. Please install it to enable AWS authentication' - ) -); +export const aws4: AWS4 | { kModuleError: MongoMissingDependencyError } = loadAws4(); -try { - // Ensure you always wrap an optional require in the try block NODE-3199 - aws4 = require('aws4'); -} catch {} // eslint-disable-line +function loadAws4() { + let aws4: AWS4 | { kModuleError: MongoMissingDependencyError }; + try { + aws4 = require('aws4'); + } catch (error) { + aws4 = makeErrorModule( + new MongoMissingDependencyError( + 'Optional module `aws4` not found. Please install it to enable AWS authentication', + 'aws4', + { cause: error } + ) + ); + } + + return aws4; +} /** A utility function to get the instance of mongodb-client-encryption, if it exists. */ export function getMongoDBClientEncryption(): @@ -259,6 +278,7 @@ export function getMongoDBClientEncryption(): } catch (cause) { const kModuleError = new MongoMissingDependencyError( 'Optional module `mongodb-client-encryption` not found. Please install it to use auto encryption or ClientEncryption.', + 'mongodb-client-encryption', { cause } ); return { kModuleError }; diff --git a/src/encrypter.ts b/src/encrypter.ts index 24fe33a7b96..9bcd22eda28 100644 --- a/src/encrypter.ts +++ b/src/encrypter.ts @@ -128,7 +128,9 @@ export class Encrypter { if ('kModuleError' in mongodbClientEncryption) { throw new MongoMissingDependencyError( 'Auto-encryption requested, but the module is not installed. ' + - 'Please add `mongodb-client-encryption` as a dependency of your project' + 'Please add `mongodb-client-encryption` as a dependency of your project', + 'mongodb-client-encryption', + { cause: mongodbClientEncryption['kModuleError'] } ); } } diff --git a/src/error.ts b/src/error.ts index ffb85435e1d..b5beb424d99 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1008,6 +1008,7 @@ export class MongoMissingCredentialsError extends MongoAPIError { * @category Error */ export class MongoMissingDependencyError extends MongoAPIError { + dependencyName: string; /** * **Do not use this constructor!** * @@ -1019,8 +1020,9 @@ export class MongoMissingDependencyError extends MongoAPIError { * * @public **/ - constructor(message: string, options: { cause?: Error } = {}) { + constructor(message: string, dependencyName: string, options: { cause?: Error } = {}) { super(message, options); + this.dependencyName = dependencyName; } override get name(): string { From b08f6d849a99c2dcb6e68165e8eac80fd006221f Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 3 Apr 2024 09:21:52 -0600 Subject: [PATCH 2/5] make cause required on missing dependency error --- src/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.ts b/src/error.ts index b5beb424d99..959cfa9e958 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1020,7 +1020,7 @@ export class MongoMissingDependencyError extends MongoAPIError { * * @public **/ - constructor(message: string, dependencyName: string, options: { cause?: Error } = {}) { + constructor(message: string, dependencyName: string, options: { cause: Error }) { super(message, options); this.dependencyName = dependencyName; } From 44fba7e16761d04955b74d9a543d90bf218164ee Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Wed, 3 Apr 2024 09:33:01 -0600 Subject: [PATCH 3/5] make error required and move to ooptions --- src/deps.ts | 30 +++++++++++------------------- src/encrypter.ts | 6 ++++-- src/error.ts | 7 +++++-- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/deps.ts b/src/deps.ts index 407be401a2b..c3517ebc524 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -29,8 +29,7 @@ export function getKerberos(): Kerberos { kerberos = makeErrorModule( new MongoMissingDependencyError( 'Optional module `kerberos` not found. Please install it to enable kerberos authentication', - 'kerberos', - { cause: error } + { cause: error, dependencyName: 'kerberos' } ) ); } @@ -69,8 +68,7 @@ export function getZstdLibrary(): ZStandardLib | { kModuleError: MongoMissingDep ZStandard = makeErrorModule( new MongoMissingDependencyError( 'Optional module `@mongodb-js/zstd` not found. Please install it to enable zstd compression', - 'zstd', - { cause: error } + { cause: error, dependencyName: 'zstd' } ) ); } @@ -110,8 +108,7 @@ export function getAwsCredentialProvider(): new MongoMissingDependencyError( 'Optional module `@aws-sdk/credential-providers` not found.' + ' Please install it to enable getting aws credentials via the official sdk.', - '@aws-sdk/credential-providers', - { cause: error } + { cause: error, dependencyName: '@aws-sdk/credential-providers' } ) ); } @@ -132,8 +129,7 @@ export function getGcpMetadata(): GcpMetadata { new MongoMissingDependencyError( 'Optional module `gcp-metadata` not found.' + ' Please install it to enable getting gcp credentials via the official sdk.', - 'gcp-metadata', - { cause: error } + { cause: error, dependencyName: 'gcp-metadata' } ) ); } @@ -159,11 +155,10 @@ export function getSnappy(): SnappyLib | { kModuleError: MongoMissingDependencyE // Ensure you always wrap an optional require in the try block NODE-3199 const value = require('snappy'); return value; - } catch (cause) { + } catch (error) { const kModuleError = new MongoMissingDependencyError( 'Optional module `snappy` not found. Please install it to enable snappy compression', - 'snappy', - { cause } + { cause: error, dependencyName: 'snappy' } ); return { kModuleError }; } @@ -194,11 +189,10 @@ export function getSocks(): SocksLib | { kModuleError: MongoMissingDependencyErr // Ensure you always wrap an optional require in the try block NODE-3199 const value = require('socks'); return value; - } catch (cause) { + } catch (error) { const kModuleError = new MongoMissingDependencyError( 'Optional module `socks` not found. Please install it to connections over a SOCKS5 proxy', - 'socks', - { cause } + { cause: error, dependencyName: 'socks' } ); return { kModuleError }; } @@ -255,8 +249,7 @@ function loadAws4() { aws4 = makeErrorModule( new MongoMissingDependencyError( 'Optional module `aws4` not found. Please install it to enable AWS authentication', - 'aws4', - { cause: error } + { cause: error, dependencyName: 'aws4' } ) ); } @@ -275,11 +268,10 @@ export function getMongoDBClientEncryption(): // Cannot be moved to helper utility function, bundlers search and replace the actual require call // in a way that makes this line throw at bundle time, not runtime, catching here will make bundling succeed mongodbClientEncryption = require('mongodb-client-encryption'); - } catch (cause) { + } catch (error) { const kModuleError = new MongoMissingDependencyError( 'Optional module `mongodb-client-encryption` not found. Please install it to use auto encryption or ClientEncryption.', - 'mongodb-client-encryption', - { cause } + { cause: error, dependencyName: 'mongodb-client-encryption' } ); return { kModuleError }; } diff --git a/src/encrypter.ts b/src/encrypter.ts index 9bcd22eda28..007a0799e61 100644 --- a/src/encrypter.ts +++ b/src/encrypter.ts @@ -129,8 +129,10 @@ export class Encrypter { throw new MongoMissingDependencyError( 'Auto-encryption requested, but the module is not installed. ' + 'Please add `mongodb-client-encryption` as a dependency of your project', - 'mongodb-client-encryption', - { cause: mongodbClientEncryption['kModuleError'] } + { + cause: mongodbClientEncryption['kModuleError'].cause, + dependencyName: 'mongodb-client-encryption' + } ); } } diff --git a/src/error.ts b/src/error.ts index 959cfa9e958..cec8796eebb 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1009,6 +1009,8 @@ export class MongoMissingCredentialsError extends MongoAPIError { */ export class MongoMissingDependencyError extends MongoAPIError { dependencyName: string; + override cause: Error; + /** * **Do not use this constructor!** * @@ -1020,9 +1022,10 @@ export class MongoMissingDependencyError extends MongoAPIError { * * @public **/ - constructor(message: string, dependencyName: string, options: { cause: Error }) { + constructor(message: string, options: { cause: Error; dependencyName: string }) { super(message, options); - this.dependencyName = dependencyName; + this.dependencyName = options.dependencyName; + this.cause = options.cause; } override get name(): string { From 9f872484b384a77366be27703ac7373387e4852c Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Thu, 4 Apr 2024 09:59:27 -0600 Subject: [PATCH 4/5] address PR comments --- src/cmap/connection.ts | 8 ++++++-- src/encrypter.ts | 2 +- src/error.ts | 5 +++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index 4f9ebce644b..fd51299a24a 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -13,8 +13,8 @@ import { UNPINNED } from '../constants'; import { - MongoAPIError, MongoCompatibilityError, + MongoMissingDependencyError, MongoNetworkError, MongoNetworkTimeoutError, MongoParseError, @@ -695,7 +695,11 @@ export class CryptoConnection extends Connection { ): Promise { const { autoEncrypter } = this; if (!autoEncrypter) { - throw new MongoAPIError('No AutoEncrypter available for encryption'); + // TODO(NODE-6065): throw a MongoMissingDependencyError in Node V7 + // @ts-expect-error No cause provided because there is no underlying error. + throw new MongoMissingDependencyError('No AutoEncrypter available for encryption', { + dependencyName: 'n/a' + }); } const serverWireVersion = maxWireVersion(this); diff --git a/src/encrypter.ts b/src/encrypter.ts index 007a0799e61..fbcf7c195d9 100644 --- a/src/encrypter.ts +++ b/src/encrypter.ts @@ -130,7 +130,7 @@ export class Encrypter { 'Auto-encryption requested, but the module is not installed. ' + 'Please add `mongodb-client-encryption` as a dependency of your project', { - cause: mongodbClientEncryption['kModuleError'].cause, + cause: mongodbClientEncryption['kModuleError'], dependencyName: 'mongodb-client-encryption' } ); diff --git a/src/error.ts b/src/error.ts index cec8796eebb..28c269af6be 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1009,7 +1009,9 @@ export class MongoMissingCredentialsError extends MongoAPIError { */ export class MongoMissingDependencyError extends MongoAPIError { dependencyName: string; - override cause: Error; + + /** @remarks This property is assigned in the `Error` constructor. */ + declare cause: Error; /** * **Do not use this constructor!** @@ -1025,7 +1027,6 @@ export class MongoMissingDependencyError extends MongoAPIError { constructor(message: string, options: { cause: Error; dependencyName: string }) { super(message, options); this.dependencyName = options.dependencyName; - this.cause = options.cause; } override get name(): string { From 45cca737cabcb60807ddf6249381a27f17deef34 Mon Sep 17 00:00:00 2001 From: Bailey Pearson Date: Fri, 5 Apr 2024 10:48:25 -0600 Subject: [PATCH 5/5] Update src/cmap/connection.ts Co-authored-by: Anna Henningsen --- src/cmap/connection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index fd51299a24a..061d7f8332c 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -695,7 +695,7 @@ export class CryptoConnection extends Connection { ): Promise { const { autoEncrypter } = this; if (!autoEncrypter) { - // TODO(NODE-6065): throw a MongoMissingDependencyError in Node V7 + // TODO(NODE-6065): throw a MongoRuntimeError in Node V7 // @ts-expect-error No cause provided because there is no underlying error. throw new MongoMissingDependencyError('No AutoEncrypter available for encryption', { dependencyName: 'n/a'