1use thiserror::Error;
4
5#[derive(Error, Debug)]
10pub enum ProviderError {
11 #[error("Provider API error (status {status}): {message}")]
13 ApiError {
14 status: u16,
16 message: String,
18 },
19
20 #[error("Rate limit exceeded for provider {provider_id}")]
22 RateLimitExceeded {
23 provider_id: String,
25 },
26
27 #[error("Network error: {0}")]
29 NetworkError(String),
30
31 #[error("Invalid data from provider: {0}")]
33 InvalidData(String),
34
35 #[error("Resource not found: {0}")]
37 NotFound(String),
38
39 #[error("Authentication failed: {0}")]
41 AuthenticationFailed(String),
42
43 #[error("Provider unavailable: {0}")]
45 ProviderUnavailable(String),
46
47 #[error("Serialization error: {0}")]
49 SerializationError(String),
50
51 #[error("Invalid configuration: {0}")]
53 InvalidConfiguration(String),
54
55 #[error("Operation timed out after {timeout_ms}ms")]
57 Timeout {
58 timeout_ms: u64,
60 },
61
62 #[error("Provider error: {0}")]
64 Other(String),
65}
66
67impl ProviderError {
68 pub fn api_error(status: u16, message: impl Into<String>) -> Self {
70 Self::ApiError {
71 status,
72 message: message.into(),
73 }
74 }
75
76 pub fn rate_limit(provider_id: impl Into<String>) -> Self {
78 Self::RateLimitExceeded {
79 provider_id: provider_id.into(),
80 }
81 }
82
83 pub fn network(message: impl Into<String>) -> Self {
85 Self::NetworkError(message.into())
86 }
87
88 pub fn invalid_data(message: impl Into<String>) -> Self {
90 Self::InvalidData(message.into())
91 }
92
93 pub fn not_found(resource: impl Into<String>) -> Self {
95 Self::NotFound(resource.into())
96 }
97
98 pub fn auth_failed(message: impl Into<String>) -> Self {
100 Self::AuthenticationFailed(message.into())
101 }
102
103 pub fn unavailable(message: impl Into<String>) -> Self {
105 Self::ProviderUnavailable(message.into())
106 }
107
108 pub fn timeout(timeout_ms: u64) -> Self {
110 Self::Timeout { timeout_ms }
111 }
112
113 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 pub fn is_auth_error(&self) -> bool {
129 matches!(self, Self::AuthenticationFailed(_))
130 }
131
132 pub fn is_rate_limit(&self) -> bool {
134 matches!(self, Self::RateLimitExceeded { .. })
135 }
136}
137
138#[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}