""" 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" } } } }