diff --git a/packages/devtools-connect/src/fast-failure-connect.spec.ts b/packages/devtools-connect/src/fast-failure-connect.spec.ts index 2e20eb41..212098aa 100644 --- a/packages/devtools-connect/src/fast-failure-connect.spec.ts +++ b/packages/devtools-connect/src/fast-failure-connect.spec.ts @@ -46,4 +46,71 @@ describe('isFastFailureConnectionError', function () { isFastFailureConnectionError(new Error('could not connect')), ).to.equal(false); }); + + describe('isCompassSocketServiceError', function () { + class CompassSocketServiceError extends Error { + constructor( + msg: string, + public code: number, + ) { + super(msg); + this.name = 'CompassSocketServiceError'; + } + } + + it('returns true for UNAUTHORIZED (3000)', function () { + const error = new CompassSocketServiceError('Unauthorized', 3000); + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns true for FORBIDDEN (3003)', function () { + const error = new CompassSocketServiceError('Forbidden', 3003); + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns true for NOT_FOUND (4004)', function () { + const error = new CompassSocketServiceError('Not found', 4004); + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns true for VIOLATED_POLICY (1008)', function () { + const error = new CompassSocketServiceError('Violated policy', 1008); + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns true for DO_NOT_TRY_AGAIN (4101)', function () { + const error = new CompassSocketServiceError('Do not try again', 4101); + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns false for CompassSocketServiceError with non-fail-fast code', function () { + const error = new CompassSocketServiceError('Some other error', 9999); + expect(isFastFailureConnectionError(error)).to.equal(false); + }); + + it('returns true when CompassSocketServiceError is the cause of MongoNetworkError', function () { + const cause = new CompassSocketServiceError('Unauthorized', 3000); + const error = new MongoNetworkError('Connection failed'); + (error as any).cause = cause; + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns true when CompassSocketServiceError is nested deeply', function () { + const cause = new CompassSocketServiceError('Forbidden', 3003); + const wrappedError = new Error('Wrapped error'); + (wrappedError as any).cause = cause; + const error = new MongoNetworkError('Connection failed'); + (error as any).cause = wrappedError; + expect(isFastFailureConnectionError(error)).to.equal(true); + }); + + it('returns true when CompassSocketServiceError is in an AggregateError', function () { + const cause = new CompassSocketServiceError('Not found', 4004); + const aggregateError = new AggregateError( + [new Error('Other error'), cause], + 'Multiple errors', + ); + expect(isFastFailureConnectionError(aggregateError)).to.equal(true); + }); + }); }); diff --git a/packages/devtools-connect/src/fast-failure-connect.ts b/packages/devtools-connect/src/fast-failure-connect.ts index e937af3b..dc792fb3 100644 --- a/packages/devtools-connect/src/fast-failure-connect.ts +++ b/packages/devtools-connect/src/fast-failure-connect.ts @@ -1,21 +1,48 @@ // It probably makes sense to put this into its own package/repository once // other tools start using it. +const NODE_SOCKET_NON_RETRY_CODES = [ + 'ECONNREFUSED', + 'ENOTFOUND', + 'ENETUNREACH', + 'EINVAL', +]; +const COMPASS_SOCKET_SERVICE_NON_RETRY_CODES = [ + 3000, // UNAUTHORIZED + 3003, // FORBIDDEN + 4004, // NOT_FOUND + 1008, // VIOLATED_POLICY + 4101, // DO_NOT_TRY_AGAIN +]; + +const isCompassSocketServiceError = handleNestedErrors( + (error: Error & { code?: string | number }): boolean => { + if (error.name === 'CompassSocketServiceError') { + return ( + typeof error.code === 'number' && + COMPASS_SOCKET_SERVICE_NON_RETRY_CODES.includes(error.code) + ); + } + return false; + }, +); + function isFastFailureConnectionSingleError( - error: Error & { code?: string }, + error: Error & { code?: string | number }, ): boolean { switch (error.name) { case 'MongoNetworkError': - return /\b(ECONNREFUSED|ENOTFOUND|ENETUNREACH|EINVAL)\b/.test( - error.message, - ); + return new RegExp( + String.raw`\b(${NODE_SOCKET_NON_RETRY_CODES.join('|')})\b`, + ).test(error.message); case 'MongoError': return /The apiVersion parameter is required/.test(error.message); default: return ( - ['ECONNREFUSED', 'ENOTFOUND', 'ENETUNREACH', 'EINVAL'].includes( - error.code ?? '', - ) || isPotentialTLSCertificateError(error) + (typeof error.code === 'string' && + NODE_SOCKET_NON_RETRY_CODES.includes(error.code)) || + isPotentialTLSCertificateError(error) || + isCompassSocketServiceError(error) ); } } diff --git a/packages/devtools-connect/tsconfig.json b/packages/devtools-connect/tsconfig.json index 415b3a29..d52fffc9 100644 --- a/packages/devtools-connect/tsconfig.json +++ b/packages/devtools-connect/tsconfig.json @@ -2,7 +2,7 @@ "extends": "@mongodb-js/tsconfig-devtools/tsconfig.common.json", "compilerOptions": { "target": "es2020", - "lib": ["es2020", "DOM"], + "lib": ["es2021", "DOM"], "module": "commonjs", "moduleResolution": "node" }