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
//! # Cookie 管理
//!
//! 提供 Cookie 存储和自动管理功能。
//!
//! ## 使用示例
//!
//! ```rust
//! use reqres::{Client, CookieJar};
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut jar = CookieJar::new();
//! jar.insert("session".to_string(), "abc123".to_string());
//!
//! let client = Client::builder()
//!     .cookie_jar(jar)
//!     .build()?;
//!
//! // Cookie 会自动发送
//! let response = client.get("https://httpbin.org/cookies").await?;
//!
//! // Set-Cookie 会自动存储
//! let _ = client.get("https://httpbin.org/cookies/set/test=value").await?;
//! # Ok(())
//! # }
//! ```

use std::collections::HashMap;

/// Cookie 管理器
#[derive(Debug, Clone)]
pub struct CookieJar {
    cookies: HashMap<String, String>,
}

impl CookieJar {
    /// 创建新的 Cookie 管理器
    pub fn new() -> Self {
        CookieJar {
            cookies: HashMap::new(),
        }
    }

    /// 添加 Cookie
    pub fn insert(&mut self, name: String, value: String) {
        self.cookies.insert(name, value);
    }

    /// 获取 Cookie
    pub fn get(&self, name: &str) -> Option<&String> {
        self.cookies.get(name)
    }

    /// 删除 Cookie
    pub fn remove(&mut self, name: &str) {
        self.cookies.remove(name);
    }

    /// 从响应头解析并存储 Cookie
    pub fn parse_from_headers(&mut self, headers: &HashMap<String, String>) {
        // 查找 Set-Cookie 头
        for (key, value) in headers {
            if key.eq_ignore_ascii_case("set-cookie") {
                // 简单解析 Set-Cookie 头
                // 格式: "name=value; Expires=...; Path=/; ..."
                if let Some(cookie_pair) = value.split(';').next() {
                    if let Some((name, value)) = cookie_pair.split_once('=') {
                        self.cookies.insert(
                            name.trim().to_string(),
                            value.trim().to_string(),
                        );
                    }
                }
            }
        }
    }

    /// 构建 Cookie 请求头
    pub fn build_cookie_header(&self) -> String {
        self.cookies
            .iter()
            .map(|(name, value)| format!("{}={}", name, value))
            .collect::<Vec<_>>()
            .join("; ")
    }

    /// 是否为空
    pub fn is_empty(&self) -> bool {
        self.cookies.is_empty()
    }

    /// Cookie 数量
    pub fn len(&self) -> usize {
        self.cookies.len()
    }

    /// 清空所有 Cookie
    pub fn clear(&mut self) {
        self.cookies.clear();
    }

