use reqwest::StatusCode; use std::time::Duration; use thiserror::Error; #[derive(Error, Debug, Clone, PartialEq)] pub enum ProviderError { #[error("Authentication error: {0}")] Authentication(String), #[error("Context length exceeded: {0}")] ContextLengthExceeded(String), #[error("Rate limit exceeded: {details}")] RateLimitExceeded { details: String, retry_delay: Option, }, #[error("Server error: {0}")] ServerError(String), #[error("Network error: {0}")] NetworkError(String), #[error("Request failed: {0}")] RequestFailed(String), #[error("Execution error: {0}")] ExecutionError(String), #[error("Usage data error: {0}")] UsageError(String), #[error("Unsupported operation: {0}")] NotImplemented(String), #[error("Credits exhausted: {details}")] CreditsExhausted { details: String, top_up_url: Option, }, } impl ProviderError { pub fn telemetry_type(&self) -> &'static str { match self { ProviderError::Authentication(_) => "auth", ProviderError::ContextLengthExceeded(_) => "context_length", ProviderError::RateLimitExceeded { .. } => "rate_limit", ProviderError::ServerError(_) => "server", ProviderError::NetworkError(_) => "network", ProviderError::RequestFailed(_) => "request", ProviderError::ExecutionError(_) => "execution", ProviderError::UsageError(_) => "usage", ProviderError::NotImplemented(_) => "not_implemented", ProviderError::CreditsExhausted { .. } => "credits_exhausted", } } } fn is_network_error(err: &reqwest::Error) -> bool { err.is_connect() || err.is_timeout() || (err.status().is_none() && err.is_request()) } fn provider_error_from_reqwest(error: &reqwest::Error) -> ProviderError { if is_network_error(error) { let msg = if error.is_timeout() { "Request timed out — check your network connection and try again.".to_string() } else if error.is_connect() { if let Some(url) = error.url() { if let Some(host) = url.host_str() { let port_info = url.port().map(|p| format!(":{}", p)).unwrap_or_default(); format!( "Could not connect to {}{} — check your network connection and try again.", host, port_info ) } else { "Could not connect to the provider — check your network connection and try again.".to_string() } } else { "Could not connect to the provider — check your network connection and try again." .to_string() } } else { "Network error — check your network connection and try again.".to_string() }; return ProviderError::NetworkError(msg); } let mut details = vec![]; if let Some(status) = error.status() { details.push(format!("status: {}", status)); } let msg = if details.is_empty() { error.to_string() } else { format!("{} ({})", error, details.join(", ")) }; ProviderError::RequestFailed(msg) } impl From for ProviderError { fn from(error: anyhow::Error) -> Self { if let Some(reqwest_err) = error.downcast_ref::() { return provider_error_from_reqwest(reqwest_err); } ProviderError::ExecutionError(error.to_string()) } } impl From for ProviderError { fn from(error: reqwest::Error) -> Self { provider_error_from_reqwest(&error) } } #[derive(Debug)] pub enum GoogleErrorCode { BadRequest = 400, Unauthorized = 401, Forbidden = 403, NotFound = 404, TooManyRequests = 429, InternalServerError = 500, ServiceUnavailable = 503, } impl GoogleErrorCode { pub fn to_status_code(&self) -> StatusCode { match self { Self::BadRequest => StatusCode::BAD_REQUEST, Self::Unauthorized => StatusCode::UNAUTHORIZED, Self::Forbidden => StatusCode::FORBIDDEN, Self::NotFound => StatusCode::NOT_FOUND, Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS, Self::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, Self::ServiceUnavailable => StatusCode::SERVICE_UNAVAILABLE, } } pub fn from_code(code: u64) -> Option { match code { 400 => Some(Self::BadRequest), 401 => Some(Self::Unauthorized), 403 => Some(Self::Forbidden), 404 => Some(Self::NotFound), 429 => Some(Self::TooManyRequests), 500 => Some(Self::InternalServerError), 503 => Some(Self::ServiceUnavailable), _ => Some(Self::InternalServerError), } } }