unistore_http/
response.rs

1//! HTTP 响应包装
2//!
3//! 职责:
4//! - 封装 HTTP 响应数据
5//! - 提供便捷的响应体解析方法
6//! - 支持流式读取
7
8use super::error::HttpError;
9use serde::de::DeserializeOwned;
10
11/// HTTP 响应
12#[derive(Debug)]
13pub struct Response {
14    /// HTTP 状态码
15    status: u16,
16
17    /// 响应头
18    headers: Vec<(String, String)>,
19
20    /// 响应体(字节)
21    body: Vec<u8>,
22
23    /// 请求 URL
24    url: String,
25}
26
27impl Response {
28    /// 从原始数据创建响应
29    ///
30    /// 注:此方法主要用于测试和内部构造
31    #[cfg_attr(not(test), allow(dead_code))]
32    pub(crate) fn new(status: u16, headers: Vec<(String, String)>, body: Vec<u8>, url: String) -> Self {
33        Self {
34            status,
35            headers,
36            body,
37            url,
38        }
39    }
40
41    /// 获取 HTTP 状态码
42    pub fn status(&self) -> u16 {
43        self.status
44    }
45
46    /// 检查响应是否成功 (2xx)
47    pub fn is_success(&self) -> bool {
48        (200..300).contains(&self.status)
49    }
50
51    /// 检查是否为信息响应 (1xx)
52    pub fn is_informational(&self) -> bool {
53        (100..200).contains(&self.status)
54    }
55
56    /// 检查是否为重定向 (3xx)
57    pub fn is_redirection(&self) -> bool {
58        (300..400).contains(&self.status)
59    }
60
61    /// 检查是否为客户端错误 (4xx)
62    pub fn is_client_error(&self) -> bool {
63        (400..500).contains(&self.status)
64    }
65
66    /// 检查是否为服务器错误 (5xx)
67    pub fn is_server_error(&self) -> bool {
68        (500..600).contains(&self.status)
69    }
70
71    /// 获取请求 URL
72    pub fn url(&self) -> &str {
73        &self.url
74    }
75
76    /// 获取响应头
77    pub fn headers(&self) -> &[(String, String)] {
78        &self.headers
79    }
80
81    /// 获取指定响应头的值
82    pub fn header(&self, name: &str) -> Option<&str> {
83        let name_lower = name.to_lowercase();
84        self.headers
85            .iter()
86            .find(|(k, _)| k.to_lowercase() == name_lower)
87            .map(|(_, v)| v.as_str())
88    }
89
90    /// 获取 Content-Type
91    pub fn content_type(&self) -> Option<&str> {
92        self.header("content-type")
93    }
94
95    /// 获取 Content-Length
96    pub fn content_length(&self) -> Option<usize> {
97        self.header("content-length")
98            .and_then(|v| v.parse().ok())
99    }
100
101    /// 获取响应体字节
102    pub fn bytes(&self) -> &[u8] {
103        &self.body
104    }
105
106    /// 消费响应,获取响应体字节
107    pub fn into_bytes(self) -> Vec<u8> {
108        self.body
109    }
110
111    /// 将响应体解析为文本
112    pub fn text(&self) -> Result<String, HttpError> {
113        String::from_utf8(self.body.clone())
114            .map_err(|e| HttpError::ResponseBody(format!("UTF-8 解码失败: {}", e)))
115    }
116
117    /// 消费响应,将响应体解析为文本
118    pub fn into_text(self) -> Result<String, HttpError> {
119        String::from_utf8(self.body)
120            .map_err(|e| HttpError::ResponseBody(format!("UTF-8 解码失败: {}", e)))
121    }
122
123    /// 将响应体解析为 JSON
124    pub fn json<T: DeserializeOwned>(&self) -> Result<T, HttpError> {
125        serde_json::from_slice(&self.body)
126            .map_err(|e| HttpError::JsonDeserialize(e.to_string()))
127    }
128
129    /// 消费响应,将响应体解析为 JSON
130    pub fn into_json<T: DeserializeOwned>(self) -> Result<T, HttpError> {
131        serde_json::from_slice(&self.body)
132            .map_err(|e| HttpError::JsonDeserialize(e.to_string()))
133    }
134
135    /// 确保响应成功,否则返回错误
136    pub fn error_for_status(self) -> Result<Self, HttpError> {
137        if self.is_success() {
138            Ok(self)
139        } else if self.is_client_error() {
140            Err(HttpError::ClientError(
141                self.status,
142                self.text().unwrap_or_else(|_| "Unknown error".into()),
143            ))
144        } else if self.is_server_error() {
145            Err(HttpError::ServerError(self.status))
146        } else {
147            Ok(self)
148        }
149    }
150
151    /// 从 reqwest Response 创建
152    pub(crate) async fn from_reqwest(resp: reqwest::Response) -> Result<Self, HttpError> {
153        let status = resp.status().as_u16();
154        let url = resp.url().to_string();
155
156        let headers: Vec<(String, String)> = resp
157            .headers()
158            .iter()
159            .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
160            .collect();
161
162        let body = resp.bytes().await?.to_vec();
163
164        Ok(Self {
165            status,
166            headers,
167            body,
168            url,
169        })
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    fn mock_response(status: u16, body: &str) -> Response {
178        Response::new(
179            status,
180            vec![
181                ("content-type".into(), "application/json".into()),
182                ("content-length".into(), body.len().to_string()),
183            ],
184            body.as_bytes().to_vec(),
185            "http://example.com".into(),
186        )
187    }
188
189    #[test]
190    fn test_status_checks() {
191        assert!(mock_response(200, "").is_success());
192        assert!(mock_response(201, "").is_success());
193        assert!(mock_response(204, "").is_success());
194        assert!(!mock_response(400, "").is_success());
195        assert!(!mock_response(500, "").is_success());
196
197        assert!(mock_response(100, "").is_informational());
198        assert!(mock_response(301, "").is_redirection());
199        assert!(mock_response(404, "").is_client_error());
200        assert!(mock_response(503, "").is_server_error());
201    }
202
203    #[test]
204    fn test_headers() {
205        let resp = mock_response(200, "test");
206        assert_eq!(resp.content_type(), Some("application/json"));
207        assert_eq!(resp.content_length(), Some(4));
208        assert_eq!(resp.header("Content-Type"), Some("application/json")); // case-insensitive
209    }
210
211    #[test]
212    fn test_body_parsing() {
213        let resp = mock_response(200, "hello");
214        assert_eq!(resp.text().unwrap(), "hello");
215        assert_eq!(resp.bytes(), b"hello");
216    }
217
218    #[test]
219    fn test_json_parsing() {
220        let resp = mock_response(200, r#"{"name":"test"}"#);
221        let data: serde_json::Value = resp.json().unwrap();
222        assert_eq!(data["name"], "test");
223    }
224
225    #[test]
226    fn test_error_for_status() {
227        let ok = mock_response(200, "ok");
228        assert!(ok.error_for_status().is_ok());
229
230        let client_err = mock_response(404, "Not Found");
231        assert!(client_err.error_for_status().is_err());
232
233        let server_err = mock_response(500, "Internal Server Error");
234        assert!(server_err.error_for_status().is_err());
235    }
236}