unistore-http 0.1.0

HTTP client capability for UniStore
Documentation
//! HTTP 请求构建器
//!
//! 职责:
//! - 提供流畅的请求构建 API
//! - 支持各种请求体格式(JSON、表单、原始字节)
//! - 与任务取消系统集成

use super::config::RetryStrategy;
use super::error::HttpError;
use serde::Serialize;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;

/// HTTP 方法
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Method {
    GET,
    POST,
    PUT,
    DELETE,
    PATCH,
    HEAD,
    OPTIONS,
}

impl std::fmt::Display for Method {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Method::GET => write!(f, "GET"),
            Method::POST => write!(f, "POST"),
            Method::PUT => write!(f, "PUT"),
            Method::DELETE => write!(f, "DELETE"),
            Method::PATCH => write!(f, "PATCH"),
            Method::HEAD => write!(f, "HEAD"),
            Method::OPTIONS => write!(f, "OPTIONS"),
        }
    }
}

/// 请求体类型
#[derive(Debug, Clone)]
pub(crate) enum RequestBody {
    /// 无请求体
    None,
    /// JSON 请求体
    Json(Vec<u8>),
    /// 表单请求体
    Form(String),
    /// 原始字节
    Bytes(Vec<u8>),
    /// 文本
    Text(String),
}

/// 请求构建器
///
/// 通过流畅 API 构建 HTTP 请求
///
/// # Example
///
/// ```ignore
/// let response = client
///     .request(Method::POST, "https://api.example.com/users")
///     .header("X-Custom", "value")
///     .bearer_auth("token")
///     .json(&user_data)
///     .send()
///     .await?;
/// ```
#[derive(Debug)]
pub struct RequestBuilder {
    pub(crate) method: Method,
    pub(crate) url: String,
    pub(crate) headers: HashMap<String, String>,
    pub(crate) query: Vec<(String, String)>,
    pub(crate) body: RequestBody,
    pub(crate) timeout: Option<Duration>,
    pub(crate) max_retries: Option<u32>,
    pub(crate) retry_strategy: Option<RetryStrategy>,
}

impl RequestBuilder {
    /// 创建新的请求构建器
    pub fn new(method: Method, url: impl Into<String>) -> Self {
        Self {
            method,
            url: url.into(),
            headers: HashMap::new(),
            query: Vec::new(),
            body: RequestBody::None,
            timeout: None,
            max_retries: None,
            retry_strategy: None,
        }
    }

    /// 添加请求头
    pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
        self.headers.insert(name.into(), value.into());
        self
    }

    /// 批量添加请求头
    pub fn headers(mut self, headers: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>) -> Self {
        for (k, v) in headers {
            self.headers.insert(k.into(), v.into());
        }
        self
    }

    /// 设置 Bearer Token 认证
    pub fn bearer_auth(self, token: impl AsRef<str>) -> Self {
        self.header("Authorization", format!("Bearer {}", token.as_ref()))
    }

    /// 设置 Basic Auth 认证
    pub fn basic_auth(self, username: impl AsRef<str>, password: Option<&str>) -> Self {
        use base64::Engine;
        let credentials = match password {
            Some(p) => format!("{}:{}", username.as_ref(), p),
            None => username.as_ref().to_string(),
        };
        let encoded = base64::engine::general_purpose::STANDARD.encode(credentials);
        self.header("Authorization", format!("Basic {}", encoded))
    }

    /// 添加查询参数
    pub fn query(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.query.push((key.into(), value.into()));
        self
    }

    /// 批量添加查询参数
    pub fn queries(mut self, params: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>) -> Self {
        for (k, v) in params {
            self.query.push((k.into(), v.into()));
        }
        self
    }

    /// 设置 JSON 请求体
    pub fn json<T: Serialize>(mut self, body: &T) -> Result<Self, HttpError> {
        let bytes = serde_json::to_vec(body)
            .map_err(|e| HttpError::JsonSerialize(e.to_string()))?;
        self.body = RequestBody::Json(bytes);
        self.headers.insert("Content-Type".into(), "application/json".into());
        Ok(self)
    }

    /// 设置表单请求体
    pub fn form<T: Serialize>(mut self, body: &T) -> Result<Self, HttpError> {
        let encoded = serde_urlencoded::to_string(body)
            .map_err(|e| HttpError::FormEncode(e.to_string()))?;
        self.body = RequestBody::Form(encoded);
        self.headers
            .insert("Content-Type".into(), "application/x-www-form-urlencoded".into());
        Ok(self)
    }

    /// 设置原始字节请求体
    pub fn body(mut self, bytes: impl Into<Vec<u8>>) -> Self {
        self.body = RequestBody::Bytes(bytes.into());
        self
    }

    /// 设置文本请求体
    pub fn text(mut self, text: impl Into<String>) -> Self {
        self.body = RequestBody::Text(text.into());
        self.headers.insert("Content-Type".into(), "text/plain; charset=utf-8".into());
        self
    }

    /// 设置请求超时(覆盖客户端默认值)
    pub fn timeout(mut self, timeout: Duration) -> Self {
        self.timeout = Some(timeout);
        self
    }

    /// 设置此请求的重试次数(覆盖客户端默认值)
    pub fn max_retries(mut self, retries: u32) -> Self {
        self.max_retries = Some(retries);
        self
    }

    /// 禁用此请求的重试
    pub fn no_retry(mut self) -> Self {
        self.max_retries = Some(0);
        self.retry_strategy = Some(RetryStrategy::None);
        self
    }

    /// 附加取消令牌,使请求可取消
    pub fn with_cancellation(self, token: CancellationToken) -> CancellableRequest {
        CancellableRequest::new(self, token)
    }

    /// 构建最终 URL(包含查询参数)
    pub(crate) fn build_url(&self) -> Result<String, HttpError> {
        if self.query.is_empty() {
            return Ok(self.url.clone());
        }

        let query_string = self
            .query
            .iter()
            .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
            .collect::<Vec<_>>()
            .join("&");

        let separator = if self.url.contains('?') { "&" } else { "?" };
        Ok(format!("{}{}{}", self.url, separator, query_string))
    }
}

