use std::sync::Arc;
use async_trait::async_trait;
use uuid::Uuid;
use crate::error::ProxyResult;
use crate::types::ProxyMetrics;
mod least_used;
mod random;
mod round_robin;
mod weighted;
pub use least_used::LeastUsedStrategy;
pub use random::RandomStrategy;
pub use round_robin::RoundRobinStrategy;
pub use weighted::WeightedStrategy;
#[derive(Debug, Clone)]
pub struct ProxyCandidate {
pub id: Uuid,
pub weight: u32,
pub metrics: Arc<ProxyMetrics>,
pub healthy: bool,
}
#[async_trait]
pub trait RotationStrategy: Send + Sync + 'static {
async fn select<'a>(&self, candidates: &'a [ProxyCandidate])
-> ProxyResult<&'a ProxyCandidate>;
}
pub type BoxedRotationStrategy = Arc<dyn RotationStrategy>;
pub fn healthy_candidates(all: &[ProxyCandidate]) -> Vec<&ProxyCandidate> {
all.iter().filter(|c| c.healthy).collect()
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::error::ProxyError;
use std::sync::atomic::Ordering;
pub fn candidate(id: u128, healthy: bool, weight: u32, requests: u64) -> ProxyCandidate {
let metrics = Arc::new(ProxyMetrics::default());
metrics.requests_total.store(requests, Ordering::Relaxed);
ProxyCandidate {
id: Uuid::from_u128(id),
weight,
metrics,
healthy,
}
}
#[tokio::test]
async fn healthy_candidates_filters() {
let c = vec![
candidate(1, true, 1, 0),
candidate(2, false, 1, 0),
candidate(3, true, 1, 0),
];
let healthy = healthy_candidates(&c);
assert_eq!(healthy.len(), 2);
assert!(healthy.iter().all(|c| c.healthy));
}
#[tokio::test]
async fn all_unhealthy_returns_error() {
let c = vec![candidate(1, false, 1, 0), candidate(2, false, 1, 0)];
assert!(matches!(
RoundRobinStrategy::default().select(&c).await,
Err(ProxyError::AllProxiesUnhealthy)
));
}
}