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

163 lines
4.4 KiB
TypeScript
Raw Normal View History

import { AxiosError } from 'axios';
import type { ApiError } from '@/types/api';
/**
* Helper de gestion d'erreurs API
* Transforme les erreurs Axios brutes en objets ApiError standardisés
* selon le format défini dans FRONTEND_INTEGRATION.md
*/
/**
* Parse une erreur Axios en ApiError standardisé
* @param error - Erreur Axios ou Error
* @returns ApiError formaté selon le contrat backend
*/
export function parseApiError(error: unknown): ApiError {
// Si c'est déjà une ApiError, la retourner telle quelle
if (isApiError(error)) {
return error;
}
// Si c'est une erreur Axios
if (isAxiosError(error)) {
const axiosError = error as AxiosError;
// Si le backend retourne le format standardisé { success: false, error: {...} }
if (
axiosError.response?.data &&
typeof axiosError.response.data === 'object' &&
'success' in axiosError.response.data &&
axiosError.response.data.success === false &&
'error' in axiosError.response.data
) {
const backendError = (axiosError.response.data as { error: any }).error;
return normalizeApiError(backendError);
}
// Si le backend retourne directement un objet error
if (
axiosError.response?.data &&
typeof axiosError.response.data === 'object' &&
'code' in axiosError.response.data &&
'message' in axiosError.response.data
) {
return normalizeApiError(axiosError.response.data);
}
// Erreur réseau (pas de réponse)
if (axiosError.request && !axiosError.response) {
return {
code: 0,
message: 'Network error: Unable to connect to server',
timestamp: new Date().toISOString(),
};
}
// Erreur HTTP sans format standardisé
return {
code: axiosError.response?.status || 0,
message:
(axiosError.response?.data as any)?.message ||
axiosError.message ||
'An unexpected error occurred',
timestamp: new Date().toISOString(),
};
}
// Erreur JavaScript standard
if (error instanceof Error) {
return {
code: 0,
message: error.message || 'An unexpected error occurred',
timestamp: new Date().toISOString(),
};
}
// Erreur inconnue
return {
code: 0,
message: 'An unexpected error occurred',
timestamp: new Date().toISOString(),
};
}
/**
* Normalise un objet d'erreur backend en ApiError standardisé
*/
function normalizeApiError(error: any): ApiError {
return {
code: typeof error.code === 'number' ? error.code : parseInt(String(error.code || 0), 10),
message: error.message || 'An error occurred',
details: error.details || (Array.isArray(error.details) ? error.details : undefined),
request_id: error.request_id,
timestamp: error.timestamp || new Date().toISOString(),
context: error.context,
};
}
/**
* Formate un message d'erreur pour l'affichage dans l'UI
* @param error - ApiError
* @returns Message formaté pour l'utilisateur
*/
export function formatErrorMessage(error: ApiError): string {
// Si l'erreur a des détails de validation, les inclure
if (error.details && Array.isArray(error.details) && error.details.length > 0) {
const detailsMessages = error.details
.map((detail) => `${detail.field}: ${detail.message}`)
.join(', ');
return `${error.message} (${detailsMessages})`;
}
return error.message;
}
/**
* Extrait les erreurs de validation par champ
* @param error - ApiError
* @returns Record avec les erreurs par champ (field -> message)
*/
export function getValidationErrors(
error: ApiError,
): Record<string, string> {
if (!error.details || !Array.isArray(error.details)) {
return {};
}
const errors: Record<string, string> = {};
for (const detail of error.details) {
if (detail.field && detail.message) {
errors[detail.field] = detail.message;
}
}
return errors;
}
/**
* Vérifie si une erreur est une ApiError
*/
function isApiError(error: unknown): error is ApiError {
return (
typeof error === 'object' &&
error !== null &&
'code' in error &&
'message' in error &&
typeof (error as any).code === 'number' &&
typeof (error as any).message === 'string'
);
}
/**
* Vérifie si une erreur est une AxiosError
*/
function isAxiosError(error: unknown): error is AxiosError {
return (
typeof error === 'object' &&
error !== null &&
'isAxiosError' in error &&
(error as any).isAxiosError === true
);
}