Skip to main content

oxirs_core/
config_parser.rs

1//! Configuration parsing and loading for OxiRS Core.
2//!
3//! Handles TOML/JSON deserialization, config file loading, and
4//! environment variable overrides.
5
6use crate::config_types::{
7    ConfigError, ConfigSource, ConfigWatcher, ConfigurationManager, Environment, OxirsConfig,
8    PerformanceProfile,
9};
10use std::path::Path;
11use std::sync::{Arc, RwLock};
12
13impl ConfigurationManager {
14    /// Create a new configuration manager with default settings.
15    pub fn new() -> Self {
16        Self {
17            config: Arc::new(RwLock::new(OxirsConfig::default())),
18            environment: Environment::Development,
19            config_sources: Vec::new(),
20            watchers: Vec::new(),
21        }
22    }
23
24    /// Load configuration from a file (TOML or JSON, detected by extension).
25    pub fn load_from_file<P: AsRef<Path>>(&mut self, path: P) -> Result<(), ConfigError> {
26        let content = std::fs::read_to_string(&path)
27            .map_err(|_| ConfigError::FileNotFound(path.as_ref().to_path_buf()))?;
28
29        let config: OxirsConfig = if path.as_ref().extension() == Some(std::ffi::OsStr::new("toml"))
30        {
31            toml::from_str(&content).map_err(|e| ConfigError::InvalidFormat(e.to_string()))?
32        } else {
33            serde_json::from_str(&content)?
34        };
35
36        self.update_config(config)?;
37        self.config_sources.push(ConfigSource::File {
38            path: path.as_ref().to_path_buf(),
39        });
40
41        Ok(())
42    }
43
44    /// Load configuration overrides from environment variables.
45    ///
46    /// Supported variables:
47    /// - `OXIRS_PERFORMANCE_PROFILE` — name of a [`PerformanceProfile`] variant
48    /// - `OXIRS_THREAD_COUNT`       — worker thread count (usize)
49    pub fn load_from_environment(&mut self) -> Result<(), ConfigError> {
50        let mut config = self.get_config();
51
52        if let Ok(profile_str) = std::env::var("OXIRS_PERFORMANCE_PROFILE") {
53            if let Ok(profile) =
54                serde_json::from_str::<PerformanceProfile>(&format!("\"{}\"", profile_str))
55            {
56                config.performance.profile = profile;
57            }
58        }
59
60        if let Ok(threads_str) = std::env::var("OXIRS_THREAD_COUNT") {
61            if let Ok(threads) = threads_str.parse::<usize>() {
62                config.concurrency.thread_pool.worker_threads = threads;
63            }
64        }
65
66        self.update_config(config)?;
67        self.config_sources.push(ConfigSource::Environment);
68
69        Ok(())
70    }
71
72    /// Return a clone of the current configuration.
73    pub fn get_config(&self) -> OxirsConfig {
74        self.config
75            .read()
76            .expect("config RwLock should not be poisoned")
77            .clone()
78    }
79
80    /// Replace the current configuration after validation.
81    pub fn update_config(&mut self, new_config: OxirsConfig) -> Result<(), ConfigError> {
82        crate::config_validation::validate_config(&new_config)?;
83        *self
84            .config
85            .write()
86            .expect("config RwLock should not be poisoned") = new_config;
87        Ok(())
88    }
89
90    /// Set the active performance profile and apply its default settings.
91    pub fn set_performance_profile(
92        &mut self,
93        profile: PerformanceProfile,
94    ) -> Result<(), ConfigError> {
95        let mut config = self.get_config();
96        config.performance.profile = profile;
97        config.performance.custom_settings = profile.get_config();
98        self.update_config(config)
99    }
100
101    /// Return the current performance profile.
102    pub fn get_performance_profile(&self) -> PerformanceProfile {
103        self.get_config().performance.profile
104    }
105
106    /// Register a callback to be invoked when the configuration changes.
107    pub fn add_watcher<F>(&mut self, source: ConfigSource, callback: F)
108    where
109        F: Fn(&OxirsConfig) + Send + Sync + 'static,
110    {
111        self.watchers.push(ConfigWatcher {
112            source,
113            callback: Box::new(callback),
114        });
115    }
116
117    /// Start asynchronous configuration monitoring (file watchers, env monitors, …).
118    pub async fn start_monitoring(&self) -> Result<(), ConfigError> {
119        Ok(())
120    }
121}
122
123impl Default for ConfigurationManager {
124    fn default() -> Self {
125        Self::new()
126    }
127}