unistore_http/
error.rs

1//! HTTP 错误类型定义
2//!
3//! 职责:
4//! - 定义 HTTP 客户端操作的所有可能错误
5//! - 提供错误分类(可重试/不可重试)
6//! - 封装底层 reqwest 错误
7
8use std::fmt;
9
10/// HTTP 客户端错误
11#[derive(Debug)]
12pub enum HttpError {
13    /// 连接超时
14    ConnectTimeout,
15
16    /// 请求超时
17    RequestTimeout,
18
19    /// 连接失败
20    ConnectionFailed(String),
21
22    /// 服务器错误 (5xx)
23    ServerError(u16),
24
25    /// 客户端错误 (4xx)
26    ClientError(u16, String),
27
28    /// 请求被取消
29    Cancelled,
30
31    /// 重定向过多
32    TooManyRedirects,
33
34    /// 无效的 URL
35    InvalidUrl(String),
36
37    /// 无效的请求头名称
38    InvalidHeaderName(String),
39
40    /// 无效的请求头值
41    InvalidHeaderValue(String),
42
43    /// JSON 序列化错误
44    JsonSerialize(String),
45
46    /// JSON 反序列化错误
47    JsonDeserialize(String),
48
49    /// 表单编码错误
50    FormEncode(String),
51
52    /// 请求体过大
53    BodyTooLarge,
54
55    /// 响应体读取错误
56    ResponseBody(String),
57
58    /// 底层请求错误
59    Request(reqwest::Error),
60
61    /// 构建客户端失败
62    ClientBuild(String),
63
64    /// 其他错误
65    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    /// 判断错误是否可重试
122    ///
123    /// 可重试的错误类型:
124    /// - 连接超时/请求超时
125    /// - 连接失败(网络抖动)
126    /// - 服务器错误 (5xx)
127    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    /// 判断是否为客户端错误(通常不应重试)
138    pub fn is_client_error(&self) -> bool {
139        matches!(self, Self::ClientError(_, _))
140    }
141
142    /// 判断是否为服务器错误
143    pub fn is_server_error(&self) -> bool {
144        matches!(self, Self::ServerError(_))
145    }
146
147    /// 获取 HTTP 状态码(如果有)
148    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}