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}