feat: revue de code, doc CODE_REVIEW, forfaits 2026, traduction LLM, providers avec modèle
Made-with: Cursor
This commit is contained in:
279
schemas/errors.py
Normal file
279
schemas/errors.py
Normal file
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
Error response models for API documentation
|
||||
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
|
||||
class ErrorCode(str, Enum):
|
||||
"""All error codes used in the API"""
|
||||
|
||||
# Client errors (4xx)
|
||||
INVALID_FORMAT = "INVALID_FORMAT"
|
||||
CORRUPTED_FILE = "CORRUPTED_FILE"
|
||||
FILE_TOO_LARGE = "FILE_TOO_LARGE"
|
||||
URL_DOWNLOAD_FAILED = "URL_DOWNLOAD_FAILED"
|
||||
URL_UNREACHABLE = "URL_UNREACHABLE"
|
||||
QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
|
||||
UNAUTHORIZED = "UNAUTHORIZED"
|
||||
FORBIDDEN = "FORBIDDEN"
|
||||
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
|
||||
USER_NOT_FOUND = "USER_NOT_FOUND"
|
||||
EMAIL_EXISTS = "EMAIL_EXISTS"
|
||||
INVALID_EMAIL = "INVALID_EMAIL"
|
||||
TOKEN_EXPIRED = "TOKEN_EXPIRED"
|
||||
TOKEN_MISSING = "TOKEN_MISSING"
|
||||
TOKEN_INVALID = "TOKEN_INVALID"
|
||||
MISSING_API_KEY = "MISSING_API_KEY"
|
||||
INVALID_API_KEY = "INVALID_API_KEY"
|
||||
API_KEY_REVOKED = "API_KEY_REVOKED"
|
||||
API_KEY_NOT_FOUND = "API_KEY_NOT_FOUND"
|
||||
API_KEY_LIMIT_REACHED = "API_KEY_LIMIT_REACHED"
|
||||
PRO_FEATURE_REQUIRED = "PRO_FEATURE_REQUIRED"
|
||||
GLOSSARY_NOT_FOUND = "GLOSSARY_NOT_FOUND"
|
||||
PROMPT_NOT_FOUND = "PROMPT_NOT_FOUND"
|
||||
INVALID_WEBHOOK_URL = "INVALID_WEBHOOK_URL"
|
||||
FILE_EXPIRED = "FILE_EXPIRED"
|
||||
NOT_READY = "NOT_READY"
|
||||
NOT_FOUND = "NOT_FOUND"
|
||||
INVALID_REQUEST = "INVALID_REQUEST"
|
||||
INVALID_JOB_ID = "INVALID_JOB_ID"
|
||||
ACCESS_DENIED = "ACCESS_DENIED"
|
||||
|
||||
# Provider errors (5xx but not 500)
|
||||
PROVIDER_UNAVAILABLE = "PROVIDER_UNAVAILABLE"
|
||||
PROVIDER_RATE_LIMITED = "PROVIDER_RATE_LIMITED"
|
||||
ALL_PROVIDERS_FAILED = "ALL_PROVIDERS_FAILED"
|
||||
WEBHOOK_FAILED = "WEBHOOK_FAILED"
|
||||
|
||||
# System errors (5xx)
|
||||
INTERNAL_ERROR = "INTERNAL_ERROR"
|
||||
AUTH_HASHING_UNAVAILABLE = "AUTH_HASHING_UNAVAILABLE"
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error response format"""
|
||||
|
||||
error: ErrorCode = Field(
|
||||
...,
|
||||
description="Code d'erreur standardisé",
|
||||
example="INVALID_FORMAT"
|
||||
)
|
||||
message: str = Field(
|
||||
...,
|
||||
description="Message d'erreur lisible en français",
|
||||
example="Format de fichier non supporté. Formats acceptés: .xlsx, .docx, .pptx"
|
||||
)
|
||||
details: Optional[Dict[str, Any]] = Field(
|
||||
None,
|
||||
description="Détails supplémentaires sur l'erreur"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"error": "INVALID_FORMAT",
|
||||
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
|
||||
"details": {
|
||||
"accepted_formats": [".xlsx", ".docx", ".pptx"],
|
||||
"detected_format": ".pdf"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Pre-defined error examples for OpenAPI documentation
|
||||
ERROR_EXAMPLES = {
|
||||
"INVALID_FORMAT": {
|
||||
"summary": "Format de fichier non supporté",
|
||||
"value": {
|
||||
"error": "INVALID_FORMAT",
|
||||
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
|
||||
"details": {
|
||||
"accepted_formats": [".xlsx", ".docx", ".pptx"],
|
||||
"detected_format": ".pdf"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CORRUPTED_FILE": {
|
||||
"summary": "Fichier corrompu",
|
||||
"value": {
|
||||
"error": "CORRUPTED_FILE",
|
||||
"message": "Le fichier n'est pas un document Office valide ou est corrompu.",
|
||||
"details": {
|
||||
"reason": "Invalid magic bytes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FILE_TOO_LARGE": {
|
||||
"summary": "Fichier trop volumineux",
|
||||
"value": {
|
||||
"error": "FILE_TOO_LARGE",
|
||||
"message": "Le fichier dépasse la limite de 50 MB.",
|
||||
"details": {
|
||||
"max_size_mb": 50,
|
||||
"actual_size_mb": 65.3
|
||||
}
|
||||
}
|
||||
},
|
||||
"QUOTA_EXCEEDED": {
|
||||
"summary": "Limite quotidienne atteinte",
|
||||
"value": {
|
||||
"error": "QUOTA_EXCEEDED",
|
||||
"message": "Limite quotidienne de 5 traductions atteinte. Réessayez après minuit UTC.",
|
||||
"details": {
|
||||
"current_usage": 5,
|
||||
"limit": 5,
|
||||
"tier": "free",
|
||||
"reset_at": "2024-01-16T00:00:00Z"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UNAUTHORIZED": {
|
||||
"summary": "Authentification requise",
|
||||
"value": {
|
||||
"error": "UNAUTHORIZED",
|
||||
"message": "Authentification requise.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"FORBIDDEN": {
|
||||
"summary": "Accès interdit",
|
||||
"value": {
|
||||
"error": "FORBIDDEN",
|
||||
"message": "Vous n'avez pas accès à cette ressource.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"INVALID_CREDENTIALS": {
|
||||
"summary": "Identifiants invalides",
|
||||
"value": {
|
||||
"error": "INVALID_CREDENTIALS",
|
||||
"message": "Email ou mot de passe incorrect.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"EMAIL_EXISTS": {
|
||||
"summary": "Email déjà utilisé",
|
||||
"value": {
|
||||
"error": "EMAIL_EXISTS",
|
||||
"message": "Un compte existe déjà avec cette adresse email.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"TOKEN_EXPIRED": {
|
||||
"summary": "Token expiré",
|
||||
"value": {
|
||||
"error": "TOKEN_EXPIRED",
|
||||
"message": "Token invalide ou expiré.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"PRO_FEATURE_REQUIRED": {
|
||||
"summary": "Fonctionnalité Pro requise",
|
||||
"value": {
|
||||
"error": "PRO_FEATURE_REQUIRED",
|
||||
"message": "Cette fonctionnalité nécessite un abonnement Pro.",
|
||||
"details": {
|
||||
"feature": "llm_translation",
|
||||
"current_tier": "free",
|
||||
"required_tier": "pro"
|
||||
}
|
||||
}
|
||||
},
|
||||
"API_KEY_NOT_FOUND": {
|
||||
"summary": "Clé API non trouvée",
|
||||
"value": {
|
||||
"error": "API_KEY_NOT_FOUND",
|
||||
"message": "Clé API non trouvée, n'appartient pas à l'utilisateur ou déjà révoquée.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"FILE_EXPIRED": {
|
||||
"summary": "Fichier expiré",
|
||||
"value": {
|
||||
"error": "FILE_EXPIRED",
|
||||
"message": "Le fichier traduit n'est plus disponible ou a expiré.",
|
||||
"details": {
|
||||
"job_id": "tr_abc123",
|
||||
"status": "not_found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NOT_READY": {
|
||||
"summary": "Traduction en cours",
|
||||
"value": {
|
||||
"error": "NOT_READY",
|
||||
"message": "La traduction est encore en cours.",
|
||||
"details": {
|
||||
"job_id": "tr_abc123",
|
||||
"status": "processing",
|
||||
"progress_percent": 45
|
||||
}
|
||||
}
|
||||
},
|
||||
"NOT_FOUND": {
|
||||
"summary": "Ressource non trouvée",
|
||||
"value": {
|
||||
"error": "NOT_FOUND",
|
||||
"message": "Ressource non trouvée.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"INTERNAL_ERROR": {
|
||||
"summary": "Erreur interne",
|
||||
"value": {
|
||||
"error": "INTERNAL_ERROR",
|
||||
"message": "Une erreur interne est survenue. Veuillez réessayer.",
|
||||
"details": None
|
||||
}
|
||||
},
|
||||
"INVALID_WEBHOOK_URL": {
|
||||
"summary": "URL webhook invalide",
|
||||
"value": {
|
||||
"error": "INVALID_WEBHOOK_URL",
|
||||
"message": "L'URL du webhook doit être une URL HTTP/HTTPS valide.",
|
||||
"details": {
|
||||
"field": "webhook_url",
|
||||
"allowed_schemes": ["http", "https"],
|
||||
"hint": "L'URL doit commencer par http:// ou https://"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WEBHOOK_LOCALHOST_BLOCKED": {
|
||||
"summary": "Localhost non autorisé",
|
||||
"value": {
|
||||
"error": "INVALID_WEBHOOK_URL",
|
||||
"message": "Les URLs localhost ne sont pas autorisées.",
|
||||
"details": {
|
||||
"field": "webhook_url",
|
||||
"reason": "localhost_blocked"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WEBHOOK_PRIVATE_IP_BLOCKED": {
|
||||
"summary": "IP privée non autorisée",
|
||||
"value": {
|
||||
"error": "INVALID_WEBHOOK_URL",
|
||||
"message": "Les adresses IP privées ne sont pas autorisées.",
|
||||
"details": {
|
||||
"field": "webhook_url",
|
||||
"reason": "private_ip_blocked"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WEBHOOK_CREDENTIALS_IN_URL": {
|
||||
"summary": "Credentials dans l'URL",
|
||||
"value": {
|
||||
"error": "INVALID_WEBHOOK_URL",
|
||||
"message": "L'URL ne doit pas contenir d'identifiants (credentials).",
|
||||
"details": {
|
||||
"field": "webhook_url",
|
||||
"reason": "credentials_in_url"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user