scatter-proxy 0.3.0

Async request scheduler for unreliable SOCKS5 proxies — multi-path race for maximum throughput
Documentation
use std::fmt;
use std::time::Duration;

/// Errors produced by the ScatterProxy scheduler.
#[derive(Debug)]
pub enum ScatterProxyError {
    /// A user-specified timeout elapsed before the task completed.
    Timeout { elapsed: Duration },
    /// The task pool is at capacity and cannot accept new submissions (non-blocking path).
    PoolFull { capacity: usize },
    /// An error occurred during initialization (e.g. failed to fetch proxy sources).
    Init(String),
}

impl fmt::Display for ScatterProxyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ScatterProxyError::Timeout { elapsed } => {
                write!(f, "task timeout after {:.1}s", elapsed.as_secs_f64())
            }
            ScatterProxyError::PoolFull { capacity } => {
                write!(f, "task pool is full (capacity: {capacity})")
            }
            ScatterProxyError::Init(reason) => {
                write!(f, "initialization error: {reason}")
            }
        }
    }
}

impl std::error::Error for ScatterProxyError {}

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

    #[test]
    fn display_timeout() {
        let err = ScatterProxyError::Timeout {
            elapsed: Duration::from_millis(8500),
        };
        let msg = err.to_string();
        assert!(msg.contains("8.5s"));
    }

    #[test]
    fn display_pool_full() {
        let err = ScatterProxyError::PoolFull { capacity: 1000 };
        let msg = err.to_string();
        assert!(msg.contains("task pool is full"));
        assert!(msg.contains("1000"));
    }

    #[test]
    fn display_init() {
        let err = ScatterProxyError::Init("failed to fetch proxy list".into());
        let msg = err.to_string();
        assert!(msg.contains("initialization error"));
        assert!(msg.contains("failed to fetch proxy list"));
    }

    #[test]
    fn error_trait_is_implemented() {
        let err: Box<dyn std::error::Error> = Box::new(ScatterProxyError::Init("test".into()));
        assert!(err.source().is_none());
    }

    #[test]
    fn debug_format_includes_variant_name() {
        let err = ScatterProxyError::Timeout {
            elapsed: Duration::from_secs(1),
        };
        let dbg = format!("{err:?}");
        assert!(dbg.contains("Timeout"));
    }

    #[test]
    fn timeout_sub_second_formatting() {
        let err = ScatterProxyError::Timeout {
            elapsed: Duration::from_millis(200),
        };
        assert!(err.to_string().contains("0.2s"));
    }

    #[test]
    fn timeout_exact_seconds_formatting() {
        let err = ScatterProxyError::Timeout {
            elapsed: Duration::from_secs(60),
        };
        assert!(err.to_string().contains("60.0s"));
    }
}