use std::fmt;
#[derive(Clone, Debug)]
pub struct ProxyConfig {
proxy_type: ProxyType,
url: String,
username: Option<String>,
password: Option<String>,
no_proxy: Vec<String>,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ProxyType {
Http,
Https,
Socks5,
}
impl ProxyConfig {
pub fn new(proxy_type: ProxyType, url: impl Into<String>) -> Self {
let url = url.into();
tracing::debug!(
proxy_type = ?proxy_type,
url = %url,
"🔧 Creating proxy config"
);
Self {
proxy_type,
url,
username: None,
password: None,
no_proxy: Vec::new(),
}
}
pub fn with_auth(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
let username = username.into();
tracing::debug!(
username = %username,
"🔧 Adding proxy authentication"
);
self.username = Some(username);
self.password = Some(password.into());
self
}
pub fn with_no_proxy(mut self, no_proxy: Vec<String>) -> Self {
tracing::debug!(
no_proxy = ?no_proxy,
count = no_proxy.len(),
"🔧 Setting no-proxy list"
);
self.no_proxy = no_proxy;
self
}
pub fn proxy_type(&self) -> &ProxyType {
&self.proxy_type
}
pub fn url(&self) -> &str {
&self.url
}
pub fn username(&self) -> Option<&str> {
self.username.as_deref()
}
pub fn password(&self) -> Option<&str> {
self.password.as_deref()
}
pub fn no_proxy(&self) -> &[String] {
&self.no_proxy
}
pub fn build_url(&self) -> String {
tracing::debug!(has_auth = self.username.is_some(), "🔧 Building proxy URL");
if let (Some(username), Some(password)) = (&self.username, &self.password) {
let username_enc = url::form_urlencoded::byte_serialize(username.as_bytes()).collect::<String>();
let password_enc = url::form_urlencoded::byte_serialize(password.as_bytes()).collect::<String>();
if let Some(idx) = self.url.find("://") {
let scheme = &self.url[..idx];
let rest = &self.url[idx + 3..];
format!("{}://{}:{}@{}", scheme, username_enc, password_enc, rest)
} else {
format!("{}:{}@{}", username_enc, password_enc, self.url)
}
} else {
self.url.clone()
}
}
pub fn to_reqwest_proxy(&self) -> reqwest::Result<reqwest::Proxy> {
let url = self.build_url();
tracing::debug!(proxy_type = ?self.proxy_type, no_proxy_count = self.no_proxy.len(), "🔧 Converting to reqwest proxy");
match self.proxy_type {
ProxyType::Http => reqwest::Proxy::http(&url),
ProxyType::Https => reqwest::Proxy::https(&url),
ProxyType::Socks5 => reqwest::Proxy::all(&url),
}
.map(|mut proxy| {
if !self.no_proxy.is_empty() {
proxy = proxy.no_proxy(reqwest::NoProxy::from_string(&self.no_proxy.join(",")));
}
proxy
})
}
pub fn to_ytdlp_arg(&self) -> String {
self.build_url()
}
}
impl fmt::Display for ProxyType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Http => f.write_str("Http"),
Self::Https => f.write_str("Https"),
Self::Socks5 => f.write_str("Socks5"),
}
}
}
impl fmt::Display for ProxyConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ProxyConfig(type={}, url={}, auth={})",
self.proxy_type,
self.url,
self.username.is_some()
)
}
}