Skip to main content

trait_kit/core/
config.rs

1// Copyright © 2026 Kirky.X. All rights reserved.
2
3//! Config key trait and ConfigHandle for configuration management.
4
5use std::sync::Arc;
6
7use arc_swap::ArcSwap;
8
9/// The key trait for identifying configuration in Kit.
10///
11/// Each configuration is identified by a unique key type that implements this trait.
12/// The `Config` associated type specifies the configuration value type.
13/// The `NAME` constant provides a diagnostic name for error messages.
14///
15/// # Type Constraints
16///
17/// The `Config` type must satisfy `Send + Sync + 'static` for thread-safe storage.
18/// Note: `Config` must be `Sized` (no `?Sized` bound) because `ArcSwap<T>` requires `T: Sized`.
19///
20/// The key type itself must satisfy `'static` for TypeId stability.
21///
22/// # Example
23///
24/// ```
25/// use trait_kit::core::config::ConfigKey;
26///
27/// struct MyConfig;
28/// impl ConfigKey for MyConfig {
29///     type Config = i32;
30///     const NAME: &'static str = "my_config";
31/// }
32///
33/// assert_eq!(MyConfig::NAME, "my_config");
34/// ```
35pub trait ConfigKey: 'static {
36    /// The configuration value type.
37    /// Must satisfy `Send + Sync + 'static` and `Sized`.
38    type Config: Send + Sync + 'static;
39
40    /// The diagnostic name for this config key.
41    /// Used in error messages like `MissingConfig { key: "logger_config" }`.
42    const NAME: &'static str;
43}
44
45/// A shared handle to a configuration value.
46///
47/// `ConfigHandle<T>` wraps an `Arc<ArcSwap<T>>` to provide:
48/// - `load()`: Read the current configuration snapshot (returns `Arc<T>`).
49/// - `set()`: Update the configuration value.
50/// - `clone`: Share the same underlying swap cell across multiple handles.
51///
52/// Multiple `ConfigHandle<T>` clones point to the same underlying swap cell.
53/// Updates via one handle are immediately visible to all other handles.
54///
55/// Old snapshots (returned by `load()`) remain valid after updates.
56///
57/// # Type Constraints
58///
59/// `T` must satisfy `Send + Sync + 'static` and `Sized`.
60///
61/// # Example
62///
63/// ```
64/// use trait_kit::core::config::ConfigHandle;
65///
66/// let handle = ConfigHandle::new(42);
67/// assert_eq!(*handle.load(), 42);
68///
69/// let cloned = handle.clone();
70/// handle.set(100);
71/// assert_eq!(*cloned.load(), 100);
72/// ```
73#[derive(Debug)]
74pub struct ConfigHandle<T: Send + Sync + 'static> {
75    inner: Arc<ArcSwap<T>>,
76}
77
78impl<T: Send + Sync + 'static> ConfigHandle<T> {
79    /// Create a new ConfigHandle with an initial value.
80    pub fn new(value: T) -> Self {
81        ConfigHandle {
82            inner: Arc::new(ArcSwap::new(Arc::new(value))),
83        }
84    }
85
86    /// Load the current configuration snapshot.
87    ///
88    /// Returns an `Arc<T>` that remains valid even after subsequent `set()` calls.
89    pub fn load(&self) -> Arc<T> {
90        self.inner.load_full()
91    }
92
93    /// Update the configuration value.
94    ///
95    /// All clones of this handle will see the new value on their next `load()` call.
96    pub fn set(&self, value: T) {
97        self.inner.store(Arc::new(value));
98    }
99}
100
101impl<T: Send + Sync + 'static> Clone for ConfigHandle<T> {
102    fn clone(&self) -> Self {
103        ConfigHandle {
104            inner: Arc::clone(&self.inner),
105        }
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[allow(dead_code)]
114    struct TestConfig;
115    impl ConfigKey for TestConfig {
116        type Config = i32;
117        const NAME: &'static str = "test_config";
118    }
119
120    #[test]
121    fn new_creates_handle_with_initial_value() {
122        let handle = ConfigHandle::new(42);
123        assert_eq!(*handle.load(), 42);
124    }
125
126    #[test]
127    fn load_returns_current_value() {
128        let handle = ConfigHandle::new(10);
129        assert_eq!(*handle.load(), 10);
130    }
131
132    #[test]
133    fn set_updates_value() {
134        let handle = ConfigHandle::new(1);
135        handle.set(2);
136        assert_eq!(*handle.load(), 2);
137    }
138
139    #[test]
140    fn clone_shares_same_inner_cell() {
141        let handle = ConfigHandle::new(100);
142        let cloned = handle.clone();
143        handle.set(200);
144        assert_eq!(*cloned.load(), 200);
145    }
146}