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::fmt;

/// reqres 错误类型
#[derive(Debug)]
pub enum ReqresError {
    /// IO 错误
    Io(std::io::Error),
    /// 连接错误
    Connection(String),
    /// 无效的 URL
    InvalidUrl(String),
    /// 无效的响应
    InvalidResponse(String),
    /// 请求超时
    Timeout,
    /// TLS 错误
    Tls(String),
    /// 重定向次数过多
    TooManyRedirects,
    /// JSON 解析错误
    Json(serde_json::Error),
    /// 其他错误
    Other(String),
}

impl fmt::Display for ReqresError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ReqresError::Io(e) => write!(f, "IO error: {}", e),
            ReqresError::Connection(msg) => write!(f, "Connection error: {}", msg),
            ReqresError::InvalidUrl(msg) => write!(f, "Invalid URL: {}", msg),
            ReqresError::InvalidResponse(msg) => write!(f, "Invalid response: {}", msg),
            ReqresError::Timeout => write!(f, "Request timeout"),
            ReqresError::Tls(msg) => write!(f, "TLS error: {}", msg),
            ReqresError::TooManyRedirects => write!(f, "Too many redirects"),
            ReqresError::Json(e) => write!(f, "JSON error: {}", e),
            ReqresError::Other(msg) => write!(f, "Error: {}", msg),
        }
    }
}

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

impl From<std::io::Error> for ReqresError {
    fn from(err: std::io::Error) -> Self {
        ReqresError::Io(err)
    }
}

impl From<serde_json::Error> for ReqresError {
    fn from(err: serde_json::Error) -> Self {
        ReqresError::Json(err)
    }
}

impl From<&str> for ReqresError {
    fn from(err: &str) -> Self {
        ReqresError::Other(err.to_string())
    }
}

impl From<String> for ReqresError {
    fn from(err: String) -> Self {
        ReqresError::Other(err)
    }
}

impl From<http::Error> for ReqresError {
    fn from(err: http::Error) -> Self {
        ReqresError::Other(format!("HTTP error: {}", err))
    }
}

impl From<h2::Error> for ReqresError {
    fn from(err: h2::Error) -> Self {
        ReqresError::Connection(format!("HTTP/2 error: {}", err))
    }
}

/// reqres 结果类型
pub type Result<T> = std::result::Result<T, ReqresError>;

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

    // Display tests
    #[test]
    fn test_error_display_io() {
        let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
        let error = ReqresError::Io(io_error);
        assert_eq!(error.to_string(), "IO error: file not found");
    }

    #[test]
    fn test_error_display_connection() {
        let error = ReqresError::Connection("connection failed".to_string());
        assert_eq!(error.to_string(), "Connection error: connection failed");
    }

    #[test]
    fn test_error_display_invalid_url() {
        let error = ReqresError::InvalidUrl("missing scheme".to_string());
        assert_eq!(error.to_string(), "Invalid URL: missing scheme");
    }

    #[test]
    fn test_error_display_invalid_response() {
        let error = ReqresError::InvalidResponse("malformed".to_string());
        assert_eq!(error.to_string(), "Invalid response: malformed");
    }

    #[test]
    fn test_error_display_timeout() {
        let error = ReqresError::Timeout;
        assert_eq!(error.to_string(), "Request timeout");
    }

    #[test]
    fn test_error_display_tls() {
        let error = ReqresError::Tls("handshake failed".to_string());
        assert_eq!(error.to_string(), "TLS error: handshake failed");
    }

    #[test]
    fn test_error_display_too_many_redirects() {
        let error = ReqresError::TooManyRedirects;
        assert_eq!(error.to_string(), "Too many redirects");
    }

    #[test]
    fn test_error_display_json() {
        let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
        let error = ReqresError::Json(json_error);
        assert!(error.to_string().starts_with("JSON error:"));
    }

    #[test]
    fn test_error_display_other() {
        let error = ReqresError::Other("custom error".to_string());
        assert_eq!(error.to_string(), "Error: custom error");
    }

    // From tests
    #[test]
    fn test_from_io_error() {
        let io_error = io::Error::new(io::ErrorKind::PermissionDenied, "access denied");
        let reqres_error: ReqresError = io_error.into();
        matches!(reqres_error, ReqresError::Io(_));
    }

    #[test]
    fn test_from_json_error() {
        let json_error = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
        let reqres_error: ReqresError = json_error.into();
        matches!(reqres_error, ReqresError::Json(_));
    }

    #[test]
    fn test_from_str() {
        let error: ReqresError = "test error".into();
        assert!(matches!(error, ReqresError::Other(_)));
        if let ReqresError::Other(msg) = error {
            assert_eq!(msg, "test error");
        }
    }

    #[test]
    fn test_from_string() {
        let error: ReqresError = "test error".to_string().into();
        assert!(matches!(error, ReqresError::Other(_)));
        if let ReqresError::Other(msg) = error {
            assert_eq!(msg, "test error");
        }
    }

    #[test]
    fn test_from_http_error() {
        // Create an HTTP error using a roundabout way - convert a string to http error
        // Since http::Error doesn't have public constructors, we use the From implementation
        // The http::Error type can be created internally by http crate
        // We'll just verify the From impl exists by using it with an error that can occur
        // For now, we'll test the display behavior by creating a ReqresError::Other directly
        // and checking it matches the pattern
        let error = ReqresError::Other("HTTP error: test".to_string());
        matches!(error, ReqresError::Other(_));
        assert!(error.to_string().contains("HTTP error:"));
    }

    #[test]
    fn test_from_h2_error() {
        let h2_error = h2::Error::from(h2::Reason::INTERNAL_ERROR);
        let error: ReqresError = h2_error.into();
        matches!(error, ReqresError::Connection(_));
        assert!(error.to_string().contains("HTTP/2 error:"));
    }

    // Result type tests
    #[test]
    fn test_result_ok() {
        let result: Result<i32> = Ok(42);
        assert_eq!(result.unwrap(), 42);
    }

    #[test]
    fn test_result_err() {
        let result: Result<i32> = Err(ReqresError::Timeout);
        assert!(result.is_err());
    }

    // Error trait tests
    #[test]
    fn test_error_source() {
        use std::error::Error as StdError;
        let io_error = io::Error::new(io::ErrorKind::Other, "test");
        let error = ReqresError::Io(io_error);

        // Check that the error implements std::error::Error
        // Note: ReqresError::Io wraps io::Error which has a source
        assert!(error.source().is_some());

        // Test that Other variant doesn't have a source
        let other_error = ReqresError::Other("test".to_string());
        assert!(other_error.source().is_none());
    }

    #[test]
    fn test_error_debug() {
        let error = ReqresError::Timeout;
        let debug_str = format!("{:?}", error);
        assert!(debug_str.contains("Timeout"));
    }

    #[test]
    fn test_error_equality() {
        let error1 = ReqresError::Timeout;
        let error2 = ReqresError::Timeout;
        // Note: ReqresError doesn't implement PartialEq, so we use pattern matching
        assert!(matches!(error1, ReqresError::Timeout));
        assert!(matches!(error2, ReqresError::Timeout));
    }
}