gglib_core/ports/event_emitter.rs
1//! Event emitter trait for cross-crate event broadcasting.
2//!
3//! This module defines the abstraction for emitting application events.
4//! Implementations handle transport details (channels, Tauri events, SSE, etc.).
5
6use crate::events::AppEvent;
7
8/// Trait for emitting application events.
9///
10/// This abstraction keeps event plumbing consistent across domains and prevents
11/// channel types from becoming part of the public API surface.
12///
13/// # Implementations
14///
15/// - `NoopEmitter` - For tests and CLI contexts that don't need events
16/// - Adapter-specific implementations (Tauri, Axum SSE, etc.)
17///
18/// # Example
19///
20/// ```ignore
21/// // In a service
22/// fn start_server(&self, emitter: Arc<dyn AppEventEmitter>) {
23/// // ... start server logic ...
24/// emitter.emit(AppEvent::McpServerStarted { ... });
25/// }
26/// ```
27pub trait AppEventEmitter: Send + Sync {
28 /// Emit an application event.
29 ///
30 /// Implementations should handle the event asynchronously or buffer it.
31 /// This method should not block.
32 fn emit(&self, event: AppEvent);
33
34 /// Clone this emitter into a boxed trait object.
35 ///
36 /// This enables cloning of `Arc<dyn AppEventEmitter>` without requiring
37 /// the underlying type to implement Clone.
38 fn clone_box(&self) -> Box<dyn AppEventEmitter>;
39}
40
41/// A no-op event emitter for tests and CLI contexts.
42///
43/// This implementation discards all events, making it suitable for:
44/// - Unit tests that don't need to verify event emission
45/// - CLI applications that don't have an event listener
46/// - Contexts where event emission is optional
47#[derive(Debug, Clone, Default)]
48pub struct NoopEmitter;
49
50impl NoopEmitter {
51 /// Create a new no-op emitter.
52 pub const fn new() -> Self {
53 Self
54 }
55}
56
57impl AppEventEmitter for NoopEmitter {
58 fn emit(&self, _event: AppEvent) {
59 // Intentionally do nothing
60 }
61
62 fn clone_box(&self) -> Box<dyn AppEventEmitter> {
63 Box::new(self.clone())
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use std::sync::Arc;
71
72 #[test]
73 fn test_noop_emitter() {
74 let emitter = NoopEmitter::new();
75
76 // Should not panic
77 emitter.emit(AppEvent::model_removed(1));
78 }
79
80 #[test]
81 fn test_noop_emitter_clone_box() {
82 let emitter = NoopEmitter::new();
83 let _boxed: Box<dyn AppEventEmitter> = emitter.clone_box();
84 }
85
86 #[test]
87 fn test_arc_emitter() {
88 let emitter: Arc<dyn AppEventEmitter> = Arc::new(NoopEmitter::new());
89 emitter.emit(AppEvent::model_removed(1));
90 }
91}