use super::error::HttpError;
use serde::de::DeserializeOwned;
#[derive(Debug)]
pub struct Response {
status: u16,
headers: Vec<(String, String)>,
body: Vec<u8>,
url: String,
}
impl Response {
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn new(status: u16, headers: Vec<(String, String)>, body: Vec<u8>, url: String) -> Self {
Self {
status,
headers,
body,
url,
}
}
pub fn status(&self) -> u16 {
self.status
}
pub fn is_success(&self) -> bool {
(200..300).contains(&self.status)
}
pub fn is_informational(&self) -> bool {
(100..200).contains(&self.status)
}
pub fn is_redirection(&self) -> bool {
(300..400).contains(&self.status)
}
pub fn is_client_error(&self) -> bool {
(400..500).contains(&self.status)
}
pub fn is_server_error(&self) -> bool {
(500..600).contains(&self.status)
}
pub fn url(&self) -> &str {
&self.url
}
pub fn headers(&self) -> &[(String, String)] {
&self.headers
}
pub fn header(&self, name: &str) -> Option<&str> {
let name_lower = name.to_lowercase();
self.headers
.iter()
.find(|(k, _)| k.to_lowercase() == name_lower)
.map(|(_, v)| v.as_str())
}
pub fn content_type(&self) -> Option<&str> {
self.header("content-type")
}
pub fn content_length(&self) -> Option<usize> {
self.header("content-length")
.and_then(|v| v.parse().ok())
}
pub fn bytes(&self) -> &[u8] {
&self.body
}
pub fn into_bytes(self) -> Vec<u8> {
self.body
}
pub fn text(&self) -> Result<String, HttpError> {
String::from_utf8(self.body.clone())
.map_err(|e| HttpError::ResponseBody(format!("UTF-8 解码失败: {}", e)))
}
pub fn into_text(self) -> Result<String, HttpError> {
String::from_utf8(self.body)
.map_err(|e| HttpError::ResponseBody(format!("UTF-8 解码失败: {}", e)))
}
pub fn json<T: DeserializeOwned>(&self) -> Result<T, HttpError> {
serde_json::from_slice(&self.body)
.map_err(|e| HttpError::JsonDeserialize(e.to_string()))
}
pub fn into_json<T: DeserializeOwned>(self) -> Result<T, HttpError> {
serde_json::from_slice(&self.body)
.map_err(|e| HttpError::JsonDeserialize(e.to_string()))
}
pub fn error_for_status(self) -> Result<Self, HttpError> {
if self.is_success() {
Ok(self)
} else if self.is_client_error() {
Err(HttpError::ClientError(
self.status,
self.text().unwrap_or_else(|_| "Unknown error".into()),
))
} else if self.is_server_error() {
Err(HttpError::ServerError(self.status))
} else {
Ok(self)
}
}
pub(crate) async fn from_reqwest(resp: reqwest::Response) -> Result<Self, HttpError> {
let status = resp.status().as_u16();
let url = resp.url().to_string();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = resp.bytes().await?.to_vec();
Ok(Self {
status,
headers,
body,
url,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mock_response(status: u16, body: &str) -> Response {
Response::new(
status,
vec![
("content-type".into(), "application/json".into()),
("content-length".into(), body.len().to_string()),
],
body.as_bytes().to_vec(),
"http://example.com".into(),
)
}
#[test]
fn test_status_checks() {
assert!(mock_response(200, "").is_success());
assert!(mock_response(201, "").is_success());
assert!(mock_response(204, "").is_success());
assert!(!mock_response(400, "").is_success());
assert!(!mock_response(500, "").is_success());
assert!(mock_response(100, "").is_informational());
assert!(mock_response(301, "").is_redirection());
assert!(mock_response(404, "").is_client_error());
assert!(mock_response(503, "").is_server_error());
}
#[test]
fn test_headers() {
let resp = mock_response(200, "test");
assert_eq!(resp.content_type(), Some("application/json"));
assert_eq!(resp.content_length(), Some(4));
assert_eq!(resp.header("Content-Type"), Some("application/json")); }
#[test]
fn test_body_parsing() {
let resp = mock_response(200, "hello");
assert_eq!(resp.text().unwrap(), "hello");
assert_eq!(resp.bytes(), b"hello");
}
#[test]
fn test_json_parsing() {
let resp = mock_response(200, r#"{"name":"test"}"#);
let data: serde_json::Value = resp.json().unwrap();
assert_eq!(data["name"], "test");
}
#[test]
fn test_error_for_status() {
let ok = mock_response(200, "ok");
assert!(ok.error_for_status().is_ok());
let client_err = mock_response(404, "Not Found");
assert!(client_err.error_for_status().is_err());
let server_err = mock_response(500, "Internal Server Error");
assert!(server_err.error_for_status().is_err());
}
}