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]
52 pub fn get() -> Arc<ConfigModel> {
53 CONFIG.with(|cfg| {
54 cfg.borrow()
55 .as_ref()
56 .cloned()
57 .expect("⚠️ Config must be initialized before use")
58 })
59 }
60
61 #[must_use]
63 pub fn try_get() -> Option<Arc<ConfigModel>> {
64 CONFIG.with(|cfg| cfg.borrow().as_ref().cloned())
65 }
66
67 pub fn init_from_toml(config_str: &str) -> Result<Arc<ConfigModel>, Error> {
70 let config: ConfigModel =
71 toml::from_str(config_str).map_err(|e| ConfigError::CannotParseToml(e.to_string()))?;
72
73 config.validate().map_err(ConfigError::from)?;
75
76 CONFIG.with(|cfg| {
77 let mut borrow = cfg.borrow_mut();
78 if borrow.is_some() {
79 return Err(ConfigError::AlreadyInitialized.into());
80 }
81 let arc = Arc::new(config);
82 *borrow = Some(arc.clone());
83
84 Ok(arc)
85 })
86 }
87
88 pub fn to_toml() -> Result<String, Error> {
90 let cfg = Self::try_get()
91 .ok_or_else(|| Error::custom("config must be initialized before use"))?;
92
93 toml::to_string_pretty(&*cfg)
94 .map_err(|e| ConfigError::CannotParseToml(e.to_string()).into())
95 }
96
97 #[cfg(test)]
99 pub fn reset_for_tests() {
100 CONFIG.with(|cfg| {
101 *cfg.borrow_mut() = None;
102 });
103 }
104}