1use 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, max_files: 10,
219 compress: true,
220 }
221 }
222}
223
224fn 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 }
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, 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 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, 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, }
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, 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 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 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 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 Ok(errors)
710 }
711
712 async fn merge_configurations(&self) -> Result<()> {
713 let mut merged = Value::Object(Map::new());
714
715 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 if let Value::Object(ref mut map) = current {
792 map.insert(k.to_string(), value);
793 }
794 return; } else {
796 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#[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 let env_prefix = self.env_prefix.clone();
874 self.add_env_layer("environment", &env_prefix, 1000);
875
876 self.merge_configurations().await?;
878
879 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 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 let env_prefix = self.env_prefix.clone();
927 self.add_env_layer("environment", &env_prefix, 1000);
928
929 self.merge_configurations().await?;
931
932 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 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 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 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 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}