fix messaging error parsing (#16448)

This commit is contained in:
neo773
2025-12-15 18:03:37 +05:30
committed by GitHub
parent 042972d7b2
commit a83732d7a6
7 changed files with 153 additions and 349 deletions
@@ -1,10 +1,11 @@
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-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';
@@ -21,7 +22,7 @@ export class GmailEmailAliasErrorHandlerService {
throw parseGmailNetworkError(error);
}
if (isGmailApiError(error)) {
if (error instanceof GaxiosError) {
throw parseGmailApiError(error);
}
@@ -1,10 +1,11 @@
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-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';
@@ -23,7 +24,7 @@ export class GmailFoldersErrorHandlerService {
throw parseGmailNetworkError(error);
}
if (isGmailApiError(error)) {
if (error instanceof GaxiosError) {
throw parseGmailApiError(error);
}
@@ -1,173 +1,88 @@
import { type GmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type';
import { GaxiosError } from 'gaxios';
const gmailApiErrorMocks = {
// 400 Bad Request - Invalid query parameters
badRequest: {
code: 400,
message: 'badRequest',
},
// 400 Invalid Grant
invalidGrant: {
code: 400,
message: 'invalid_grant',
},
// 400 Failed Precondition
failedPrecondition: {
code: 400,
message: 'failedPrecondition',
},
invalidCredentials: {
code: 401,
message: 'authError',
},
notFound: {
code: 404,
message: 'notFound',
},
gone: {
code: 410,
message: 'resourceGone',
},
dailyLimitExceeded: {
code: 403,
message: 'dailyLimitExceeded',
},
userRateLimitExceeded: {
code: 403,
message: 'userRateLimitExceeded',
},
rateLimitExceeded: {
code: 403,
message: 'rateLimitExceeded',
},
domainPolicyError: {
code: 403,
message: 'domainPolicy',
},
tooManyConcurrentRequests: {
code: 429,
message: 'tooManyConcurrentRequests',
},
backendError: {
code: 500,
message: 'backendError',
},
type ErrorConfig = {
reason: string;
message: string;
};
const convertToErrorWithErrorCodeStringOrNumber = ({
error,
errorCodeAsString,
}: {
error: GmailApiError;
errorCodeAsString: boolean;
}): GmailApiError => {
return {
code: errorCodeAsString ? error.code.toString() : error.code,
message: error.message,
};
const ERROR_DEFINITIONS: Record<number, Record<string, ErrorConfig>> = {
400: {
default: { reason: 'badRequest', message: 'Bad Request' },
invalid_grant: { reason: 'invalid_grant', message: 'invalid_grant' },
failedPrecondition: {
reason: 'failedPrecondition',
message: 'Precondition check failed.',
},
},
401: {
default: { reason: 'authError', message: 'Invalid Credentials' },
},
403: {
default: { reason: 'rateLimitExceeded', message: 'Rate Limit Exceeded' },
dailyLimit: {
reason: 'dailyLimitExceeded',
message: 'Daily Limit Exceeded',
},
userRateLimit: {
reason: 'userRateLimitExceeded',
message: 'User Rate Limit Exceeded',
},
rateLimit: { reason: 'rateLimitExceeded', message: 'Rate Limit Exceeded' },
domainPolicy: { reason: 'domainPolicy', message: 'Domain Policy Error' },
},
404: {
default: { reason: 'notFound', message: 'Not Found' },
},
410: {
default: { reason: 'resourceGone', message: 'Resource Gone' },
},
429: {
default: {
reason: 'tooManyConcurrentRequests',
message: 'Too Many Concurrent Requests',
},
},
500: {
default: { reason: 'backendError', message: 'Backend Error' },
},
};
export const getGmailApiError = ({
code,
type,
errorCodeAsString = false,
reason,
}: {
code: number;
type?: string;
errorCodeAsString?: boolean;
}): GmailApiError => {
switch (code) {
case 400:
switch (type) {
case 'invalid_grant':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.invalidGrant,
errorCodeAsString,
});
case 'failedPrecondition':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.failedPrecondition,
errorCodeAsString,
});
default:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.badRequest,
errorCodeAsString,
});
}
case 401:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.invalidCredentials,
errorCodeAsString,
});
case 403:
switch (type) {
case 'dailyLimit':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.dailyLimitExceeded,
errorCodeAsString,
});
case 'userRateLimit':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.userRateLimitExceeded,
errorCodeAsString,
});
case 'rateLimit':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.rateLimitExceeded,
errorCodeAsString,
});
case 'domainPolicy':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.domainPolicyError,
errorCodeAsString,
});
default:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.rateLimitExceeded,
errorCodeAsString,
});
}
case 404:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.notFound,
errorCodeAsString,
});
case 410:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.gone,
errorCodeAsString,
});
case 429:
switch (type) {
case 'concurrent':
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.tooManyConcurrentRequests,
errorCodeAsString,
});
default:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.tooManyConcurrentRequests,
errorCodeAsString,
});
}
case 500:
return convertToErrorWithErrorCodeStringOrNumber({
error: gmailApiErrorMocks.backendError,
errorCodeAsString,
});
default:
throw new Error(`Unknown error code: ${code}`);
reason?: string;
}): GaxiosError => {
const statusMap = ERROR_DEFINITIONS[code];
if (!statusMap) {
throw new Error(`Unknown error code: ${code}`);
}
const config = statusMap[reason || ''] ?? statusMap.default;
return new GaxiosError(
config.message,
{ url: 'https://gmail.googleapis.com/mocks' },
{
status: code,
statusText: config.message,
data: {
error: {
code,
message: config.message,
errors: [
{
message: config.message,
reason: config.reason,
},
],
},
},
headers: {},
config: { url: 'https://gmail.googleapis.com/mocks' },
request: { responseURL: 'https://gmail.googleapis.com/mocks' },
},
);
};
@@ -1,10 +1,11 @@
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-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';
@@ -23,7 +24,7 @@ export class GmailMessageListFetchErrorHandler {
throw parseGmailNetworkError(error);
}
if (isGmailApiError(error)) {
if (error instanceof GaxiosError) {
throw parseGmailApiError(error);
}
@@ -7,10 +7,7 @@ import { parseGmailApiError } from 'src/modules/messaging/message-import-manager
describe('parseGmailApiError', () => {
it('should handle 400 Bad Request', () => {
const error = getGmailApiError({
code: 400,
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 400 });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -18,11 +15,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 400 Invalid Grant', () => {
const error = getGmailApiError({
code: 400,
type: 'invalid_grant',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 400, reason: 'invalid_grant' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -32,11 +25,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 400 Failed Precondition', () => {
const error = getGmailApiError({
code: 400,
type: 'failedPrecondition',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 400, reason: 'failedPrecondition' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -46,10 +35,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 401 Invalid Credentials', () => {
const error = getGmailApiError({
code: 401,
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 401 });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -59,11 +45,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 Daily Limit Exceeded', () => {
const error = getGmailApiError({
code: 403,
type: 'dailyLimit',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 403, reason: 'dailyLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -73,11 +55,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 User Rate Limit Exceeded', () => {
const error = getGmailApiError({
code: 403,
type: 'userRateLimit',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 403, reason: 'userRateLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -87,11 +65,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 Rate Limit Exceeded', () => {
const error = getGmailApiError({
code: 403,
type: 'rateLimit',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 403, reason: 'rateLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -101,11 +75,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 Domain Policy Error', () => {
const error = getGmailApiError({
code: 403,
type: 'domainPolicy',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 403, reason: 'domainPolicy' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -115,10 +85,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 404 as sync cursor error', () => {
const error = getGmailApiError({
code: 404,
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 404 });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -128,10 +95,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 410 Gone', () => {
const error = getGmailApiError({
code: 410,
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 410 });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -139,11 +103,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 429 Too Many Requests', () => {
const error = getGmailApiError({
code: 429,
type: 'concurrent',
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 429, reason: 'rateLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -153,10 +113,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 500 Backend Error', () => {
const error = getGmailApiError({
code: 500,
errorCodeAsString: false,
});
const error = getGmailApiError({ code: 500, reason: 'backendError' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -166,10 +123,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 400 Bad Request with errorCodeAsString', () => {
const error = getGmailApiError({
code: 400,
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 400, reason: 'badRequest' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -177,11 +131,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 400 Invalid Grant with errorCodeAsString', () => {
const error = getGmailApiError({
code: 400,
type: 'invalid_grant',
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 400, reason: 'invalid_grant' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -191,11 +141,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 400 Failed Precondition with errorCodeAsString', () => {
const error = getGmailApiError({
code: 400,
type: 'failedPrecondition',
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 400, reason: 'failedPrecondition' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -205,10 +151,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 401 Invalid Credentials with errorCodeAsString', () => {
const error = getGmailApiError({
code: 401,
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 401 });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -218,11 +161,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 Daily Limit Exceeded with errorCodeAsString', () => {
const error = getGmailApiError({
code: 403,
type: 'dailyLimit',
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 403, reason: 'dailyLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -232,11 +171,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 User Rate Limit Exceeded with errorCodeAsString', () => {
const error = getGmailApiError({
code: 403,
type: 'userRateLimit',
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 403, reason: 'userRateLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -246,11 +181,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 Rate Limit Exceeded with errorCodeAsString', () => {
const error = getGmailApiError({
code: 403,
type: 'rateLimit',
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 403, reason: 'rateLimit' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -260,11 +191,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 403 Domain Policy Error with errorCodeAsString', () => {
const error = getGmailApiError({
code: 403,
type: 'domainPolicy',
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 403, reason: 'domainPolicy' });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -274,10 +201,7 @@ describe('parseGmailApiError', () => {
});
it('should handle 404 as sync cursor error with errorCodeAsString', () => {
const error = getGmailApiError({
code: 404,
errorCodeAsString: true,
});
const error = getGmailApiError({ code: 404 });
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
@@ -285,42 +209,4 @@ describe('parseGmailApiError', () => {
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
);
});
it('should handle 410 Gone with errorCodeAsString', () => {
const error = getGmailApiError({
code: 410,
errorCodeAsString: true,
});
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(MessageImportDriverExceptionCode.UNKNOWN);
});
it('should handle 429 Too Many Requests with errorCodeAsString', () => {
const error = getGmailApiError({
code: 429,
type: 'concurrent',
errorCodeAsString: true,
});
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
it('should handle 500 Backend Error with errorCodeAsString', () => {
const error = getGmailApiError({
code: 500,
errorCodeAsString: true,
});
const exception = parseGmailApiError(error);
expect(exception).toBeInstanceOf(MessageImportDriverException);
expect(exception.code).toBe(
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
});
});
@@ -1,18 +0,0 @@
import { type GmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type';
export const isGmailApiError = (error: unknown): error is GmailApiError => {
if (error === null || typeof error !== 'object') {
return false;
}
if (
!('code' in error) ||
(typeof error.code !== 'number' && typeof error.code !== 'string') ||
!('message' in error) ||
typeof error.message !== 'string'
) {
return false;
}
return true;
};
@@ -1,69 +1,85 @@
import { type GaxiosError } from 'gaxios';
import {
MessageImportDriverException,
MessageImportDriverExceptionCode,
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
import { type GmailApiError } from 'src/modules/messaging/message-import-manager/drivers/gmail/types/gmail-api-error.type';
export const parseGmailApiError = (
error: GmailApiError,
error: GaxiosError,
): MessageImportDriverException => {
const { code, message } = error;
const gmailApiError = {
code: error.response?.status,
reason:
error.response?.data?.error?.errors?.[0].reason ||
error.response?.data?.error ||
'Unknown reason',
message:
error.response?.data?.error?.errors?.[0].message ||
error.response?.data?.error_description ||
'Unknown error',
};
const codeAsNumber = Number(code);
switch (codeAsNumber) {
switch (gmailApiError.code) {
case 400:
if (message === 'invalid_grant') {
if (gmailApiError.reason === 'invalid_grant') {
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
if (message === 'failedPrecondition') {
if (message.includes('Mail service not enabled')) {
if (gmailApiError.reason === 'failedPrecondition') {
if (gmailApiError.message.includes('Mail service not enabled')) {
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.UNKNOWN,
);
case 404:
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
);
case 429:
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
case 403:
if (
message === 'rateLimitExceeded' ||
message === 'userRateLimitExceeded' ||
message === 'dailyLimitExceeded'
gmailApiError.reason === 'rateLimitExceeded' ||
gmailApiError.reason === 'userRateLimitExceeded' ||
gmailApiError.reason === 'dailyLimitExceeded'
) {
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
if (message === 'domainPolicy') {
if (gmailApiError.reason === 'domainPolicy') {
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
if (gmailApiError.reason === 'insufficientPermissions') {
return new MessageImportDriverException(
gmailApiError.message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
}
@@ -72,29 +88,31 @@ export const parseGmailApiError = (
case 401:
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS,
);
case 503:
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
case 500:
case 502:
case 504:
if (message === 'backendError') {
if (gmailApiError.reason === 'backendError') {
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
if (message.includes(`Authentication backend unavailable`)) {
if (
gmailApiError.message.includes(`Authentication backend unavailable`)
) {
return new MessageImportDriverException(
`${code} - ${message}`,
`${gmailApiError.code} - ${gmailApiError.message}`,
MessageImportDriverExceptionCode.TEMPORARY_ERROR,
);
}
@@ -105,7 +123,7 @@ export const parseGmailApiError = (
}
return new MessageImportDriverException(
message,
gmailApiError.message,
MessageImportDriverExceptionCode.UNKNOWN,
);
};