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("config has not been initialized")]
35 NotInitialized,
36
37 #[error("toml error: {0}")]
39 CannotParseToml(String),
40
41 #[error(transparent)]
43 ConfigSchemaError(#[from] ConfigSchemaError),
44}
45
46pub struct Config {}
51
52impl Config {
53 pub fn get() -> Result<Arc<ConfigModel>, Error> {
54 CONFIG.with(|cfg| {
55 if let Some(config) = cfg.borrow().as_ref() {
56 return Ok(config.clone());
57 }
58
59 #[cfg(test)]
60 {
61 Ok(Self::init_for_tests())
62 }
63
64 #[cfg(not(test))]
65 {
66 Err(ConfigError::NotInitialized.into())
67 }
68 })
69 }
70
71 pub fn init_from_toml(config_str: &str) -> Result<Arc<ConfigModel>, Error> {
74 let config: ConfigModel =
75 toml::from_str(config_str).map_err(|e| ConfigError::CannotParseToml(e.to_string()))?;
76
77 config.validate().map_err(ConfigError::from)?;
79
80 CONFIG.with(|cfg| {
81 let mut borrow = cfg.borrow_mut();
82 if borrow.is_some() {
83 return Err(ConfigError::AlreadyInitialized.into());
84 }
85 let arc = Arc::new(config);
86 *borrow = Some(arc.clone());
87
88 Ok(arc)
89 })
90 }
91
92 pub fn to_toml() -> Result<String, Error> {
94 let cfg = Self::get()?;
95
96 toml::to_string_pretty(&*cfg)
97 .map_err(|e| ConfigError::CannotParseToml(e.to_string()).into())
98 }
99
100 #[cfg(test)]
102 pub fn reset_for_tests() {
103 CONFIG.with(|cfg| {
104 *cfg.borrow_mut() = None;
105 });
106 }
107
108 #[cfg(test)]
110 #[must_use]
111 pub fn init_for_tests() -> Arc<ConfigModel> {
112 CONFIG.with(|cfg| {
113 let mut borrow = cfg.borrow_mut();
114 if let Some(existing) = borrow.as_ref() {
115 return existing.clone();
116 }
117
118 let config = ConfigModel::test_default();
119 config.validate().expect("test config must validate");
120
121 let arc = Arc::new(config);
122 *borrow = Some(arc.clone());
123 arc
124 })
125 }
126}