use std::fmt;
use std::io;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Io(io::Error),
InvalidUrl {
url: String,
reason: String,
},
ConnectionFailed {
host: String,
reason: String,
},
Timeout {
seconds: u64,
},
InvalidMethod {
method: String,
},
InvalidStatusCode {
code: u16,
},
ParseError {
message: String,
},
UnexpectedEof,
InvalidUtf8 {
message: String,
},
Internal {
message: String,
},
JsonError {
message: String,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "IO error: {}", e),
Error::InvalidUrl { url, reason } => {
write!(f, "Invalid URL '{}': {}", url, reason)
}
Error::ConnectionFailed { host, reason } => {
write!(f, "Failed to connect to {}: {}", host, reason)
}
Error::Timeout { seconds } => {
write!(f, "Operation timed out after {}s", seconds)
}
Error::InvalidMethod { method } => {
write!(f, "Invalid HTTP method: {}", method)
}
Error::InvalidStatusCode { code } => {
write!(f, "Invalid status code: {}", code)
}
Error::ParseError { message } => {
write!(f, "Parse error: {}", message)
}
Error::UnexpectedEof => {
write!(f, "Unexpected end of file")
}
Error::InvalidUtf8 { message } => {
write!(f, "Invalid UTF-8: {}", message)
}
Error::Internal { message } => {
write!(f, "Internal error: {}", message)
}
Error::JsonError { message } => {
write!(f, "JSON error: {}", message)
}
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Self {
Error::Io(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::InvalidUrl {
url: "not-a-url".to_string(),
reason: "missing scheme".to_string(),
};
assert!(err.to_string().contains("Invalid URL"));
assert!(err.to_string().contains("not-a-url"));
}
#[test]
fn test_error_from_io() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(matches!(err, Error::Io(_)));
}
#[test]
fn test_timeout_error() {
let err = Error::Timeout { seconds: 30 };
assert!(err.to_string().contains("timed out"));
assert!(err.to_string().contains("30"));
}
}