use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("HTTP request failed with status {status}: {message}")]
Http { status: u16, message: String },
#[error("JSON parsing failed: {0}")]
Json(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Authentication failed: {0}")]
Authentication(String),
#[error("Validation error: {0}")]
Validation(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Session error: {0}")]
Session(String),
#[error("Invalid parameter: {0}")]
InvalidParameter(String),
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Rate limited")]
RateLimited { retry_after: Option<u32> },
#[error("Server error: {0}")]
Server(String),
#[error("Request timeout")]
Timeout,
#[error("Network error: {0}")]
Network(String),
#[error("Not implemented: {0}")]
NotImplemented(String),
#[error("Unknown error: {0}")]
Unknown(String),
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Error::Json(err.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
if err.is_timeout() {
Error::Timeout
} else if err.is_connect() {
Error::Network(format!("Connection failed: {}", err))
} else if err.is_status() {
if let Some(status) = err.status() {
match status.as_u16() {
401 | 403 => Error::Authentication(format!("Authentication error: {}", status)),
404 => Error::NotFound(format!("Resource not found: {}", status)),
429 => Error::RateLimited { retry_after: None },
500..=599 => Error::Server(format!("Server error: {}", status)),
code => Error::Http {
status: code,
message: format!("HTTP error: {}", status),
},
}
} else {
Error::Http {
status: 0,
message: err.to_string(),
}
}
} else {
Error::Http {
status: 0,
message: err.to_string(),
}
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Error::Network(format!("IO error: {}", err))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_http_variant() {
let status = 400;
let msg = "Bad Request".to_string();
let error = Error::Http {
status,
message: msg.clone(),
};
assert_eq!(
error.to_string(),
format!("HTTP request failed with status {}: {}", status, msg)
);
}
#[test]
fn test_error_json_variant() {
let msg = "Invalid JSON structure".to_string();
let error = Error::Json(msg.clone());
assert_eq!(error.to_string(), format!("JSON parsing failed: {}", msg));
}
#[test]
fn test_error_auth_variant() {
let msg = "Invalid credentials".to_string();
let error = Error::Authentication(msg.clone());
assert_eq!(error.to_string(), format!("Authentication failed: {}", msg));
}
#[test]
fn test_error_not_found_variant() {
let msg = "Card with id 123".to_string();
let error = Error::NotFound(msg.clone());
assert_eq!(error.to_string(), format!("Resource not found: {}", msg));
}
#[test]
fn test_error_rate_limited() {
let error = Error::RateLimited { retry_after: None };
assert_eq!(error.to_string(), "Rate limited");
}
#[test]
fn test_error_from_reqwest_error() {
}
#[test]
fn test_error_from_serde_json_error() {
let json_err = serde_json::from_str::<String>("invalid json").unwrap_err();
let error: Error = json_err.into();
match error {
Error::Json(_) => (),
_ => panic!("Expected Json error"),
}
}
#[test]
fn test_result_type_alias() {
fn test_function() -> Result<String> {
Ok("success".to_string())
}
let result = test_function();
assert!(result.is_ok());
assert_eq!(result.unwrap(), "success");
}
#[test]
fn test_error_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Error>();
}
}