// ============================================================================
// 取消机制(简化版,不依赖外部 tasks 模块)
// ============================================================================

/// 取消令牌(只读)
///
/// 用于检查取消状态,传递给执行任务的代码
#[derive(Clone)]
pub struct CancellationToken {
    cancelled: Arc<AtomicBool>,
}

impl CancellationToken {
    /// 检查是否已取消
    pub fn is_cancelled(&self) -> bool {
        self.cancelled.load(Ordering::Acquire)
    }

    /// 异步等待取消信号
    pub async fn cancelled(&self) {
        while !self.is_cancelled() {
            tokio::time::sleep(Duration::from_millis(10)).await;
        }
    }
}

/// 取消触发器(只写)
///
/// 用于触发取消,保留在调用方
pub struct CancellationTrigger {
    cancelled: Arc<AtomicBool>,
}

impl CancellationTrigger {
    /// 触发取消
    pub fn cancel(&self) {
        self.cancelled.store(true, Ordering::Release);
    }
}

/// 创建取消令牌对
///
/// 返回 (CancellationToken, CancellationTrigger)
pub fn cancellation_pair() -> (CancellationToken, CancellationTrigger) {
    let cancelled = Arc::new(AtomicBool::new(false));
    (
        CancellationToken { cancelled: cancelled.clone() },
        CancellationTrigger { cancelled },
    )
}

/// 可取消的请求
///
/// 与取消系统集成,支持取消操作
pub struct CancellableRequest {
    pub(crate) builder: RequestBuilder,
    pub(crate) cancel_token: CancellationToken,
}

impl CancellableRequest {
    /// 创建可取消请求
    pub fn new(builder: RequestBuilder, token: CancellationToken) -> Self {
        Self {
            builder,
            cancel_token: token,
        }
    }
}

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

    #[test]
    fn test_request_builder_basic() {
        let req = RequestBuilder::new(Method::GET, "https://example.com")
            .header("Accept", "application/json")
            .query("page", "1")
            .query("limit", "10");

        assert_eq!(req.method, Method::GET);
        assert_eq!(req.headers.get("Accept"), Some(&"application/json".into()));
        assert_eq!(req.query.len(), 2);
    }

    #[test]
    fn test_build_url_with_query() {
        let req = RequestBuilder::new(Method::GET, "https://example.com/api")
            .query("name", "test")
            .query("value", "hello world");

        let url = req.build_url().unwrap();
        assert!(url.contains("name=test"));
        assert!(url.contains("value=hello%20world"));
    }

    #[test]
    fn test_bearer_auth() {
        let req = RequestBuilder::new(Method::GET, "https://example.com")
            .bearer_auth("my_token");

        assert_eq!(
            req.headers.get("Authorization"),
            Some(&"Bearer my_token".into())
        );
    }

    #[test]
    fn test_basic_auth() {
        let req = RequestBuilder::new(Method::GET, "https://example.com")
            .basic_auth("user", Some("pass"));

        let auth = req.headers.get("Authorization").unwrap();
        assert!(auth.starts_with("Basic "));
    }

    #[test]
    fn test_json_body() {
        #[derive(Serialize)]
        struct Data {
            name: String,
        }

        let req = RequestBuilder::new(Method::POST, "https://example.com")
            .json(&Data { name: "test".into() })
            .unwrap();

        assert_eq!(
            req.headers.get("Content-Type"),
            Some(&"application/json".into())
        );
        matches!(req.body, RequestBody::Json(_));
    }

    #[test]
    fn test_method_display() {
        assert_eq!(Method::GET.to_string(), "GET");
        assert_eq!(Method::POST.to_string(), "POST");
        assert_eq!(Method::PUT.to_string(), "PUT");
        assert_eq!(Method::DELETE.to_string(), "DELETE");
    }

    #[test]
    fn test_cancellation_token() {
        let (token, trigger) = cancellation_pair();
        assert!(!token.is_cancelled());
        trigger.cancel();
        assert!(token.is_cancelled());
    }
}