use std::time::Duration; pub struct Config { pub token: String, pub device_id: String, pub base_url: String, pub max_output_bytes: u64, pub writer_channel_capacity: usize, pub ping_interval: Duration, pub ping_timeout: Duration, } impl Config { const DEFAULT_BASE_URL: &str = "ws://127.0.0.1:8000"; const DEFAULT_DEVICE_ID: &str = "device"; const DEFAULT_MAX_OUTPUT_BYTES: u64 = 10 * 1024 * 1024; const DEFAULT_WRITER_CHANNEL_CAPACITY: usize = 32; const DEFAULT_PING_INTERVAL_SECS: u64 = 30; const DEFAULT_PING_TIMEOUT_SECS: u64 = 10; pub fn from_env() -> Result { let token = std::env::var("TOKEN") .map_err(|_| "TOKEN environment variable is required".to_string())?; let device_id = std::env::var("DEVICE_ID").unwrap_or_else(|_| Self::DEFAULT_DEVICE_ID.to_string()); validate_device_id(&device_id).map_err(|e| format!("Invalid DEVICE_ID: {e}"))?; let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| Self::DEFAULT_BASE_URL.to_string()); Ok(Self { token, device_id, base_url, max_output_bytes: parse_env("MAX_OUTPUT_BYTES", Self::DEFAULT_MAX_OUTPUT_BYTES), writer_channel_capacity: parse_env( "WRITER_CHANNEL_CAPACITY", Self::DEFAULT_WRITER_CHANNEL_CAPACITY, ), ping_interval: Duration::from_secs(parse_env( "PING_INTERVAL_SECS", Self::DEFAULT_PING_INTERVAL_SECS, )), ping_timeout: Duration::from_secs(parse_env( "PING_TIMEOUT_SECS", Self::DEFAULT_PING_TIMEOUT_SECS, )), }) } } fn parse_env(key: &str, default: T) -> T { std::env::var(key) .ok() .and_then(|v| v.parse().ok()) .unwrap_or(default) } fn validate_device_id(id: &str) -> Result<(), String> { if id.is_empty() { return Err("cannot be empty".into()); } if id.len() > 64 { return Err("too long (max 64 characters)".into()); } if !id .chars() .all(|c| c.is_alphanumeric() || c == '-' || c == '_') { return Err("contains invalid characters (allowed: a-z A-Z 0-9 - _)".into()); } Ok(()) }