use std::env;
use std::path::PathBuf;
use std::time::Duration;
use crate::Error;
const DEFAULT_HEALTH_CHECK_TIMEOUT: Duration = Duration::from_secs(2);
const DEFAULT_REGISTER_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_UNREGISTER_TIMEOUT: Duration = Duration::from_secs(2);
const DEFAULT_LLDB_START_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, Clone)]
pub struct Config {
pub name: Option<String>,
pub control_host: String,
pub advertise_host: Option<String>,
pub control_port: u16,
pub debug_port: u16,
pub daemon_url: String,
pub lldb_dap_path: Option<PathBuf>,
pub detrix_home: Option<PathBuf>,
pub workspace_root: Option<String>,
pub safe_mode: bool,
pub build_commit: Option<String>,
pub build_tag: Option<String>,
pub health_check_timeout: Duration,
pub register_timeout: Duration,
pub unregister_timeout: Duration,
pub lldb_start_timeout: Duration,
}
impl Default for Config {
fn default() -> Self {
Self {
name: None,
control_host: "127.0.0.1".to_string(),
advertise_host: None,
control_port: 0,
debug_port: 0,
daemon_url: "http://127.0.0.1:8090".to_string(),
lldb_dap_path: None,
detrix_home: None,
workspace_root: None,
safe_mode: false,
build_commit: None,
build_tag: None,
health_check_timeout: DEFAULT_HEALTH_CHECK_TIMEOUT,
register_timeout: DEFAULT_REGISTER_TIMEOUT,
unregister_timeout: DEFAULT_UNREGISTER_TIMEOUT,
lldb_start_timeout: DEFAULT_LLDB_START_TIMEOUT,
}
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn with_env_overrides(mut self) -> Self {
if self.name.is_none() {
if let Ok(name) = env::var("DETRIX_NAME") {
if !name.is_empty() {
self.name = Some(name);
}
}
}
if self.control_host == "127.0.0.1" {
if let Ok(host) = env::var("DETRIX_CONTROL_HOST") {
if !host.is_empty() {
self.control_host = host;
}
}
}
if self.advertise_host.is_none() {
if let Ok(host) = env::var("DETRIX_HOST") {
if !host.is_empty() {
self.advertise_host = Some(host);
}
}
}
if self.control_port == 0 {
if let Ok(port_str) = env::var("DETRIX_CONTROL_PORT") {
if let Ok(port) = port_str.parse() {
self.control_port = port;
}
}
}
if self.debug_port == 0 {
if let Ok(port_str) = env::var("DETRIX_DEBUG_PORT") {
if let Ok(port) = port_str.parse() {
self.debug_port = port;
}
}
}
if self.daemon_url == "http://127.0.0.1:8090" {
if let Ok(url) = env::var("DETRIX_DAEMON_URL") {
if !url.is_empty() {
self.daemon_url = url;
}
}
}
if self.lldb_dap_path.is_none() {
if let Ok(path) = env::var("DETRIX_LLDB_DAP_PATH") {
if !path.is_empty() {
self.lldb_dap_path = Some(PathBuf::from(path));
}
}
}
if self.detrix_home.is_none() {
if let Ok(home) = env::var("DETRIX_HOME") {
if !home.is_empty() {
self.detrix_home = Some(PathBuf::from(home));
}
}
}
if self.workspace_root.is_none() {
if let Ok(root) = env::var("DETRIX_WORKSPACE_ROOT") {
if !root.is_empty() {
self.workspace_root = Some(root);
}
}
}
if self.health_check_timeout == DEFAULT_HEALTH_CHECK_TIMEOUT {
if let Some(d) = Self::parse_duration_env("DETRIX_HEALTH_CHECK_TIMEOUT") {
self.health_check_timeout = d;
}
}
if self.register_timeout == DEFAULT_REGISTER_TIMEOUT {
if let Some(d) = Self::parse_duration_env("DETRIX_REGISTER_TIMEOUT") {
self.register_timeout = d;
}
}
if self.unregister_timeout == DEFAULT_UNREGISTER_TIMEOUT {
if let Some(d) = Self::parse_duration_env("DETRIX_UNREGISTER_TIMEOUT") {
self.unregister_timeout = d;
}
}
self
}
fn parse_duration_env(key: &str) -> Option<Duration> {
env::var(key)
.ok()
.and_then(|v| v.parse::<f64>().ok())
.map(Duration::from_secs_f64)
}
pub fn connection_name(&self) -> String {
self.name
.clone()
.unwrap_or_else(|| format!("detrix-client-{}", std::process::id()))
}
pub fn detrix_home_path(&self) -> Option<PathBuf> {
self.detrix_home
.clone()
.or_else(|| dirs::home_dir().map(|home| home.join("detrix")))
}
}
#[derive(Debug, Clone)]
pub struct TlsConfig {
pub verify: bool,
pub ca_bundle: Option<PathBuf>,
}
impl Default for TlsConfig {
fn default() -> Self {
Self {
verify: true,
ca_bundle: None,
}
}
}
impl TlsConfig {
pub fn validate(&self) -> Result<(), Error> {
if let Some(ref path) = self.ca_bundle {
if !path.is_file() {
return Err(Error::ConfigError(format!(
"CA bundle not found: {}",
path.display()
)));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert!(config.name.is_none());
assert_eq!(config.control_host, "127.0.0.1");
assert_eq!(config.control_port, 0);
assert_eq!(config.debug_port, 0);
assert_eq!(config.daemon_url, "http://127.0.0.1:8090");
assert!(config.lldb_dap_path.is_none());
assert!(!config.safe_mode);
}
#[test]
fn test_register_timeout_default() {
let config = Config::default();
assert_eq!(config.register_timeout, Duration::from_secs(5));
}
#[test]
fn test_connection_name_default() {
let config = Config::default();
let name = config.connection_name();
assert!(name.starts_with("detrix-client-"));
}
#[test]
fn test_connection_name_custom() {
let config = Config {
name: Some("my-service".to_string()),
..Config::default()
};
assert_eq!(config.connection_name(), "my-service");
}
#[test]
fn test_detrix_home_path() {
let config = Config::default();
if let Some(path) = config.detrix_home_path() {
assert!(path.ends_with("detrix"));
}
}
#[test]
fn test_tls_config_default() {
let config = TlsConfig::default();
assert!(config.verify);
assert!(config.ca_bundle.is_none());
}
#[test]
fn test_tls_config_validate_missing_ca_bundle() {
let config = TlsConfig {
verify: true,
ca_bundle: Some(PathBuf::from("/nonexistent/ca.pem")),
};
assert!(config.validate().is_err());
}
#[test]
fn test_tls_config_validate_no_ca_bundle() {
let config = TlsConfig::default();
assert!(config.validate().is_ok());
}
}