qorzen_oxide/config/
mod.rs

1// src/config/mod.rs
2
3//! Configuration management system with hot-reload support
4//!
5//! This module provides a flexible configuration system that supports:
6//! - Multiple configuration formats (YAML, JSON, TOML)
7//! - Environment variable overrides
8//! - Configuration validation
9//! - Hot-reloading with file watching
10//! - Hierarchical configuration merging
11//! - Type-safe configuration access
12
13use std::collections::HashMap;
14use std::fmt;
15use std::path::{Path, PathBuf};
16use std::sync::Arc;
17
18use crate::utils::Time;
19use async_trait::async_trait;
20use chrono::{DateTime, Utc};
21use serde::{Deserialize, Serialize};
22use serde_json::{Map, Number, Value};
23use tokio::sync::broadcast;
24use tokio::sync::RwLock;
25use uuid::Uuid;
26
27use crate::error::{Error, Result};
28use crate::event::{Event, EventBusManager};
29use crate::manager::{ManagedState, Manager, ManagerStatus};
30use crate::types::Metadata;
31
32pub mod tiered;
33pub use tiered::{ConfigurationTier, MemoryConfigStore, TieredConfigManager};
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct ConfigChangeEvent {
37    pub key: String,
38    pub value: Value,
39    pub old_value: Option<Value>,
40    pub timestamp: DateTime<Utc>,
41    pub source: String,
42    pub metadata: Metadata,
43}
44
45impl Event for ConfigChangeEvent {
46    fn event_type(&self) -> &'static str {
47        "config.changed"
48    }
49
50    fn source(&self) -> &str {
51        &self.source
52    }
53
54    fn metadata(&self) -> &Metadata {
55        &self.metadata
56    }
57
58    fn as_any(&self) -> &dyn std::any::Any {
59        self
60    }
61
62    fn timestamp(&self) -> DateTime<Utc> {
63        self.timestamp
64    }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct SettingsSchema {
69    pub version: String,
70    pub schema: Value,
71    pub defaults: Value,
72}
73
74#[derive(Debug, Clone)]
75pub struct ValidationError {
76    pub key: String,
77    pub message: String,
78}
79
80impl fmt::Display for ValidationError {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        write!(f, "Validation error for '{}': {}", self.key, self.message)
83    }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum ConfigFormat {
88    Yaml,
89    Json,
90    Toml,
91}
92
93impl ConfigFormat {
94    pub fn from_extension(path: &Path) -> Option<Self> {
95        match path.extension()?.to_str()? {
96            "yaml" | "yml" => Some(Self::Yaml),
97            "json" => Some(Self::Json),
98            "toml" => Some(Self::Toml),
99            _ => None,
100        }
101    }
102}
103
104#[derive(Debug, Clone)]
105pub enum ConfigSource {
106    File { path: PathBuf, format: ConfigFormat },
107    Environment { prefix: String },
108    Memory { data: Value },
109}
110
111#[derive(Debug, Clone)]
112pub struct ConfigLayer {
113    pub name: String,
114    pub source: ConfigSource,
115    pub priority: u32,
116    pub hot_reload: bool,
117}
118
119#[derive(Debug, Clone, Default, Serialize, Deserialize)]
120pub struct AppConfig {
121    pub app: AppSettings,
122    pub logging: LoggingConfig,
123    pub event_bus: EventBusConfig,
124    pub files: FileConfig,
125    pub tasks: TaskConfig,
126    pub concurrency: ConcurrencyConfig,
127    pub plugins: PluginConfig,
128    pub database: DatabaseConfig,
129    pub network: NetworkConfig,
130    pub security: SecurityConfig,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct AppSettings {
135    pub name: String,
136    pub version: String,
137    pub description: String,
138    pub environment: String,
139    pub debug: bool,
140    pub data_dir: PathBuf,
141    pub config_dir: PathBuf,
142    pub log_dir: PathBuf,
143    pub pid_file: Option<PathBuf>,
144}
145
146impl Default for AppSettings {
147    fn default() -> Self {
148        Self {
149            name: "Qorzen".to_string(),
150            version: env!("CARGO_PKG_VERSION").to_string(),
151            description: "Qorzen Application Framework".to_string(),
152            environment: "development".to_string(),
153            debug: cfg!(debug_assertions),
154            data_dir: PathBuf::from("./data"),
155            config_dir: PathBuf::from("./config"),
156            log_dir: PathBuf::from("./logs"),
157            pid_file: None,
158        }
159    }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct LoggingConfig {
164    pub level: String,
165    pub format: LogFormat,
166    pub console: ConsoleLogConfig,
167    pub file: Option<FileLogConfig>,
168}
169
170impl Default for LoggingConfig {
171    fn default() -> Self {
172        Self {
173            level: "info".to_string(),
174            format: LogFormat::Pretty,
175            console: ConsoleLogConfig::default(),
176            file: Some(FileLogConfig::default()),
177        }
178    }
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
182pub enum LogFormat {
183    Json,
184    Pretty,
185    Compact,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct ConsoleLogConfig {
190    pub enabled: bool,
191    pub level: String,
192    pub colored: bool,
193}
194
195impl Default for ConsoleLogConfig {
196    fn default() -> Self {
197        Self {
198            enabled: true,
199            level: "info".to_string(),
200            colored: true,
201        }
202    }
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
206pub struct FileLogConfig {
207    pub path: PathBuf,
208    pub max_size: u64,
209    pub max_files: u32,
210    pub compress: bool,
211}
212
213impl Default for FileLogConfig {
214    fn default() -> Self {
215        Self {
216            path: PathBuf::from("./logs/app.log"),
217            max_size: 100 * 1024 * 1024, // 100MB
218            max_files: 10,
219            compress: true,
220        }
221    }
222}
223
224/// Get default CPU count for the platform
225fn get_default_cpu_count() -> usize {
226    #[cfg(not(target_arch = "wasm32"))]
227    {
228        num_cpus::get()
229    }
230    #[cfg(target_arch = "wasm32")]
231    {
232        1 // Default to 1 for WASM
233    }
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct EventBusConfig {
238    pub worker_count: usize,
239    pub queue_size: usize,
240    pub publish_timeout_ms: u64,
241    pub enable_persistence: bool,
242    pub enable_metrics: bool,
243}
244
245impl Default for EventBusConfig {
246    fn default() -> Self {
247        Self {
248            worker_count: get_default_cpu_count().max(1) * 2,
249            queue_size: 10000,
250            publish_timeout_ms: 5000,
251            enable_persistence: false,
252            enable_metrics: true,
253        }
254    }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct FileConfig {
259    pub default_permissions: u32,
260    pub max_file_size: u64,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub temp_dir: Option<PathBuf>,
263    pub operation_timeout_secs: u64,
264    pub enable_watching: bool,
265    pub enable_compression: bool,
266}
267
268impl Default for FileConfig {
269    fn default() -> Self {
270        Self {
271            default_permissions: 0o644,
272            max_file_size: 1024 * 1024 * 1024, // 1GB
273            temp_dir: get_default_temp_dir(),
274            operation_timeout_secs: 30,
275            enable_watching: true,
276            enable_compression: false,
277        }
278    }
279}
280
281impl FileConfig {
282    pub fn temp_path(&self, filename: &str) -> PathBuf {
283        self.temp_dir
284            .as_ref()
285            .expect("Temp directory unavailable — likely running in WASM")
286            .join(filename)
287    }
288}
289
290fn get_default_temp_dir() -> Option<PathBuf> {
291    #[cfg(target_arch = "wasm32")]
292    {
293        // WASM: No actual temp dir
294        None
295    }
296
297    #[cfg(not(target_arch = "wasm32"))]
298    {
299        use std::fs;
300        use uuid::Uuid;
301
302        let mut temp = std::env::temp_dir();
303        temp.push(format!("qorzen_{}", Uuid::new_v4()));
304
305        if let Err(e) = fs::create_dir_all(&temp) {
306            eprintln!("Failed to create temp dir: {}", e);
307            return None;
308        }
309
310        Some(temp)
311    }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct TaskConfig {
316    pub max_concurrent: usize,
317    pub default_timeout_ms: u64,
318    pub keep_completed: bool,
319    pub progress_update_interval_ms: u64,
320}
321
322impl Default for TaskConfig {
323    fn default() -> Self {
324        Self {
325            max_concurrent: get_default_cpu_count().max(1) * 2,
326            default_timeout_ms: 300_000, // 5 minutes
327            keep_completed: true,
328            progress_update_interval_ms: 1000,
329        }
330    }
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct ConcurrencyConfig {
335    pub thread_pool_size: usize,
336    pub io_thread_pool_size: usize,
337    pub blocking_thread_pool_size: usize,
338    pub max_queue_size: usize,
339    pub thread_keep_alive_secs: u64,
340}
341
342impl Default for ConcurrencyConfig {
343    fn default() -> Self {
344        let cpu_count = get_default_cpu_count();
345        Self {
346            thread_pool_size: cpu_count,
347            io_thread_pool_size: cpu_count * 2,
348            blocking_thread_pool_size: cpu_count.max(4),
349            max_queue_size: 1000,
350            thread_keep_alive_secs: 60,
351        }
352    }
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct PluginConfig {
357    pub plugin_dir: PathBuf,
358    pub auto_load: bool,
359    pub load_timeout_secs: u64,
360    pub max_plugins: usize,
361    pub hot_reload: bool,
362}
363
364impl Default for PluginConfig {
365    fn default() -> Self {
366        Self {
367            plugin_dir: PathBuf::from("./plugins"),
368            auto_load: true,
369            load_timeout_secs: 30,
370            max_plugins: 100,
371            hot_reload: false,
372        }
373    }
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377pub struct DatabaseConfig {
378    pub url: String,
379    pub max_connections: u32,
380    pub connect_timeout_secs: u64,
381    pub query_timeout_secs: u64,
382    pub enable_pooling: bool,
383    pub enable_query_logging: bool,
384}
385
386impl Default for DatabaseConfig {
387    fn default() -> Self {
388        Self {
389            url: "sqlite://./data/app.db".to_string(),
390            max_connections: 10,
391            connect_timeout_secs: 30,
392            query_timeout_secs: 60,
393            enable_pooling: true,
394            enable_query_logging: false,
395        }
396    }
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct NetworkConfig {
401    pub bind_address: String,
402    pub port: u16,
403    pub enable_tls: bool,
404    pub tls_cert_path: Option<PathBuf>,
405    pub tls_key_path: Option<PathBuf>,
406    pub request_timeout_secs: u64,
407    pub max_request_size: u64,
408}
409
410impl Default for NetworkConfig {
411    fn default() -> Self {
412        Self {
413            bind_address: "127.0.0.1".to_string(),
414            port: 8080,
415            enable_tls: false,
416            tls_cert_path: None,
417            tls_key_path: None,
418            request_timeout_secs: 30,
419            max_request_size: 16 * 1024 * 1024, // 16MB
420        }
421    }
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct SecurityConfig {
426    pub jwt_secret: String,
427    pub jwt_expiration_secs: u64,
428    pub api_key: Option<String>,
429    pub enable_rate_limiting: bool,
430    pub rate_limit_rpm: u64,
431    pub enable_cors: bool,
432    pub cors_origins: Vec<String>,
433}
434
435impl Default for SecurityConfig {
436    fn default() -> Self {
437        Self {
438            jwt_secret: "change-this-in-production".to_string(),
439            jwt_expiration_secs: 3600, // 1 hour
440            api_key: None,
441            enable_rate_limiting: true,
442            rate_limit_rpm: 1000,
443            enable_cors: true,
444            cors_origins: vec!["*".to_string()],
445        }
446    }
447}
448
449pub struct ConfigManager {
450    state: ManagedState,
451    layers: Vec<ConfigLayer>,
452    merged_config: Arc<RwLock<Value>>,
453    change_notifier: broadcast::Sender<ConfigChangeEvent>,
454    watch_enabled: bool,
455    env_prefix: String,
456    event_bus: Option<Arc<EventBusManager>>,
457}
458
459impl fmt::Debug for ConfigManager {
460    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461        f.debug_struct("ConfigManager")
462            .field("layers", &self.layers.len())
463            .field("watch_enabled", &self.watch_enabled)
464            .field("env_prefix", &self.env_prefix)
465            .finish()
466    }
467}
468
469fn set_nested_env_value(config: &mut Map<String, Value>, keys: &[&str], value: String) {
470    if keys.is_empty() {
471        return;
472    }
473
474    if keys.len() == 1 {
475        // Try to parse as different types
476        let parsed_value = if let Ok(bool_val) = value.parse::<bool>() {
477            Value::Bool(bool_val)
478        } else if let Ok(int_val) = value.parse::<i64>() {
479            Value::Number(Number::from(int_val))
480        } else if let Ok(float_val) = value.parse::<f64>() {
481            Value::Number(Number::from_f64(float_val).unwrap())
482        } else {
483            Value::String(value)
484        };
485
486        config.insert(keys[0].to_string(), parsed_value);
487    } else {
488        let first_key = keys[0];
489        if !config.contains_key(first_key) {
490            config.insert(first_key.to_string(), Value::Object(Map::new()));
491        }
492
493        if let Some(Value::Object(nested_map)) = config.get_mut(first_key) {
494            set_nested_env_value(nested_map, &keys[1..], value);
495        }
496    }
497}
498
499fn merge_values(target: &mut Value, source: Value) {
500    match (target, source) {
501        (Value::Object(target_map), Value::Object(source_map)) => {
502            for (key, source_value) in source_map {
503                match target_map.get_mut(&key) {
504                    Some(target_value) => {
505                        merge_values(target_value, source_value);
506                    }
507                    None => {
508                        target_map.insert(key, source_value);
509                    }
510                }
511            }
512        }
513        (target, source) => {
514            *target = source;
515        }
516    }
517}
518
519impl ConfigManager {
520    pub fn new() -> Self {
521        let (change_notifier, _) = broadcast::channel(100);
522
523        Self {
524            state: ManagedState::new(Uuid::new_v4(), "config_manager"),
525            layers: Vec::new(),
526            merged_config: Arc::new(RwLock::new(Value::Object(Map::new()))),
527            change_notifier,
528            watch_enabled: true,
529            env_prefix: "QORZEN".to_string(),
530            event_bus: None,
531        }
532    }
533
534    pub fn with_config_file<P: AsRef<Path>>(config_path: P) -> Self {
535        let mut manager = Self::new();
536        let _ = manager.add_file_layer("default", config_path, 0, true);
537        manager
538    }
539
540    pub fn add_file_layer<P: AsRef<Path>>(
541        &mut self,
542        name: impl Into<String>,
543        path: P,
544        priority: u32,
545        hot_reload: bool,
546    ) -> Result<()> {
547        let path = path.as_ref().to_path_buf();
548        let format = ConfigFormat::from_extension(&path)
549            .ok_or_else(|| Error::config("Unsupported configuration file format"))?;
550
551        let layer = ConfigLayer {
552            name: name.into(),
553            source: ConfigSource::File { path, format },
554            priority,
555            hot_reload,
556        };
557
558        self.layers.push(layer);
559        self.layers.sort_by_key(|l| l.priority);
560
561        Ok(())
562    }
563
564    pub fn add_env_layer(
565        &mut self,
566        name: impl Into<String>,
567        prefix: impl Into<String>,
568        priority: u32,
569    ) {
570        let layer = ConfigLayer {
571            name: name.into(),
572            source: ConfigSource::Environment {
573                prefix: prefix.into(),
574            },
575            priority,
576            hot_reload: false,
577        };
578
579        self.layers.push(layer);
580        self.layers.sort_by_key(|l| l.priority);
581    }
582
583    pub fn add_memory_layer(&mut self, name: impl Into<String>, data: Value, priority: u32) {
584        let layer = ConfigLayer {
585            name: name.into(),
586            source: ConfigSource::Memory { data },
587            priority,
588            hot_reload: false,
589        };
590
591        self.layers.push(layer);
592        self.layers.sort_by_key(|l| l.priority);
593    }
594
595    pub fn set_event_bus(&mut self, event_bus: Arc<EventBusManager>) {
596        self.event_bus = Some(event_bus);
597    }
598
599    pub async fn set<T>(&self, key: &str, value: T) -> Result<()>
600    where
601        T: Serialize,
602    {
603        let serialized_value = serde_json::to_value(value).map_err(|e| {
604            Error::new(
605                crate::error::ErrorKind::Configuration {
606                    key: Some(key.to_string()),
607                    validation_errors: vec![format!("Failed to serialize config value: {}", e)],
608                },
609                format!("Failed to serialize config value: {}", e),
610            )
611        })?;
612
613        let mut config = self.merged_config.write().await;
614        let old_value = self.get_nested_value(&config, key);
615
616        self.set_nested_value(&mut config, key, serialized_value.clone());
617
618        // Publish change event
619        let change_event = ConfigChangeEvent {
620            key: key.to_string(),
621            value: serialized_value,
622            old_value,
623            timestamp: Time::now(),
624            source: "config_manager".to_string(),
625            metadata: HashMap::new(),
626        };
627
628        if let Some(event_bus) = &self.event_bus {
629            let _ = event_bus.publish(change_event.clone()).await;
630        }
631
632        let _ = self.change_notifier.send(change_event);
633
634        Ok(())
635    }
636
637    pub async fn get<T>(&self, key: &str) -> Result<T>
638    where
639        T: for<'de> Deserialize<'de>,
640    {
641        let config = self.merged_config.read().await;
642        let value = self.get_nested_value(&config, key).ok_or_else(|| {
643            Error::new(
644                crate::error::ErrorKind::Configuration {
645                    key: Some(key.to_string()),
646                    validation_errors: vec![format!("Configuration key '{}' not found", key)],
647                },
648                "Configuration key not found",
649            )
650        })?;
651
652        serde_json::from_value(value).map_err(|e| {
653            Error::new(
654                crate::error::ErrorKind::Configuration {
655                    key: Some(key.to_string()),
656                    validation_errors: vec![format!("Failed to deserialize config value: {}", e)],
657                },
658                format!("Failed to deserialize config value: {}", e),
659            )
660        })
661    }
662
663    pub async fn get_config(&self) -> AppConfig {
664        let config = self.merged_config.read().await;
665        match serde_json::from_value(config.clone()) {
666            Ok(parsed) => parsed,
667            Err(e) => {
668                eprintln!("⚠️ Failed to deserialize config: {:?}", e);
669                eprintln!("🔎 Raw config: {}", config);
670                AppConfig::default()
671            }
672        }
673    }
674
675    pub fn subscribe_to_changes(&self) -> broadcast::Receiver<ConfigChangeEvent> {
676        self.change_notifier.subscribe()
677    }
678
679    pub async fn reload(&self) -> Result<()> {
680        self.merge_configurations().await?;
681
682        // Publish reload event
683        let reload_event = ConfigChangeEvent {
684            key: "_reload".to_string(),
685            value: Value::String("reloaded".to_string()),
686            old_value: None,
687            timestamp: Time::now(),
688            source: "config_manager".to_string(),
689            metadata: HashMap::new(),
690        };
691
692        if let Some(event_bus) = &self.event_bus {
693            let _ = event_bus.publish(reload_event.clone()).await;
694        }
695
696        let _ = self.change_notifier.send(reload_event);
697
698        Ok(())
699    }
700
701    pub async fn validate(&self) -> Result<Vec<ValidationError>> {
702        let _config = self.merged_config.read().await;
703        let errors = Vec::new();
704
705        // Add validation logic here
706        // This is a simplified example
707        // In practice, you'd implement comprehensive validation
708
709        Ok(errors)
710    }
711
712    async fn merge_configurations(&self) -> Result<()> {
713        let mut merged = Value::Object(Map::new());
714
715        // Process layers in priority order (lowest to highest)
716        for layer in &self.layers {
717            let layer_config = self.load_layer_config(layer).await?;
718            merge_values(&mut merged, layer_config);
719        }
720
721        *self.merged_config.write().await = merged;
722        Ok(())
723    }
724
725    async fn load_layer_config(&self, layer: &ConfigLayer) -> Result<Value> {
726        match &layer.source {
727            #[cfg(not(target_arch = "wasm32"))]
728            ConfigSource::File { path, format } => {
729                let content = std::fs::read_to_string(path)
730                    .map_err(|e| Error::config(format!("Failed to read config file: {}", e)))?;
731
732                match format {
733                    ConfigFormat::Json => serde_json::from_str(&content)
734                        .map_err(|e| Error::config(format!("Failed to parse JSON config: {}", e))),
735                    ConfigFormat::Yaml => serde_yaml::from_str(&content)
736                        .map_err(|e| Error::config(format!("Failed to parse YAML config: {}", e))),
737                    ConfigFormat::Toml => toml::from_str(&content)
738                        .map_err(|e| Error::config(format!("Failed to parse TOML config: {}", e))),
739                }
740            }
741
742            #[cfg(target_arch = "wasm32")]
743            ConfigSource::File { .. } => {
744                Err(Error::config("File loading not supported in web platform"))
745            }
746
747            #[cfg(not(target_arch = "wasm32"))]
748            ConfigSource::Environment { prefix } => {
749                let mut env_config = serde_json::Map::new();
750
751                for (key, value) in std::env::vars() {
752                    if key.starts_with(prefix) {
753                        let config_key = key
754                            .strip_prefix(prefix)
755                            .unwrap()
756                            .trim_start_matches('_')
757                            .to_lowercase();
758                        let nested_keys: Vec<&str> = config_key.split('_').collect();
759                        set_nested_env_value(&mut env_config, &nested_keys, value);
760                    }
761                }
762
763                Ok(Value::Object(env_config))
764            }
765
766            #[cfg(target_arch = "wasm32")]
767            ConfigSource::Environment { .. } => Ok(Value::Object(serde_json::Map::new())),
768
769            ConfigSource::Memory { data } => Ok(data.clone()),
770        }
771    }
772
773    fn get_nested_value(&self, config: &Value, key: &str) -> Option<Value> {
774        let keys: Vec<&str> = key.split('.').collect();
775        let mut current = config;
776
777        for k in keys {
778            current = current.get(k)?;
779        }
780
781        Some(current.clone())
782    }
783
784    fn set_nested_value(&self, config: &mut Value, key: &str, value: Value) {
785        let keys: Vec<&str> = key.split('.').collect();
786        let mut current = config;
787
788        for (i, k) in keys.iter().enumerate() {
789            if i == keys.len() - 1 {
790                // Last key - set the value
791                if let Value::Object(ref mut map) = current {
792                    map.insert(k.to_string(), value);
793                }
794                return; // Exit early to avoid moving value multiple times
795            } else {
796                // Navigate or create intermediate objects
797                if !current.is_object() {
798                    *current = Value::Object(Map::new());
799                }
800
801                let map = current.as_object_mut().unwrap();
802                if !map.contains_key(*k) {
803                    map.insert(k.to_string(), Value::Object(Map::new()));
804                }
805
806                current = map.get_mut(*k).unwrap();
807            }
808        }
809    }
810
811    pub async fn debug_config(&self) -> Value {
812        let config = self.merged_config.read().await;
813        config.clone()
814    }
815
816    pub fn get_metadata(&self) -> Value {
817        serde_json::json!({
818            "layers": self.layers.len(),
819            "layer_info": self.layers.iter().map(|l| {
820                serde_json::json!({
821                    "name": l.name,
822                    "priority": l.priority,
823                    "hot_reload": l.hot_reload,
824                    "source_type": match &l.source {
825                        ConfigSource::File { path, .. } => {
826                            path.display().to_string()
827                        }
828                        ConfigSource::Environment { prefix } => {
829                            format!("env:{}", prefix)
830                        }
831                        ConfigSource::Memory { .. } => "memory".to_string(),
832                    }
833                })
834            }).collect::<Vec<_>>(),
835            "config_path": self.layers.iter()
836                .find_map(|l| match &l.source {
837                    ConfigSource::File { path, .. } => {
838                        Some(path.display().to_string())
839                    }
840                    _ => None,
841                })
842                .unwrap_or_else(|| "none".to_string()),
843            "watch_enabled": self.watch_enabled,
844            "env_prefix": self.env_prefix.clone()
845        })
846    }
847}
848
849impl Default for ConfigManager {
850    fn default() -> Self {
851        Self::new()
852    }
853}
854
855/// Conditional Manager implementation for ConfigManager
856#[cfg(not(target_arch = "wasm32"))]
857#[async_trait]
858impl Manager for ConfigManager {
859    fn name(&self) -> &str {
860        "config_manager"
861    }
862
863    fn id(&self) -> Uuid {
864        self.state.id()
865    }
866
867    async fn initialize(&mut self) -> Result<()> {
868        self.state
869            .set_state(crate::manager::ManagerState::Initializing)
870            .await;
871
872        // Store env_prefix in a local variable to avoid borrow conflicts
873        let env_prefix = self.env_prefix.clone();
874        self.add_env_layer("environment", &env_prefix, 1000);
875
876        // Load and merge all configurations
877        self.merge_configurations().await?;
878
879        // TODO: Setup file watching for hot-reload
880
881        self.state
882            .set_state(crate::manager::ManagerState::Running)
883            .await;
884        Ok(())
885    }
886
887    async fn shutdown(&mut self) -> Result<()> {
888        self.state
889            .set_state(crate::manager::ManagerState::ShuttingDown)
890            .await;
891
892        // Clean up file watchers, etc.
893
894        self.state
895            .set_state(crate::manager::ManagerState::Shutdown)
896            .await;
897        Ok(())
898    }
899
900    async fn status(&self) -> ManagerStatus {
901        let mut status = self.state.status().await;
902        status.add_metadata("layers", Value::from(self.layers.len()));
903        status.add_metadata("watch_enabled", Value::Bool(self.watch_enabled));
904        status.add_metadata("env_prefix", Value::String(self.env_prefix.clone()));
905        status
906    }
907}
908
909#[cfg(target_arch = "wasm32")]
910#[async_trait(?Send)]
911impl Manager for ConfigManager {
912    fn name(&self) -> &str {
913        "config_manager"
914    }
915
916    fn id(&self) -> Uuid {
917        self.state.id()
918    }
919
920    async fn initialize(&mut self) -> Result<()> {
921        self.state
922            .set_state(crate::manager::ManagerState::Initializing)
923            .await;
924
925        // Store env_prefix in a local variable to avoid borrow conflicts
926        let env_prefix = self.env_prefix.clone();
927        self.add_env_layer("environment", &env_prefix, 1000);
928
929        // Load and merge all configurations
930        self.merge_configurations().await?;
931
932        // TODO: Setup file watching for hot-reload
933
934        self.state
935            .set_state(crate::manager::ManagerState::Running)
936            .await;
937        Ok(())
938    }
939
940    async fn shutdown(&mut self) -> Result<()> {
941        self.state
942            .set_state(crate::manager::ManagerState::ShuttingDown)
943            .await;
944
945        // Clean up file watchers, etc.
946
947        self.state
948            .set_state(crate::manager::ManagerState::Shutdown)
949            .await;
950        Ok(())
951    }
952
953    async fn status(&self) -> ManagerStatus {
954        let mut status = self.state.status().await;
955        status.add_metadata("layers", Value::from(self.layers.len()));
956        status.add_metadata("watch_enabled", Value::Bool(self.watch_enabled));
957        status.add_metadata("env_prefix", Value::String(self.env_prefix.clone()));
958        status
959    }
960}
961
962#[cfg(test)]
963mod tests {
964    use super::*;
965    use std::io::Write;
966    use tempfile::NamedTempFile;
967
968    #[tokio::test]
969    async fn test_config_manager_creation() {
970        let manager = ConfigManager::new();
971        assert_eq!(manager.layers.len(), 0);
972    }
973
974    #[tokio::test]
975    async fn test_file_layer() {
976        let mut manager = ConfigManager::new();
977
978        // Create a temporary config file
979        let mut temp_file = NamedTempFile::new().unwrap();
980        temp_file
981            .write_all(b"app:\n  name: \"Test App\"\n  debug: true")
982            .unwrap();
983
984        manager
985            .add_file_layer("test", temp_file.path(), 0, false)
986            .unwrap();
987
988        manager.initialize().await.unwrap();
989
990        let app_name: String = manager.get("app.name").await.unwrap();
991        assert_eq!(app_name, "Test App");
992
993        let debug: bool = manager.get("app.debug").await.unwrap();
994        assert!(debug);
995    }
996
997    #[tokio::test]
998    async fn test_environment_layer() {
999        let mut manager = ConfigManager::new();
1000
1001        // Set environment variables
1002        std::env::set_var("TEST_APP_NAME", "Env App");
1003        std::env::set_var("TEST_APP_DEBUG", "false");
1004
1005        manager.add_env_layer("env", "TEST", 100);
1006        manager.initialize().await.unwrap();
1007
1008        let app_name: String = manager.get("app.name").await.unwrap();
1009        assert_eq!(app_name, "Env App");
1010
1011        let debug: bool = manager.get("app.debug").await.unwrap();
1012        assert!(!debug);
1013
1014        // Clean up
1015        std::env::remove_var("TEST_APP_NAME");
1016        std::env::remove_var("TEST_APP_DEBUG");
1017    }
1018
1019    #[tokio::test]
1020    async fn test_memory_layer() {
1021        let mut manager = ConfigManager::new();
1022
1023        let memory_config = serde_json::json!({
1024            "app": {
1025                "name": "Memory App",
1026                "version": "1.0.0"
1027            }
1028        });
1029
1030        manager.add_memory_layer("memory", memory_config, 50);
1031        manager.initialize().await.unwrap();
1032
1033        let app_name: String = manager.get("app.name").await.unwrap();
1034        assert_eq!(app_name, "Memory App");
1035    }
1036
1037    #[tokio::test]
1038    async fn test_configuration_change() {
1039        let mut manager = ConfigManager::new();
1040        manager.initialize().await.unwrap();
1041
1042        let change = ConfigChangeEvent {
1043            key: "test.value".to_string(),
1044            value: Value::Bool(false),
1045            old_value: None,
1046            timestamp: Time::now(),
1047            source: "test".to_string(),
1048            metadata: HashMap::new(),
1049        };
1050
1051        assert_eq!(change.value, Value::Bool(false));
1052    }
1053}