# Gestion des erreurs — Projet Batch-cooking
> Documentation du contrat d'erreurs partagé entre `apps/api` et `apps/web`.
---
## Vue d'ensemble
Trois pièces travaillent ensemble pour que **toute** erreur, du serveur jusqu'à
l'affichage utilisateur, passe par un chemin unique et prévisible :
- **`packages/shared`** — le contrat : `ErrorCode` (énumération de tous les codes
d'erreur métier) et `ApiErrorResponse` (forme JSON de toute réponse d'erreur de
l'API). Ni l'API ni le web ne définissent leur propre liste de codes.
- **`apps/api` → `ErrorHandlerService`** — centralise la traduction de n'importe
quelle erreur levée (validation zod, `HttpError` métier, erreur inattendue) en
`{ status, body }` conforme au contrat. Le middleware d'erreur d'Express
(`app.ts`) ne fait qu'appeler ce service.
- **`apps/web` → `ErrorMessageService`** — centralise la traduction de chaque
`ErrorCode` en libellé affichable, avec un système de locale (`fr` aujourd'hui,
extensible). Les composants n'écrivent jamais de texte d'erreur en dur.
```mermaid
flowchart LR
subgraph API["apps/api"]
THROW["Route / service
throw new HttpError(status, code, message)"]
EHS["ErrorHandlerService.handle()"]
THROW --> EHS
end
EHS -->|"JSON: { code, message, details? }"| HTTP["Réponse HTTP"]
subgraph WEB["apps/web"]
CLIENT["ApiClient
lève ApiError(status, code, ...)"]
EMS["ErrorMessageService.getLabel(code)"]
UI["Composant (LoginPage, SignupPage...)"]
CLIENT --> EMS --> UI
end
HTTP --> CLIENT
SHARED[("packages/shared
ErrorCode, ApiErrorResponse")]
SHARED -. contrat .-> THROW
SHARED -. contrat .-> CLIENT
SHARED -. contrat .-> EMS
style SHARED fill:none,stroke:#888,stroke-width:1px
```
---
## Le contrat (`packages/shared/src/errors/error-codes.ts`)
```ts
enum ErrorCode {
VALIDATION_ERROR,
EMAIL_ALREADY_IN_USE,
INVALID_CREDENTIALS,
NOT_AUTHENTICATED,
NOT_FOUND,
INTERNAL_ERROR,
}
interface ApiErrorResponse {
code: ErrorCode;
message: string; // anglais, dev-facing — jamais affiché tel quel côté UI
details?: Record; // uniquement pour VALIDATION_ERROR
}
```
**Règle** : `message` est destiné aux logs/au débogage (toujours en anglais, jamais
localisé). Le texte affiché à l'utilisateur vient **toujours** de
`ErrorMessageService.getLabel(code)` côté client, jamais de `message` directement.
Pour ajouter un nouveau cas d'erreur :
1. Ajouter le membre dans `ErrorCode`.
2. Le lever via `new HttpError(status, ErrorCode.XXX, "message dev-facing")`.
3. Ajouter sa traduction dans `ErrorMessageService.LABELS.fr`.
---
## Côté API (`apps/api`)
- **`lib/http-error.ts`** — `HttpError` : erreur typée portant `status` (code HTTP)
et `code` (`ErrorCode`). C'est ce que lèvent les routes/services au lieu de
construire une réponse HTTP à la main.
- **`services/error-handler.service.ts`** — `ErrorHandlerService` : un seul point
qui sait transformer n'importe quelle erreur JS (`ZodError`, `HttpError`,
n'importe quoi d'autre) en `{ status, body }`. Le cas générique (`INTERNAL_ERROR`,
500) logue l'erreur côté serveur sans jamais exposer de détail interne au client.
- **`app.ts`** — le middleware d'erreur final d'Express ne fait qu'appeler
`errorHandlerService.handle(err)` et renvoyer le résultat ; aucune logique de
mapping n'y vit directement.
## Côté Web (`apps/web`)
- **`api/client.ts`** — `ApiClient` : lève `ApiError` (porteur de `status`, `code`,
`fieldErrors`) pour toute réponse non-2xx.
- **`services/error-message.service.ts`** — `ErrorMessageService` : associe chaque
`ErrorCode` à un libellé, par locale (`Record>`).
Une seule langue existe aujourd'hui (`fr`), mais la structure est prête pour en
ajouter une deuxième sans toucher aux composants.
- Les pages (`LoginPage`, `SignupPage`) attrapent `ApiError`, récupèrent `err.code`,
et appellent `errorMessageService.getLabel(err.code)` pour l'afficher — jamais
`err.message`.
## Validation côté formulaire (distincte du contrat d'erreurs API)
Les schémas zod partagés (`packages/shared/src/schemas/auth.ts`) portent déjà des
messages en français, utilisés pour la validation **avant** l'appel réseau (retour
instantané, aucun aller-retour serveur). C'est un mécanisme séparé du contrat
`ErrorCode` : ces messages ne quittent jamais le navigateur.