155 lines
4.2 KiB
TypeScript
155 lines
4.2 KiB
TypeScript
/**
|
|
* Enhanced API Error with detailed classification and context
|
|
*/
|
|
|
|
export type ApiErrorType =
|
|
| 'NETWORK_ERROR' // Connection failed, timeout, CORS
|
|
| 'AUTH_ERROR' // 401, 403 - Authentication/Authorization issues
|
|
| 'VALIDATION_ERROR' // 400 - Bad request, invalid data
|
|
| 'NOT_FOUND' // 404 - Resource not found
|
|
| 'SERVER_ERROR' // 500, 502, 503 - Server-side issues
|
|
| 'RATE_LIMIT_ERROR' // 429 - Too many requests
|
|
| 'CANCELED_ERROR' // Request was canceled
|
|
| 'TIMEOUT_ERROR' // Request timeout
|
|
| 'UNKNOWN_ERROR'; // Everything else
|
|
|
|
export interface ApiErrorContext {
|
|
endpoint?: string;
|
|
method?: string;
|
|
requestBody?: unknown;
|
|
timestamp: string;
|
|
statusCode?: number;
|
|
responseText?: string;
|
|
retryCount?: number;
|
|
wasRetry?: boolean;
|
|
troubleshooting?: string;
|
|
source?: string;
|
|
componentStack?: string;
|
|
isRetryable?: boolean;
|
|
isConnectivity?: boolean;
|
|
developerHint?: string;
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
public readonly type: ApiErrorType;
|
|
public readonly context: ApiErrorContext;
|
|
public readonly originalError?: Error;
|
|
|
|
constructor(
|
|
message: string,
|
|
type: ApiErrorType,
|
|
context: ApiErrorContext,
|
|
originalError?: Error
|
|
) {
|
|
super(message);
|
|
this.name = 'ApiError';
|
|
this.type = type;
|
|
this.context = context;
|
|
this.originalError = originalError;
|
|
|
|
// Maintains proper stack trace for where our error was thrown
|
|
if (Error.captureStackTrace) {
|
|
Error.captureStackTrace(this, ApiError);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* User-friendly message for production environments
|
|
*/
|
|
getUserMessage(): string {
|
|
switch (this.type) {
|
|
case 'NETWORK_ERROR':
|
|
return 'Unable to connect to the server. Please check your internet connection.';
|
|
case 'AUTH_ERROR':
|
|
return 'Authentication required. Please log in again.';
|
|
case 'VALIDATION_ERROR':
|
|
return 'The data you provided is invalid. Please check your input.';
|
|
case 'NOT_FOUND':
|
|
return 'The requested resource was not found.';
|
|
case 'SERVER_ERROR':
|
|
return 'Server is experiencing issues. Please try again later.';
|
|
case 'RATE_LIMIT_ERROR':
|
|
return 'Too many requests. Please wait a moment and try again.';
|
|
case 'TIMEOUT_ERROR':
|
|
return 'Request timed out. Please try again.';
|
|
case 'CANCELED_ERROR':
|
|
return 'Request was canceled.';
|
|
default:
|
|
return 'An unexpected error occurred. Please try again.';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Developer-friendly message with full context
|
|
*/
|
|
getDeveloperMessage(): string {
|
|
const base = `[${this.type}] ${this.message}`;
|
|
const ctx = [
|
|
this.context.method,
|
|
this.context.endpoint,
|
|
this.context.statusCode ? `status:${this.context.statusCode}` : null,
|
|
this.context.retryCount ? `retry:${this.context.retryCount}` : null,
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
|
|
return ctx ? `${base} ${ctx}` : base;
|
|
}
|
|
|
|
/**
|
|
* Check if this error is retryable
|
|
*/
|
|
isRetryable(): boolean {
|
|
const retryableTypes: ApiErrorType[] = [
|
|
'NETWORK_ERROR',
|
|
'SERVER_ERROR',
|
|
'RATE_LIMIT_ERROR',
|
|
'TIMEOUT_ERROR',
|
|
];
|
|
return retryableTypes.includes(this.type);
|
|
}
|
|
|
|
/**
|
|
* Check if this error indicates connectivity issues
|
|
*/
|
|
isConnectivityIssue(): boolean {
|
|
return this.type === 'NETWORK_ERROR' || this.type === 'TIMEOUT_ERROR';
|
|
}
|
|
|
|
/**
|
|
* Get error severity for logging
|
|
*/
|
|
getSeverity(): 'error' | 'warn' | 'info' {
|
|
switch (this.type) {
|
|
case 'AUTH_ERROR':
|
|
case 'VALIDATION_ERROR':
|
|
case 'NOT_FOUND':
|
|
return 'warn';
|
|
case 'RATE_LIMIT_ERROR':
|
|
case 'CANCELED_ERROR':
|
|
return 'info';
|
|
default:
|
|
return 'error';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Type guards for error classification
|
|
*/
|
|
export function isApiError(error: unknown): error is ApiError {
|
|
return error instanceof ApiError;
|
|
}
|
|
|
|
export function isNetworkError(error: unknown): boolean {
|
|
return isApiError(error) && error.type === 'NETWORK_ERROR';
|
|
}
|
|
|
|
export function isAuthError(error: unknown): boolean {
|
|
return isApiError(error) && error.type === 'AUTH_ERROR';
|
|
}
|
|
|
|
export function isRetryableError(error: unknown): boolean {
|
|
return isApiError(error) && error.isRetryable();
|
|
}
|