HTTP Conventions — REST SOTA¶
Vue d'ensemble¶
Ce document définit les conventions HTTP/REST production-ready pour les APIs construites avec arclith.
Toutes les fonctionnalités SOTA sont implémentées via des middlewares automatiques + patterns routers.
Portée Du Scaffold CRUD¶
arclith-cli expose-feature <feature> --via fastapi génère un contrat REST
fonctionnel et typé, mais volontairement minimal. Il déclare toujours
status_code et responses, traduit NotFound en 404, la validation en
422 et un conflit de version porté dans le payload en 409.
Le blueprint applicatif retourne cependant des Results typés pour create,
update et delete. La première projection conserve ces représentations :
PATCH et DELETE répondent donc 200 avec un body, sans inventer
If-Match, ETag, Location, Prefer ou une politique d'idempotence absents
des ports applicatifs. Les routes et presenters générés sont propriété du projet
et constituent le point de versionnement pour appliquer les conventions
production détaillées ci-dessous. Passer à 204, 412 ou aux headers SOTA
exige de modifier ensemble le contrat public, son mapper/presenter et ses tests.
La projection CLI est ainsi un baseline explicite, pas une affirmation que les politiques HTTP optionnelles sont déjà actives. Ajouter les capabilities HTTP requises et valider le contrat final avant mise en production.
Fonctionnalités clés :
- ✅ Headers Location/Content-Location (RFC 7231)
- ✅ ETag/If-Match optimistic locking (RFC 7232)
- ✅ Cache-Control par verbe/ressource (RFC 7234)
- ✅ Prefer: return=minimal|representation (RFC 7240)
- ✅ Link headers HATEOAS (RFC 8288)
- ✅ Idempotency-Key (draft-ietf-httpapi)
- ✅ 422 Unprocessable Entity vs 400
Status Codes (SOTA)¶
Tous les endpoints FastAPI DOIVENT déclarer explicitement leur status_code et responses.
POST — Create¶
| Endpoint | Status | Response Body | Headers | Cas |
|---|---|---|---|---|
POST /v1/resources |
201 Created | { "data": { "uuid": "..." } } |
Location, Link |
Succès création |
| 2xx original rejoué | { "data": { "uuid": "..." } } |
X-Idempotency-Replay: true |
Cache hit idempotency | |
| 400 Bad Request | { "detail": "..." } |
— | Erreur syntaxe (JSON malformé) | |
| 422 Unprocessable Entity | { "detail": [...] } |
— | Validation métier échouée | |
| 500 Internal Server Error | { "detail": "..." } |
— | Erreur serveur |
Convention SOTA :
- Body minimal : retourner
{ "data": { "uuid": "..." } }uniquement (pas l'objet complet) - Location header :
Location: /v1/resources/{uuid}(RFC 7231) - Link header :
Link: </v1/resources/{uuid}>; rel="self", ...(RFC 8288 - HATEOAS) - Prefer header : Si client envoie
Prefer: return=representation→ retourner objet complet (RFC 7240) - Idempotency-Key : Header optionnel (requis en prod e-commerce) → rejoue la réponse
2xxcachée (voirdocs/idempotency.md)
Exemple cURL :
GET — Read Single Resource¶
| Endpoint | Status | Response Body | Headers | Cas |
|---|---|---|---|---|
GET /v1/resources/{uuid} |
200 OK | { "data": { "uuid": "...", ... } } |
ETag, Cache-Control, Link |
Ressource trouvée |
| 304 Not Modified | ∅ | ETag |
If-None-Match match (cache valide) | |
| 404 Not Found | { "detail": "..." } |
— | Ressource inexistante ou soft-deleted |
Convention SOTA :
- ETag header :
ETag: "v{version}"(ex:"v1","v42") → RFC 7232 - Cache-Control :
Cache-Control: private, max-age=300(5 min) → RFC 7234 - Link header :
Link: </v1/resources/{uuid}>; rel="self", </v1/resources/{uuid}/duplicate>; rel="duplicate" - If-None-Match : Client peut envoyer
If-None-Match: "v1"→ 304 si inchangé
Exemple cURL :
GET — List / Collection¶
| Endpoint | Status | Response Body | Headers | Cas |
|---|---|---|---|---|
GET /v1/resources |
200 OK | { "data": [...], "pagination": {...} } |
X-Total-Count, Cache-Control |
Liste (vide ou non) |
| 400 Bad Request | { "detail": "..." } |
— | Paramètres query invalides |
Convention SOTA :
- Always 200 : Liste vide =
{ "data": [] }, jamais 404 - X-Total-Count : Header avec count total (utile pour pagination UI)
- Cache-Control :
Cache-Control: private, max-age=60(1 min, shorter TTL que single)
Exemple cURL :
| Bash | |
|---|---|
PUT — Replace¶
| Endpoint | Status | Response Body | Headers | Cas |
|---|---|---|---|---|
PUT /v1/resources/{uuid} |
204 No Content | ∅ | Content-Location, ETag |
Succès remplacement |
| 404 Not Found | { "detail": "..." } |
— | Ressource inexistante | |
| 412 Precondition Failed | { "detail": "..." } |
— | If-Match version mismatch | |
| 422 Unprocessable Entity | { "detail": [...] } |
— | Validation métier échouée |
Convention SOTA :
- If-Match requis : Header
If-Match: "v1"pour optimistic locking (RFC 7232) - 412 si mismatch : Version conflict → client doit re-fetch
- Content-Location :
Content-Location: /v1/resources/{uuid}(RFC 7231) - New ETag :
ETag: "v2"après update réussi
Exemple cURL :
PATCH — Partial Update¶
Identique à PUT (204, If-Match, Content-Location, ETag).
DELETE — Soft Delete¶
| Endpoint | Status | Response Body | Headers | Cas |
|---|---|---|---|---|
DELETE /v1/resources/{uuid} |
204 No Content | ∅ | Cache-Control: no-cache |
Succès soft-delete |
| 404 Not Found | { "detail": "..." } |
— | Ressource inexistante |
Convention SOTA :
- Idempotent : DELETE sur ressource déjà deleted = 204 (pas 404)
- Cache-Control :
no-cache, no-store(jamais cacher mutations)
POST — Duplicate¶
Identique à POST Create (201, Location, Link, UUID seul, Prefer header support).
Headers HTTP (SOTA)¶
Request Headers¶
| Header | Verbe | Requis | Exemple | Rôle |
|---|---|---|---|---|
| Idempotency-Key | POST | Recommandé (requis en prod) | 550e8400-e29b-41d4-a716-446655440000 |
Prévenir duplicatas (voir docs/idempotency.md) |
| If-Match | PUT/PATCH | Recommandé | "v1" |
Optimistic locking (version check) |
| If-None-Match | GET | Optionnel | "v1" |
Cache validation (304 si match) |
| Prefer | POST/PUT/PATCH | Optionnel | return=representation |
Demander full object au lieu de minimal |
| X-Request-ID | ALL | Optionnel | <uuid> |
Tracing distribué (propagé dans metadata.request_id) |
Response Headers¶
| Header | Verbe | Toujours présent | Exemple | Rôle |
|---|---|---|---|---|
| Location | POST | Oui (201) | /v1/ingredients/01951234... |
URL de la ressource créée (RFC 7231) |
| Content-Location | PUT/PATCH | Oui (204) | /v1/ingredients/01951234... |
URL de la ressource modifiée (RFC 7231) |
| ETag | GET/PUT/PATCH | Oui | "v1" |
Version entité pour optimistic locking (RFC 7232) |
| Cache-Control | ALL | Oui (middleware) | private, max-age=300 |
Directives cache (RFC 7234, voir docs/caching.md) |
| Link | GET/POST | Oui | </v1/ingredients/{uuid}>; rel="self" |
HATEOAS navigation (RFC 8288) |
| X-Total-Count | GET (list) | Oui | 42 |
Total items (pagination) |
| X-Process-Time-Ms | ALL | Oui (middleware) | 18 |
Durée traitement API |
| X-Idempotency-Replay | POST | Si cache hit | true |
Indique réponse rejouée depuis cache |
400 vs 422 vs 409¶
| Status | Cas | Exemple |
|---|---|---|
| 400 Bad Request | Erreur syntaxe/format | JSON malformé, header manquant |
| 422 Unprocessable Entity | Validation métier échouée | name vide, email invalide, contrainte check |
| 409 Conflict | Contrainte unicité violée | Doublon email unique |
| 412 Precondition Failed | Version mismatch (If-Match) | Optimistic lock failure |
FastAPI par défaut :
- Validation Pydantic → 422
- Exceptions levées → configurable
Convention arclith-reference :
| Python | |
|---|---|
Déclaration dans FastAPI¶
✅ SOTA — Déclaration complète¶
Handler signature :
❌ Mauvais — Ancien pattern¶
MCP Tools — Retours¶
Les MCP tools ne retournent pas de status codes HTTP — ils retournent des objets JSON ou None.
Convention MCP¶
| Opération | Retour | Erreur |
|---|---|---|
| Create | dict (objet complet) |
Exception levée |
| Read | dict \| None |
None si non trouvé (pas d'exception) |
| Update | dict (objet mis à jour) ou None |
Exception si non trouvé |
| Delete | None ou { deleted: true } |
Exception si non trouvé |
| List | list[dict] |
Toujours [] si vide, jamais None |
Règle : les MCP tools ne lèvent pas HTTPException. Ils retournent None ou une liste vide. Les erreurs métier génèrent des exceptions Python classiques (ValueError, RuntimeError).
Résumé SOTA¶
| Verbe | Action | Status | Body | Headers |
|---|---|---|---|---|
| POST | Create | 201 | { "uuid": "..." } |
Location, Link, [ETag si Prefer], Cache-Control |
| POST | Duplicate | 201 | { "uuid": "..." } |
Location, Link |
| GET | Read One | 200 | { "uuid": "...", ... } |
ETag, Cache-Control, Link |
| GET | List | 200 | [...] |
X-Total-Count, Cache-Control |
| PUT | Replace | 204 | ∅ | Content-Location, ETag, Cache-Control |
| PATCH | Partial | 204 | ∅ | Content-Location, ETag, Cache-Control |
| DELETE | Soft | 204 | ∅ | Cache-Control |
| DELETE | Purge | 200 | { "purged": N } |
— |
Principes SOTA¶
- UUID seul en POST : retourner
{ "data": { "uuid": "..." } }— client fait GET si besoin - Location header obligatoire : 201 Created →
Location: /v1/resources/{uuid} - ETag pour optimistic locking : remplace
versiondans payload PUT/PATCH - Cache-Control automatique : middleware injecte selon verbe/ressource
- Link headers HATEOAS : navigation API découvrable (RFC 8288)
- Prefer header flexible : client choisit minimal vs full representation
- 422 vs 400 : 422 pour validation métier, 400 pour syntaxe
- Idempotency-Key e-commerce : requis en prod pour POST (paiements)
Middleware Stack (ordre)¶
| Text Only | |
|---|---|
Références¶
- RFC 7231: HTTP Semantics (Location, Content-Location)
- RFC 7232: Conditional Requests (ETag, If-Match, If-None-Match)
- RFC 7234: Caching (Cache-Control)
- RFC 7240: Prefer Header (return=representation)
- RFC 8288: Web Linking (Link header, HATEOAS)
- RFC 9110: HTTP Semantics (Latest consolidated)
- Draft: Idempotency-Key
Guides complémentaires :
docs/idempotency.md— E-commerce production patternsdocs/caching.md— Stratégie cache HTTP multi-niveaux