Aller au contenu

4. Erreurs métier comme code

Règle. Toute erreur métier remonte comme DomainError(code, message, details). Le code est 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)},
)
  1. code : identifiant stable, namespace par domaine. Ex: mail.invalid_recipient, audit.not_found, search.invalid_query.
  2. message : phrase humaine pour les logs / debug. Le client peut l'afficher en repli mais ne devrait pas s'en servir comme contrat.
  3. 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.