diff --git a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service.ts b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service.ts index e4c095613f..43d1346ea0 100644 --- a/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service.ts +++ b/packages/twenty-server/src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service.ts @@ -1,11 +1,10 @@ import { Injectable, Logger } from '@nestjs/common'; -import { GaxiosError } from 'gaxios'; - import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util'; import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util'; import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util'; import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util'; @@ -17,12 +16,16 @@ export class GmailEmailAliasErrorHandlerService { constructor() {} public handleError(error: unknown): void { - this.logger.error(`Google: Error getting email aliases: ${error}`); + const constructorName = (error as unknown)?.constructor?.name ?? 'Unknown'; + + this.logger.error( + `Google: Error getting email aliases: ${error}, constructor: ${constructorName}`, + ); if (isGmailNetworkError(error)) { throw parseGmailNetworkError(error); } - if (error instanceof GaxiosError) { + if (isGmailApiError(error)) { throw parseGmailApiError(error); } diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service.ts index 1326c74c97..e9d8a78a07 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service.ts @@ -1,11 +1,10 @@ import { Injectable, Logger } from '@nestjs/common'; -import { GaxiosError } from 'gaxios'; - import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util'; import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util'; import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util'; import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util'; @@ -17,14 +16,16 @@ export class GmailFoldersErrorHandlerService { constructor() {} public handleError(error: unknown): void { + const constructorName = (error as unknown)?.constructor?.name ?? 'Unknown'; + this.logger.error( - `Gmail: Error fetching folders: ${JSON.stringify(error)}`, + `Gmail: Error fetching folders: ${JSON.stringify(error)}, constructor: ${constructorName}`, ); if (isGmailNetworkError(error)) { throw parseGmailNetworkError(error); } - if (error instanceof GaxiosError) { + if (isGmailApiError(error)) { throw parseGmailApiError(error); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service.ts index 5f8402a5f6..ce62bd78de 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service.ts @@ -1,11 +1,10 @@ import { Injectable, Logger } from '@nestjs/common'; -import { GaxiosError } from 'gaxios'; - import { MessageImportDriverException, MessageImportDriverExceptionCode, } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception'; +import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util'; import { isGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-network-error.util'; import { parseGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-api-error.util'; import { parseGmailNetworkError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-network-error.util'; @@ -17,14 +16,16 @@ export class GmailMessageListFetchErrorHandler { constructor() {} public handleError(error: unknown): void { + const constructorName = (error as unknown)?.constructor?.name ?? 'Unknown'; + this.logger.error( - `Gmail: Error fetching message list: ${JSON.stringify(error)}`, + `Gmail: Error fetching message list: ${JSON.stringify(error)}, constructor: ${constructorName}`, ); if (isGmailNetworkError(error)) { throw parseGmailNetworkError(error); } - if (error instanceof GaxiosError) { + if (isGmailApiError(error)) { throw parseGmailApiError(error); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/is-gmail-api-error.util.spec.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/is-gmail-api-error.util.spec.ts new file mode 100644 index 0000000000..00aaed43a7 --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/__tests__/is-gmail-api-error.util.spec.ts @@ -0,0 +1,51 @@ +import { GaxiosError } from 'gaxios'; + +import { isGmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util'; + +describe('isGmailApiError', () => { + it('should detect Gmail API error shape even when instanceof GaxiosError fails', () => { + const originalError = new GaxiosError( + 'Rate limit exceeded', + {}, + { + status: 429, + statusText: 'Too Many Requests', + headers: {}, + config: {}, + request: { responseURL: '' }, + data: { + error: { + errors: [ + { reason: 'rateLimitExceeded', message: 'Rate limit exceeded' }, + ], + }, + }, + }, + ); + + const serialized = JSON.parse(JSON.stringify(originalError)); + + expect(serialized instanceof GaxiosError).toBe(false); + expect(isGmailApiError(serialized)).toBe(true); + }); + + it('should detect error when data.error is a string instead of object', () => { + const error = { + response: { + status: 429, + data: { + error: 'userRateLimitExceeded', + error_description: 'User Rate Limit Exceeded', + }, + }, + }; + + expect(isGmailApiError(error)).toBe(true); + }); + + it('should reject network errors that have no response', () => { + const networkError = new Error('connect ECONNREFUSED'); + + expect(isGmailApiError(networkError)).toBe(false); + }); +}); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util.ts new file mode 100644 index 0000000000..a8559b2a5e --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/utils/is-gmail-api-error.util.ts @@ -0,0 +1,32 @@ +import { type GaxiosError } from 'gaxios'; +import { z } from 'zod'; + +const gmailApiErrorSchema = z.object({ + response: z.object({ + status: z.number().optional(), + data: z + .object({ + error: z + .union([ + z.object({ + errors: z + .array( + z.object({ + reason: z.string().optional(), + message: z.string().optional(), + }), + ) + .optional(), + }), + z.string(), + ]) + .optional(), + error_description: z.string().optional(), + }) + .optional(), + }), +}); + +export const isGmailApiError = (error: unknown): error is GaxiosError => { + return gmailApiErrorSchema.safeParse(error).success; +};