4. Erreurs métier comme code¶
Règle. Toute erreur métier remonte comme
DomainError(code, message, details). Lecodeest stable (ex:mail.invalid_recipient) et consommable par le client.
Pourquoi¶
raise ValueError("recipient invalid") est inutilisable côté client :
- Le client ne peut pas se caler sur la chaîne sans risque (elle change).
- Le mappage HTTP est arbitraire (400 ? 422 ? 500 ?).
- Aucun lieu central pour documenter les erreurs possibles.
Le contrat¶
from mic.model.errors import DomainError, TransientError
# Erreur métier "permanente" → 400 Bad Request
raise DomainError(
code="mail.invalid_recipient", # (1)
message="recipient must be a non-empty email address", # (2)
details={"received_recipient": value}, # (3)
)
# Erreur transitoire (DB down, upstream timeout) → 503 Service Unavailable
raise TransientError(
code="transient.database_unavailable",
message="Database is not reachable",
details={"database_url": _redact(self._database_url)},
)
code: identifiant stable, namespace par domaine. Ex:mail.invalid_recipient,audit.not_found,search.invalid_query.message: phrase humaine pour les logs / debug. Le client peut l'afficher en repli mais ne devrait pas s'en servir comme contrat.details: dict structuré avec les infos contextualisées.
Le mappage HTTP canonique¶
Les adaptateurs mappent automatiquement :
| Exception | HTTP | Corps |
|---|---|---|
DomainError(...) |
400 | {"success": false, "code": "...", "message": "...", "details": {...}} |
TransientError(...) |
503 | idem |
pydantic.ValidationError |
400 | {"success": false, "code": "http.validation_error", ...} |
Exception (autre) |
500 | (laissé au framework) |
Le client se cale sur code :
const r = await fetch("/api/v1/messages", { method: "POST", body: ... });
const data = await r.json();
if (!data.success) {
if (data.code === "mail.invalid_recipient") {
showFieldError("recipient", "Adresse email invalide");
} else {
showGenericError(data.message);
}
}
Documenter les erreurs d'un module¶
Convention : un commentaire en tête du module ou un tableau dans la docstring :
"""Controllers du module ``mail`` (use cases).
Codes d'erreur émis :
- ``mail.invalid_recipient`` : recipient vide ou sans @
- ``mail.invalid_subject`` : subject vide
- ``mail.invalid_max_attempts``: max_attempts < 1
- ``mail.not_found`` : MailJob.id inconnu
"""
Pour un catalogue centralisé multi-services, voir le
CATALOG_ERREURS.md côté sentinel.
La règle d'or¶
Une erreur métier = un code stable. Si vous changez le code, c'est un breaking change pour le client. Augmentez la version majeure.