Skip to main content

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}