    /// 获取所有 Cookie
    pub fn all(&self) -> &HashMap<String, String> {
        &self.cookies
    }
}

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

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

    #[test]
    fn test_cookie_jar_insert_and_get() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value".to_string());
        assert_eq!(jar.get("name"), Some(&"value".to_string()));
    }

    #[test]
    fn test_cookie_jar_remove() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value".to_string());
        jar.remove("name");
        assert_eq!(jar.get("name"), None);
    }

    #[test]
    fn test_cookie_jar_build_header() {
        let mut jar = CookieJar::new();
        jar.insert("a".to_string(), "1".to_string());
        jar.insert("b".to_string(), "2".to_string());
        let header = jar.build_cookie_header();
        assert!(header.contains("a=1"));
        assert!(header.contains("b=2"));
    }

    #[test]
    fn test_cookie_jar_parse_from_headers() {
        let mut jar = CookieJar::new();
        let mut headers = HashMap::new();
        headers.insert("set-cookie".to_string(), "session=abc123; Path=/".to_string());
        jar.parse_from_headers(&headers);
        assert_eq!(jar.get("session"), Some(&"abc123".to_string()));
    }

    #[test]
    fn test_cookie_jar_clear() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value".to_string());
        jar.insert("another".to_string(), "test".to_string());
        assert_eq!(jar.len(), 2);

        jar.clear();
        assert!(jar.is_empty());
        assert_eq!(jar.len(), 0);
    }

    #[test]
    fn test_cookie_jar_len() {
        let mut jar = CookieJar::new();
        assert_eq!(jar.len(), 0);

        jar.insert("a".to_string(), "1".to_string());
        assert_eq!(jar.len(), 1);

        jar.insert("b".to_string(), "2".to_string());
        assert_eq!(jar.len(), 2);
    }

    #[test]
    fn test_cookie_jar_is_empty() {
        let mut jar = CookieJar::new();
        assert!(jar.is_empty());

        jar.insert("name".to_string(), "value".to_string());
        assert!(!jar.is_empty());
    }

    #[test]
    fn test_cookie_jar_all() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value".to_string());
        jar.insert("session".to_string(), "abc123".to_string());

        let all_cookies = jar.all();
        assert_eq!(all_cookies.len(), 2);
        assert_eq!(all_cookies.get("name"), Some(&"value".to_string()));
        assert_eq!(all_cookies.get("session"), Some(&"abc123".to_string()));
    }

    #[test]
    fn test_cookie_jar_default() {
        let jar = CookieJar::default();
        assert!(jar.is_empty());
    }

    #[test]
    fn test_cookie_jar_parse_multiple_set_cookie() {
        let mut jar = CookieJar::new();
        let mut headers = HashMap::new();
        headers.insert("set-cookie".to_string(), "session=abc123; Path=/".to_string());
        headers.insert("Set-Cookie".to_string(), "user=john; Domain=.example.com".to_string());
        jar.parse_from_headers(&headers);

        assert_eq!(jar.get("session"), Some(&"abc123".to_string()));
        assert_eq!(jar.get("user"), Some(&"john".to_string()));
    }

    #[test]
    fn test_cookie_jar_parse_case_insensitive() {
        let mut jar = CookieJar::new();
        let mut headers = HashMap::new();
        headers.insert("SET-COOKIE".to_string(), "test=value".to_string());
        jar.parse_from_headers(&headers);

        assert_eq!(jar.get("test"), Some(&"value".to_string()));
    }

    #[test]
    fn test_cookie_jar_parse_no_set_cookie() {
        let mut jar = CookieJar::new();
        let mut headers = HashMap::new();
        headers.insert("content-type".to_string(), "text/html".to_string());
        jar.parse_from_headers(&headers);

        assert!(jar.is_empty());
    }

    #[test]
    fn test_cookie_jar_parse_empty_value() {
        let mut jar = CookieJar::new();
        let mut headers = HashMap::new();
        headers.insert("set-cookie".to_string(), "test=".to_string());
        jar.parse_from_headers(&headers);

        assert_eq!(jar.get("test"), Some(&"".to_string()));
    }

    #[test]
    fn test_cookie_jar_parse_with_attributes() {
        let mut jar = CookieJar::new();
        let mut headers = HashMap::new();
        headers.insert("set-cookie".to_string(), "session=abc123; Path=/; Domain=.example.com; Secure; HttpOnly".to_string());
        jar.parse_from_headers(&headers);

        // Should extract only the name=value pair
        assert_eq!(jar.get("session"), Some(&"abc123".to_string()));
    }

    #[test]
    fn test_cookie_jar_insert_replace() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value1".to_string());
        assert_eq!(jar.get("name"), Some(&"value1".to_string()));

        jar.insert("name".to_string(), "value2".to_string());
        assert_eq!(jar.get("name"), Some(&"value2".to_string()));
    }

    #[test]
    fn test_cookie_jar_build_empty() {
        let jar = CookieJar::new();
        let header = jar.build_cookie_header();
        assert_eq!(header, "");
    }

    #[test]
    fn test_cookie_jar_build_single() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value".to_string());
        let header = jar.build_cookie_header();
        assert_eq!(header, "name=value");
    }

    #[test]
    fn test_cookie_jar_build_multiple() {
        let mut jar = CookieJar::new();
        jar.insert("a".to_string(), "1".to_string());
        jar.insert("b".to_string(), "2".to_string());
        jar.insert("c".to_string(), "3".to_string());
        let header = jar.build_cookie_header();

        // Check that all cookies are present
        assert!(header.contains("a=1"));
        assert!(header.contains("b=2"));
        assert!(header.contains("c=3"));
        // Check format
        assert!(header.contains("; "));
    }

    #[test]
    fn test_cookie_jar_get_nonexistent() {
        let jar = CookieJar::new();
        assert_eq!(jar.get("nonexistent"), None);
    }

    #[test]
    fn test_cookie_jar_remove_nonexistent() {
        let mut jar = CookieJar::new();
        jar.insert("name".to_string(), "value".to_string());
        jar.remove("nonexistent");

        assert_eq!(jar.len(), 1);
        assert_eq!(jar.get("name"), Some(&"value".to_string()));
    }
}