1use std::fmt;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug)]
10pub enum Error {
11 InvalidUrl { url: String, reason: String },
13
14 ConnectionFailed { addr: String, source: std::io::Error },
16
17 Timeout { duration: std::time::Duration },
19
20 InvalidMethod { method: String },
22
23 InvalidHeader { name: String, value: String },
25
26 StatusError { status: u16, body: String },
28
29 BodyReadError { source: std::io::Error },
31
32 JsonError { source: String },
34
35 InvalidConfig { message: String },
37
38 AuthError { message: String },
40
41 Internal { message: String },
43}
44
45impl fmt::Display for Error {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Error::InvalidUrl { url, reason } => {
49 write!(f, "Invalid URL '{}': {}", url, reason)
50 }
51 Error::ConnectionFailed { addr, source } => {
52 write!(f, "Failed to connect to {}: {}", addr, source)
53 }
54 Error::Timeout { duration } => {
55 write!(f, "Request timed out after {:?}", duration)
56 }
57 Error::InvalidMethod { method } => {
58 write!(f, "Invalid HTTP method: {}", method)
59 }
60 Error::InvalidHeader { name, value } => {
61 write!(f, "Invalid header '{}': {}", name, value)
62 }
63 Error::StatusError { status, body } => {
64 write!(f, "HTTP error {}: {}", status, body)
65 }
66 Error::BodyReadError { source } => {
67 write!(f, "Failed to read response body: {}", source)
68 }
69 Error::JsonError { source } => {
70 write!(f, "JSON error: {}", source)
71 }
72 Error::InvalidConfig { message } => {
73 write!(f, "Invalid configuration: {}", message)
74 }
75 Error::AuthError { message } => {
76 write!(f, "Authentication error: {}", message)
77 }
78 Error::Internal { message } => {
79 write!(f, "Internal error: {}", message)
80 }
81 }
82 }
83}
84
85impl std::error::Error for Error {
86 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
87 match self {
88 Error::ConnectionFailed { source, .. } => Some(source),
89 Error::BodyReadError { source } => Some(source),
90 _ => None,
91 }
92 }
93}
94
95impl From<std::io::Error> for Error {
96 fn from(err: std::io::Error) -> Self {
97 Error::BodyReadError { source: err }
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_error_display() {
107 let err = Error::InvalidUrl {
108 url: "not-a-url".to_string(),
109 reason: "missing scheme".to_string(),
110 };
111 assert!(err.to_string().contains("Invalid URL"));
112 assert!(err.to_string().contains("not-a-url"));
113 }
114
115 #[test]
116 fn test_error_from_io() {
117 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
118 let err: Error = io_err.into();
119 assert!(matches!(err, Error::BodyReadError { .. }));
120 }
121
122 #[test]
123 fn test_timeout_error() {
124 let err = Error::Timeout {
125 duration: std::time::Duration::from_secs(30),
126 };
127 assert!(err.to_string().contains("timed out"));
128 assert!(err.to_string().contains("30s"));
129 }
130}