280 lines
8.6 KiB
Python
280 lines
8.6 KiB
Python
"""
|
|
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"
|
|
}
|
|
}
|
|
}
|
|
}
|