//! Configuration management for CalDAV synchronizer use serde::{Deserialize, Serialize}; use std::path::Path; use anyhow::Result; /// Main configuration structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { /// Server configuration pub server: ServerConfig, /// Calendar configuration pub calendar: CalendarConfig, /// Filter configuration pub filters: Option, /// Sync configuration pub sync: SyncConfig, } /// Server connection configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { /// CalDAV server URL pub url: String, /// Username for authentication pub username: String, /// Password for authentication pub password: String, /// Whether to use HTTPS pub use_https: bool, /// Timeout in seconds pub timeout: u64, /// Custom headers to send with requests pub headers: Option>, } /// Calendar-specific configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CalendarConfig { /// Calendar name/path pub name: String, /// Calendar display name pub display_name: Option, /// Calendar color pub color: Option, /// Calendar timezone pub timezone: String, /// Whether to sync this calendar pub enabled: bool, } /// Filter configuration for events #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FilterConfig { /// Start date filter (ISO 8601) pub start_date: Option, /// End date filter (ISO 8601) pub end_date: Option, /// Event types to include pub event_types: Option>, /// Keywords to filter by pub keywords: Option>, /// Exclude keywords pub exclude_keywords: Option>, } /// Synchronization configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SyncConfig { /// Sync interval in seconds pub interval: u64, /// Whether to sync on startup pub sync_on_startup: bool, /// Maximum number of retries pub max_retries: u32, /// Retry delay in seconds pub retry_delay: u64, /// Whether to delete events not found on server pub delete_missing: bool, /// Date range configuration pub date_range: DateRangeConfig, } /// Date range configuration for event synchronization #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DateRangeConfig { /// Number of days ahead to sync pub days_ahead: i64, /// Number of days in the past to sync pub days_back: i64, /// Whether to sync all events regardless of date pub sync_all_events: bool, } impl Default for Config { fn default() -> Self { Self { server: ServerConfig::default(), calendar: CalendarConfig::default(), filters: None, sync: SyncConfig::default(), } } } impl Default for ServerConfig { fn default() -> Self { Self { url: "https://caldav.example.com".to_string(), username: String::new(), password: String::new(), use_https: true, timeout: 30, headers: None, } } } impl Default for CalendarConfig { fn default() -> Self { Self { name: "calendar".to_string(), display_name: None, color: None, timezone: "UTC".to_string(), enabled: true, } } } impl Default for SyncConfig { fn default() -> Self { Self { interval: 300, // 5 minutes sync_on_startup: true, max_retries: 3, retry_delay: 5, delete_missing: false, date_range: DateRangeConfig::default(), } } } impl Default for DateRangeConfig { fn default() -> Self { Self { days_ahead: 7, // Next week days_back: 0, // Today only sync_all_events: false, } } } impl Config { /// Load configuration from a TOML file pub fn from_file>(path: P) -> Result { let content = std::fs::read_to_string(path)?; let config: Config = toml::from_str(&content)?; Ok(config) } /// Save configuration to a TOML file pub fn to_file>(&self, path: P) -> Result<()> { let content = toml::to_string_pretty(self)?; std::fs::write(path, content)?; Ok(()) } /// Load configuration from environment variables pub fn from_env() -> Result { let mut config = Config::default(); if let Ok(url) = std::env::var("CALDAV_URL") { config.server.url = url; } if let Ok(username) = std::env::var("CALDAV_USERNAME") { config.server.username = username; } if let Ok(password) = std::env::var("CALDAV_PASSWORD") { config.server.password = password; } if let Ok(calendar) = std::env::var("CALDAV_CALENDAR") { config.calendar.name = calendar; } Ok(config) } /// Validate configuration pub fn validate(&self) -> Result<()> { if self.server.url.is_empty() { anyhow::bail!("Server URL cannot be empty"); } if self.server.username.is_empty() { anyhow::bail!("Username cannot be empty"); } if self.server.password.is_empty() { anyhow::bail!("Password cannot be empty"); } if self.calendar.name.is_empty() { anyhow::bail!("Calendar name cannot be empty"); } Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_default_config() { let config = Config::default(); assert_eq!(config.server.url, "https://caldav.example.com"); assert_eq!(config.calendar.name, "calendar"); assert_eq!(config.sync.interval, 300); } #[test] fn test_config_validation() { let mut config = Config::default(); assert!(config.validate().is_err()); // Empty username/password config.server.username = "test".to_string(); config.server.password = "test".to_string(); assert!(config.validate().is_ok()); } }