aster_forge_events/
transient_bus.rs1use std::sync::Arc;
2
3use tokio::sync::broadcast;
4
5pub struct TransientEventBus<T, R = ()> {
11 local: broadcast::Sender<T>,
12 transport: Option<Arc<R>>,
13}
14
15impl<T, R> Clone for TransientEventBus<T, R> {
16 fn clone(&self) -> Self {
17 Self {
18 local: self.local.clone(),
19 transport: self.transport.clone(),
20 }
21 }
22}
23
24impl<T, R> TransientEventBus<T, R>
25where
26 T: Clone,
27{
28 pub fn new(capacity: usize) -> Self {
30 Self::from_optional_transport(capacity, None)
31 }
32
33 pub fn with_transport(capacity: usize, transport: R) -> Self {
35 Self::from_optional_transport(capacity, Some(transport))
36 }
37
38 pub fn from_optional_transport(capacity: usize, transport: Option<R>) -> Self {
40 let (local, _) = broadcast::channel(capacity.max(1));
41 Self {
42 local,
43 transport: transport.map(Arc::new),
44 }
45 }
46
47 pub fn publish_local(&self, event: T) -> Result<usize, broadcast::error::SendError<T>> {
49 self.local.send(event)
50 }
51
52 pub fn subscribe(&self) -> broadcast::Receiver<T> {
54 self.local.subscribe()
55 }
56
57 pub fn local_subscriber_count(&self) -> usize {
59 self.local.receiver_count()
60 }
61
62 pub fn has_transport(&self) -> bool {
64 self.transport.is_some()
65 }
66
67 pub fn transport(&self) -> Option<Arc<R>> {
69 self.transport.clone()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use std::sync::Arc;
76
77 use super::TransientEventBus;
78 use tokio::sync::broadcast;
79
80 #[tokio::test]
81 async fn local_bus_delivers_to_all_current_subscribers() {
82 let bus = TransientEventBus::<String>::new(4);
83 let mut first = bus.subscribe();
84 let mut second = bus.subscribe();
85
86 assert_eq!(bus.local_subscriber_count(), 2);
87 assert_eq!(bus.publish_local("event".to_string()).expect("publish"), 2);
88 assert_eq!(first.recv().await, Ok("event".to_string()));
89 assert_eq!(second.recv().await, Ok("event".to_string()));
90 }
91
92 #[test]
93 fn zero_capacity_is_clamped_and_publish_without_subscribers_returns_event() {
94 let bus = TransientEventBus::<String>::new(0);
95
96 let error = bus
97 .publish_local("unobserved".to_string())
98 .expect_err("publish without subscribers should report the undelivered event");
99 assert_eq!(error.0, "unobserved");
100 }
101
102 #[tokio::test]
103 async fn bounded_local_channel_reports_lag() {
104 let bus = TransientEventBus::<u8>::new(1);
105 let mut receiver = bus.subscribe();
106
107 assert_eq!(bus.publish_local(1).expect("publish first"), 1);
108 assert_eq!(bus.publish_local(2).expect("publish second"), 1);
109 assert_eq!(
110 receiver.recv().await,
111 Err(broadcast::error::RecvError::Lagged(1))
112 );
113 assert_eq!(receiver.recv().await, Ok(2));
114 }
115
116 #[tokio::test]
117 async fn cloned_bus_shares_local_subscribers() {
118 let bus = TransientEventBus::<u8>::new(2);
119 let cloned = bus.clone();
120 let mut receiver = bus.subscribe();
121
122 assert_eq!(cloned.publish_local(7).expect("publish from clone"), 1);
123 assert_eq!(receiver.recv().await, Ok(7));
124 }
125
126 #[test]
127 fn optional_transport_is_shared_across_clones() {
128 let bus = TransientEventBus::<u8, String>::with_transport(2, "transport".to_string());
129 let cloned = bus.clone();
130
131 assert!(bus.has_transport());
132 assert!(Arc::ptr_eq(
133 &bus.transport().expect("transport"),
134 &cloned.transport().expect("cloned transport")
135 ));
136
137 let local = TransientEventBus::<u8, String>::new(2);
138 assert!(!local.has_transport());
139 assert!(local.transport().is_none());
140 }
141}