Files
Job-Info/frontend/src/services/errorHandler.ts
T
aewing 42ff3e8f33
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
Add Mode6Test: copy of Mode5Test running on port 3000
2026-01-20 13:43:29 +00:00

332 lines
9.1 KiB
TypeScript

/**
* SERVICE: Error Handler
*
* PURPOSE: Centralized error handling and user messaging
* DEPENDENCIES: UI state store, constants
*
* FEATURES:
* - Convert errors to user-friendly messages
* - Log errors with context
* - Handle different error types (network, auth, validation)
* - Retry logic for transient failures
* - Error recovery suggestions
*/
import type { ApiError } from '../utils/types';
import { ERROR_MESSAGES } from '../utils/constants';
// ============================================================================
// ERROR TYPES
// ============================================================================
export enum ErrorType {
NETWORK = 'network',
AUTH = 'auth',
VALIDATION = 'validation',
NOTFOUND = 'not_found',
PERMISSION = 'permission',
SERVER = 'server',
UNKNOWN = 'unknown',
}
export interface ErrorContext {
type: ErrorType;
message: string;
originalError?: Error;
statusCode?: number;
userMessage: string;
retryable: boolean;
recoverySteps?: string[];
}
// ============================================================================
// ERROR CLASSIFICATION
// ============================================================================
/**
* FUNCTION: classifyError
*
* Determine error type and user message
*
* PARAMS:
* - error: unknown - The error to classify
*
* RETURNS: ErrorContext with type, message, and user guidance
*/
export function classifyError(error: unknown): ErrorContext {
// Handle network errors
if (error instanceof TypeError && error.message.includes('fetch')) {
return {
type: ErrorType.NETWORK,
message: 'Network request failed',
originalError: error,
userMessage: ERROR_MESSAGES.NETWORK_ERROR,
retryable: true,
recoverySteps: [
'Check your internet connection',
'Try again in a few moments',
'If problem persists, refresh the page',
],
};
}
// Handle timeout errors
if (error instanceof Error && error.message.includes('timeout')) {
return {
type: ErrorType.NETWORK,
message: 'Request timeout',
originalError: error,
userMessage: 'Request took too long. Please try again.',
retryable: true,
recoverySteps: ['Check internet speed', 'Try again', 'Refresh page if needed'],
};
}
// Handle auth errors (401)
if (error instanceof Error && error.message.includes('401')) {
return {
type: ErrorType.AUTH,
message: 'Invalid or expired token',
originalError: error,
userMessage: ERROR_MESSAGES.AUTH_FAILED,
retryable: false,
recoverySteps: ['Please sign in again'],
};
}
// Handle permission errors (403)
if (error instanceof Error && error.message.includes('403')) {
return {
type: ErrorType.PERMISSION,
message: 'Access denied',
originalError: error,
userMessage: ERROR_MESSAGES.PERMISSION_DENIED,
retryable: false,
recoverySteps: [
'Check that you have permission to access this resource',
'Contact administrator if needed',
],
};
}
// Handle not found errors (404)
if (error instanceof Error && error.message.includes('404')) {
return {
type: ErrorType.NOTFOUND,
message: 'Resource not found',
originalError: error,
userMessage: 'The requested resource was not found.',
retryable: false,
recoverySteps: ['Go back and try again', 'Refresh the page'],
};
}
// Handle server errors (5xx)
if (error instanceof Error && error.message.includes('5')) {
return {
type: ErrorType.SERVER,
message: 'Server error',
originalError: error,
userMessage: ERROR_MESSAGES.SERVER_ERROR,
retryable: true,
recoverySteps: [
'The server is having issues',
'Please try again in a few moments',
],
};
}
// Handle validation errors
if (error instanceof Error && error.message.includes('validation')) {
return {
type: ErrorType.VALIDATION,
message: 'Validation failed',
originalError: error,
userMessage: 'Please check your input and try again.',
retryable: true,
recoverySteps: ['Review the form', 'Correct any errors', 'Submit again'],
};
}
// Default unknown error
return {
type: ErrorType.UNKNOWN,
message: error instanceof Error ? error.message : String(error),
originalError: error instanceof Error ? error : new Error(String(error)),
userMessage: ERROR_MESSAGES.UNKNOWN_ERROR,
retryable: true,
recoverySteps: ['Try again', 'Refresh the page if needed'],
};
}
// ============================================================================
// LOGGING
// ============================================================================
/**
* FUNCTION: logError
*
* Log error with context and timestamp
* Helps with debugging and error tracking
*
* PARAMS:
* - context: ErrorContext - Error details
* - additionalContext?: string - Extra info
*/
export function logError(context: ErrorContext, additionalContext?: string): void {
const timestamp = new Date().toISOString();
const prefix = `[ERROR ${timestamp}]`;
console.error(`${prefix} [${context.type.toUpperCase()}]`, {
message: context.message,
userMessage: context.userMessage,
statusCode: context.statusCode,
retryable: context.retryable,
additional: additionalContext,
stack: context.originalError?.stack,
});
}
/**
* FUNCTION: captureErrorMetrics
*
* Capture error metrics for analytics
* Could be sent to error tracking service
*
* PARAMS:
* - context: ErrorContext - Error details
*/
export function captureErrorMetrics(context: ErrorContext): void {
// Could integrate with error tracking service here
// (Sentry, LogRocket, etc.)
console.debug('[Metrics] Error captured:', {
type: context.type,
retryable: context.retryable,
timestamp: new Date().toISOString(),
});
}
// ============================================================================
// ERROR RECOVERY
// ============================================================================
/**
* FUNCTION: shouldRetry
*
* Determine if error should be retried
* Checks error type and attempt count
*
* PARAMS:
* - error: ErrorContext - Classified error
* - attempts: number - Number of retry attempts made
*
* RETURNS: boolean - true if should retry
*/
export function shouldRetry(error: ErrorContext, attempts: number): boolean {
// Don't retry too many times
if (attempts >= 3) return false;
// Only retry retryable errors
return error.retryable;
}
/**
* FUNCTION: getRetryDelay
*
* Calculate exponential backoff delay
* Reduces hammering server on repeated failures
*
* PARAMS:
* - attempt: number - Which attempt number (1, 2, 3...)
*
* RETURNS: number - Milliseconds to wait before retry
*/
export function getRetryDelay(attempt: number): number {
// Exponential backoff: 100ms, 200ms, 400ms, 800ms...
const baseDelay = 100;
const delay = baseDelay * Math.pow(2, attempt - 1);
// Add random jitter (±20%) to prevent thundering herd
const jitter = delay * 0.2 * (Math.random() - 0.5);
return Math.max(100, Math.min(delay + jitter, 5000));
}
/**
* FUNCTION: retryOperation
*
* Retry operation with exponential backoff
*
* PARAMS:
* - operation: () => Promise<T> - Function to retry
* - maxAttempts: number - Max number of attempts (default: 3)
*
* RETURNS: Promise<T> - Result of successful operation
* THROWS: Error if all attempts fail
*/
export async function retryOperation<T>(
operation: () => Promise<T>,
maxAttempts: number = 3
): Promise<T> {
let lastError: ErrorContext | null = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await operation();
} catch (error) {
lastError = classifyError(error);
if (!shouldRetry(lastError, attempt)) {
logError(lastError, `Attempt ${attempt} failed, not retrying`);
throw error;
}
const delay = getRetryDelay(attempt);
console.log(`[Retry] Attempt ${attempt} failed, waiting ${delay}ms before retry`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// All attempts failed
if (lastError) {
logError(lastError, `All ${maxAttempts} attempts failed`);
}
throw new Error(`Operation failed after ${maxAttempts} attempts`);
}
// ============================================================================
// USER MESSAGES
// ============================================================================
/**
* FUNCTION: getUserMessage
*
* Get user-friendly error message
* Safe to display in UI
*
* PARAMS:
* - error: unknown - The error
*
* RETURNS: string - User-friendly message
*/
export function getUserMessage(error: unknown): string {
const context = classifyError(error);
return context.userMessage;
}
/**
* FUNCTION: getRecoverySteps
*
* Get suggested recovery steps
* Help user recover from error
*
* PARAMS:
* - error: unknown - The error
*
* RETURNS: string[] - List of suggested steps
*/
export function getRecoverySteps(error: unknown): string[] {
const context = classifyError(error);
return context.recoverySteps || [];
}