2025-12-25 12:20:07 +00:00
|
|
|
/**
|
|
|
|
|
* Service Error Handler
|
|
|
|
|
* FE-API-013: Standardized error handling for API services
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2025-12-25 12:20:07 +00:00
|
|
|
* Provides a consistent way to handle errors in API services with
|
|
|
|
|
* proper error parsing, user-friendly messages, and context awareness.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { AxiosError } from 'axios';
|
2025-12-28 15:07:02 +00:00
|
|
|
import { parseApiError, getValidationErrors } from './apiErrorHandler';
|
|
|
|
|
import { formatUserFriendlyError } from './errorMessages';
|
2026-01-15 16:03:35 +00:00
|
|
|
import type { ApiError } from '@/schemas/apiSchemas';
|
2025-12-25 12:20:07 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Options for error handling
|
|
|
|
|
*/
|
|
|
|
|
export interface ErrorHandlerOptions {
|
|
|
|
|
/** Context for context-specific error messages */
|
2026-01-13 18:47:57 +00:00
|
|
|
context?:
|
|
|
|
|
| 'auth'
|
|
|
|
|
| 'upload'
|
|
|
|
|
| 'playlist'
|
|
|
|
|
| 'track'
|
|
|
|
|
| 'conversation'
|
|
|
|
|
| 'search';
|
2025-12-25 12:20:07 +00:00
|
|
|
/** 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
|
2026-01-13 18:47:57 +00:00
|
|
|
const userMessage = formatUserFriendlyError(
|
|
|
|
|
apiError,
|
|
|
|
|
context,
|
|
|
|
|
includeDetails,
|
|
|
|
|
);
|
2025-12-25 12:20:07 +00:00
|
|
|
|
|
|
|
|
if (throwError) {
|
|
|
|
|
// Create a new Error with the user-friendly message
|
|
|
|
|
// but preserve the ApiError structure for debugging
|
2026-01-13 18:47:57 +00:00
|
|
|
const errorWithContext = new Error(userMessage) as Error & {
|
|
|
|
|
apiError?: ApiError;
|
|
|
|
|
};
|
2025-12-25 12:20:07 +00:00
|
|
|
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;
|
|
|
|
|
}
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2025-12-25 12:20:07 +00:00
|
|
|
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
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2025-12-25 12:20:07 +00:00
|
|
|
* 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;
|
|
|
|
|
}
|