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}