La Cafête utilise un format d'erreur standardisé pour toutes les réponses d'erreur.
{
"error_code": "CARD_NOT_FOUND",
"message": "Carte introuvable"
}
L'énumération DomainError utilise #[serde(tag = "error_code", content = "message")] qui produit une réponse plate avec deux champs : error_code (le nom du variant) et message (le contenu textuel). Un champ optionnel request_id peut être ajouté pour le tracing.
DomainError — Variants
| Variant | HTTP | Usage |
|---|
Validation | 422 | Données invalides, règle métier non respectée |
NotFound | 404 | Ressource introuvable |
Conflict | 409 | Conflit de données (doublon, idempotence, etc.) |
InsufficientBalance | 422 | Solde insuffisant pour le débit demandé |
CardNotActive | 422 | Carte non active (désactivée, perdue) |
Forbidden | 403 | Permissions insuffisantes |
Unauthorized | 401 | Authentification requise ou invalide |
RoleNotAllowed | 403 | Le rôle de l'utilisateur n'est pas autorisé |
Persistence | 500 | Erreur base de données |
ExternalService | 502 | Erreur fournisseur externe (SendGrid, S3, etc.) |
Configuration | 500 | Configuration manquante ou invalide |
Internal | 500 | Erreur interne inattendue |
Codes d'erreur détaillés (ErrorCode)
Les handlers API utilisent l'énumération ErrorCode qui fournit des codes machine plus précis :
Authentification / Authorization
| Code | HTTP | Signification |
|---|
AUTH_INVALID_CREDENTIALS | 401 | Email ou mot de passe incorrect |
AUTH_TOKEN_EXPIRED | 401 | Session expirée |
AUTH_TOKEN_INVALID | 401 | Token malformé |
AUTH_PASSWORD_CHANGE_REQUIRED | 403 | Changement de mot de passe requis |
AUTH_ACCOUNT_DISABLED | 403 | Compte désactivé |
AUTH_MISSING_TENANT | 400 | En-tête X-Tenant-Id manquant |
AUTH_OTP_REQUIRED | 400 | Code OTP requis |
AUTH_OTP_INVALID | 422 | Code OTP invalide |
Cartes
| Code | HTTP | Signification |
|---|
CARD_NOT_FOUND | 404 | Carte introuvable |
CARD_NOT_ACTIVE | 422 | Carte non active |
CARD_LOST | 422 | Carte signalée perdue |
CARD_ALREADY_ASSIGNED | 409 | Carte déjà attribuée |
CARD_NO_HOLDER | 422 | Carte sans titulaire |
CARD_BALANCE_INSUFFICIENT | 422 | Solde insuffisant |
CARD_DAILY_LIMIT_REACHED | 422 | Plafond quotidien atteint |
CARD_CODE_GENERATION_FAILED | 422 | Échec génération code |
Produits / Stock
| Code | HTTP | Signification |
|---|
PRODUCT_NOT_FOUND | 404 | Produit introuvable |
PRODUCT_INACTIVE | 422 | Produit désactivé |
PRODUCT_STOCK_INSUFFICIENT | 422 | Stock insuffisant |
PRODUCT_UNAVAILABLE_DAY | 422 | Produit non disponible ce jour |
STOCK_NEGATIVE_NOT_ALLOWED | 422 | Stock négatif non autorisé |
STOCK_TRANSFER_INSUFFICIENT | 422 | Stock insuffisant pour transfert |
| Code | HTTP | Signification |
|---|
MENU_NOT_FOUND | 404 | Menu introuvable |
MENU_ALREADY_EXISTS | 409 | Menu déjà existant |
MENU_DATE_MISMATCH | 422 | Incohérence de date |
MENU_EMPTY | 422 | Menu vide |
MENU_PRODUCT_NOT_IN_MENU | 422 | Produit pas dans le menu |
Réservations
| Code | HTTP | Signification |
|---|
RESERVATION_CUTOFF_PASSED | 422 | Délai de réservation dépassé |
RESERVATION_TOO_FAR | 422 | Date trop lointaine |
RESERVATION_PERIOD_TOO_LONG | 422 | Période trop longue |
RESERVATION_EMPTY | 422 | Réservation vide |
RESERVATION_DAYS_EMPTY | 422 | Aucun jour sélectionné |
RESERVATION_MENU_NOT_FOUND | 422 | Menu de réservation introuvable |
Caisses
| Code | HTTP | Signification |
|---|
REGISTER_ALREADY_OPEN | 422 | Caisse déjà ouverte |
REGISTER_NOT_FOUND | 404 | Caisse introuvable |
REGISTER_NOT_OPEN | 422 | Caisse non ouverte |
Transactions
| Code | HTTP | Signification |
|---|
TRANSACTION_NOT_REFUNDABLE | 422 | Transaction non remboursable |
TRANSACTION_IDEMPOTENCY_CONFLICT | 422 | Conflit de clé d'idempotence |
Organisations
| Code | HTTP | Signification |
|---|
ORG_NOT_FOUND | 404 | Organisation introuvable |
ORG_SLUG_TAKEN | 409 | Slug déjà utilisé |
Statistiques
| Code | HTTP | Signification |
|---|
STATS_DATE_RANGE_INVALID | 422 | Plage de dates invalide |
Codes génériques
| Code | HTTP | Signification |
|---|
NOT_FOUND | 404 | Ressource introuvable |
UNAUTHORIZED | 401 | Authentification requise |
FORBIDDEN | 403 | Permission refusée |
VALIDATION_ERROR | 422 | Données invalides |
CONFLICT | 409 | Conflit |
INTERNAL_ERROR | 500 | Erreur interne |
EXTERNAL_SERVICE_ERROR | 502 | Erreur fournisseur externe |
CONFIGURATION_ERROR | 500 | Erreur de configuration |
ROLE_NOT_ALLOWED | 403 | Rôle non autorisé |
Erreurs PostgreSQL
Codes d'erreur customs (PG → Rust)
Les procédures stockées PL/pgSQL (fn_apply_card_transaction) lèvent des codes d'erreur personnalisés interceptés par la couche PersistenceError :
| Code PG | Mapping | Signification |
|---|
CRD01 | PersistenceError::NotFound | CardNotFound — Carte introuvable |
CRD02 | PersistenceError::Conflict | CardNotActive — Carte non active |
CRD03 | PersistenceError::Conflict | InsufficientBalance — Solde insuffisant |
23505 | PersistenceError::Conflict | UNIQUE_VIOLATION — Doublon sur clé unique |
Les violations de contraintes PG sont interceptées par le handler SQLx et converties en erreurs applicatives.
Gestion côté client
Retry conditionnel
| HTTP | Retry ? | Stratégie |
|---|
500 | Oui | Exponential backoff (1s, 2s, 4s, 8s, max 30s) |
502 | Oui | Exponential backoff (1s, 2s, 4s, 8s, max 30s) |
429 | Oui | Respecter Retry-After header |
413 | Non | Réduire la taille du fichier |
400 | Non | Corriger la requête |
401 | Non | Rafraîchir le token |
403 | Non | Vérifier les permissions |
404 | Non | Vérifier l'identifiant de la ressource |
409 | Non | Vérifier les données avant resubmit |
415 | Non | Utiliser un type de fichier supporté |
422 | Non | Corriger la requête |
Idempotence
Pour les opérations financières, utilisez toujours un Idempotency-Key :
POST /api/v1/transactions/recharge
Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Content-Type: application/json
Authorization: Bearer ...
{
"card_id": "550e8400-e29b-41d4-a716-446655440000",
"amount_cents": 5000,
"method": "cash",
"site_id": "uuid"
}
Si vous recevez une erreur 5xx, vous pouvez réessayer avec la même clé d'idempotence — le système garantit que la transaction ne sera pas appliquée deux fois.
Une clé d'idempotence différente sur la même opération entraînera un IDEMPOTENCY_CONFLICT (409).
Rate Limiting
Le rate limiting est appliqué sur les endpoints d'authentification :
| Endpoint | Limite | Fenêtre |
|---|
POST /auth/login | 5 req | 1 minute |
POST /auth/cardholder-login | 3 req | 1 minute |
POST /auth/resolve-code | 10 req | 1 minute |
POST /auth/refresh | 30 req | 1 minute |
En cas de dépassement : 429 Too Many Requests avec header Retry-After.
Logs et debugging
Les erreurs sont tracées avec tracing :
tracing::error!(error = %e, card_id = %card_id, "Transaction failed");
tracing::warn!(org_id = %org_id, "Rate limit approaching");
tracing::info!(user_id = %user_id, action = "login", "User logged in");
Pour activer les logs détaillés en développement :
RUST_LOG=debug cargo run -p caffette-api