unistore-http 0.1.0

HTTP client capability for UniStore
Documentation
//! HTTP 错误类型定义
//!
//! 职责:
//! - 定义 HTTP 客户端操作的所有可能错误
//! - 提供错误分类(可重试/不可重试)
//! - 封装底层 reqwest 错误

use std::fmt;

/// HTTP 客户端错误
#[derive(Debug)]
pub enum HttpError {
    /// 连接超时
    ConnectTimeout,

    /// 请求超时
    RequestTimeout,

    /// 连接失败
    ConnectionFailed(String),

    /// 服务器错误 (5xx)
    ServerError(u16),

    /// 客户端错误 (4xx)
    ClientError(u16, String),

    /// 请求被取消
    Cancelled,

    /// 重定向过多
    TooManyRedirects,

    /// 无效的 URL
    InvalidUrl(String),

    /// 无效的请求头名称
    InvalidHeaderName(String),

    /// 无效的请求头值
    InvalidHeaderValue(String),

    /// JSON 序列化错误
    JsonSerialize(String),

    /// JSON 反序列化错误
    JsonDeserialize(String),

    /// 表单编码错误
    FormEncode(String),

    /// 请求体过大
    BodyTooLarge,

    /// 响应体读取错误
    ResponseBody(String),

    /// 底层请求错误
    Request(reqwest::Error),

    /// 构建客户端失败
    ClientBuild(String),

    /// 其他错误
    Other(String),
}

impl fmt::Display for HttpError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ConnectTimeout => write!(f, "连接超时"),
            Self::RequestTimeout => write!(f, "请求超时"),
            Self::ConnectionFailed(msg) => write!(f, "连接失败: {}", msg),
            Self::ServerError(code) => write!(f, "服务器错误: {}", code),
            Self::ClientError(code, msg) => write!(f, "客户端错误 {}: {}", code, msg),
            Self::Cancelled => write!(f, "请求被取消"),
            Self::TooManyRedirects => write!(f, "重定向过多"),
            Self::InvalidUrl(url) => write!(f, "无效的 URL: {}", url),
            Self::InvalidHeaderName(name) => write!(f, "无效的请求头名称: {}", name),
            Self::InvalidHeaderValue(value) => write!(f, "无效的请求头值: {}", value),
            Self::JsonSerialize(msg) => write!(f, "JSON 序列化失败: {}", msg),
            Self::JsonDeserialize(msg) => write!(f, "JSON 反序列化失败: {}", msg),
            Self::FormEncode(msg) => write!(f, "表单编码失败: {}", msg),
            Self::BodyTooLarge => write!(f, "请求体过大"),
            Self::ResponseBody(msg) => write!(f, "响应体读取错误: {}", msg),
            Self::Request(e) => write!(f, "请求错误: {}", e),
            Self::ClientBuild(msg) => write!(f, "构建客户端失败: {}", msg),
            Self::Other(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for HttpError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Request(e) => Some(e),
            _ => None,
        }
    }
}

impl From<reqwest::Error> for HttpError {
    fn from(err: reqwest::Error) -> Self {
        if err.is_timeout() {
            if err.is_connect() {
                Self::ConnectTimeout
            } else {
                Self::RequestTimeout
            }
        } else if err.is_connect() {
            Self::ConnectionFailed(err.to_string())
        } else if err.is_redirect() {
            Self::TooManyRedirects
        } else {
            Self::Request(err)
        }
    }
}

impl HttpError {
    /// 判断错误是否可重试
    ///
    /// 可重试的错误类型:
    /// - 连接超时/请求超时
    /// - 连接失败(网络抖动)
    /// - 服务器错误 (5xx)
    pub fn is_retryable(&self) -> bool {
        matches!(
            self,
            Self::ConnectTimeout
                | Self::RequestTimeout
                | Self::ConnectionFailed(_)
                | Self::ServerError(_)
        )
    }

    /// 判断是否为客户端错误(通常不应重试)
    pub fn is_client_error(&self) -> bool {
        matches!(self, Self::ClientError(_, _))
    }

    /// 判断是否为服务器错误
    pub fn is_server_error(&self) -> bool {
        matches!(self, Self::ServerError(_))
    }

    /// 获取 HTTP 状态码(如果有)
    pub fn status_code(&self) -> Option<u16> {
        match self {
            Self::ServerError(code) | Self::ClientError(code, _) => Some(*code),
            _ => None,
        }
    }
}

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

    #[test]
    fn test_retryable_errors() {
        assert!(HttpError::ConnectTimeout.is_retryable());
        assert!(HttpError::RequestTimeout.is_retryable());
        assert!(HttpError::ConnectionFailed("test".into()).is_retryable());
        assert!(HttpError::ServerError(500).is_retryable());
        assert!(HttpError::ServerError(503).is_retryable());

        assert!(!HttpError::ClientError(400, "Bad Request".into()).is_retryable());
        assert!(!HttpError::InvalidUrl("bad".into()).is_retryable());
        assert!(!HttpError::Cancelled.is_retryable());
    }

    #[test]
    fn test_error_display() {
        assert_eq!(HttpError::ConnectTimeout.to_string(), "连接超时");
        assert_eq!(HttpError::ServerError(500).to_string(), "服务器错误: 500");
        assert!(HttpError::InvalidUrl("bad".into())
            .to_string()
            .contains("bad"));
    }

    #[test]
    fn test_status_code() {
        assert_eq!(HttpError::ServerError(500).status_code(), Some(500));
        assert_eq!(
            HttpError::ClientError(404, "Not Found".into()).status_code(),
            Some(404)
        );
        assert_eq!(HttpError::ConnectTimeout.status_code(), None);
    }
}