verdure_ioc/event.rs
1//! Container lifecycle event system for the Verdure ecosystem
2//!
3//! This module provides a comprehensive event system for monitoring container
4//! and component lifecycle events across the Verdure ecosystem. It enables
5//! observability and debugging of the dependency injection process that powers
6//! all Verdure applications, from simple services to complex web applications.
7
8use crate::container::ComponentContainer;
9use std::any::TypeId;
10use std::time::Duration;
11
12/// Container lifecycle events enumeration
13///
14/// This enum represents different events that occur during the container's lifecycle,
15/// including initialization phases and component creation events.
16///
17/// # Examples
18///
19/// ```rust
20/// use verdure_ioc::{ContainerLifecycleEvent, ComponentContainer};
21///
22/// fn handle_event(event: &ContainerLifecycleEvent) {
23/// match event {
24/// ContainerLifecycleEvent::InitializationStarted { component_count, .. } => {
25/// println!("Starting initialization of {} components", component_count);
26/// }
27/// ContainerLifecycleEvent::InitializationCompleted { duration, .. } => {
28/// println!("Initialization completed in {:?}", duration);
29/// }
30/// ContainerLifecycleEvent::ComponentCreated { component_name, .. } => {
31/// println!("Created component: {}", component_name);
32/// }
33/// }
34/// }
35/// ```
36pub enum ContainerLifecycleEvent<'a> {
37 /// Fired when container initialization begins
38 InitializationStarted {
39 /// Reference to the container being initialized
40 container: &'a ComponentContainer,
41 /// Total number of components to be initialized
42 component_count: usize,
43 },
44 /// Fired when container initialization completes successfully
45 InitializationCompleted {
46 /// Reference to the initialized container
47 container: &'a ComponentContainer,
48 /// Number of components that were successfully initialized
49 component_count: usize,
50 /// Total time taken for initialization
51 duration: Duration,
52 },
53 /// Fired when an individual component is created
54 ComponentCreated {
55 /// Reference to the container
56 container: &'a ComponentContainer,
57 /// Human-readable name of the component type
58 component_name: &'static str,
59 /// TypeId of the created component
60 component_type_id: TypeId,
61 /// Time taken to create this specific component
62 creation_duration: Duration,
63 },
64}
65
66/// Trait for implementing lifecycle event listeners
67///
68/// Implement this trait to receive notifications about container lifecycle events.
69/// Listeners must be thread-safe as they may be called from multiple threads.
70///
71/// # Examples
72///
73/// ```rust
74/// use verdure_ioc::{LifecycleListener, ContainerLifecycleEvent};
75///
76/// struct MyListener;
77///
78/// impl LifecycleListener for MyListener {
79/// fn on_lifecycle_event(&self, event: &ContainerLifecycleEvent) {
80/// println!("Received lifecycle event");
81/// }
82/// }
83/// ```
84pub trait LifecycleListener: Send + Sync {
85 /// Called when a lifecycle event occurs
86 ///
87 /// # Arguments
88 ///
89 /// * `event` - The lifecycle event that occurred
90 fn on_lifecycle_event(&self, event: &ContainerLifecycleEvent);
91}
92
93/// Static definition of a lifecycle event listener
94///
95/// This structure is used to register event listeners with the container
96/// using the `lifecycle_listener!` macro. It contains the listener's name
97/// and handler function.
98///
99/// # Examples
100///
101/// ```rust
102/// use verdure_ioc::{LifecycleListenerDefinition, ContainerLifecycleEvent};
103///
104/// fn my_handler(event: &ContainerLifecycleEvent) {
105/// println!("Event received");
106/// }
107///
108/// let definition = LifecycleListenerDefinition {
109/// name: "my_listener",
110/// handler: my_handler,
111/// };
112/// ```
113pub struct LifecycleListenerDefinition {
114 /// Unique name identifying this listener
115 pub name: &'static str,
116 /// Function to call when events occur
117 pub handler: fn(&ContainerLifecycleEvent),
118}
119
120inventory::collect!(LifecycleListenerDefinition);
121
122/// Publisher for container lifecycle events
123///
124/// `LifecycleEventPublisher` manages the collection of registered event listeners
125/// and dispatches events to all registered handlers. It is used internally by
126/// the container to publish events during various lifecycle phases.
127///
128/// # Thread Safety
129///
130/// This struct is thread-safe and can be shared across multiple threads.
131/// Event publishing is synchronous and will call all listeners in sequence.
132pub struct LifecycleEventPublisher {
133 /// Collection of all registered listener definitions
134 listeners: Vec<&'static LifecycleListenerDefinition>,
135}
136
137impl LifecycleEventPublisher {
138 /// Creates a new LifecycleEventPublisher
139 ///
140 /// This method automatically discovers all registered lifecycle listeners
141 /// using the inventory system and prepares them for event dispatching.
142 ///
143 /// # Examples
144 ///
145 /// ```rust
146 /// use verdure_ioc::LifecycleEventPublisher;
147 ///
148 /// let publisher = LifecycleEventPublisher::new();
149 /// ```
150 pub fn new() -> Self {
151 let listeners: Vec<&'static LifecycleListenerDefinition> =
152 inventory::iter::<LifecycleListenerDefinition>().collect();
153
154 Self { listeners }
155 }
156
157 /// Publishes an event to all registered listeners
158 ///
159 /// This method synchronously calls all registered event handlers with the provided event.
160 /// If any listener panics, the panic will propagate up to the caller.
161 ///
162 /// # Arguments
163 ///
164 /// * `event` - The lifecycle event to publish
165 ///
166 /// # Examples
167 ///
168 /// ```rust
169 /// use verdure_ioc::{LifecycleEventPublisher, ContainerLifecycleEvent, ComponentContainer};
170 ///
171 /// let publisher = LifecycleEventPublisher::new();
172 /// let container = ComponentContainer::new();
173 ///
174 /// let event = ContainerLifecycleEvent::InitializationStarted {
175 /// container: &container,
176 /// component_count: 0,
177 /// };
178 ///
179 /// publisher.publish(&event);
180 /// ```
181 pub fn publish(&self, event: &ContainerLifecycleEvent) {
182 for listener in &self.listeners {
183 (listener.handler)(event);
184 }
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::ComponentContainer;
192 use std::sync::Arc;
193 use std::sync::atomic::{AtomicUsize, Ordering};
194 use std::time::Duration;
195
196 static EVENT_COUNTER: AtomicUsize = AtomicUsize::new(0);
197 static LAST_EVENT_TYPE: AtomicUsize = AtomicUsize::new(0);
198
199 fn test_event_handler(event: &ContainerLifecycleEvent) {
200 EVENT_COUNTER.fetch_add(1, Ordering::SeqCst);
201
202 match event {
203 ContainerLifecycleEvent::InitializationStarted { .. } => {
204 LAST_EVENT_TYPE.store(1, Ordering::SeqCst);
205 }
206 ContainerLifecycleEvent::InitializationCompleted { .. } => {
207 LAST_EVENT_TYPE.store(2, Ordering::SeqCst);
208 }
209 ContainerLifecycleEvent::ComponentCreated { .. } => {
210 LAST_EVENT_TYPE.store(3, Ordering::SeqCst);
211 }
212 }
213 }
214
215 #[test]
216 fn test_lifecycle_listener_definition() {
217 let definition = LifecycleListenerDefinition {
218 name: "test_listener",
219 handler: test_event_handler,
220 };
221
222 assert_eq!(definition.name, "test_listener");
223
224 // Reset counter
225 EVENT_COUNTER.store(0, Ordering::SeqCst);
226
227 let container = ComponentContainer::new();
228 let event = ContainerLifecycleEvent::InitializationStarted {
229 container: &container,
230 component_count: 5,
231 };
232
233 (definition.handler)(&event);
234 assert_eq!(EVENT_COUNTER.load(Ordering::SeqCst), 1);
235 assert_eq!(LAST_EVENT_TYPE.load(Ordering::SeqCst), 1);
236 }
237
238 #[test]
239 fn test_lifecycle_event_publisher_creation() {
240 let publisher = LifecycleEventPublisher::new();
241 // The listeners vec will be populated from inventory,
242 // but since we're in a test environment without registered listeners,
243 // it should be empty or contain only test listeners
244 assert!(publisher.listeners.len() >= 0);
245 }
246
247 #[test]
248 fn test_container_lifecycle_events() {
249 let container = ComponentContainer::new();
250
251 // Test InitializationStarted event
252 let init_started = ContainerLifecycleEvent::InitializationStarted {
253 container: &container,
254 component_count: 10,
255 };
256
257 match &init_started {
258 ContainerLifecycleEvent::InitializationStarted {
259 component_count, ..
260 } => {
261 assert_eq!(*component_count, 10);
262 }
263 _ => panic!("Expected InitializationStarted event"),
264 }
265
266 // Test InitializationCompleted event
267 let init_completed = ContainerLifecycleEvent::InitializationCompleted {
268 container: &container,
269 component_count: 10,
270 duration: Duration::from_millis(100),
271 };
272
273 match &init_completed {
274 ContainerLifecycleEvent::InitializationCompleted {
275 component_count,
276 duration,
277 ..
278 } => {
279 assert_eq!(*component_count, 10);
280 assert_eq!(duration.as_millis(), 100);
281 }
282 _ => panic!("Expected InitializationCompleted event"),
283 }
284
285 // Test ComponentCreated event
286 let component_created = ContainerLifecycleEvent::ComponentCreated {
287 container: &container,
288 component_name: "TestComponent",
289 component_type_id: std::any::TypeId::of::<i32>(),
290 creation_duration: Duration::from_millis(50),
291 };
292
293 match &component_created {
294 ContainerLifecycleEvent::ComponentCreated {
295 component_name,
296 component_type_id,
297 creation_duration,
298 ..
299 } => {
300 assert_eq!(*component_name, "TestComponent");
301 assert_eq!(*component_type_id, std::any::TypeId::of::<i32>());
302 assert_eq!(creation_duration.as_millis(), 50);
303 }
304 _ => panic!("Expected ComponentCreated event"),
305 }
306 }
307
308 struct MockLifecycleListener {
309 name: String,
310 events_received: Arc<AtomicUsize>,
311 }
312
313 impl MockLifecycleListener {
314 fn new(name: &str) -> Self {
315 Self {
316 name: name.to_string(),
317 events_received: Arc::new(AtomicUsize::new(0)),
318 }
319 }
320
321 fn handle_event(&self, _event: &ContainerLifecycleEvent) {
322 self.events_received.fetch_add(1, Ordering::SeqCst);
323 }
324
325 fn get_event_count(&self) -> usize {
326 self.events_received.load(Ordering::SeqCst)
327 }
328 }
329
330 impl LifecycleListener for MockLifecycleListener {
331 fn on_lifecycle_event(&self, event: &ContainerLifecycleEvent) {
332 self.handle_event(event);
333 }
334 }
335
336 #[test]
337 fn test_lifecycle_listener_trait() {
338 let listener = MockLifecycleListener::new("mock_listener");
339 assert_eq!(listener.get_event_count(), 0);
340
341 let container = ComponentContainer::new();
342 let event = ContainerLifecycleEvent::InitializationStarted {
343 container: &container,
344 component_count: 3,
345 };
346
347 listener.on_lifecycle_event(&event);
348 assert_eq!(listener.get_event_count(), 1);
349
350 let event2 = ContainerLifecycleEvent::InitializationCompleted {
351 container: &container,
352 component_count: 3,
353 duration: Duration::from_millis(200),
354 };
355
356 listener.on_lifecycle_event(&event2);
357 assert_eq!(listener.get_event_count(), 2);
358 }
359
360 #[test]
361 fn test_event_publisher_with_custom_listeners() {
362 // Create a publisher and test publishing events
363 let publisher = LifecycleEventPublisher {
364 listeners: vec![], // Empty for this test since we can't easily inject listeners
365 };
366
367 let container = ComponentContainer::new();
368 let event = ContainerLifecycleEvent::InitializationStarted {
369 container: &container,
370 component_count: 1,
371 };
372
373 // This should not panic even with no listeners
374 publisher.publish(&event);
375 }
376
377 #[test]
378 fn test_event_types_pattern_matching() {
379 let container = ComponentContainer::new();
380
381 let events = vec![
382 ContainerLifecycleEvent::InitializationStarted {
383 container: &container,
384 component_count: 1,
385 },
386 ContainerLifecycleEvent::InitializationCompleted {
387 container: &container,
388 component_count: 1,
389 duration: Duration::from_millis(10),
390 },
391 ContainerLifecycleEvent::ComponentCreated {
392 container: &container,
393 component_name: "Test",
394 component_type_id: std::any::TypeId::of::<String>(),
395 creation_duration: Duration::from_millis(5),
396 },
397 ];
398
399 let mut event_types = Vec::new();
400
401 for event in events {
402 match event {
403 ContainerLifecycleEvent::InitializationStarted { .. } => {
404 event_types.push("started");
405 }
406 ContainerLifecycleEvent::InitializationCompleted { .. } => {
407 event_types.push("completed");
408 }
409 ContainerLifecycleEvent::ComponentCreated { .. } => {
410 event_types.push("created");
411 }
412 }
413 }
414
415 assert_eq!(event_types, vec!["started", "completed", "created"]);
416 }
417}