reqres 1.0.0

A pure Rust async HTTP client library based on Tokio with HTTP/2, connection pooling, proxy, cookie, compression, benchmarks, and comprehensive tests
Documentation
use std::collections::HashMap;
use bytes::Bytes;

/// HTTP 响应结构体
#[derive(Debug, Clone)]
pub struct Response {
    /// HTTP 版本
    pub version: String,
    /// 状态码
    pub status: u16,
    /// 状态文本
    pub status_text: String,
    /// 响应头
    pub headers: HashMap<String, String>,
    /// 响应体
    pub body: Bytes,
}

impl Response {
    /// 创建一个新的响应
    pub fn new() -> Self {
        Response {
            version: "HTTP/1.1".to_string(),
            status: 200,
            status_text: "OK".to_string(),
            headers: HashMap::new(),
            body: Bytes::new(),
        }
    }

    /// 检查响应是否成功 (2xx)
    pub fn is_success(&self) -> bool {
        self.status >= 200 && self.status < 300
    }

    /// 检查响应是否重定向 (3xx)
    pub fn is_redirect(&self) -> bool {
        self.status >= 300 && self.status < 400
    }

    /// 检查响应是否客户端错误 (4xx)
    pub fn is_client_error(&self) -> bool {
        self.status >= 400 && self.status < 500
    }

    /// 检查响应是否服务器错误 (5xx)
    pub fn is_server_error(&self) -> bool {
        self.status >= 500 && self.status < 600
    }

    /// 获取响应体文本
    pub fn text(&self) -> Result<String, std::string::FromUtf8Error> {
        String::from_utf8(self.body.to_vec())
    }

    /// 获取响应体字节
    pub fn bytes(&self) -> &Bytes {
        &self.body
    }

    /// 解析 JSON 响应
    pub fn json<T: serde::de::DeserializeOwned>(&self) -> crate::Result<T> {
        let text = self.text().map_err(|e| crate::ReqresError::Other(format!("UTF-8 error: {}", e)))?;
        Ok(serde_json::from_str(&text)?)
    }

    /// 获取状态码
    pub fn status(&self) -> u16 {
        self.status
    }

    /// 获取响应头
    pub fn header(&self, name: &str) -> Option<&String> {
        let name_lower = name.to_lowercase();
        self.headers
            .iter()
            .find(|(k, _)| k.to_lowercase() == name_lower)
            .map(|(_, v)| v)
    }

    /// 获取重定向位置
    pub fn location(&self) -> Option<&String> {
        self.header("Location")
    }
}

impl Default for Response {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::Deserialize;

    #[test]
    fn test_response_new() {
        let response = Response::new();
        assert_eq!(response.version, "HTTP/1.1");
        assert_eq!(response.status, 200);
        assert_eq!(response.status_text, "OK");
        assert!(response.headers.is_empty());
        assert!(response.body.is_empty());
    }

    #[test]
    fn test_response_default() {
        let response = Response::default();
        assert_eq!(response.version, "HTTP/1.1");
        assert_eq!(response.status, 200);
    }

    #[test]
    fn test_is_success() {
        let mut response = Response::new();
        assert!(response.is_success());

        response.status = 100; // Informational
        assert!(!response.is_success());

        response.status = 299; // Success (edge case)
        assert!(response.is_success());

        response.status = 300; // Redirect
        assert!(!response.is_success());
    }

    #[test]
    fn test_is_redirect() {
        let mut response = Response::new();
        response.status = 301;
        assert!(response.is_redirect());

        response.status = 300;
        assert!(response.is_redirect());

        response.status = 399;
        assert!(response.is_redirect());

        response.status = 299;
        assert!(!response.is_redirect());

        response.status = 400;
        assert!(!response.is_redirect());
    }

    #[test]
    fn test_is_client_error() {
        let mut response = Response::new();
        response.status = 404;
        assert!(response.is_client_error());

        response.status = 400;
        assert!(response.is_client_error());

        response.status = 499;
        assert!(response.is_client_error());

        response.status = 399;
        assert!(!response.is_client_error());

        response.status = 500;
        assert!(!response.is_client_error());
    }

    #[test]
    fn test_is_server_error() {
        let mut response = Response::new();
        response.status = 500;
        assert!(response.is_server_error());

        response.status = 503;
        assert!(response.is_server_error());

        response.status = 599;
        assert!(response.is_server_error());

        response.status = 499;
        assert!(!response.is_server_error());

        response.status = 600;
        assert!(!response.is_server_error());
    }

    #[test]
    fn test_text() {
        let mut response = Response::new();
        response.body = Bytes::from("Hello, world!");

        let text = response.text().unwrap();
        assert_eq!(text, "Hello, world!");
    }

    #[test]
    fn test_text_invalid_utf8() {
        let mut response = Response::new();
        response.body = Bytes::from(vec![0xFF, 0xFE, 0xFD]);

        let result = response.text();
        assert!(result.is_err());
    }

    #[test]
    fn test_bytes() {
        let mut response = Response::new();
        let data: &[u8] = b"Hello, world!";
        response.body = Bytes::from(data);

        let bytes = response.bytes();
        assert_eq!(bytes, data);
    }

    #[test]
    fn test_json() {
        #[derive(Deserialize, Debug, PartialEq)]
        struct TestData {
            name: String,
            value: i32,
        }

        let mut response = Response::new();
        response.body = Bytes::from(r#"{"name":"test","value":42}"#);

        let result: TestData = response.json().unwrap();
        assert_eq!(result.name, "test");
        assert_eq!(result.value, 42);
    }

    #[test]
    fn test_json_invalid() {
        let mut response = Response::new();
        response.body = Bytes::from(r#"{"invalid json"#);

        let result: std::result::Result<serde_json::Value, crate::ReqresError> = response.json();
        assert!(result.is_err());
    }

    #[test]
    fn test_header() {
        let mut response = Response::new();
        response.headers.insert("Content-Type".to_string(), "application/json".to_string());
        response.headers.insert("Content-Length".to_string(), "100".to_string());

        assert_eq!(response.header("Content-Type"), Some(&"application/json".to_string()));
        assert_eq!(response.header("content-type"), Some(&"application/json".to_string()));
        assert_eq!(response.header("CONTENT-TYPE"), Some(&"application/json".to_string()));
        assert_eq!(response.header("Content-Length"), Some(&"100".to_string()));
        assert_eq!(response.header("Non-Existent"), None);
    }

    #[test]
    fn test_location() {
        let mut response = Response::new();
        response.status = 301;
        response.headers.insert("Location".to_string(), "https://example.com/new".to_string());

        assert_eq!(response.location(), Some(&"https://example.com/new".to_string()));
    }

    #[test]
    fn test_location_missing() {
        let mut response = Response::new();
        response.status = 301;

        assert_eq!(response.location(), None);
    }
}