veza/apps/web/src/utils/serviceErrorHandler.ts

173 lines
4.9 KiB
TypeScript
Raw Normal View History

/**
* Service Error Handler
* FE-API-013: Standardized error handling for API services
*
* Provides a consistent way to handle errors in API services with
* proper error parsing, user-friendly messages, and context awareness.
*/
import { AxiosError } from 'axios';
import { parseApiError, formatErrorMessage, getValidationErrors } from './apiErrorHandler';
import { formatUserFriendlyError, isRetryableError } from './errorMessages';
import type { ApiError } from '@/types/api';
/**
* Options for error handling
*/
export interface ErrorHandlerOptions {
/** Context for context-specific error messages */
context?: 'auth' | 'upload' | 'playlist' | 'track' | 'conversation' | 'search';
/** Whether to include validation details in error messages */
includeDetails?: boolean;
/** Custom error message overrides */
customMessages?: Record<number, string>;
/** Whether to throw the error or return it */
throwError?: boolean;
}
/**
* Standard error handler for API services
* @param error Unknown error from API call
* @param options Error handling options
* @returns Formatted error message or throws ApiError
*/
export function handleServiceError(
error: unknown,
options: ErrorHandlerOptions = {},
): string {
const {
context,
includeDetails = false,
customMessages = {},
throwError = true,
} = options;
// Parse error to ApiError format
const apiError = parseApiError(error);
// Check for custom message override
const status = typeof apiError.code === 'number' ? apiError.code : 0;
if (status in customMessages) {
const customMessage = customMessages[status];
if (throwError) {
throw new Error(customMessage);
}
return customMessage;
}
// Format user-friendly message
const userMessage = formatUserFriendlyError(apiError, context, includeDetails);
if (throwError) {
// Create a new Error with the user-friendly message
// but preserve the ApiError structure for debugging
const errorWithContext = new Error(userMessage) as Error & { apiError?: ApiError };
errorWithContext.apiError = apiError;
throw errorWithContext;
}
return userMessage;
}
/**
* Wrapper for API service functions with standardized error handling
* @param apiCall Async function that makes an API call
* @param options Error handling options
* @returns Result of the API call
* @throws Error with user-friendly message
*/
export async function withErrorHandling<T>(
apiCall: () => Promise<T>,
options: ErrorHandlerOptions = {},
): Promise<T> {
try {
return await apiCall();
} catch (error) {
handleServiceError(error, options);
// This should never be reached due to throwError default, but TypeScript needs it
throw error;
}
}
/**
* Gets validation errors from an API error
* @param error Unknown error from API call
* @returns Record of field -> error message
*/
export function getServiceValidationErrors(
error: unknown,
): Record<string, string> {
const apiError = parseApiError(error);
return getValidationErrors(apiError);
}
/**
* Checks if an error is a specific HTTP status
* @param error Unknown error
* @param status HTTP status code to check
* @returns True if error matches the status
*/
export function isErrorStatus(error: unknown, status: number): boolean {
const apiError = parseApiError(error);
const errorStatus = typeof apiError.code === 'number' ? apiError.code : 0;
return errorStatus === status;
}
/**
* Checks if an error is a network error
* @param error Unknown error
* @returns True if it's a network error
*/
export function isNetworkError(error: unknown): boolean {
if (error instanceof AxiosError) {
return !error.response && !!error.request;
}
const apiError = parseApiError(error);
const code = typeof apiError.code === 'number' ? apiError.code : 0;
return code === 0 || code === 502 || code === 503;
}
/**
* Gets a user-friendly error message for display in UI
* @param error Unknown error
* @param context Optional context for context-specific messages
* @returns User-friendly error message
*/
export function getUserFriendlyMessage(
error: unknown,
context?: ErrorHandlerOptions['context'],
): string {
const apiError = parseApiError(error);
return formatUserFriendlyError(apiError, context);
}
/**
* Error handler specifically for API service methods
* Provides consistent error handling pattern for all service functions
*
* Usage:
* ```typescript
* export async function getSomething(id: string): Promise<Something> {
* try {
* const response = await apiClient.get(`/something/${id}`);
* return response.data;
* } catch (error) {
* return handleApiServiceError(error, {
* context: 'track',
* customMessages: {
* 404: 'Le morceau est introuvable',
* },
* });
* }
* }
* ```
*/
export function handleApiServiceError(
error: unknown,
options: ErrorHandlerOptions = {},
): never {
return handleServiceError(error, { ...options, throwError: true }) as never;
}