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
//! # 代理配置
//!
//! 提供代理配置 API,支持 HTTP、HTTPS、SOCKS5 代理。
//!
//! ## 注意
//!
//! 当前版本仅提供框架级支持,实际的代理路由逻辑尚未实现。
//!
//! ## 使用示例
//!
//! ```rust
//! use reqres::{Client, Proxy, ProxyBuilder, ProxyType};
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // 从 URL 创建代理
//! let proxy = Proxy::from_url("http://proxy.example.com:8080")?;
//!
//! // 使用 Builder 创建代理
//! let proxy_with_auth = ProxyBuilder::new("proxy.com:3128", ProxyType::Http)
//!     .credentials("user", "pass")
//!     .build();
//!
//! let client = Client::builder()
//!     .proxy(proxy)
//!     .build()?;
//! # Ok(())
//! # }
//! ```

use std::net::SocketAddr;
use crate::error::{ReqresError, Result};

/// 代理类型
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProxyType {
    Http,
    Https,
    Socks5,
}

/// 代理配置
#[derive(Debug, Clone)]
pub struct Proxy {
    /// 代理地址 (host:port)
    pub addr: String,
    /// 代理类型
    pub proxy_type: ProxyType,
    /// 用户名(可选)
    pub username: Option<String>,
    /// 密码(可选)
    pub password: Option<String>,
}

impl Proxy {
    /// 创建新的代理配置
    pub fn new(addr: String, proxy_type: ProxyType) -> Self {
        Proxy {
            addr,
            proxy_type,
            username: None,
            password: None,
        }
    }

    /// 设置认证信息
    pub fn with_auth(mut self, username: String, password: String) -> Self {
        self.username = Some(username);
        self.password = Some(password);
        self
    }

    /// 从 URL 字符串解析代理
    ///
    /// 支持的格式:
    /// - http://proxy.example.com:8080
    /// - https://proxy.example.com:8080
    /// - socks5://proxy.example.com:1080
    /// - http://user:pass@proxy.example.com:8080
    pub fn from_url(url: &str) -> Result<Self> {
        let (scheme, rest) = if url.starts_with("http://") {
            (ProxyType::Http, &url[7..])
        } else if url.starts_with("https://") {
            (ProxyType::Https, &url[8..])
        } else if url.starts_with("socks5://") {
            (ProxyType::Socks5, &url[9..])
        } else if url.contains("://") {
            return Err(ReqresError::InvalidUrl(format!(
                "Unsupported proxy scheme: {}",
                url
            )));
        } else {
            // 默认 HTTP 代理
            (ProxyType::Http, url)
        };

        // 解析认证信息
        let (addr, username, password) = if let Some(at_pos) = rest.find('@') {
            let auth = &rest[..at_pos];
            let addr = &rest[at_pos + 1..];

            let (username, password) = if let Some(colon_pos) = auth.find(':') {
                (
                    Some(auth[..colon_pos].to_string()),
                    Some(auth[colon_pos + 1..].to_string()),
                )
            } else {
                (Some(auth.to_string()), None)
            };

            (addr.to_string(), username, password)
        } else {
            (rest.to_string(), None, None)
        };

        Ok(Proxy {
            addr,
            proxy_type: scheme,
            username,
            password,
        })
    }

    /// 获取代理地址
    pub fn address(&self) -> Result<SocketAddr> {
        self.addr
            .parse()
            .map_err(|_| ReqresError::InvalidUrl(format!("Invalid proxy address: {}", self.addr)))
    }

    /// 是否需要认证
    pub fn needs_auth(&self) -> bool {
        self.username.is_some()
    }

    /// 构建 Proxy-Authorization 头
    pub fn build_auth_header(&self) -> Option<String> {
        if let (Some(username), Some(password)) = (&self.username, &self.password) {
            use base64::prelude::*;
            let auth = format!("{}:{}", username, password);
            let encoded = BASE64_STANDARD.encode(auth);
            Some(format!("Basic {}", encoded))
        } else {
            None
        }
    }
}

/// 代理构建器
pub struct ProxyBuilder {
    addr: String,
    proxy_type: ProxyType,
    username: Option<String>,
    password: Option<String>,
}

impl ProxyBuilder {
    /// 创建新的代理构建器
    pub fn new(addr: impl Into<String>, proxy_type: ProxyType) -> Self {
        ProxyBuilder {
            addr: addr.into(),
            proxy_type,
            username: None,
            password: None,
        }
    }

    /// 设置用户名和密码
    pub fn credentials(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
        self.username = Some(username.into());
        self.password = Some(password.into());
        self
    }

    /// 构建代理配置
    pub fn build(self) -> Proxy {
        Proxy {
            addr: self.addr,
            proxy_type: self.proxy_type,
            username: self.username,
            password: self.password,
        }
    }
}

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

    #[test]
    fn test_proxy_from_url_http() {
        let proxy = Proxy::from_url("http://proxy.example.com:8080").unwrap();
        assert_eq!(proxy.proxy_type, ProxyType::Http);
        assert_eq!(proxy.addr, "proxy.example.com:8080");
        assert!(!proxy.needs_auth());
    }

    #[test]
    fn test_proxy_from_url_https() {
        let proxy = Proxy::from_url("https://proxy.example.com:8080").unwrap();
        assert_eq!(proxy.proxy_type, ProxyType::Https);
        assert_eq!(proxy.addr, "proxy.example.com:8080");
    }

    #[test]
    fn test_proxy_from_url_socks5() {
        let proxy = Proxy::from_url("socks5://proxy.example.com:1080").unwrap();
        assert_eq!(proxy.proxy_type, ProxyType::Socks5);
        assert_eq!(proxy.addr, "proxy.example.com:1080");
    }

    #[test]
    fn test_proxy_from_url_with_auth() {
        let proxy = Proxy::from_url("http://user:pass@proxy.example.com:8080").unwrap();
        assert_eq!(proxy.proxy_type, ProxyType::Http);
        assert_eq!(proxy.addr, "proxy.example.com:8080");
        assert!(proxy.needs_auth());
        assert_eq!(proxy.username, Some("user".to_string()));
        assert_eq!(proxy.password, Some("pass".to_string()));
    }

    #[test]
    fn test_proxy_builder() {
        let proxy = ProxyBuilder::new("proxy.example.com:8080", ProxyType::Http)
            .credentials("user", "pass")
            .build();
        assert_eq!(proxy.addr, "proxy.example.com:8080");
        assert!(proxy.needs_auth());
    }

    #[test]
    fn test_proxy_build_auth_header() {
        let proxy = Proxy::new("proxy.example.com:8080".to_string(), ProxyType::Http)
            .with_auth("user".to_string(), "pass".to_string());
        
        let header = proxy.build_auth_header();
        assert!(header.is_some());
        assert!(header.unwrap().starts_with("Basic "));
    }
}