use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProxyType {
Http,
Https,
#[cfg(feature = "socks")]
Socks4,
#[cfg(feature = "socks")]
Socks5,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct Proxy {
pub url: String,
pub proxy_type: ProxyType,
pub username: Option<String>,
pub password: Option<String>,
pub weight: u32,
pub tags: Vec<String>,
}
impl std::fmt::Debug for Proxy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Proxy")
.field("url", &self.url)
.field("proxy_type", &self.proxy_type)
.field("username", &self.username)
.field("password", &self.password.as_deref().map(|_| "***"))
.field("weight", &self.weight)
.field("tags", &self.tags)
.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct ProxyRecord {
pub id: Uuid,
pub proxy: Proxy,
#[serde(skip, default = "Instant::now")]
pub added_at: Instant,
}
impl ProxyRecord {
pub fn new(proxy: Proxy) -> Self {
Self {
id: Uuid::new_v4(),
proxy,
added_at: Instant::now(),
}
}
}
#[derive(Debug, Default)]
pub struct ProxyMetrics {
pub requests_total: AtomicU64,
pub successes: AtomicU64,
pub failures: AtomicU64,
pub total_latency_ms: AtomicU64,
}
impl ProxyMetrics {
pub fn success_rate(&self) -> f64 {
let total = self.requests_total.load(Ordering::Relaxed);
if total == 0 {
return 0.0;
}
self.successes.load(Ordering::Relaxed) as f64 / total as f64
}
pub fn avg_latency_ms(&self) -> f64 {
let total = self.requests_total.load(Ordering::Relaxed);
if total == 0 {
return 0.0;
}
self.total_latency_ms.load(Ordering::Relaxed) as f64 / total as f64
}
}
mod serde_duration_secs {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::time::Duration;
pub fn serialize<S: Serializer>(d: &Duration, s: S) -> Result<S::Ok, S::Error> {
d.as_secs().serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Duration, D::Error> {
Ok(Duration::from_secs(u64::deserialize(d)?))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct ProxyConfig {
pub health_check_url: String,
#[serde(with = "serde_duration_secs")]
pub health_check_interval: Duration,
#[serde(with = "serde_duration_secs")]
pub health_check_timeout: Duration,
pub circuit_open_threshold: u32,
#[serde(with = "serde_duration_secs")]
pub circuit_half_open_after: Duration,
#[serde(default)]
pub sticky_policy: crate::session::StickyPolicy,
}
impl Default for ProxyConfig {
fn default() -> Self {
Self {
health_check_url: "https://httpbin.org/ip".into(),
health_check_interval: Duration::from_secs(60),
health_check_timeout: Duration::from_secs(5),
circuit_open_threshold: 5,
circuit_half_open_after: Duration::from_secs(30),
sticky_policy: crate::session::StickyPolicy::default(),
}
}
}