use thiserror::Error;
pub type Result<T> = std::result::Result<T, NetworkError>;
#[derive(Error, Debug)]
pub enum NetworkError {
#[error("HTTP error: {status} - {message}")]
Http { status: u16, message: String },
#[error("Connection failed: {0}")]
Connection(String),
#[error("Request timeout after {0}ms")]
Timeout(u64),
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[error("Fragment not found: {0}")]
NotFound(String),
#[error("Checksum mismatch for {fragment_id}: expected {expected}, got {actual}")]
ChecksumMismatch {
fragment_id: String,
expected: String,
actual: String,
},
#[error("Cache error: {0}")]
Cache(String),
#[error("All retries exhausted: {0}")]
RetriesExhausted(String),
#[error("Rate limited, retry after {retry_after_ms}ms")]
RateLimited { retry_after_ms: u64 },
#[error("CDN configuration error: {0}")]
Configuration(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Request cancelled")]
Cancelled,
}
impl NetworkError {
pub fn is_retryable(&self) -> bool {
match self {
NetworkError::Connection(_) => true,
NetworkError::Timeout(_) => true,
NetworkError::RateLimited { .. } => true,
NetworkError::Http { status, .. } => *status >= 500,
_ => false,
}
}
pub fn retry_after(&self) -> Option<std::time::Duration> {
if let NetworkError::RateLimited { retry_after_ms } = self {
Some(std::time::Duration::from_millis(*retry_after_ms))
} else {
None
}
}
}
impl From<reqwest::Error> for NetworkError {
fn from(e: reqwest::Error) -> Self {
if e.is_timeout() {
NetworkError::Timeout(30000)
} else if e.is_connect() {
NetworkError::Connection(e.to_string())
} else if let Some(status) = e.status() {
NetworkError::Http {
status: status.as_u16(),
message: e.to_string(),
}
} else {
NetworkError::Connection(e.to_string())
}
}
}
impl From<url::ParseError> for NetworkError {
fn from(e: url::ParseError) -> Self {
NetworkError::InvalidUrl(e.to_string())
}
}