use std::error;
use std::fmt;
use std::io;
use std::str::Utf8Error;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub enum Error {
Api {
status_code: u16,
error_type: Option<String>,
message: String,
request_id: Option<String>,
},
Authentication {
message: String,
},
Permission {
message: String,
},
NotFound {
message: String,
resource_type: Option<String>,
resource_id: Option<String>,
},
RateLimit {
message: String,
retry_after: Option<u64>,
},
BadRequest {
message: String,
param: Option<String>,
},
Timeout {
message: String,
duration: Option<f64>,
},
Abort {
message: String,
},
Connection {
message: String,
source: Option<Arc<dyn error::Error + Send + Sync>>,
},
InternalServer {
message: String,
request_id: Option<String>,
},
ServiceUnavailable {
message: String,
retry_after: Option<u64>,
},
Serialization {
message: String,
source: Option<Arc<dyn error::Error + Send + Sync>>,
},
Io {
message: String,
source: Arc<io::Error>,
},
HttpClient {
message: String,
source: Option<Arc<dyn error::Error + Send + Sync>>,
},
Validation {
message: String,
param: Option<String>,
},
Url {
message: String,
source: Option<url::ParseError>,
},
Streaming {
message: String,
source: Option<Arc<dyn error::Error + Send + Sync>>,
},
Encoding {
message: String,
source: Option<Arc<dyn error::Error + Send + Sync>>,
},
Unknown {
message: String,
},
ToDo {
message: String,
},
}
impl Error {
pub fn api(
status_code: u16,
error_type: Option<String>,
message: String,
request_id: Option<String>,
) -> Self {
Error::Api { status_code, error_type, message, request_id }
}
pub fn authentication(message: impl Into<String>) -> Self {
Error::Authentication { message: message.into() }
}
pub fn permission(message: impl Into<String>) -> Self {
Error::Permission { message: message.into() }
}
pub fn not_found(
message: impl Into<String>,
resource_type: Option<String>,
resource_id: Option<String>,
) -> Self {
Error::NotFound { message: message.into(), resource_type, resource_id }
}
pub fn rate_limit(message: impl Into<String>, retry_after: Option<u64>) -> Self {
Error::RateLimit { message: message.into(), retry_after }
}
pub fn bad_request(message: impl Into<String>, param: Option<String>) -> Self {
Error::BadRequest { message: message.into(), param }
}
pub fn timeout(message: impl Into<String>, duration: Option<f64>) -> Self {
Error::Timeout { message: message.into(), duration }
}
pub fn abort(message: impl Into<String>) -> Self {
Error::Abort { message: message.into() }
}
pub fn connection(
message: impl Into<String>,
source: Option<Box<dyn error::Error + Send + Sync>>,
) -> Self {
Error::Connection { message: message.into(), source: source.map(Arc::from) }
}
pub fn internal_server(message: impl Into<String>, request_id: Option<String>) -> Self {
Error::InternalServer { message: message.into(), request_id }
}
pub fn service_unavailable(message: impl Into<String>, retry_after: Option<u64>) -> Self {
Error::ServiceUnavailable { message: message.into(), retry_after }
}
pub fn serialization(
message: impl Into<String>,
source: Option<Box<dyn error::Error + Send + Sync>>,
) -> Self {
Error::Serialization { message: message.into(), source: source.map(Arc::from) }
}
pub fn io(message: impl Into<String>, source: io::Error) -> Self {
Error::Io { message: message.into(), source: Arc::new(source) }
}
pub fn http_client(
message: impl Into<String>,
source: Option<Box<dyn error::Error + Send + Sync>>,
) -> Self {
Error::HttpClient { message: message.into(), source: source.map(Arc::from) }
}
pub fn validation(message: impl Into<String>, param: Option<String>) -> Self {
Error::Validation { message: message.into(), param }
}
pub fn url(message: impl Into<String>, source: Option<url::ParseError>) -> Self {
Error::Url { message: message.into(), source }
}
pub fn streaming(
message: impl Into<String>,
source: Option<Box<dyn error::Error + Send + Sync>>,
) -> Self {
Error::Streaming { message: message.into(), source: source.map(Arc::from) }
}
pub fn encoding(
message: impl Into<String>,
source: Option<Box<dyn error::Error + Send + Sync>>,
) -> Self {
Error::Encoding { message: message.into(), source: source.map(Arc::from) }
}
pub fn unknown(message: impl Into<String>) -> Self {
Error::Unknown { message: message.into() }
}
pub fn todo(message: impl Into<String>) -> Self {
Error::ToDo { message: message.into() }
}
pub fn is_authentication(&self) -> bool {
matches!(self, Error::Authentication { .. })
}
pub fn is_permission(&self) -> bool {
matches!(self, Error::Permission { .. })
}
pub fn is_not_found(&self) -> bool {
matches!(self, Error::NotFound { .. })
}
pub fn is_rate_limit(&self) -> bool {
matches!(self, Error::RateLimit { .. })
}
pub fn is_bad_request(&self) -> bool {
matches!(self, Error::BadRequest { .. })
}
pub fn is_timeout(&self) -> bool {
matches!(self, Error::Timeout { .. })
}
pub fn is_abort(&self) -> bool {
matches!(self, Error::Abort { .. })
}
pub fn is_connection(&self) -> bool {
matches!(self, Error::Connection { .. })
}
pub fn is_server_error(&self) -> bool {
matches!(self, Error::InternalServer { .. } | Error::ServiceUnavailable { .. })
}
pub fn is_retryable(&self) -> bool {
match self {
Error::Api { status_code, .. } => {
matches!(status_code, 408 | 409 | 429 | 500..=599)
}
Error::Timeout { .. } => true,
Error::Connection { .. } => true,
Error::RateLimit { .. } => true,
Error::ServiceUnavailable { .. } => true,
Error::InternalServer { .. } => true,
_ => false,
}
}
pub fn is_todo(&self) -> bool {
matches!(self, Error::ToDo { .. })
}
pub fn is_validation(&self) -> bool {
matches!(self, Error::Validation { .. })
}
pub fn request_id(&self) -> Option<&str> {
match self {
Error::Api { request_id, .. } => request_id.as_deref(),
Error::InternalServer { request_id, .. } => request_id.as_deref(),
_ => None,
}
}
pub fn status_code(&self) -> Option<u16> {
match self {
Error::Api { status_code, .. } => Some(*status_code),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Api { message, error_type, request_id, .. } => {
if let Some(error_type) = error_type {
if let Some(request_id) = request_id {
write!(f, "{error_type}: {message} (Request ID: {request_id})")
} else {
write!(f, "{error_type}: {message}")
}
} else if let Some(request_id) = request_id {
write!(f, "API error: {message} (Request ID: {request_id})")
} else {
write!(f, "API error: {message}")
}
}
Error::Authentication { message } => {
write!(f, "Authentication error: {message}")
}
Error::Permission { message } => {
write!(f, "Permission error: {message}")
}
Error::NotFound { message, resource_type, resource_id } => {
let prefix = if let Some(resource_type) = resource_type {
format!("Resource not found ({resource_type})")
} else {
"Resource not found".to_string()
};
let suffix = if let Some(resource_id) = resource_id {
format!(" [ID: {resource_id}]")
} else {
"".to_string()
};
write!(f, "{prefix}: {message}{suffix}")
}
Error::RateLimit { message, retry_after } => {
if let Some(retry_after) = retry_after {
write!(f, "Rate limit exceeded: {message} (retry after {retry_after} seconds)")
} else {
write!(f, "Rate limit exceeded: {message}")
}
}
Error::BadRequest { message, param } => {
if let Some(param) = param {
write!(f, "Bad request: {message} (parameter: {param})")
} else {
write!(f, "Bad request: {message}")
}
}
Error::Timeout { message, duration } => {
if let Some(duration) = duration {
write!(f, "Timeout error: {message} ({duration} seconds)")
} else {
write!(f, "Timeout error: {message}")
}
}
Error::Abort { message } => {
write!(f, "Request aborted: {message}")
}
Error::Connection { message, .. } => {
write!(f, "Connection error: {message}")
}
Error::InternalServer { message, request_id } => {
if let Some(request_id) = request_id {
write!(f, "Internal server error: {message} (Request ID: {request_id})")
} else {
write!(f, "Internal server error: {message}")
}
}
Error::ServiceUnavailable { message, retry_after } => {
if let Some(retry_after) = retry_after {
write!(f, "Service unavailable: {message} (retry after {retry_after} seconds)")
} else {
write!(f, "Service unavailable: {message}")
}
}
Error::Serialization { message, .. } => {
write!(f, "Serialization error: {message}")
}
Error::Io { message, .. } => {
write!(f, "I/O error: {message}")
}
Error::HttpClient { message, .. } => {
write!(f, "HTTP client error: {message}")
}
Error::Validation { message, param } => {
if let Some(param) = param {
write!(f, "Validation error: {message} (parameter: {param})")
} else {
write!(f, "Validation error: {message}")
}
}
Error::Url { message, .. } => {
write!(f, "URL error: {message}")
}
Error::Streaming { message, .. } => {
write!(f, "Streaming error: {message}")
}
Error::Encoding { message, .. } => {
write!(f, "Encoding error: {message}")
}
Error::Unknown { message } => {
write!(f, "Unknown error: {message}")
}
Error::ToDo { message } => {
write!(f, "Unimplemented: {message}")
}
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Connection { source, .. } => {
source.as_ref().map(|e| e.as_ref() as &(dyn error::Error + 'static))
}
Error::Serialization { source, .. } => {
source.as_ref().map(|e| e.as_ref() as &(dyn error::Error + 'static))
}
Error::Io { source, .. } => Some(source),
Error::HttpClient { source, .. } => {
source.as_ref().map(|e| e.as_ref() as &(dyn error::Error + 'static))
}
Error::Url { source, .. } => {
source.as_ref().map(|e| e as &(dyn error::Error + 'static))
}
Error::Streaming { source, .. } => {
source.as_ref().map(|e| e.as_ref() as &(dyn error::Error + 'static))
}
Error::Encoding { source, .. } => {
source.as_ref().map(|e| e.as_ref() as &(dyn error::Error + 'static))
}
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::io(err.to_string(), err)
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::serialization(format!("JSON error: {err}"), Some(Box::new(err)))
}
}
impl From<url::ParseError> for Error {
fn from(err: url::ParseError) -> Self {
Error::url(format!("URL parse error: {err}"), Some(err))
}
}
impl From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self {
Error::encoding(format!("UTF-8 error: {err}"), Some(Box::new(err)))
}
}
pub type Result<T> = std::result::Result<T, Error>;