use serde::Deserialize;
use trojan_config::{LoggingConfig, TcpConfig};
use crate::error::ClientError;
#[derive(Debug, Clone, Deserialize)]
pub struct ClientConfig {
pub client: ClientSettings,
#[serde(default)]
pub logging: LoggingConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClientSettings {
pub listen: String,
pub remote: String,
pub password: String,
#[serde(default)]
pub tls: ClientTlsConfig,
#[serde(default)]
pub tcp: TcpConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ClientTlsConfig {
pub sni: Option<String>,
#[serde(default = "default_alpn")]
pub alpn: Vec<String>,
#[serde(default)]
pub skip_verify: bool,
pub ca: Option<String>,
}
impl Default for ClientTlsConfig {
fn default() -> Self {
Self {
sni: None,
alpn: default_alpn(),
skip_verify: false,
ca: None,
}
}
}
fn default_alpn() -> Vec<String> {
vec!["h2".into(), "http/1.1".into()]
}
pub fn load_client_config(path: &std::path::Path) -> Result<ClientConfig, ClientError> {
let content = std::fs::read_to_string(path)
.map_err(|e| ClientError::Config(format!("failed to read config: {e}")))?;
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("toml");
match ext {
"toml" => toml::from_str(&content)
.map_err(|e| ClientError::Config(format!("TOML parse error: {e}"))),
"json" | "jsonc" => {
let stripped: String = content
.lines()
.map(|line| {
if let Some(idx) = line.find("//") {
let before = &line[..idx];
let quotes = before.chars().filter(|&c| c == '"').count();
if quotes % 2 == 0 { before } else { line }
} else {
line
}
})
.collect::<Vec<_>>()
.join("\n");
serde_json::from_str(&stripped)
.map_err(|e| ClientError::Config(format!("JSON parse error: {e}")))
}
_ => toml::from_str(&content)
.map_err(|e| ClientError::Config(format!("config parse error: {e}"))),
}
}