use std::sync::Arc;
use std::time::Duration;
pub type AuthProvider = Arc<dyn Fn() -> Option<String> + Send + Sync>;
#[derive(Clone)]
pub struct Config {
socks5: Option<Socks5Config>,
timeout: Option<Duration>,
retry: u8,
validate_domain: bool,
authorization_provider: Option<AuthProvider>,
}
impl std::fmt::Debug for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Config")
.field("socks5", &self.socks5)
.field("timeout", &self.timeout)
.field("retry", &self.retry)
.field("validate_domain", &self.validate_domain)
.field(
"authorization_provider",
&self.authorization_provider.as_ref().map(|_| "<provider>"),
)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct Socks5Config {
pub addr: String,
pub credentials: Option<Socks5Credential>,
}
#[derive(Debug, Clone)]
pub struct Socks5Credential {
pub username: String,
pub password: String,
}
pub struct ConfigBuilder {
config: Config,
}
impl ConfigBuilder {
pub fn new() -> Self {
ConfigBuilder {
config: Config::default(),
}
}
pub fn socks5(mut self, socks5_config: Option<Socks5Config>) -> Self {
self.config.socks5 = socks5_config;
self
}
pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
self.config.timeout = timeout;
self
}
pub fn retry(mut self, retry: u8) -> Self {
self.config.retry = retry;
self
}
pub fn validate_domain(mut self, validate_domain: bool) -> Self {
self.config.validate_domain = validate_domain;
self
}
pub fn authorization_provider(mut self, provider: Option<AuthProvider>) -> Self {
self.config.authorization_provider = provider;
self
}
pub fn build(self) -> Config {
self.config
}
}
impl Default for ConfigBuilder {
fn default() -> Self {
Self::new()
}
}
impl Socks5Config {
pub fn new(addr: impl ToString) -> Self {
let addr = addr.to_string().replacen("socks5://", "", 1);
Socks5Config {
addr,
credentials: None,
}
}
pub fn with_credentials(addr: impl ToString, username: String, password: String) -> Self {
let mut config = Socks5Config::new(addr);
config.credentials = Some(Socks5Credential { username, password });
config
}
}
impl Config {
pub fn socks5(&self) -> &Option<Socks5Config> {
&self.socks5
}
pub fn retry(&self) -> u8 {
self.retry
}
pub fn timeout(&self) -> Option<Duration> {
self.timeout
}
pub fn validate_domain(&self) -> bool {
self.validate_domain
}
pub fn authorization_provider(&self) -> Option<&AuthProvider> {
self.authorization_provider.as_ref()
}
pub fn builder() -> ConfigBuilder {
ConfigBuilder::new()
}
}
impl Default for Config {
fn default() -> Self {
Config {
socks5: None,
timeout: None,
retry: 1,
validate_domain: true,
authorization_provider: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_authorization_provider_builder() {
let token = "test-token-123".to_string();
let provider = Arc::new(move || Some(format!("Bearer {}", token)));
let config = ConfigBuilder::new()
.authorization_provider(Some(provider.clone()))
.build();
assert!(config.authorization_provider().is_some());
if let Some(auth_provider) = config.authorization_provider() {
assert_eq!(auth_provider(), Some("Bearer test-token-123".to_string()));
}
}
#[test]
fn test_authorization_provider_none() {
let config = ConfigBuilder::new().build();
assert!(config.authorization_provider().is_none());
}
#[test]
fn test_authorization_provider_returns_none() {
let provider = Arc::new(|| None);
let config = ConfigBuilder::new()
.authorization_provider(Some(provider))
.build();
assert!(config.authorization_provider().is_some());
if let Some(auth_provider) = config.authorization_provider() {
assert_eq!(auth_provider(), None);
}
}
#[test]
fn test_authorization_provider_dynamic_token() {
use std::sync::RwLock;
let token = Arc::new(RwLock::new("initial-token".to_string()));
let token_clone = token.clone();
let provider = Arc::new(move || Some(token_clone.read().unwrap().clone()));
let config = ConfigBuilder::new()
.authorization_provider(Some(provider.clone()))
.build();
if let Some(auth_provider) = config.authorization_provider() {
assert_eq!(auth_provider(), Some("initial-token".to_string()));
}
*token.write().unwrap() = "refreshed-token".to_string();
if let Some(auth_provider) = config.authorization_provider() {
assert_eq!(auth_provider(), Some("refreshed-token".to_string()));
}
}
#[test]
fn test_config_debug_with_provider() {
let provider = Arc::new(|| Some("secret-token".to_string()));
let config = ConfigBuilder::new()
.authorization_provider(Some(provider))
.build();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("<provider>"));
assert!(!debug_str.contains("secret-token"));
}
#[test]
fn test_config_debug_without_provider() {
let config = ConfigBuilder::new().build();
let debug_str = format!("{:?}", config);
assert!(debug_str.contains("authorization_provider"));
}
}