Skip to main content

singleton_registry/
macros.rs

1#![allow(dead_code)]
2
3//! Macros for creating singleton registries.
4//!
5//! This module provides a simple macro-based approach to create type-safe,
6//! thread-safe singleton registries with zero external dependencies.
7
8/// Creates a singleton registry module with ergonomic free functions.
9///
10/// The macro generates a module containing storage, tracing infrastructure,
11/// and a private `Api` struct implementing `RegistryApi`.
12///
13/// # Example
14///
15/// ```rust
16/// use singleton_registry::define_registry;
17/// use std::sync::Arc;
18///
19/// // Create registries - each is isolated
20/// define_registry!(global);
21/// define_registry!(cache);
22///
23/// // Register and retrieve values
24/// global::register(42i32);
25/// cache::register("redis".to_string());
26///
27/// let num: Arc<i32> = global::get().unwrap();
28/// let msg: Arc<String> = cache::get().unwrap();
29///
30/// assert_eq!(*num, 42);
31/// assert_eq!(&**msg, "redis");
32/// ```
33#[macro_export]
34macro_rules! define_registry {
35    ($name:ident) => {
36        pub mod $name {
37            use std::sync::{Arc, LazyLock, Mutex};
38            use std::collections::HashMap;
39            use std::any::{TypeId, Any};
40
41            // Storage for registered values (module-private)
42            static STORAGE: LazyLock<Mutex<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>> =
43                LazyLock::new(|| Mutex::new(HashMap::new()));
44
45            // Trace callback storage (module-private)
46            // Note: This type matches TraceCallback in registry_trait.rs - keep in sync
47            type TraceCallback = LazyLock<Mutex<Option<Arc<dyn Fn(&$crate::RegistryEvent) + Send + Sync>>>>;
48            static TRACE: TraceCallback = LazyLock::new(|| Mutex::new(None));
49
50            /// Zero-sized type that implements the registry API.
51            ///
52            /// All registry operations are provided by the `RegistryApi` trait's
53            /// default implementations. This struct only provides access to the statics.
54            struct Api;
55
56            impl $crate::RegistryApi for Api {
57                fn storage() -> &'static LazyLock<Mutex<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>> {
58                    &STORAGE
59                }
60
61                fn trace() -> &'static TraceCallback {
62                    &TRACE
63                }
64
65                // All other methods (register, get, contains, etc.) are provided by
66                // the trait's default implementations!
67            }
68
69            /// Convenient constant for accessing the registry API.
70            const API: Api = Api;
71
72            // Free functions for ergonomic usage - they delegate to API
73
74            /// Register a value in the registry.
75            pub fn register<T: Send + Sync + 'static>(value: T) {
76                use $crate::RegistryApi;
77                API.register(value)
78            }
79
80            /// Register an Arc-wrapped value in the registry.
81            pub fn register_arc<T: Send + Sync + 'static>(value: Arc<T>) {
82                use $crate::RegistryApi;
83                API.register_arc(value)
84            }
85
86            /// Retrieve a value from the registry.
87            pub fn get<T: Send + Sync + 'static>() -> Result<Arc<T>, $crate::RegistryError> {
88                use $crate::RegistryApi;
89                API.get()
90            }
91
92            /// Retrieve a cloned value from the registry.
93            pub fn get_cloned<T: Send + Sync + Clone + 'static>() -> Result<T, $crate::RegistryError> {
94                use $crate::RegistryApi;
95                API.get_cloned()
96            }
97
98            /// Retrieve a value from the registry, returning None if not registered.
99            pub fn try_get<T: Send + Sync + 'static>() -> Option<Arc<T>> {
100                use $crate::RegistryApi;
101                API.try_get()
102            }
103
104            /// Check if a type is registered in the registry.
105            pub fn contains<T: Send + Sync + 'static>() -> Result<bool, $crate::RegistryError> {
106                use $crate::RegistryApi;
107                API.contains::<T>()
108            }
109
110            /// Set a tracing callback for registry operations.
111            pub fn set_trace_callback(callback: impl Fn(&$crate::RegistryEvent) + Send + Sync + 'static) {
112                use $crate::RegistryApi;
113                API.set_trace_callback(callback)
114            }
115
116            /// Clear the tracing callback.
117            pub fn clear_trace_callback() {
118                use $crate::RegistryApi;
119                API.clear_trace_callback()
120            }
121
122            /// Clear the registry.
123            #[doc(hidden)]
124            pub fn clear() {
125                use $crate::RegistryApi;
126                API.clear()
127            }
128        }
129    };
130}
131
132#[cfg(test)]
133mod tests {
134    // use crate::RegistryApi;
135    use std::sync::Arc;
136
137    #[test]
138    fn test_define_registry_macro() {
139        define_registry!(test_reg);
140
141        // Test register and get (ergonomic free functions)
142        test_reg::register(100i32);
143        let value: Arc<i32> = test_reg::get().unwrap();
144        assert_eq!(*value, 100);
145
146        // Test contains
147        assert!(test_reg::contains::<i32>().unwrap());
148        assert!(!test_reg::contains::<f64>().unwrap());
149    }
150
151    #[test]
152    fn test_multiple_registries() {
153        define_registry!(reg_a);
154        define_registry!(reg_b);
155
156        // Register different values in each
157        reg_a::register(1i32);
158        reg_b::register(2i32);
159
160        // Verify isolation
161        let a_val: Arc<i32> = reg_a::get().unwrap();
162        let b_val: Arc<i32> = reg_b::get().unwrap();
163
164        assert_eq!(*a_val, 1);
165        assert_eq!(*b_val, 2);
166    }
167
168    #[test]
169    fn test_tracing() {
170        define_registry!(trace_test);
171
172        use std::sync::Mutex;
173        let events = Arc::new(Mutex::new(Vec::new()));
174        let events_clone = events.clone();
175
176        trace_test::set_trace_callback(move |event| {
177            events_clone.lock().unwrap().push(format!("{}", event));
178        });
179
180        trace_test::register(42i32);
181        let _: Arc<i32> = trace_test::get().unwrap();
182        let _ = trace_test::contains::<i32>();
183
184        let recorded = events.lock().unwrap();
185        assert_eq!(recorded.len(), 4);
186        assert!(recorded[0].contains("register"));
187        assert!(recorded[1].contains("register_completed"));
188        assert!(recorded[2].contains("get"));
189        assert!(recorded[3].contains("contains"));
190    }
191
192    #[test]
193    fn test_additional_functions() {
194        define_registry!(extra_test);
195
196        // Test register_arc
197        let val = Arc::new(99i32);
198        extra_test::register_arc(val);
199
200        // Test get_cloned
201        let cloned: i32 = extra_test::get_cloned().unwrap();
202        assert_eq!(cloned, 99);
203
204        // Test clear_trace_callback
205        extra_test::set_trace_callback(|_| {});
206        extra_test::clear_trace_callback(); // Just verify it doesn't panic
207    }
208
209    #[test]
210    fn test_try_get() {
211        define_registry!(try_get_test);
212
213        assert!(try_get_test::try_get::<i32>().is_none());
214        try_get_test::register(77i32);
215        let val = try_get_test::try_get::<i32>().expect("should be Some after register");
216        assert_eq!(*val, 77);
217    }
218}