use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProviderError {
#[error("Provider API error (status {status}): {message}")]
ApiError {
status: u16,
message: String,
},
#[error("Rate limit exceeded for provider {provider_id}")]
RateLimitExceeded {
provider_id: String,
},
#[error("Network error: {0}")]
NetworkError(String),
#[error("Invalid data from provider: {0}")]
InvalidData(String),
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Provider unavailable: {0}")]
ProviderUnavailable(String),
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Invalid configuration: {0}")]
InvalidConfiguration(String),
#[error("Operation timed out after {timeout_ms}ms")]
Timeout {
timeout_ms: u64,
},
#[error("Provider error: {0}")]
Other(String),
}
impl ProviderError {
pub fn api_error(status: u16, message: impl Into<String>) -> Self {
Self::ApiError {
status,
message: message.into(),
}
}
pub fn rate_limit(provider_id: impl Into<String>) -> Self {
Self::RateLimitExceeded {
provider_id: provider_id.into(),
}
}
pub fn network(message: impl Into<String>) -> Self {
Self::NetworkError(message.into())
}
pub fn invalid_data(message: impl Into<String>) -> Self {
Self::InvalidData(message.into())
}
pub fn not_found(resource: impl Into<String>) -> Self {
Self::NotFound(resource.into())
}
pub fn auth_failed(message: impl Into<String>) -> Self {
Self::AuthenticationFailed(message.into())
}
pub fn unavailable(message: impl Into<String>) -> Self {
Self::ProviderUnavailable(message.into())
}
pub fn timeout(timeout_ms: u64) -> Self {
Self::Timeout { timeout_ms }
}
pub fn is_retriable(&self) -> bool {
matches!(
self,
Self::NetworkError(_)
| Self::ProviderUnavailable(_)
| Self::Timeout { .. }
| Self::RateLimitExceeded { .. }
)
}
pub fn is_auth_error(&self) -> bool {
matches!(self, Self::AuthenticationFailed(_))
}
pub fn is_rate_limit(&self) -> bool {
matches!(self, Self::RateLimitExceeded { .. })
}
}
#[cfg(feature = "reqwest")]
impl From<reqwest::Error> for ProviderError {
fn from(err: reqwest::Error) -> Self {
Self::network(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_api_error() {
let err = ProviderError::api_error(404, "Not found");
assert!(matches!(err, ProviderError::ApiError { status: 404, .. }));
assert_eq!(err.to_string(), "Provider API error (status 404): Not found");
}
#[test]
fn test_rate_limit() {
let err = ProviderError::rate_limit("test-provider");
assert!(matches!(err, ProviderError::RateLimitExceeded { .. }));
assert!(err.is_rate_limit());
assert!(err.is_retriable());
}
#[test]
fn test_network_error() {
let err = ProviderError::network("Connection refused");
assert!(matches!(err, ProviderError::NetworkError(_)));
assert!(err.is_retriable());
}
#[test]
fn test_auth_error() {
let err = ProviderError::auth_failed("Invalid API key");
assert!(err.is_auth_error());
assert!(!err.is_retriable());
}
#[test]
fn test_timeout_error() {
let err = ProviderError::timeout(5000);
assert!(matches!(err, ProviderError::Timeout { timeout_ms: 5000 }));
assert!(err.is_retriable());
}
#[test]
fn test_not_found_not_retriable() {
let err = ProviderError::not_found("Product 123");
assert!(!err.is_retriable());
}
}