All files / src/utils/errorHandling errorMessages.ts

100% Statements 30/30
97.5% Branches 39/40
100% Functions 3/3
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145                            16x                                         25x 8x       17x             25x                       15x 1x       14x 14x   7x   7x 5x         9x 9x 1x     8x                             16x   16x         16x 12x     12x   3x     3x                     9x 9x   9x 12x   12x                   4x 16x          
import { AxiosError } from 'axios';
 
export interface FormattedError {
  message: string;
  statusCode?: number;
  correlationId?: string;
  isNetworkError?: boolean;
  isTimeoutError?: boolean;
  originalError?: Error | AxiosError | unknown;
}
 
/**
 * Error message mapping for common HTTP status codes
 */
const ERROR_MESSAGES: Record<number, string> = {
  0: 'Unable to connect to the server. Please check your network connection.',
  400: 'Invalid request. Please check your input and try again.',
  401: 'Your session has expired. Please log in again.',
  403: 'You do not have permission to access this resource.',
  404: 'The requested resource was not found. It may have been deleted or does not exist.',
  409: 'Conflict: This item already exists. Please check and try again.',
  429: 'Too many requests. Rate limit exceeded. Please try again later.',
  500: 'Server error: Something went wrong. Please try again later.',
  503: 'Service temporarily unavailable due to maintenance. Please try again later.',
};
 
/**
 * Get user-friendly error message based on HTTP status code
 *
 * @param statusCode - HTTP status code
 * @param customMessage - Optional custom message from API
 * @returns User-friendly error message
 */
export function getErrorMessage(statusCode: number, customMessage?: string): string {
  // If custom message provided and not empty, use it
  if (customMessage && customMessage.trim()) {
    return customMessage;
  }
 
  // Get message for status code or use generic message
  const message = ERROR_MESSAGES[statusCode] || 'An unexpected error occurred. Please try again.';
 
  // TODO: Implement i18n translation when error message translations are available
  // For now, return English messages
  // const translationKey = `errors.http.${statusCode}`;
  // return i18n.t(translationKey, { defaultValue: message });
 
  return message;
}
 
/**
 * Extract correlation ID from error response
 *
 * Checks both response headers and response data for correlation ID
 *
 * @param error - Axios error object
 * @returns Correlation ID if found, undefined otherwise
 */
export function extractCorrelationId(error: AxiosError): string | undefined {
  if (!error.response) {
    return undefined;
  }
 
  // Check headers first (case-insensitive)
  const headers = error.response.headers;
  if (headers) {
    const correlationId =
      headers['x-correlation-id'] || headers['X-Correlation-ID'] || headers['X-CORRELATION-ID'];
 
    if (correlationId) {
      return correlationId as string;
    }
  }
 
  // Check response data
  const data = error.response.data as Record<string, unknown>;
  if (data && typeof data.correlationId === 'string') {
    return data.correlationId;
  }
 
  return undefined;
}
 
/**
 * Format error for display in UI
 *
 * Extracts relevant information from Error or AxiosError and formats it
 * for consistent display in the UI
 *
 * @param error - Error or AxiosError object
 * @returns Formatted error object
 */
export function formatErrorForDisplay(error: unknown): FormattedError {
  // Handle AxiosError (API errors)
  // Check for isAxiosError flag, response object, or axios-specific error codes
  const errorObj = error as Partial<AxiosError>;
  const isAxiosLike =
    errorObj.isAxiosError ||
    errorObj.response ||
    errorObj.code === 'ERR_NETWORK' ||
    errorObj.code === 'ECONNABORTED';
 
  if (isAxiosLike) {
    const axiosError = error as AxiosError;
 
    // Network error (no response)
    if (!axiosError.response) {
      const isTimeout =
        axiosError.code === 'ECONNABORTED' ||
        (axiosError.message && axiosError.message.includes('timeout'));
 
      return {
        message: isTimeout
          ? 'Timeout: The request is taking longer than expected. Please try again.'
          : getErrorMessage(0),
        isNetworkError: isTimeout ? false : true,
        isTimeoutError: isTimeout ? true : false,
        originalError: error,
      };
    }
 
    // HTTP error with response
    const statusCode = axiosError.response.status;
    const responseData = axiosError.response.data as Record<string, unknown>;
    const customMessage =
      typeof responseData?.message === 'string' ? responseData.message : undefined;
    const correlationId = extractCorrelationId(axiosError);
 
    return {
      message: getErrorMessage(statusCode, customMessage),
      statusCode,
      correlationId,
      originalError: error,
    };
  }
 
  // Handle generic Error
  const errorMessage =
    error instanceof Error ? error.message : 'An unexpected error occurred. Please try again.';
  return {
    message: errorMessage,
    originalError: error,
  };
}