1use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::{broadcast, mpsc};
10use tracing::debug;
11use uuid::Uuid;
12
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15pub enum ChangeType {
16 Created,
17 Updated,
18 Deleted,
19 Cleared,
20}
21
22impl std::fmt::Display for ChangeType {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 match self {
25 ChangeType::Created => write!(f, "CREATED"),
26 ChangeType::Updated => write!(f, "UPDATED"),
27 ChangeType::Deleted => write!(f, "DELETED"),
28 ChangeType::Cleared => write!(f, "CLEARED"),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ChangeNotification {
36 pub key: String,
37 pub change_type: ChangeType,
38 pub old_value: Option<String>,
39 pub new_value: Option<String>,
40 pub version: u64,
41 pub timestamp: u64,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub struct SubscriptionId(Uuid);
47
48impl SubscriptionId {
49 pub fn new() -> Self {
50 Self(Uuid::new_v4())
51 }
52}
53
54impl Default for SubscriptionId {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60#[derive(Debug, Clone)]
62pub enum NotificationFilter {
63 All,
65 Key(String),
67 KeyPrefix(String),
69 ChangeType(ChangeType),
71 And(Vec<NotificationFilter>),
73 Or(Vec<NotificationFilter>),
75}
76
77impl NotificationFilter {
78 pub fn matches(&self, notification: &ChangeNotification) -> bool {
80 match self {
81 NotificationFilter::All => true,
82 NotificationFilter::Key(key) => notification.key == *key,
83 NotificationFilter::KeyPrefix(prefix) => notification.key.starts_with(prefix),
84 NotificationFilter::ChangeType(change_type) => notification.change_type == *change_type,
85 NotificationFilter::And(filters) => filters.iter().all(|f| f.matches(notification)),
86 NotificationFilter::Or(filters) => filters.iter().any(|f| f.matches(notification)),
87 }
88 }
89}
90
91pub struct Subscription {
93 pub id: SubscriptionId,
94 pub filter: NotificationFilter,
95 pub receiver: mpsc::UnboundedReceiver<ChangeNotification>,
96}
97
98#[derive(Debug, Clone, Default)]
100pub struct NotificationStats {
101 pub total_notifications_sent: u64,
102 pub total_subscribers: usize,
103 pub dropped_notifications: u64,
104}
105
106pub struct NotificationBus {
108 broadcast_tx: broadcast::Sender<ChangeNotification>,
110
111 #[allow(clippy::type_complexity)]
113 subscribers: Arc<
114 RwLock<
115 HashMap<
116 SubscriptionId,
117 (
118 NotificationFilter,
119 mpsc::UnboundedSender<ChangeNotification>,
120 ),
121 >,
122 >,
123 >,
124
125 stats: Arc<RwLock<NotificationStats>>,
127}
128
129impl NotificationBus {
130 pub fn new() -> Self {
132 let (broadcast_tx, _) = broadcast::channel(1000);
133
134 Self {
135 broadcast_tx,
136 subscribers: Arc::new(RwLock::new(HashMap::new())),
137 stats: Arc::new(RwLock::new(NotificationStats::default())),
138 }
139 }
140
141 pub fn subscribe(&self, filter: NotificationFilter) -> Subscription {
143 let id = SubscriptionId::new();
144 let (tx, rx) = mpsc::unbounded_channel();
145
146 {
147 let mut subscribers = self.subscribers.write();
148 subscribers.insert(id, (filter.clone(), tx));
149 }
150
151 {
152 let mut stats = self.stats.write();
153 stats.total_subscribers += 1;
154 }
155
156 debug!(
157 "New subscription created: {:?} with filter: {:?}",
158 id, filter
159 );
160
161 Subscription {
162 id,
163 filter,
164 receiver: rx,
165 }
166 }
167
168 pub fn subscribe_all(&self) -> Subscription {
170 self.subscribe(NotificationFilter::All)
171 }
172
173 pub fn subscribe_key(&self, key: &str) -> Subscription {
175 self.subscribe(NotificationFilter::Key(key.to_string()))
176 }
177
178 pub fn subscribe_prefix(&self, prefix: &str) -> Subscription {
180 self.subscribe(NotificationFilter::KeyPrefix(prefix.to_string()))
181 }
182
183 pub fn subscribe_change_type(&self, change_type: ChangeType) -> Subscription {
185 self.subscribe(NotificationFilter::ChangeType(change_type))
186 }
187
188 pub fn unsubscribe(&self, subscription_id: SubscriptionId) {
190 let mut subscribers = self.subscribers.write();
191 if subscribers.remove(&subscription_id).is_some() {
192 let mut stats = self.stats.write();
193 stats.total_subscribers = stats.total_subscribers.saturating_sub(1);
194 debug!("Subscription removed: {:?}", subscription_id);
195 }
196 }
197
198 pub async fn publish(&self, notification: ChangeNotification) {
200 {
202 let mut stats = self.stats.write();
203 stats.total_notifications_sent += 1;
204 }
205
206 if self.broadcast_tx.send(notification.clone()).is_err() {
208 }
210
211 let subscribers = self.subscribers.read();
213 let mut dropped_count = 0;
214
215 for (filter, sender) in subscribers.values() {
216 if filter.matches(¬ification) && sender.send(notification.clone()).is_err() {
217 dropped_count += 1;
219 }
220 }
221
222 if dropped_count > 0 {
223 let mut stats = self.stats.write();
224 stats.dropped_notifications += dropped_count;
225 debug!(
226 "Dropped {} notifications due to closed channels",
227 dropped_count
228 );
229 }
230
231 debug!(
232 "Published notification: key={}, type={:?}",
233 notification.key, notification.change_type
234 );
235 }
236
237 pub fn broadcast_receiver(&self) -> broadcast::Receiver<ChangeNotification> {
239 self.broadcast_tx.subscribe()
240 }
241
242 pub fn get_stats(&self) -> NotificationStats {
244 let stats = self.stats.read();
245 let subscribers = self.subscribers.read();
246
247 NotificationStats {
248 total_notifications_sent: stats.total_notifications_sent,
249 total_subscribers: subscribers.len(),
250 dropped_notifications: stats.dropped_notifications,
251 }
252 }
253
254 pub fn cleanup_closed_subscribers(&self) {
256 let mut subscribers = self.subscribers.write();
257 let initial_count = subscribers.len();
258
259 subscribers.retain(|_, (_, sender)| !sender.is_closed());
260
261 let removed = initial_count - subscribers.len();
262 if removed > 0 {
263 debug!("Cleaned up {} closed subscriber channels", removed);
264 }
265 }
266
267 pub fn subscriber_count(&self) -> usize {
269 self.subscribers.read().len()
270 }
271}
272
273impl Default for NotificationBus {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279pub struct NotificationListener {
281 subscription: Subscription,
282 name: String,
283}
284
285impl NotificationListener {
286 pub fn new(subscription: Subscription, name: String) -> Self {
288 Self { subscription, name }
289 }
290
291 pub async fn listen<F, Fut>(&mut self, mut handler: F)
293 where
294 F: FnMut(ChangeNotification) -> Fut,
295 Fut: std::future::Future<Output = ()>,
296 {
297 debug!("Starting notification listener: {}", self.name);
298
299 while let Some(notification) = self.subscription.receiver.recv().await {
300 debug!(
301 "Listener {} received notification: {:?}",
302 self.name, notification
303 );
304 handler(notification).await;
305 }
306
307 debug!("Notification listener {} stopped", self.name);
308 }
309
310 pub fn subscription_id(&self) -> SubscriptionId {
312 self.subscription.id
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[tokio::test]
321 async fn test_notification_bus_basic() {
322 let bus = NotificationBus::new();
323 let mut subscription = bus.subscribe_all();
324
325 let notification = ChangeNotification {
326 key: "test_key".to_string(),
327 change_type: ChangeType::Created,
328 old_value: None,
329 new_value: Some("test_value".to_string()),
330 version: 1,
331 timestamp: 123456789,
332 };
333
334 bus.publish(notification.clone()).await;
336
337 let received = subscription.receiver.recv().await.unwrap();
339 assert_eq!(received.key, notification.key);
340 assert_eq!(received.change_type, notification.change_type);
341 assert_eq!(received.new_value, notification.new_value);
342 }
343
344 #[tokio::test]
345 async fn test_notification_filtering() {
346 let bus = NotificationBus::new();
347 let mut key_subscription = bus.subscribe_key("specific_key");
348 let mut prefix_subscription = bus.subscribe_prefix("prefix_");
349 let mut type_subscription = bus.subscribe_change_type(ChangeType::Updated);
350
351 let key_notification = ChangeNotification {
353 key: "specific_key".to_string(),
354 change_type: ChangeType::Created,
355 old_value: None,
356 new_value: Some("value".to_string()),
357 version: 1,
358 timestamp: 123456789,
359 };
360
361 let prefix_notification = ChangeNotification {
363 key: "prefix_test".to_string(),
364 change_type: ChangeType::Created,
365 old_value: None,
366 new_value: Some("value".to_string()),
367 version: 2,
368 timestamp: 123456790,
369 };
370
371 let type_notification = ChangeNotification {
373 key: "any_key".to_string(),
374 change_type: ChangeType::Updated,
375 old_value: Some("old".to_string()),
376 new_value: Some("new".to_string()),
377 version: 3,
378 timestamp: 123456791,
379 };
380
381 bus.publish(key_notification.clone()).await;
383 bus.publish(prefix_notification.clone()).await;
384 bus.publish(type_notification.clone()).await;
385
386 let received = key_subscription.receiver.recv().await.unwrap();
388 assert_eq!(received.key, "specific_key");
389
390 let received = prefix_subscription.receiver.recv().await.unwrap();
392 assert_eq!(received.key, "prefix_test");
393
394 let received = type_subscription.receiver.recv().await.unwrap();
396 assert_eq!(received.change_type, ChangeType::Updated);
397 }
398
399 #[tokio::test]
400 async fn test_notification_stats() {
401 let bus = NotificationBus::new();
402 let _subscription = bus.subscribe_all();
403
404 let notification = ChangeNotification {
405 key: "test".to_string(),
406 change_type: ChangeType::Created,
407 old_value: None,
408 new_value: Some("value".to_string()),
409 version: 1,
410 timestamp: 123456789,
411 };
412
413 bus.publish(notification).await;
414
415 let stats = bus.get_stats();
416 assert_eq!(stats.total_notifications_sent, 1);
417 assert_eq!(stats.total_subscribers, 1);
418 }
419
420 #[test]
421 fn test_notification_filter_logic() {
422 let notification = ChangeNotification {
423 key: "test_key".to_string(),
424 change_type: ChangeType::Updated,
425 old_value: Some("old".to_string()),
426 new_value: Some("new".to_string()),
427 version: 1,
428 timestamp: 123456789,
429 };
430
431 assert!(NotificationFilter::All.matches(¬ification));
433 assert!(NotificationFilter::Key("test_key".to_string()).matches(¬ification));
434 assert!(!NotificationFilter::Key("other_key".to_string()).matches(¬ification));
435 assert!(NotificationFilter::KeyPrefix("test_".to_string()).matches(¬ification));
436 assert!(!NotificationFilter::KeyPrefix("other_".to_string()).matches(¬ification));
437 assert!(NotificationFilter::ChangeType(ChangeType::Updated).matches(¬ification));
438 assert!(!NotificationFilter::ChangeType(ChangeType::Created).matches(¬ification));
439
440 let and_filter = NotificationFilter::And(vec![
442 NotificationFilter::KeyPrefix("test_".to_string()),
443 NotificationFilter::ChangeType(ChangeType::Updated),
444 ]);
445 assert!(and_filter.matches(¬ification));
446
447 let or_filter = NotificationFilter::Or(vec![
449 NotificationFilter::Key("wrong_key".to_string()),
450 NotificationFilter::ChangeType(ChangeType::Updated),
451 ]);
452 assert!(or_filter.matches(¬ification));
453 }
454}