Skip to main content

pleme_providers/
error.rs

1//! Provider error types
2
3use thiserror::Error;
4
5/// Errors that can occur during provider operations
6///
7/// These errors cover common failure modes across all provider types,
8/// including network errors, API errors, authentication failures, and rate limiting.
9#[derive(Error, Debug)]
10pub enum ProviderError {
11    /// Provider API returned an error response
12    #[error("Provider API error (status {status}): {message}")]
13    ApiError {
14        /// HTTP status code
15        status: u16,
16        /// Error message from provider
17        message: String,
18    },
19
20    /// Rate limit exceeded for this provider
21    #[error("Rate limit exceeded for provider {provider_id}")]
22    RateLimitExceeded {
23        /// Provider identifier
24        provider_id: String,
25    },
26
27    /// Network error occurred during communication with provider
28    #[error("Network error: {0}")]
29    NetworkError(String),
30
31    /// Invalid or malformed data received from provider
32    #[error("Invalid data from provider: {0}")]
33    InvalidData(String),
34
35    /// Requested resource not found
36    #[error("Resource not found: {0}")]
37    NotFound(String),
38
39    /// Authentication or authorization failed
40    #[error("Authentication failed: {0}")]
41    AuthenticationFailed(String),
42
43    /// Provider is temporarily unavailable
44    #[error("Provider unavailable: {0}")]
45    ProviderUnavailable(String),
46
47    /// Serialization/deserialization error
48    #[error("Serialization error: {0}")]
49    SerializationError(String),
50
51    /// Invalid configuration
52    #[error("Invalid configuration: {0}")]
53    InvalidConfiguration(String),
54
55    /// Operation timeout
56    #[error("Operation timed out after {timeout_ms}ms")]
57    Timeout {
58        /// Timeout duration in milliseconds
59        timeout_ms: u64,
60    },
61
62    /// General error with custom message
63    #[error("Provider error: {0}")]
64    Other(String),
65}
66
67impl ProviderError {
68    /// Create an API error
69    pub fn api_error(status: u16, message: impl Into<String>) -> Self {
70        Self::ApiError {
71            status,
72            message: message.into(),
73        }
74    }
75
76    /// Create a rate limit error
77    pub fn rate_limit(provider_id: impl Into<String>) -> Self {
78        Self::RateLimitExceeded {
79            provider_id: provider_id.into(),
80        }
81    }
82
83    /// Create a network error
84    pub fn network(message: impl Into<String>) -> Self {
85        Self::NetworkError(message.into())
86    }
87
88    /// Create an invalid data error
89    pub fn invalid_data(message: impl Into<String>) -> Self {
90        Self::InvalidData(message.into())
91    }
92
93    /// Create a not found error
94    pub fn not_found(resource: impl Into<String>) -> Self {
95        Self::NotFound(resource.into())
96    }
97
98    /// Create an authentication error
99    pub fn auth_failed(message: impl Into<String>) -> Self {
100        Self::AuthenticationFailed(message.into())
101    }
102
103    /// Create a provider unavailable error
104    pub fn unavailable(message: impl Into<String>) -> Self {
105        Self::ProviderUnavailable(message.into())
106    }
107
108    /// Create a timeout error
109    pub fn timeout(timeout_ms: u64) -> Self {
110        Self::Timeout { timeout_ms }
111    }
112
113    /// Check if this is a retriable error
114    ///
115    /// Returns `true` for errors that may succeed on retry (network errors, timeouts,
116    /// provider unavailable), and `false` for permanent errors (authentication, not found).
117    pub fn is_retriable(&self) -> bool {
118        matches!(
119            self,
120            Self::NetworkError(_)
121                | Self::ProviderUnavailable(_)
122                | Self::Timeout { .. }
123                | Self::RateLimitExceeded { .. }
124        )
125    }
126
127    /// Check if this is an authentication error
128    pub fn is_auth_error(&self) -> bool {
129        matches!(self, Self::AuthenticationFailed(_))
130    }
131
132    /// Check if this is a rate limit error
133    pub fn is_rate_limit(&self) -> bool {
134        matches!(self, Self::RateLimitExceeded { .. })
135    }
136}
137
138// Optional reqwest integration
139#[cfg(feature = "reqwest")]
140impl From<reqwest::Error> for ProviderError {
141    fn from(err: reqwest::Error) -> Self {
142        Self::network(err.to_string())
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_api_error() {
152        let err = ProviderError::api_error(404, "Not found");
153        assert!(matches!(err, ProviderError::ApiError { status: 404, .. }));
154        assert_eq!(err.to_string(), "Provider API error (status 404): Not found");
155    }
156
157    #[test]
158    fn test_rate_limit() {
159        let err = ProviderError::rate_limit("test-provider");
160        assert!(matches!(err, ProviderError::RateLimitExceeded { .. }));
161        assert!(err.is_rate_limit());
162        assert!(err.is_retriable());
163    }
164
165    #[test]
166    fn test_network_error() {
167        let err = ProviderError::network("Connection refused");
168        assert!(matches!(err, ProviderError::NetworkError(_)));
169        assert!(err.is_retriable());
170    }
171
172    #[test]
173    fn test_auth_error() {
174        let err = ProviderError::auth_failed("Invalid API key");
175        assert!(err.is_auth_error());
176        assert!(!err.is_retriable());
177    }
178
179    #[test]
180    fn test_timeout_error() {
181        let err = ProviderError::timeout(5000);
182        assert!(matches!(err, ProviderError::Timeout { timeout_ms: 5000 }));
183        assert!(err.is_retriable());
184    }
185
186    #[test]
187    fn test_not_found_not_retriable() {
188        let err = ProviderError::not_found("Product 123");
189        assert!(!err.is_retriable());
190    }
191}