armature_http_client/
error.rs

1//! HTTP Client error types.
2
3use std::time::Duration;
4use thiserror::Error;
5
6/// Result type for HTTP client operations.
7pub type Result<T> = std::result::Result<T, HttpClientError>;
8
9/// HTTP client errors.
10#[derive(Debug, Error)]
11pub enum HttpClientError {
12    /// Request failed after all retries exhausted.
13    #[error("Request failed after {attempts} attempts: {message}")]
14    RetryExhausted {
15        /// Number of attempts made.
16        attempts: u32,
17        /// Last error message.
18        message: String,
19    },
20
21    /// Circuit breaker is open, rejecting requests.
22    #[error("Circuit breaker is open, request rejected")]
23    CircuitOpen,
24
25    /// Request timed out.
26    #[error("Request timed out after {0:?}")]
27    Timeout(Duration),
28
29    /// Connection error.
30    #[error("Connection error: {0}")]
31    Connection(String),
32
33    /// Invalid URL.
34    #[error("Invalid URL: {0}")]
35    InvalidUrl(String),
36
37    /// Request building error.
38    #[error("Failed to build request: {0}")]
39    RequestBuild(String),
40
41    /// Response error.
42    #[error("Response error: {status} - {message}")]
43    Response {
44        /// HTTP status code.
45        status: u16,
46        /// Error message.
47        message: String,
48    },
49
50    /// JSON serialization/deserialization error.
51    #[error("JSON error: {0}")]
52    Json(String),
53
54    /// Interceptor error.
55    #[error("Interceptor error: {0}")]
56    Interceptor(String),
57
58    /// Underlying HTTP client error.
59    #[error("HTTP error: {0}")]
60    Http(#[from] reqwest::Error),
61
62    /// URL parsing error.
63    #[error("URL parse error: {0}")]
64    UrlParse(#[from] url::ParseError),
65
66    /// I/O error.
67    #[error("I/O error: {0}")]
68    Io(#[from] std::io::Error),
69}
70
71impl HttpClientError {
72    /// Check if this error is retryable.
73    pub fn is_retryable(&self) -> bool {
74        match self {
75            Self::Timeout(_) => true,
76            Self::Connection(_) => true,
77            Self::Http(e) => e.is_timeout() || e.is_connect(),
78            Self::Response { status, .. } => {
79                // Retry on 5xx server errors and 429 rate limit
80                *status >= 500 || *status == 429
81            }
82            _ => false,
83        }
84    }
85
86    /// Check if this is a timeout error.
87    pub fn is_timeout(&self) -> bool {
88        matches!(self, Self::Timeout(_)) || matches!(self, Self::Http(e) if e.is_timeout())
89    }
90
91    /// Check if this is a connection error.
92    pub fn is_connection(&self) -> bool {
93        matches!(self, Self::Connection(_)) || matches!(self, Self::Http(e) if e.is_connect())
94    }
95
96    /// Get the HTTP status code if this is a response error.
97    pub fn status_code(&self) -> Option<u16> {
98        match self {
99            Self::Response { status, .. } => Some(*status),
100            Self::Http(e) => e.status().map(|s| s.as_u16()),
101            _ => None,
102        }
103    }
104}