1use std::sync::OnceLock;
7
8pub trait EventEmitter: Send + Sync + 'static {
14 fn emit(&self, event: &str, payload: serde_json::Value);
15}
16
17pub struct NoOpEmitter;
19
20impl EventEmitter for NoOpEmitter {
21 fn emit(&self, _event: &str, _payload: serde_json::Value) {}
22}
23
24static EVENT_EMITTER: OnceLock<Box<dyn EventEmitter>> = OnceLock::new();
26
27pub fn set_event_emitter(emitter: Box<dyn EventEmitter>) {
29 let _ = EVENT_EMITTER.set(emitter);
30}
31
32pub fn emit_event<T: serde::Serialize>(event: &str, payload: &T) {
34 if let Some(emitter) = EVENT_EMITTER.get() {
35 if let Ok(value) = serde_json::to_value(payload) {
36 emitter.emit(event, value);
37 }
38 }
39}
40
41pub fn emit_event_json(event: &str, payload: serde_json::Value) {
43 if let Some(emitter) = EVENT_EMITTER.get() {
44 emitter.emit(event, payload);
45 }
46}
47
48pub fn has_event_emitter() -> bool {
50 EVENT_EMITTER.get().is_some()
51}
52
53pub trait SubscriptionRefresher: Send + Sync + 'static {
63 fn refresh(&self);
64}
65
66pub struct NoOpSubscriptionRefresher;
67impl SubscriptionRefresher for NoOpSubscriptionRefresher {
68 fn refresh(&self) {}
69}
70
71static SUBSCRIPTION_REFRESHER: OnceLock<Box<dyn SubscriptionRefresher>> = OnceLock::new();
72
73pub fn set_subscription_refresher(refresher: Box<dyn SubscriptionRefresher>) {
74 let _ = SUBSCRIPTION_REFRESHER.set(refresher);
75}
76
77pub fn refresh_subscriptions() {
78 if let Some(r) = SUBSCRIPTION_REFRESHER.get() {
79 r.refresh();
80 }
81}
82
83pub trait ProgressReporter: Send + Sync {
85 fn report_progress(&self, percentage: Option<u8>, bytes: Option<u64>, bytes_per_sec: Option<f64>) -> Result<(), &'static str>;
86 fn report_complete(&self) -> Result<(), &'static str>;
87}
88
89pub struct NoOpProgressReporter;
91
92impl ProgressReporter for NoOpProgressReporter {
93 fn report_progress(&self, _: Option<u8>, _: Option<u64>, _: Option<f64>) -> Result<(), &'static str> { Ok(()) }
94 fn report_complete(&self) -> Result<(), &'static str> { Ok(()) }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use std::sync::atomic::{AtomicUsize, Ordering};
101
102 static REFRESH_CALLS: AtomicUsize = AtomicUsize::new(0);
111 static REFRESH_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
112
113 struct CountingRefresher;
114 impl SubscriptionRefresher for CountingRefresher {
115 fn refresh(&self) {
116 REFRESH_CALLS.fetch_add(1, Ordering::Relaxed);
117 }
118 }
119
120 fn refresh_test_setup() -> std::sync::MutexGuard<'static, ()> {
126 let guard = REFRESH_TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
127 set_subscription_refresher(Box::new(CountingRefresher));
128 REFRESH_CALLS.store(0, Ordering::Relaxed);
129 guard
130 }
131
132 #[test]
133 fn refresh_subscriptions_invokes_registered_impl_once() {
134 let _g = refresh_test_setup();
135 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 0);
136
137 refresh_subscriptions();
138
139 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1);
140 }
141
142 #[test]
143 fn refresh_subscriptions_invokes_registered_impl_per_call() {
144 let _g = refresh_test_setup();
148
149 for _ in 0..5 {
150 refresh_subscriptions();
151 }
152
153 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 5);
154 }
155
156 #[test]
157 fn set_subscription_refresher_is_idempotent() {
158 let _g = refresh_test_setup();
164
165 struct PanicRefresher;
166 impl SubscriptionRefresher for PanicRefresher {
167 fn refresh(&self) {
168 panic!("PanicRefresher::refresh() should never be reached — OnceLock should have ignored the second set_subscription_refresher call");
169 }
170 }
171 set_subscription_refresher(Box::new(PanicRefresher));
172
173 refresh_subscriptions();
177
178 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1);
179 }
180
181 #[test]
182 fn refresh_subscriptions_is_thread_safe() {
183 let _g = refresh_test_setup();
187 let handles: Vec<_> = (0..10).map(|_| {
188 std::thread::spawn(|| {
189 for _ in 0..100 {
190 refresh_subscriptions();
191 }
192 })
193 }).collect();
194 for h in handles { h.join().unwrap(); }
195
196 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), 1_000);
197 }
198
199 #[test]
200 fn no_op_subscription_refresher_returns_quietly() {
201 let _g = refresh_test_setup();
204 let initial = REFRESH_CALLS.load(Ordering::Relaxed);
205 let r = NoOpSubscriptionRefresher;
206 r.refresh();
207 r.refresh();
208 assert_eq!(REFRESH_CALLS.load(Ordering::Relaxed), initial);
209 }
210
211 #[test]
216 fn no_op_emitter_does_not_panic() {
217 let e = NoOpEmitter;
218 e.emit("test_event", serde_json::json!({"k": "v"}));
219 }
221
222 #[test]
223 fn emit_event_when_unregistered_is_silent() {
224 crate::traits::emit_event("test_event", &serde_json::json!({"k": "v"}));
230 }
231
232 #[test]
233 fn emit_event_json_when_unregistered_is_silent() {
234 crate::traits::emit_event_json("test_event", serde_json::json!({"k": "v"}));
235 }
236
237 #[test]
238 fn no_op_progress_reporter_returns_ok() {
239 let r = NoOpProgressReporter;
240 assert!(r.report_progress(Some(50), Some(1024), Some(100.0)).is_ok());
241 assert!(r.report_complete().is_ok());
242 }
243}