from __future__ import annotations import logging logger = logging.getLogger(__name__) class MattermostError(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"Mattermost API error {status_code}: {message}") class MattermostNetworkError(MattermostError): pass class MattermostAuthError(MattermostError): pass class MattermostRateLimitError(MattermostError): 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_mattermost_error(response) -> MattermostError: body = None try: body = response.json() message = body.get("message", "") or response.text except Exception: message = response.text status_code = response.status_code if status_code == 401: return MattermostAuthError(status_code, message) if status_code == 429: retry_after_ms = _parse_retry_after(response) or 5000 return MattermostRateLimitError(status_code, message, retry_after_ms) if status_code >= 500: return MattermostNetworkError(status_code, message) return MattermostError(status_code, message, body) def _parse_retry_after(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, MattermostAuthError): return "auth", False if isinstance(error, MattermostRateLimitError): return "rate_limit", True if isinstance(error, MattermostNetworkError): return "network", True if isinstance(error, MattermostError): if error.status_code == 403: return "forbidden", False if error.status_code == 404: return "not_found", False return "api_error", is_retryable_error(error.status_code) return "unknown", True