from __future__ import annotations import logging import httpx logger = logging.getLogger(__name__) class RocketChatError(Exception): def __init__(self, status_code: int, message: str, api_error: dict | None = None) -> None: self.status_code = status_code self.message = message self.api_error = api_error super().__init__(f"Rocket.Chat API error {status_code}: {message}") class RocketChatNetworkError(RocketChatError): pass class RocketChatAuthError(RocketChatError): pass class RocketChatRateLimitError(RocketChatError): def __init__(self, status_code: int, message: str, retry_after_ms: float = 5000) -> None: super().__init__(status_code, message) self.retry_after_ms = retry_after_ms def parse_rocketchat_error(response: httpx.Response) -> RocketChatError: body = None try: body = response.json() error_msg = body.get("error", body.get("message", response.text)) except Exception: error_msg = response.text status_code = response.status_code if status_code in (401, 403): return RocketChatAuthError(status_code, error_msg) if status_code == 429: retry_after_ms = _parse_retry_after(response) or 5000 return RocketChatRateLimitError(status_code, error_msg, retry_after_ms) if status_code >= 500: return RocketChatNetworkError(status_code, error_msg) return RocketChatError(status_code, error_msg, body) def _parse_retry_after(response: httpx.Response) -> float | None: header = response.headers.get("Retry-After", "") if not header: return None try: return float(header) * 1000 except ValueError: return None def is_retryable_error(status_code: int) -> bool: return status_code == 429 or status_code >= 500 def classify_error( error: Exception, ) -> tuple[str, bool]: if isinstance(error, RocketChatAuthError): return "auth", False if isinstance(error, RocketChatRateLimitError): return "rate_limit", True if isinstance(error, RocketChatNetworkError): return "network", True if isinstance(error, RocketChatError): if error.status_code == 404: return "not_found", False return "api_error", is_retryable_error(error.status_code) return "unknown", True