use std::sync::Arc;
use crate::launcher::Proxy;
use super::rotate::{RotateStrategy, Rotator};
#[derive(Clone)]
pub struct ProxyPool {
proxies: Arc<Vec<Proxy>>,
rotator: Arc<Rotator>,
}
impl ProxyPool {
pub fn new(proxies: Vec<Proxy>) -> Self {
Self::with_strategy(proxies, RotateStrategy::RoundRobin)
}
pub fn with_strategy(proxies: Vec<Proxy>, strategy: RotateStrategy) -> Self {
Self {
proxies: Arc::new(proxies),
rotator: Arc::new(Rotator::new(strategy)),
}
}
pub fn strategy(self, strategy: RotateStrategy) -> Self {
Self {
proxies: self.proxies,
rotator: Arc::new(Rotator::new(strategy)),
}
}
pub fn len(&self) -> usize {
self.proxies.len()
}
pub fn is_empty(&self) -> bool {
self.proxies.is_empty()
}
#[allow(clippy::should_implement_trait)] pub fn next(&self) -> Option<Proxy> {
self.rotator
.pick(self.proxies.len(), None)
.map(|i| self.proxies[i].clone())
}
pub fn for_key(&self, key: &str) -> Option<Proxy> {
self.rotator
.pick(self.proxies.len(), Some(key))
.map(|i| self.proxies[i].clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pool() -> ProxyPool {
ProxyPool::new(vec![
Proxy::new("http://a:1"),
Proxy::new("http://b:2"),
Proxy::new("http://c:3"),
])
}
#[test]
fn round_robin_cycles() {
let p = pool();
let got: Vec<String> = (0..4).map(|_| p.next().unwrap().server).collect();
assert_eq!(got, vec!["http://a:1", "http://b:2", "http://c:3", "http://a:1"]);
}
#[test]
fn empty_pool_returns_none() {
let p = ProxyPool::new(vec![]);
assert!(p.is_empty());
assert!(p.next().is_none());
assert!(p.for_key("x").is_none());
}
#[test]
fn sticky_same_key_same_proxy() {
let p = pool().strategy(RotateStrategy::Sticky);
let a = p.for_key("acct-7").unwrap().server;
let b = p.for_key("acct-7").unwrap().server;
assert_eq!(a, b);
}
#[test]
fn clones_share_cursor() {
let p = pool();
let q = p.clone();
assert_eq!(p.next().unwrap().server, "http://a:1");
assert_eq!(q.next().unwrap().server, "http://b:2");
assert_eq!(p.next().unwrap().server, "http://c:3");
}
}