1use std::collections::HashMap;
7use std::env;
8use std::fmt;
9use std::sync::{Arc, Mutex, RwLock};
10
11use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
12
13pub const DEFAULT_FLOAT_EPS: f64 = 1e-10;
15
16pub const DEFAULT_NUM_THREADS: usize = 4;
18
19pub const DEFAULT_MAX_ITERATIONS: usize = 1000;
21
22pub const DEFAULT_MEMORY_LIMIT: usize = 1_073_741_824; #[derive(Debug, Clone)]
30pub struct Config {
31 values: HashMap<String, ConfigValue>,
33 env_prefix: String,
35}
36
37#[derive(Debug, Clone)]
39pub enum ConfigValue {
40 Bool(bool),
42 Int(i64),
44 UInt(u64),
46 Float(f64),
48 String(String),
50}
51
52impl fmt::Display for ConfigValue {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 ConfigValue::Bool(b) => write!(f, "{b}"),
56 ConfigValue::Int(i) => write!(f, "{i}"),
57 ConfigValue::UInt(u) => write!(f, "{u}"),
58 ConfigValue::Float(fl) => write!(f, "{fl}"),
59 ConfigValue::String(s) => write!(f, "{s}"),
60 }
61 }
62}
63
64impl Default for Config {
65 fn default() -> Self {
66 let mut config = Self {
67 values: HashMap::new(),
68 env_prefix: "SCIRS_".to_string(),
69 };
70
71 config.set_default("float_eps", ConfigValue::Float(DEFAULT_FLOAT_EPS));
73 config.set_default("num_threads", ConfigValue::UInt(DEFAULT_NUM_THREADS as u64));
74 config.set_default(
75 "max_iterations",
76 ConfigValue::UInt(DEFAULT_MAX_ITERATIONS as u64),
77 );
78 config.set_default(
79 "memory_limit",
80 ConfigValue::UInt(DEFAULT_MEMORY_LIMIT as u64),
81 );
82 config.set_default("parallel_enabled", ConfigValue::Bool(true));
83 config.set_default("debug_mode", ConfigValue::Bool(false));
84 config.set_default("suppress_warnings", ConfigValue::Bool(false));
85
86 config.load_from_env();
88
89 config
90 }
91}
92
93impl Config {
94 #[must_use]
96 pub fn new() -> Self {
97 Self::default()
98 }
99
100 #[must_use]
102 pub fn get(&self, key: &str) -> Option<&ConfigValue> {
103 self.values.get(key)
104 }
105
106 pub fn set(&mut self, key: &str, value: ConfigValue) {
108 self.values.insert(key.to_string(), value);
109 }
110
111 fn set_default(&mut self, key: &str, value: ConfigValue) {
113 self.values.entry(key.to_string()).or_insert(value);
114 }
115
116 pub fn get_bool(&self, key: &str) -> CoreResult<bool> {
122 match self.get(key) {
123 Some(ConfigValue::Bool(b)) => Ok(*b),
124 Some(value) => Err(CoreError::ConfigError(
125 ErrorContext::new(format!(
126 "Expected boolean value for key '{key}', got: {value}"
127 ))
128 .with_location(ErrorLocation::new(file!(), line!())),
129 )),
130 None => Err(CoreError::ConfigError(
131 ErrorContext::new(format!("Configuration key '{key}' not found"))
132 .with_location(ErrorLocation::new(file!(), line!())),
133 )),
134 }
135 }
136
137 pub fn get_int(&self, key: &str) -> CoreResult<i64> {
143 match self.get(key) {
144 Some(ConfigValue::Int(i)) => Ok(*i),
145 Some(ConfigValue::UInt(u)) if *u <= i64::MAX as u64 => Ok(*u as i64),
146 Some(value) => Err(CoreError::ConfigError(
147 ErrorContext::new(format!(
148 "Expected integer value for key '{key}', got: {value}"
149 ))
150 .with_location(ErrorLocation::new(file!(), line!())),
151 )),
152 None => Err(CoreError::ConfigError(
153 ErrorContext::new(format!("Configuration key '{key}' not found"))
154 .with_location(ErrorLocation::new(file!(), line!())),
155 )),
156 }
157 }
158
159 pub fn get_uint(&self, key: &str) -> CoreResult<u64> {
165 match self.get(key) {
166 Some(ConfigValue::UInt(u)) => Ok(*u),
167 Some(ConfigValue::Int(i)) if *i >= 0 => Ok(*i as u64),
168 Some(value) => Err(CoreError::ConfigError(
169 ErrorContext::new(format!(
170 "Expected unsigned integer value for key '{key}', got: {value}"
171 ))
172 .with_location(ErrorLocation::new(file!(), line!())),
173 )),
174 None => Err(CoreError::ConfigError(
175 ErrorContext::new(format!("Configuration key '{key}' not found"))
176 .with_location(ErrorLocation::new(file!(), line!())),
177 )),
178 }
179 }
180
181 pub fn get_float(&self, key: &str) -> CoreResult<f64> {
187 match self.get(key) {
188 Some(ConfigValue::Float(f)) => Ok(*f),
189 Some(ConfigValue::Int(i)) => Ok(*i as f64),
190 Some(ConfigValue::UInt(u)) => Ok(*u as f64),
191 Some(value) => Err(CoreError::ConfigError(
192 ErrorContext::new(format!(
193 "Expected float value for key '{key}', got: {value}"
194 ))
195 .with_location(ErrorLocation::new(file!(), line!())),
196 )),
197 None => Err(CoreError::ConfigError(
198 ErrorContext::new(format!("Configuration key '{key}' not found"))
199 .with_location(ErrorLocation::new(file!(), line!())),
200 )),
201 }
202 }
203
204 pub fn get_string(&self, key: &str) -> CoreResult<String> {
210 match self.get(key) {
211 Some(ConfigValue::String(s)) => Ok(s.clone()),
212 Some(value) => Ok(value.to_string()),
213 None => Err(CoreError::ConfigError(
214 ErrorContext::new(format!("Configuration key '{key}' not found"))
215 .with_location(ErrorLocation::new(file!(), line!())),
216 )),
217 }
218 }
219
220 fn load_from_env(&mut self) {
222 for (key, value) in env::vars() {
224 if key.starts_with(&self.env_prefix) {
225 let config_key = key[self.env_prefix.len()..].to_lowercase();
226
227 if let Ok(bool_val) = value.parse::<bool>() {
229 self.set(&config_key, ConfigValue::Bool(bool_val));
230 } else if let Ok(int_val) = value.parse::<i64>() {
231 self.set(&config_key, ConfigValue::Int(int_val));
232 } else if let Ok(uint_val) = value.parse::<u64>() {
233 self.set(&config_key, ConfigValue::UInt(uint_val));
234 } else if let Ok(float_val) = value.parse::<f64>() {
235 self.set(&config_key, ConfigValue::Float(float_val));
236 } else {
237 self.set(&config_key, ConfigValue::String(value));
238 }
239 }
240 }
241 }
242}
243
244static GLOBAL_CONFIG: std::sync::LazyLock<RwLock<Config>> =
245 std::sync::LazyLock::new(|| RwLock::new(Config::default()));
246
247thread_local! {
248 static THREAD_LOCAL_CONFIG: Arc<Mutex<Option<Config>>> = Arc::new(Mutex::new(None));
249}
250
251#[must_use]
255#[allow(dead_code)]
256pub fn get_config() -> Config {
257 let thread_local = THREAD_LOCAL_CONFIG.with(|config| {
259 let config_lock = config.lock().unwrap();
260 config_lock.clone()
261 });
262
263 match thread_local {
265 Some(config) => config,
266 None => GLOBAL_CONFIG.read().unwrap().clone(),
267 }
268}
269
270#[allow(dead_code)]
272pub fn set_global_config(config: Config) {
273 let mut global_config = GLOBAL_CONFIG.write().unwrap();
274 *global_config = config;
275}
276
277#[allow(dead_code)]
279pub fn set_thread_local_config(config: Config) {
280 THREAD_LOCAL_CONFIG.with(|thread_config| {
281 let mut config_lock = thread_config.lock().unwrap();
282 *config_lock = Some(config);
283 });
284}
285
286#[allow(dead_code)]
288pub fn clear_thread_local_config() {
289 THREAD_LOCAL_CONFIG.with(|thread_config| {
290 let mut config_lock = thread_config.lock().unwrap();
291 *config_lock = None;
292 });
293}
294
295#[allow(dead_code)]
297pub fn set_config_value(key: &str, value: ConfigValue) {
298 let mut global_config = GLOBAL_CONFIG.write().unwrap();
299 global_config.set(key, value);
300}
301
302#[must_use]
304#[allow(dead_code)]
305pub fn get_config_value(key: &str) -> Option<ConfigValue> {
306 let config = get_config();
307 config.get(key).cloned()
308}
309
310#[allow(dead_code)]
312pub fn set_thread_local_config_value(key: &str, value: ConfigValue) {
313 THREAD_LOCAL_CONFIG.with(|thread_config| {
314 let mut config_lock = thread_config.lock().unwrap();
315
316 if config_lock.is_none() {
318 let global_config = GLOBAL_CONFIG.read().unwrap().clone();
319 *config_lock = Some(global_config);
320 }
321
322 if let Some(config) = config_lock.as_mut() {
324 config.set(key, value);
325 }
326 });
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332
333 #[test]
334 fn test_default_config() {
335 let config = Config::default();
336
337 assert!(matches!(
338 config.get("float_eps"),
339 Some(ConfigValue::Float(_))
340 ));
341 assert!(matches!(
342 config.get("num_threads"),
343 Some(ConfigValue::UInt(_))
344 ));
345 assert!(matches!(
346 config.get("max_iterations"),
347 Some(ConfigValue::UInt(_))
348 ));
349 assert!(matches!(
350 config.get("memory_limit"),
351 Some(ConfigValue::UInt(_))
352 ));
353 }
354
355 #[test]
356 fn test_config_get_methods() {
357 let mut config = Config::default();
358
359 config.set("test_bool", ConfigValue::Bool(true));
360 config.set("test_int", ConfigValue::Int(42));
361 config.set("test_uint", ConfigValue::UInt(100));
362 config.set("test_float", ConfigValue::Float(3.5));
363 config.set("test_string", ConfigValue::String("hello".to_string()));
364
365 assert!(config.get_bool("test_bool").unwrap());
366 assert_eq!(config.get_int("test_int").unwrap(), 42);
367 assert_eq!(config.get_uint("test_uint").unwrap(), 100);
368 assert_eq!(config.get_float("test_float").unwrap(), 3.5);
369 assert_eq!(config.get_string("test_string").unwrap(), "hello");
370
371 assert_eq!(config.get_float("test_int").unwrap(), 42.0);
373 assert_eq!(config.get_int("test_uint").unwrap(), 100);
374
375 assert!(config.get_bool("nonexistent").is_err());
377 assert!(config.get_bool("test_int").is_err());
378 }
379
380 #[test]
381 fn test_global_config() {
382 let process_id = std::process::id();
384 let test_key = format!("{process_id}");
385
386 let original_value = GLOBAL_CONFIG.read().unwrap().values.get(&test_key).cloned();
388
389 {
391 let mut global_config = GLOBAL_CONFIG.write().unwrap();
392 global_config.set(&test_key, ConfigValue::String("test_value".to_string()));
393 }
394
395 {
397 let config = get_config();
398 assert_eq!(config.get_string(&test_key).unwrap(), "test_value");
399 }
400
401 {
403 let mut global_config = GLOBAL_CONFIG.write().unwrap();
404 if let Some(original) = original_value {
405 global_config.set(&test_key, original);
406 } else {
407 global_config.values.remove(&test_key);
408 }
409 }
410 }
411
412 #[test]
413 fn test_thread_local_config() {
414 let test_key = "test_thread_key";
415 let original = get_config();
416
417 {
418 let mut global_config = GLOBAL_CONFIG.write().unwrap();
420 global_config.set(test_key, ConfigValue::String("global".to_string()));
421 }
422
423 let mut thread_config = Config::default();
425 thread_config.set(test_key, ConfigValue::String("thread-local".to_string()));
426 set_thread_local_config(thread_config);
427
428 let config = get_config();
430 assert_eq!(config.get_string(test_key).unwrap(), "thread-local");
431
432 clear_thread_local_config();
434
435 let thread_result = THREAD_LOCAL_CONFIG.with(|config| {
437 let locked = config.lock().unwrap();
438 locked.is_none()
439 });
440 assert!(thread_result, "Thread-local config should be cleared");
441
442 set_global_config(original);
444
445 let mut final_config = GLOBAL_CONFIG.write().unwrap();
447 final_config.values.remove(test_key);
448 }
449}
450
451pub mod production;