1pub mod schema;
2
3use crate::{Error, ThisError};
4use schema::{ConfigSchemaError, Validate};
5use std::{cell::RefCell, sync::Arc};
6
7pub use schema::ConfigModel;
8
9thread_local! {
25 static CONFIG: RefCell<Option<Arc<ConfigModel>>> = const { RefCell::new(None) };
26}
27
28#[derive(Debug, ThisError)]
30pub enum ConfigError {
31 #[error("config has already been initialized")]
32 AlreadyInitialized,
33
34 #[error("toml error: {0}")]
36 CannotParseToml(String),
37
38 #[error(transparent)]
40 ConfigSchemaError(#[from] ConfigSchemaError),
41}
42
43pub struct Config {}
48
49impl Config {
50 #[must_use]
51 pub fn get() -> Arc<ConfigModel> {
52 CONFIG.with(|cfg| {
53 if let Some(config) = cfg.borrow().as_ref() {
54 return config.clone();
55 }
56
57 #[cfg(test)]
58 {
59 Self::init_for_tests()
60 }
61
62 #[cfg(not(test))]
63 {
64 panic!("⚠️ Config must be initialized before use");
65 }
66 })
67 }
68
69 pub fn init_from_toml(config_str: &str) -> Result<Arc<ConfigModel>, Error> {
72 let config: ConfigModel =
73 toml::from_str(config_str).map_err(|e| ConfigError::CannotParseToml(e.to_string()))?;
74
75 config.validate().map_err(ConfigError::from)?;
77
78 CONFIG.with(|cfg| {
79 let mut borrow = cfg.borrow_mut();
80 if borrow.is_some() {
81 return Err(ConfigError::AlreadyInitialized.into());
82 }
83 let arc = Arc::new(config);
84 *borrow = Some(arc.clone());
85
86 Ok(arc)
87 })
88 }
89
90 pub fn to_toml() -> Result<String, Error> {
92 let cfg = Self::get();
93
94 toml::to_string_pretty(&*cfg)
95 .map_err(|e| ConfigError::CannotParseToml(e.to_string()).into())
96 }
97
98 #[cfg(test)]
100 pub fn reset_for_tests() {
101 CONFIG.with(|cfg| {
102 *cfg.borrow_mut() = None;
103 });
104 }
105
106 #[cfg(test)]
108 #[must_use]
109 pub fn init_for_tests() -> Arc<ConfigModel> {
110 CONFIG.with(|cfg| {
111 let mut borrow = cfg.borrow_mut();
112 if let Some(existing) = borrow.as_ref() {
113 return existing.clone();
114 }
115
116 let config = ConfigModel::test_default();
117 config.validate().expect("test config must validate");
118
119 let arc = Arc::new(config);
120 *borrow = Some(arc.clone());
121 arc
122 })
123 }
124}