Skip to main content
Glama
handlers.ts12.2 kB
/** * @fileoverview Error handlers for specific error types * This module provides utilities for handling different types of errors. */ import { AxiosError } from 'axios'; import { ErrorCategory } from './categories.js'; import { ClassifiedError } from './types.js'; import { createClassifiedError } from './factory.js'; import { createLogger } from '../logging/logger.js'; /** * Error classifier for GraphQL errors * @param error The error to classify * @returns The classified error category * @public */ export function classifyGraphQLError(error: Error): ErrorCategory { const message = error.message.toLowerCase(); // Define error patterns and their corresponding categories const errorPatterns: Record<ErrorCategory, string[]> = { [ErrorCategory.AUTH]: [ 'authentication', 'unauthorized', 'access denied', 'not authorized', 'forbidden', 'token', 'api key', ], [ErrorCategory.RATE_LIMIT]: ['rate limit', 'too many requests', 'throttled'], [ErrorCategory.NETWORK]: ['network', 'connection', 'econnreset', 'econnrefused'], [ErrorCategory.TIMEOUT]: ['timeout', 'timed out', 'etimedout'], [ErrorCategory.SCHEMA]: [ 'cannot query field', 'unknown argument', 'unknown type', 'field not defined', ], [ErrorCategory.NOT_FOUND]: ['not found', 'nonetype', 'does not exist'], [ErrorCategory.SERVER]: ['server error', 'internal error', '500'], [ErrorCategory.CLIENT]: [], [ErrorCategory.FORMAT]: [], [ErrorCategory.OTHER]: [], }; // Check each category's patterns for (const [category, patterns] of Object.entries(errorPatterns)) { if (patterns.some((pattern) => message.includes(pattern))) { return category as ErrorCategory; } } // Default to OTHER return ErrorCategory.OTHER; } /** * Type guard to check if an unknown error is an Error object * @param error The error to check * @returns True if the error is an Error instance * @public */ export function isError(error: unknown): error is Error { return ( error !== null && typeof error === 'object' && 'message' in error && typeof (error as Record<string, unknown>).message === 'string' ); } /** * Type guard to check if an error contains a specific message substring * @param error The error to check * @param substring The substring to search for in the error message * @returns True if the error is an Error with the specified substring * @public */ export function isErrorWithMessage(error: unknown, substring: string): error is Error { return isError(error) && error.message?.includes(substring); } /** * Checks if an error is an Axios error with specific characteristics * @param error The error to check * @param statusCode Optional HTTP status code to match * @param errorCode Optional Axios error code to match * @returns True if the error matches the criteria and is an AxiosError, false otherwise * @public */ export function isAxiosErrorWithCriteria( error: unknown, statusCode?: number, errorCode?: string ): error is AxiosError { // Check if it's an object first if (!error || typeof error !== 'object') { return false; } // Check if it has the axios error shape and matches all criteria const potentialAxiosError = error as Partial<AxiosError>; return ( Boolean(potentialAxiosError.isAxiosError) && (statusCode === undefined || potentialAxiosError.response?.status === statusCode) && (errorCode === undefined || potentialAxiosError.code === errorCode) ); } /** * Extract error messages from GraphQL error response * @param errors Array of GraphQL error objects * @returns Formatted error message string * @public */ export function extractGraphQLErrorMessages(errors: Array<{ message: string }>): string { const errorMessages = errors.map((error) => error.message); return errorMessages.join(', '); } /** * Handle GraphQL-specific errors from Axios responses * @param error The error to check for GraphQL errors * @returns A ClassifiedError if a GraphQL error is found, otherwise null * @public */ export function handleGraphQLSpecificError(error: unknown): ClassifiedError | null { if ( isAxiosErrorWithCriteria(error) && typeof error.response?.data === 'object' && error.response.data && 'errors' in error.response.data ) { const graphqlErrors: Array<{ message: string }> = error.response.data.errors as Array<{ message: string; }>; const errorMessage = extractGraphQLErrorMessages(graphqlErrors); // Create a combined error message const combinedError = new Error(`GraphQL Error: ${errorMessage}`); // Classify the error const category = classifyGraphQLError(combinedError); return createClassifiedError(combinedError.message, category, error, { graphqlErrors }); } return null; } /** * Handle network and connection errors * @param error The error to check * @returns A ClassifiedError if a network error is found, otherwise null * @public */ export function handleNetworkError(error: unknown): ClassifiedError | null { if (!isAxiosErrorWithCriteria(error)) { return null; } // Define a lookup table for error codes const errorCodeHandlers: Record<string, () => ClassifiedError> = { ECONNREFUSED: () => createClassifiedError( 'Connection error: Unable to connect to DeepSource API', ErrorCategory.NETWORK, error ), ETIMEDOUT: () => createClassifiedError( 'Timeout error: DeepSource API request timed out', ErrorCategory.TIMEOUT, error ), }; // Get the error code const errorCode = error.code; // Return the appropriate classified error based on the error code return errorCode ? (errorCodeHandlers[errorCode]?.() ?? null) : null; } /** * Handle HTTP status-specific errors * @param error The error to check * @returns A ClassifiedError if an HTTP status error is found, otherwise null * @public */ export function handleHttpStatusError(error: unknown): ClassifiedError | null { if (!isAxiosErrorWithCriteria(error)) { return null; } const status = error.response?.status; if (!status) { return null; } // Define a lookup table for specific HTTP status codes const statusHandlers: Record<number, () => ClassifiedError> = { 401: () => createClassifiedError( 'Authentication error: Invalid or expired API key', ErrorCategory.AUTH, error ), 429: () => createClassifiedError( 'Rate limit exceeded: Too many requests to DeepSource API', ErrorCategory.RATE_LIMIT, error ), 404: () => createClassifiedError( 'Not found (404): The requested resource was not found', ErrorCategory.NOT_FOUND, error ), 502: () => createClassifiedError( `Bad Gateway (${status}): DeepSource API gateway error`, ErrorCategory.SERVER, error ), 503: () => createClassifiedError( `Service Unavailable (${status}): DeepSource API temporarily unavailable`, ErrorCategory.SERVER, error ), 504: () => createClassifiedError( `Gateway Timeout (${status}): DeepSource API gateway timeout`, ErrorCategory.SERVER, error ), }; // Check for exact status code matches first if (statusHandlers[status]) { return statusHandlers[status](); } // Handle status code ranges if (status >= 500) { return createClassifiedError( `Server error (${status}): DeepSource API server error`, ErrorCategory.SERVER, error ); } if (status >= 400 && status < 500) { return createClassifiedError( `Client error (${status}): ${error.response?.statusText || 'Bad request'}`, ErrorCategory.CLIENT, error ); } return null; } /** * Check if an error is retriable based on its classification * @param error The error to check * @returns True if the error should be retried * @public */ export function isRetriableError(error: unknown): boolean { const retriableCategories = [ ErrorCategory.RATE_LIMIT, ErrorCategory.NETWORK, ErrorCategory.TIMEOUT, ErrorCategory.SERVER, ]; // Check if it's already a classified error if (error && typeof error === 'object' && 'category' in error) { const classifiedError = error as ClassifiedError; return retriableCategories.includes(classifiedError.category); } // Check for specific HTTP status codes if (isAxiosErrorWithCriteria(error)) { const status = (error as AxiosError).response?.status; if (status) { // Retriable status codes const retriableStatusCodes = [429, 502, 503, 504]; if (retriableStatusCodes.includes(status)) { return true; } // Server errors are generally retriable if (status >= 500) { return true; } } // Check for network errors const errorCode = (error as AxiosError).code; if (errorCode) { const retriableErrorCodes = ['ECONNREFUSED', 'ETIMEDOUT', 'ECONNRESET', 'EPIPE']; return retriableErrorCodes.includes(errorCode); } } return false; } /** * Extract Retry-After header from an error response * @param error The error to extract from * @returns The Retry-After header value or undefined * @public */ export function extractRetryAfterHeader(error: unknown): string | undefined { if (!isAxiosErrorWithCriteria(error)) { return undefined; } const axiosError = error as AxiosError; const retryAfter = axiosError.response?.headers?.['retry-after']; if (typeof retryAfter === 'string') { return retryAfter; } return undefined; } /** * Main error handler that processes different error types in sequence * @param error The error to handle * @returns A ClassifiedError appropriate for the error type * @public */ export function handleApiError(error: unknown): ClassifiedError { // Create a logger for error handling const logger = createLogger('ErrorHandler'); logger.debug('Handling API error', { errorType: typeof error, isObject: error !== null && typeof error === 'object', errorKeys: error !== null && typeof error === 'object' ? Object.keys(error) : [], }); // If it's already a classified error, just return it if (error && typeof error === 'object' && 'category' in error) { logger.debug('Error is already classified', { category: (error as ClassifiedError).category, message: (error as ClassifiedError).message, }); return error as ClassifiedError; } // Try handling specific error types in order of specificity logger.debug('Checking for GraphQL-specific errors'); const graphqlError = handleGraphQLSpecificError(error); if (graphqlError) { logger.debug('Identified as GraphQL error', { category: graphqlError.category, message: graphqlError.message, }); return graphqlError; } logger.debug('Checking for network errors'); const networkError = handleNetworkError(error); if (networkError) { logger.debug('Identified as network error', { category: networkError.category, message: networkError.message, }); return networkError; } logger.debug('Checking for HTTP status errors'); const httpError = handleHttpStatusError(error); if (httpError) { logger.debug('Identified as HTTP status error', { category: httpError.category, message: httpError.message, }); return httpError; } // If no specific handler worked, create a generic classified error if (isError(error)) { logger.debug('No specific handler matched, creating generic error', { message: error.message, }); const category = classifyGraphQLError(error); return createClassifiedError(`DeepSource API error: ${error.message}`, category, error); } // Last resort for truly unknown errors logger.debug('Handling completely unknown error type', { error: String(error), }); return createClassifiedError( 'Unknown error occurred while communicating with DeepSource API', ErrorCategory.OTHER, error ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sapientpants/deepsource-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server