1use std::fmt;
9
10#[derive(Debug)]
12pub enum HttpError {
13 ConnectTimeout,
15
16 RequestTimeout,
18
19 ConnectionFailed(String),
21
22 ServerError(u16),
24
25 ClientError(u16, String),
27
28 Cancelled,
30
31 TooManyRedirects,
33
34 InvalidUrl(String),
36
37 InvalidHeaderName(String),
39
40 InvalidHeaderValue(String),
42
43 JsonSerialize(String),
45
46 JsonDeserialize(String),
48
49 FormEncode(String),
51
52 BodyTooLarge,
54
55 ResponseBody(String),
57
58 Request(reqwest::Error),
60
61 ClientBuild(String),
63
64 Other(String),
66}
67
68impl fmt::Display for HttpError {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 Self::ConnectTimeout => write!(f, "连接超时"),
72 Self::RequestTimeout => write!(f, "请求超时"),
73 Self::ConnectionFailed(msg) => write!(f, "连接失败: {}", msg),
74 Self::ServerError(code) => write!(f, "服务器错误: {}", code),
75 Self::ClientError(code, msg) => write!(f, "客户端错误 {}: {}", code, msg),
76 Self::Cancelled => write!(f, "请求被取消"),
77 Self::TooManyRedirects => write!(f, "重定向过多"),
78 Self::InvalidUrl(url) => write!(f, "无效的 URL: {}", url),
79 Self::InvalidHeaderName(name) => write!(f, "无效的请求头名称: {}", name),
80 Self::InvalidHeaderValue(value) => write!(f, "无效的请求头值: {}", value),
81 Self::JsonSerialize(msg) => write!(f, "JSON 序列化失败: {}", msg),
82 Self::JsonDeserialize(msg) => write!(f, "JSON 反序列化失败: {}", msg),
83 Self::FormEncode(msg) => write!(f, "表单编码失败: {}", msg),
84 Self::BodyTooLarge => write!(f, "请求体过大"),
85 Self::ResponseBody(msg) => write!(f, "响应体读取错误: {}", msg),
86 Self::Request(e) => write!(f, "请求错误: {}", e),
87 Self::ClientBuild(msg) => write!(f, "构建客户端失败: {}", msg),
88 Self::Other(msg) => write!(f, "{}", msg),
89 }
90 }
91}
92
93impl std::error::Error for HttpError {
94 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
95 match self {
96 Self::Request(e) => Some(e),
97 _ => None,
98 }
99 }
100}
101
102impl From<reqwest::Error> for HttpError {
103 fn from(err: reqwest::Error) -> Self {
104 if err.is_timeout() {
105 if err.is_connect() {
106 Self::ConnectTimeout
107 } else {
108 Self::RequestTimeout
109 }
110 } else if err.is_connect() {
111 Self::ConnectionFailed(err.to_string())
112 } else if err.is_redirect() {
113 Self::TooManyRedirects
114 } else {
115 Self::Request(err)
116 }
117 }
118}
119
120impl HttpError {
121 pub fn is_retryable(&self) -> bool {
128 matches!(
129 self,
130 Self::ConnectTimeout
131 | Self::RequestTimeout
132 | Self::ConnectionFailed(_)
133 | Self::ServerError(_)
134 )
135 }
136
137 pub fn is_client_error(&self) -> bool {
139 matches!(self, Self::ClientError(_, _))
140 }
141
142 pub fn is_server_error(&self) -> bool {
144 matches!(self, Self::ServerError(_))
145 }
146
147 pub fn status_code(&self) -> Option<u16> {
149 match self {
150 Self::ServerError(code) | Self::ClientError(code, _) => Some(*code),
151 _ => None,
152 }
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_retryable_errors() {
162 assert!(HttpError::ConnectTimeout.is_retryable());
163 assert!(HttpError::RequestTimeout.is_retryable());
164 assert!(HttpError::ConnectionFailed("test".into()).is_retryable());
165 assert!(HttpError::ServerError(500).is_retryable());
166 assert!(HttpError::ServerError(503).is_retryable());
167
168 assert!(!HttpError::ClientError(400, "Bad Request".into()).is_retryable());
169 assert!(!HttpError::InvalidUrl("bad".into()).is_retryable());
170 assert!(!HttpError::Cancelled.is_retryable());
171 }
172
173 #[test]
174 fn test_error_display() {
175 assert_eq!(HttpError::ConnectTimeout.to_string(), "连接超时");
176 assert_eq!(HttpError::ServerError(500).to_string(), "服务器错误: 500");
177 assert!(HttpError::InvalidUrl("bad".into())
178 .to_string()
179 .contains("bad"));
180 }
181
182 #[test]
183 fn test_status_code() {
184 assert_eq!(HttpError::ServerError(500).status_code(), Some(500));
185 assert_eq!(
186 HttpError::ClientError(404, "Not Found".into()).status_code(),
187 Some(404)
188 );
189 assert_eq!(HttpError::ConnectTimeout.status_code(), None);
190 }
191}