use serde::{Deserialize, Serialize};
use url::Url;
use crate::config::{Config, MergeError, ValidationError};
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct ProxyConfig {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub https: Option<Url>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub http: Option<Url>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub non_proxy_hosts: Vec<String>,
}
impl ProxyConfig {
pub fn from_env() -> Self {
let env = |keys: &[&str]| {
keys.iter()
.find_map(|&k| std::env::var(k).ok().filter(|v| !v.is_empty()))
};
let http = env(&["http_proxy", "all_proxy", "ALL_PROXY"]);
let https = env(&["https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"]);
if http.is_none() && https.is_none() {
return Self::default();
}
Self {
https: https.and_then(|s| Url::parse(&s).ok()),
http: http.and_then(|s| Url::parse(&s).ok()),
non_proxy_hosts: env(&["no_proxy", "NO_PROXY"])
.map(|s| s.split(',').map(String::from).collect())
.unwrap_or_default(),
}
}
pub fn is_default(&self) -> bool {
self.https.is_none() && self.http.is_none() && self.non_proxy_hosts.is_empty()
}
}
impl Config for ProxyConfig {
fn merge_config(self, other: &Self) -> Result<Self, MergeError> {
Ok(Self {
https: other.https.as_ref().or(self.https.as_ref()).cloned(),
http: other.http.as_ref().or(self.http.as_ref()).cloned(),
non_proxy_hosts: if other.is_default() {
self.non_proxy_hosts.clone()
} else {
other.non_proxy_hosts.clone()
},
})
}
fn validate(&self) -> Result<(), ValidationError> {
Ok(())
}
fn keys(&self) -> Vec<String> {
vec![
"https".to_string(),
"http".to_string(),
"non-proxy-hosts".to_string(),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_accepts_empty() {
let config = ProxyConfig {
https: None,
http: None,
non_proxy_hosts: vec![],
};
config.validate().expect("empty proxy config is valid");
}
}