use std::sync::atomic::{AtomicUsize, Ordering};
use crate::types::ProxyConfig;
pub trait ProxyProvider: std::fmt::Debug + Send + Sync + 'static {
fn next_proxy(&self, host: &str) -> Option<ProxyConfig>;
}
#[derive(Debug)]
pub struct StaticProxyProvider {
entries: Vec<ProxyConfig>,
counter: AtomicUsize,
}
impl StaticProxyProvider {
pub fn new(entries: Vec<ProxyConfig>) -> Self {
Self {
entries,
counter: AtomicUsize::new(0),
}
}
pub fn empty() -> Self {
Self::new(Vec::new())
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl ProxyProvider for StaticProxyProvider {
fn next_proxy(&self, _host: &str) -> Option<ProxyConfig> {
if self.entries.is_empty() {
return None;
}
let idx = self.counter.fetch_add(1, Ordering::Relaxed) % self.entries.len();
Some(self.entries[idx].clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn proxy(url: &str) -> ProxyConfig {
ProxyConfig {
url: url.into(),
username: None,
password: None,
}
}
#[test]
fn empty_provider_returns_none() {
let provider = StaticProxyProvider::empty();
assert!(provider.next_proxy("example.com").is_none());
assert!(provider.is_empty());
assert_eq!(provider.len(), 0);
}
#[test]
fn round_robin_cycles_through_pool() {
let provider = StaticProxyProvider::new(vec![
proxy("http://p1:8080"),
proxy("http://p2:8080"),
proxy("http://p3:8080"),
]);
let urls: Vec<_> = (0..6)
.map(|_| provider.next_proxy("example.com").unwrap().url)
.collect();
assert_eq!(
urls,
vec![
"http://p1:8080",
"http://p2:8080",
"http://p3:8080",
"http://p1:8080",
"http://p2:8080",
"http://p3:8080",
]
);
}
}