1pub mod schema;
2
3use crate::Error;
4use schema::{ConfigSchemaError, Validate};
5use std::{cell::RefCell, sync::Arc};
6use thiserror::Error as ThisError;
7
8pub use schema::ConfigModel;
9
10thread_local! {
26 static CONFIG: RefCell<Option<Arc<ConfigModel>>> = const { RefCell::new(None) };
27}
28
29#[derive(Debug, ThisError)]
31pub enum ConfigError {
32 #[error("config has already been initialized")]
33 AlreadyInitialized,
34
35 #[error("toml error: {0}")]
37 CannotParseToml(String),
38
39 #[error(transparent)]
41 ConfigSchemaError(#[from] ConfigSchemaError),
42}
43
44pub struct Config {}
49
50impl Config {
51 #[must_use]
53 pub fn get() -> Arc<ConfigModel> {
54 CONFIG.with(|cfg| {
55 cfg.borrow()
56 .as_ref()
57 .cloned()
58 .expect("⚠️ Config must be initialized before use")
59 })
60 }
61
62 #[must_use]
64 pub fn try_get() -> Option<Arc<ConfigModel>> {
65 CONFIG.with(|cfg| cfg.borrow().as_ref().cloned())
66 }
67
68 pub fn init_from_toml(config_str: &str) -> Result<Arc<ConfigModel>, Error> {
71 let config: ConfigModel =
72 toml::from_str(config_str).map_err(|e| ConfigError::CannotParseToml(e.to_string()))?;
73
74 config.validate().map_err(ConfigError::from)?;
76
77 CONFIG.with(|cfg| {
78 let mut borrow = cfg.borrow_mut();
79 if borrow.is_some() {
80 return Err(ConfigError::AlreadyInitialized.into());
81 }
82 let arc = Arc::new(config);
83 *borrow = Some(arc.clone());
84
85 Ok(arc)
86 })
87 }
88
89 pub fn to_toml() -> Result<String, Error> {
91 let cfg = Self::get();
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}