charon_error/settings/global_settings.rs
1//! The [`GlobalSettings`] trait for thread-safe, set-once configuration.
2//!
3//! Provides a pattern for global singletons backed by `OnceLock<RwLock<T>>`.
4//! Implement this trait for each configuration struct that needs
5//! process-wide, write-once / read-many semantics.
6
7use crate::{ErrorReport, ResultExt, StringError};
8use std::sync::{OnceLock, RwLock, RwLockReadGuard};
9
10/// Thread-safe, write-once global configuration trait.
11///
12/// Backed by `OnceLock<RwLock<T>>` — call [`set_global_settings`](Self::set_global_settings)
13/// once at startup, then read concurrently with [`get_global_settings`](Self::get_global_settings).
14///
15/// # Implementing
16///
17/// ```rust,no_run
18/// use charon_error::prelude::*;
19/// use charon_error::prelude::global_settings::*;
20/// use std::sync::{OnceLock, RwLock};
21///
22/// #[derive(Debug, Clone)]
23/// struct MySettings { pub verbose: bool }
24///
25/// static MY_SETTINGS: OnceLock<RwLock<MySettings>> = OnceLock::new();
26///
27/// impl GlobalSettings for MySettings {
28/// type Setting = MySettings;
29/// fn once_lock() -> &'static OnceLock<RwLock<Self::Setting>> { &MY_SETTINGS }
30/// fn get_setting_object_name() -> &'static str { "MY_SETTINGS" }
31/// }
32///
33/// // At startup:
34/// MySettings::set_global_settings(MySettings { verbose: true }).unwrap();
35/// ```
36pub trait GlobalSettings {
37 /// The settings type stored in the global lock.
38 type Setting;
39
40 /// Return a reference to the static `OnceLock` that holds this setting.
41 fn once_lock() -> &'static OnceLock<RwLock<Self::Setting>>;
42 /// Return a human-readable name for this settings object (used in error messages).
43 fn get_setting_object_name() -> &'static str;
44
45 /// Initialize the global simple report configuration. Must be called once at startup.
46 ///
47 /// Returns an error if called more than once.
48 fn set_global_settings(setting: Self::Setting) -> Result<(), ErrorReport>
49 where
50 <Self as GlobalSettings>::Setting: 'static,
51 {
52 Self::once_lock()
53 .set(RwLock::new(setting))
54 .map_err(|_| {
55 StringError::new(format!(
56 "`{}` was already set",
57 Self::get_setting_object_name()
58 ))
59 })
60 .change_context(GlobalSettingsError::AlreadySet)
61 .error_attach_public_string(
62 "settings_object",
63 Self::get_setting_object_name().to_string(),
64 )
65 }
66
67 /// Read the global simple report configuration. Returns an error if not yet set.
68 fn get_global_settings() -> Result<RwLockReadGuard<'static, Self::Setting>, ErrorReport> {
69 let setting_reader = Self::once_lock()
70 .get()
71 .ok_or(GlobalSettingsError::SettingNotYetSet)
72 .error_attach_public_string(
73 "settings_object",
74 Self::get_setting_object_name().to_string(),
75 )?;
76 setting_reader
77 .read()
78 .map_err(StringError::from_error)
79 .change_context(GlobalSettingsError::AcquireReadLockFailed)
80 .error_attach_public_string(
81 "settings_object",
82 Self::get_setting_object_name().to_string(),
83 )
84 }
85
86 /// Checks if the global setting have been set.
87 /// Returns `true` if set, `false` is not yet set (or is being set).
88 fn is_set() -> bool
89 where
90 <Self as GlobalSettings>::Setting: 'static,
91 {
92 let setting_option = Self::once_lock().get();
93 setting_option.is_some()
94 }
95}
96
97/// Errors that can occur when accessing global settings.
98#[derive(thiserror::Error, Debug, Clone)]
99pub enum GlobalSettingsError {
100 /// Settings were already initialized (second call to `set_global_settings`).
101 #[error("Global Settings were already set, this can only be set once.")]
102 AlreadySet,
103 /// Settings have not been initialized yet.
104 #[error("Global Settings were not set.")]
105 SettingNotYetSet,
106 /// The `RwLock` is poisoned (a thread panicked while holding the lock).
107 #[error("Could not acquire read lock to read Global Settings.")]
108 AcquireReadLockFailed,
109}