1use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::sync::{Arc, Mutex};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct SubscriptionId(u64);
16
17impl SubscriptionId {
18 #[must_use]
20 pub const fn as_u64(self) -> u64 {
21 self.0
22 }
23}
24
25impl std::fmt::Display for SubscriptionId {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 write!(f, "sub-{}", self.0)
28 }
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct BusEvent {
34 pub topic: String,
36 pub source: String,
38 pub payload: serde_json::Value,
40 pub timestamp_ms: u64,
42 pub correlation_id: Option<String>,
44 pub metadata: HashMap<String, String>,
46}
47
48impl BusEvent {
49 #[must_use]
51 pub fn new(
52 topic: impl Into<String>,
53 source: impl Into<String>,
54 payload: serde_json::Value,
55 timestamp_ms: u64,
56 ) -> Self {
57 Self {
58 topic: topic.into(),
59 source: source.into(),
60 payload,
61 timestamp_ms,
62 correlation_id: None,
63 metadata: HashMap::new(),
64 }
65 }
66
67 #[must_use]
69 pub fn with_correlation_id(mut self, id: impl Into<String>) -> Self {
70 self.correlation_id = Some(id.into());
71 self
72 }
73
74 #[must_use]
76 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
77 self.metadata.insert(key.into(), value.into());
78 self
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct EventFilter {
85 pub source_prefix: Option<String>,
87 pub metadata_match: HashMap<String, String>,
89 pub min_values: Vec<(String, f64)>,
92}
93
94impl Default for EventFilter {
95 fn default() -> Self {
96 Self {
97 source_prefix: None,
98 metadata_match: HashMap::new(),
99 min_values: Vec::new(),
100 }
101 }
102}
103
104impl EventFilter {
105 #[must_use]
107 pub fn new() -> Self {
108 Self::default()
109 }
110
111 #[must_use]
113 pub fn with_source_prefix(mut self, prefix: impl Into<String>) -> Self {
114 self.source_prefix = Some(prefix.into());
115 self
116 }
117
118 #[must_use]
120 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
121 self.metadata_match.insert(key.into(), value.into());
122 self
123 }
124
125 #[must_use]
127 pub fn with_min_value(mut self, field: impl Into<String>, min: f64) -> Self {
128 self.min_values.push((field.into(), min));
129 self
130 }
131
132 #[must_use]
134 pub fn matches(&self, event: &BusEvent) -> bool {
135 if let Some(ref prefix) = self.source_prefix {
137 if !event.source.starts_with(prefix) {
138 return false;
139 }
140 }
141
142 for (key, expected) in &self.metadata_match {
144 match event.metadata.get(key) {
145 Some(actual) if actual == expected => {}
146 _ => return false,
147 }
148 }
149
150 for (field, min_val) in &self.min_values {
152 if let Some(val) = event.payload.get(field).and_then(|v| v.as_f64()) {
153 if val < *min_val {
154 return false;
155 }
156 } else {
157 return false; }
159 }
160
161 true
162 }
163}
164
165#[derive(Debug, Clone)]
167struct Subscription {
168 id: SubscriptionId,
169 topic_pattern: String,
170 filter: EventFilter,
171 delivered: Vec<BusEvent>,
173 max_retained: usize,
175 active: bool,
177}
178
179impl Subscription {
180 fn topic_matches(&self, topic: &str) -> bool {
182 if self.topic_pattern == "*" {
183 return true;
184 }
185 if self.topic_pattern.ends_with(".*") {
186 let prefix = &self.topic_pattern[..self.topic_pattern.len() - 2];
187 return topic.starts_with(prefix);
188 }
189 self.topic_pattern == topic
190 }
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct DeadLetterEntry {
196 pub event: BusEvent,
198 pub reason: String,
200}
201
202#[derive(Debug, Clone)]
204pub struct EventBus {
205 inner: Arc<Mutex<EventBusInner>>,
206}
207
208#[derive(Debug)]
209struct EventBusInner {
210 subscriptions: Vec<Subscription>,
211 next_sub_id: u64,
212 event_history: Vec<BusEvent>,
214 dead_letters: Vec<DeadLetterEntry>,
216 max_history: usize,
218 max_dead_letters: usize,
220 total_published: u64,
222 total_delivered: u64,
224}
225
226#[derive(Debug, Clone)]
228pub struct EventBusStats {
229 pub active_subscriptions: usize,
231 pub total_subscriptions: usize,
233 pub total_published: u64,
235 pub total_delivered: u64,
237 pub history_size: usize,
239 pub dead_letter_count: usize,
241}
242
243#[derive(Debug, Clone)]
245pub struct EventBusConfig {
246 pub max_history: usize,
248 pub max_dead_letters: usize,
250 pub default_max_retained: usize,
252}
253
254impl Default for EventBusConfig {
255 fn default() -> Self {
256 Self {
257 max_history: 10_000,
258 max_dead_letters: 1_000,
259 default_max_retained: 100,
260 }
261 }
262}
263
264impl EventBus {
265 #[must_use]
267 pub fn new() -> Self {
268 Self::with_config(EventBusConfig::default())
269 }
270
271 #[must_use]
273 pub fn with_config(config: EventBusConfig) -> Self {
274 Self {
275 inner: Arc::new(Mutex::new(EventBusInner {
276 subscriptions: Vec::new(),
277 next_sub_id: 1,
278 event_history: Vec::new(),
279 dead_letters: Vec::new(),
280 max_history: config.max_history,
281 max_dead_letters: config.max_dead_letters,
282 total_published: 0,
283 total_delivered: 0,
284 })),
285 }
286 }
287
288 pub fn subscribe(
298 &self,
299 topic_pattern: impl Into<String>,
300 filter: EventFilter,
301 ) -> SubscriptionId {
302 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
303 let id = SubscriptionId(inner.next_sub_id);
304 inner.next_sub_id += 1;
305
306 inner.subscriptions.push(Subscription {
307 id,
308 topic_pattern: topic_pattern.into(),
309 filter,
310 delivered: Vec::new(),
311 max_retained: 100,
312 active: true,
313 });
314
315 id
316 }
317
318 pub fn subscribe_with_retention(
320 &self,
321 topic_pattern: impl Into<String>,
322 filter: EventFilter,
323 max_retained: usize,
324 ) -> SubscriptionId {
325 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
326 let id = SubscriptionId(inner.next_sub_id);
327 inner.next_sub_id += 1;
328
329 inner.subscriptions.push(Subscription {
330 id,
331 topic_pattern: topic_pattern.into(),
332 filter,
333 delivered: Vec::new(),
334 max_retained,
335 active: true,
336 });
337
338 id
339 }
340
341 pub fn unsubscribe(&self, sub_id: SubscriptionId) -> bool {
343 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
344 if let Some(sub) = inner.subscriptions.iter_mut().find(|s| s.id == sub_id) {
345 sub.active = false;
346 true
347 } else {
348 false
349 }
350 }
351
352 pub fn publish(&self, event: BusEvent) -> usize {
356 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
357 inner.total_published += 1;
358 let mut delivery_count = 0usize;
359
360 for sub in inner.subscriptions.iter_mut() {
362 if !sub.active {
363 continue;
364 }
365 if !sub.topic_matches(&event.topic) {
366 continue;
367 }
368 if !sub.filter.matches(&event) {
369 continue;
370 }
371
372 sub.delivered.push(event.clone());
373 delivery_count += 1;
374
375 if sub.max_retained > 0 && sub.delivered.len() > sub.max_retained {
377 let excess = sub.delivered.len() - sub.max_retained;
378 sub.delivered.drain(..excess);
379 }
380 }
381
382 inner.total_delivered += delivery_count as u64;
383
384 if delivery_count == 0 {
386 inner.dead_letters.push(DeadLetterEntry {
387 event: event.clone(),
388 reason: "no matching subscribers".to_string(),
389 });
390 if inner.max_dead_letters > 0 && inner.dead_letters.len() > inner.max_dead_letters {
391 inner.dead_letters.remove(0);
392 }
393 }
394
395 inner.event_history.push(event);
397 if inner.max_history > 0 && inner.event_history.len() > inner.max_history {
398 inner.event_history.remove(0);
399 }
400
401 delivery_count
402 }
403
404 #[must_use]
406 pub fn get_events(&self, sub_id: SubscriptionId) -> Vec<BusEvent> {
407 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
408 inner
409 .subscriptions
410 .iter()
411 .find(|s| s.id == sub_id)
412 .map_or_else(Vec::new, |s| s.delivered.clone())
413 }
414
415 pub fn drain_events(&self, sub_id: SubscriptionId) -> Vec<BusEvent> {
417 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
418 inner
419 .subscriptions
420 .iter_mut()
421 .find(|s| s.id == sub_id)
422 .map_or_else(Vec::new, |s| std::mem::take(&mut s.delivered))
423 }
424
425 #[must_use]
427 pub fn dead_letters(&self) -> Vec<DeadLetterEntry> {
428 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
429 inner.dead_letters.clone()
430 }
431
432 #[must_use]
434 pub fn event_history(&self) -> Vec<BusEvent> {
435 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
436 inner.event_history.clone()
437 }
438
439 #[must_use]
441 pub fn events_by_topic(&self, topic: &str) -> Vec<BusEvent> {
442 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
443 inner
444 .event_history
445 .iter()
446 .filter(|e| e.topic == topic)
447 .cloned()
448 .collect()
449 }
450
451 #[must_use]
453 pub fn stats(&self) -> EventBusStats {
454 let inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
455 EventBusStats {
456 active_subscriptions: inner.subscriptions.iter().filter(|s| s.active).count(),
457 total_subscriptions: inner.subscriptions.len(),
458 total_published: inner.total_published,
459 total_delivered: inner.total_delivered,
460 history_size: inner.event_history.len(),
461 dead_letter_count: inner.dead_letters.len(),
462 }
463 }
464
465 pub fn clear(&self) {
467 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
468 inner.event_history.clear();
469 inner.dead_letters.clear();
470 for sub in &mut inner.subscriptions {
471 sub.delivered.clear();
472 }
473 }
474
475 pub fn replay_to(&self, sub_id: SubscriptionId) -> usize {
479 let mut inner = self.inner.lock().unwrap_or_else(|e| e.into_inner());
480
481 let sub_idx = match inner.subscriptions.iter().position(|s| s.id == sub_id) {
483 Some(idx) => idx,
484 None => return 0,
485 };
486
487 let history = inner.event_history.clone();
489 let mut count = 0;
490
491 for event in &history {
492 let sub = &inner.subscriptions[sub_idx];
493 if sub.active && sub.topic_matches(&event.topic) && sub.filter.matches(event) {
494 inner.subscriptions[sub_idx].delivered.push(event.clone());
495 count += 1;
496 }
497 }
498
499 count
500 }
501}
502
503impl Default for EventBus {
504 fn default() -> Self {
505 Self::new()
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 fn make_event(topic: &str, source: &str) -> BusEvent {
514 BusEvent::new(topic, source, serde_json::json!({}), 1000)
515 }
516
517 #[test]
520 fn test_event_creation() {
521 let event = make_event("task.done", "task-1");
522 assert_eq!(event.topic, "task.done");
523 assert_eq!(event.source, "task-1");
524 assert_eq!(event.timestamp_ms, 1000);
525 assert!(event.correlation_id.is_none());
526 }
527
528 #[test]
529 fn test_event_with_correlation() {
530 let event = make_event("task.done", "task-1").with_correlation_id("corr-123");
531 assert_eq!(event.correlation_id, Some("corr-123".to_string()));
532 }
533
534 #[test]
535 fn test_event_with_metadata() {
536 let event = make_event("task.done", "task-1").with_metadata("workflow", "wf-1");
537 assert_eq!(event.metadata.get("workflow"), Some(&"wf-1".to_string()));
538 }
539
540 #[test]
543 fn test_empty_filter_matches_all() {
544 let filter = EventFilter::new();
545 let event = make_event("any.topic", "any-source");
546 assert!(filter.matches(&event));
547 }
548
549 #[test]
550 fn test_source_prefix_filter() {
551 let filter = EventFilter::new().with_source_prefix("task-");
552 assert!(filter.matches(&make_event("x", "task-1")));
553 assert!(!filter.matches(&make_event("x", "workflow-1")));
554 }
555
556 #[test]
557 fn test_metadata_filter() {
558 let filter = EventFilter::new().with_metadata("env", "production");
559 let event = make_event("x", "y").with_metadata("env", "production");
560 assert!(filter.matches(&event));
561
562 let event2 = make_event("x", "y").with_metadata("env", "staging");
563 assert!(!filter.matches(&event2));
564 }
565
566 #[test]
567 fn test_min_value_filter() {
568 let filter = EventFilter::new().with_min_value("quality", 90.0);
569 let event = BusEvent::new("x", "y", serde_json::json!({"quality": 95.0}), 0);
570 assert!(filter.matches(&event));
571
572 let event2 = BusEvent::new("x", "y", serde_json::json!({"quality": 80.0}), 0);
573 assert!(!filter.matches(&event2));
574 }
575
576 #[test]
577 fn test_min_value_missing_field() {
578 let filter = EventFilter::new().with_min_value("quality", 90.0);
579 let event = BusEvent::new("x", "y", serde_json::json!({"other": 95.0}), 0);
580 assert!(!filter.matches(&event));
581 }
582
583 #[test]
586 fn test_bus_subscribe_and_publish() {
587 let bus = EventBus::new();
588 let sub = bus.subscribe("task.done", EventFilter::new());
589 let delivered = bus.publish(make_event("task.done", "task-1"));
590 assert_eq!(delivered, 1);
591
592 let events = bus.get_events(sub);
593 assert_eq!(events.len(), 1);
594 assert_eq!(events[0].source, "task-1");
595 }
596
597 #[test]
598 fn test_bus_no_match() {
599 let bus = EventBus::new();
600 bus.subscribe("task.done", EventFilter::new());
601 let delivered = bus.publish(make_event("workflow.started", "wf-1"));
602 assert_eq!(delivered, 0);
603 }
604
605 #[test]
606 fn test_bus_wildcard_topic() {
607 let bus = EventBus::new();
608 let sub = bus.subscribe("task.*", EventFilter::new());
609 bus.publish(make_event("task.done", "t1"));
610 bus.publish(make_event("task.failed", "t2"));
611 bus.publish(make_event("workflow.started", "wf1"));
612
613 let events = bus.get_events(sub);
614 assert_eq!(events.len(), 2);
615 }
616
617 #[test]
618 fn test_bus_catch_all() {
619 let bus = EventBus::new();
620 let sub = bus.subscribe("*", EventFilter::new());
621 bus.publish(make_event("task.done", "t1"));
622 bus.publish(make_event("workflow.started", "wf1"));
623
624 let events = bus.get_events(sub);
625 assert_eq!(events.len(), 2);
626 }
627
628 #[test]
629 fn test_bus_multiple_subscribers() {
630 let bus = EventBus::new();
631 let sub1 = bus.subscribe("task.done", EventFilter::new());
632 let sub2 = bus.subscribe("task.done", EventFilter::new());
633 let sub3 = bus.subscribe("task.failed", EventFilter::new());
634
635 let delivered = bus.publish(make_event("task.done", "t1"));
636 assert_eq!(delivered, 2);
637 assert_eq!(bus.get_events(sub1).len(), 1);
638 assert_eq!(bus.get_events(sub2).len(), 1);
639 assert_eq!(bus.get_events(sub3).len(), 0);
640 }
641
642 #[test]
643 fn test_bus_unsubscribe() {
644 let bus = EventBus::new();
645 let sub = bus.subscribe("task.done", EventFilter::new());
646
647 bus.publish(make_event("task.done", "t1"));
648 assert_eq!(bus.get_events(sub).len(), 1);
649
650 assert!(bus.unsubscribe(sub));
651 bus.publish(make_event("task.done", "t2"));
652 assert_eq!(bus.get_events(sub).len(), 1);
654 }
655
656 #[test]
657 fn test_bus_unsubscribe_nonexistent() {
658 let bus = EventBus::new();
659 assert!(!bus.unsubscribe(SubscriptionId(999)));
660 }
661
662 #[test]
663 fn test_bus_drain_events() {
664 let bus = EventBus::new();
665 let sub = bus.subscribe("task.done", EventFilter::new());
666 bus.publish(make_event("task.done", "t1"));
667 bus.publish(make_event("task.done", "t2"));
668
669 let drained = bus.drain_events(sub);
670 assert_eq!(drained.len(), 2);
671 assert_eq!(bus.get_events(sub).len(), 0);
672 }
673
674 #[test]
677 fn test_dead_letters() {
678 let bus = EventBus::new();
679 bus.publish(make_event("orphan.event", "src"));
681 let dead = bus.dead_letters();
682 assert_eq!(dead.len(), 1);
683 assert_eq!(dead[0].event.topic, "orphan.event");
684 }
685
686 #[test]
687 fn test_dead_letters_not_for_delivered() {
688 let bus = EventBus::new();
689 bus.subscribe("task.done", EventFilter::new());
690 bus.publish(make_event("task.done", "t1"));
691 assert!(bus.dead_letters().is_empty());
692 }
693
694 #[test]
697 fn test_event_history() {
698 let bus = EventBus::new();
699 bus.publish(make_event("a", "s1"));
700 bus.publish(make_event("b", "s2"));
701
702 let history = bus.event_history();
703 assert_eq!(history.len(), 2);
704 }
705
706 #[test]
707 fn test_events_by_topic() {
708 let bus = EventBus::new();
709 bus.publish(make_event("task.done", "t1"));
710 bus.publish(make_event("task.failed", "t2"));
711 bus.publish(make_event("task.done", "t3"));
712
713 let done_events = bus.events_by_topic("task.done");
714 assert_eq!(done_events.len(), 2);
715 }
716
717 #[test]
720 fn test_stats() {
721 let bus = EventBus::new();
722 let _sub1 = bus.subscribe("task.done", EventFilter::new());
723 let sub2 = bus.subscribe("task.done", EventFilter::new());
724 bus.unsubscribe(sub2);
725
726 bus.publish(make_event("task.done", "t1"));
727 bus.publish(make_event("orphan", "s1"));
728
729 let stats = bus.stats();
730 assert_eq!(stats.active_subscriptions, 1);
731 assert_eq!(stats.total_subscriptions, 2);
732 assert_eq!(stats.total_published, 2);
733 assert_eq!(stats.total_delivered, 1);
734 assert_eq!(stats.history_size, 2);
735 assert_eq!(stats.dead_letter_count, 1);
736 }
737
738 #[test]
741 fn test_retention_limit() {
742 let bus = EventBus::new();
743 let sub = bus.subscribe_with_retention("task.done", EventFilter::new(), 3);
744
745 for i in 0..5 {
746 bus.publish(make_event("task.done", &format!("t{i}")));
747 }
748
749 let events = bus.get_events(sub);
750 assert_eq!(events.len(), 3);
751 assert_eq!(events[0].source, "t2");
753 assert_eq!(events[2].source, "t4");
754 }
755
756 #[test]
759 fn test_clear() {
760 let bus = EventBus::new();
761 let sub = bus.subscribe("task.done", EventFilter::new());
762 bus.publish(make_event("task.done", "t1"));
763 bus.publish(make_event("orphan", "s1"));
764
765 bus.clear();
766 assert!(bus.event_history().is_empty());
767 assert!(bus.dead_letters().is_empty());
768 assert!(bus.get_events(sub).is_empty());
769 }
770
771 #[test]
774 fn test_replay_to_subscription() {
775 let bus = EventBus::new();
776
777 bus.publish(make_event("task.done", "t1"));
779 bus.publish(make_event("task.done", "t2"));
780 bus.publish(make_event("workflow.started", "wf1"));
781
782 let sub = bus.subscribe("task.done", EventFilter::new());
784 assert_eq!(bus.get_events(sub).len(), 0);
785
786 let replayed = bus.replay_to(sub);
788 assert_eq!(replayed, 2);
789 assert_eq!(bus.get_events(sub).len(), 2);
790 }
791
792 #[test]
795 fn test_topic_and_filter_combined() {
796 let bus = EventBus::new();
797 let filter = EventFilter::new().with_source_prefix("encoder-");
798 let sub = bus.subscribe("task.done", filter);
799
800 bus.publish(make_event("task.done", "encoder-1"));
801 bus.publish(make_event("task.done", "decoder-1"));
802
803 let events = bus.get_events(sub);
804 assert_eq!(events.len(), 1);
805 assert_eq!(events[0].source, "encoder-1");
806 }
807
808 #[test]
809 fn test_subscription_id_display() {
810 let id = SubscriptionId(42);
811 assert_eq!(id.to_string(), "sub-42");
812 assert_eq!(id.as_u64(), 42);
813 }
814
815 #[test]
818 fn test_bus_is_clone_and_send() {
819 let bus = EventBus::new();
820 let bus2 = bus.clone();
821 let sub = bus.subscribe("test", EventFilter::new());
822 bus2.publish(make_event("test", "src"));
823 assert_eq!(bus.get_events(sub).len(), 1);
824 }
825}