Skip to main content

rskit_hook/
bus.rs

1//! Bounded typed in-process event bus.
2
3use std::marker::PhantomData;
4use std::sync::Arc;
5
6use rskit_errors::{AppError, AppResult, ErrorCode};
7use tokio::sync::broadcast;
8
9use crate::Event;
10
11/// Configuration for a typed [`EventBus`].
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct EventBusConfig {
14    /// Maximum number of events retained for slow subscribers.
15    ///
16    /// The bus is bounded. When a subscriber falls behind this capacity,
17    /// `recv()` returns an error that reports how many events were skipped.
18    pub capacity: usize,
19}
20
21impl Default for EventBusConfig {
22    fn default() -> Self {
23        Self { capacity: 1024 }
24    }
25}
26
27/// A bounded, typed, in-process publish/subscribe bus for event `E`.
28///
29/// This bus is constructor-injected and process-local. Use `rskit-messaging`
30/// for broker-backed cross-process events.
31pub struct EventBus<E: Event> {
32    sender: broadcast::Sender<Arc<E>>,
33}
34
35impl<E: Event> Clone for EventBus<E> {
36    fn clone(&self) -> Self {
37        Self {
38            sender: self.sender.clone(),
39        }
40    }
41}
42
43impl<E: Event> EventBus<E> {
44    /// Create a new bounded bus.
45    #[must_use]
46    pub fn new(config: EventBusConfig) -> Self {
47        let capacity = config.capacity.max(1);
48        let (sender, _) = broadcast::channel(capacity);
49        Self { sender }
50    }
51
52    /// Publish an event to all current subscribers.
53    ///
54    /// Returns the number of subscribers that accepted the event. Publishing to
55    /// a bus with no subscribers succeeds and returns `0`.
56    pub fn publish(&self, event: E) -> AppResult<usize> {
57        self.sender.send(Arc::new(event)).or(Ok(0))
58    }
59
60    /// Register a new subscriber.
61    #[must_use]
62    pub fn subscribe(&self) -> Subscriber<E> {
63        Subscriber {
64            receiver: self.sender.subscribe(),
65        }
66    }
67}
68
69/// Explicit subscriber registry for a typed event bus.
70pub struct EventRegistry<E: Event> {
71    bus: EventBus<E>,
72}
73
74impl<E: Event> EventRegistry<E> {
75    /// Create an empty registry with a bounded bus.
76    #[must_use]
77    pub fn new(config: EventBusConfig) -> Self {
78        Self {
79            bus: EventBus::new(config),
80        }
81    }
82
83    /// Return a publisher handle for the registered bus.
84    #[must_use]
85    pub fn publisher(&self) -> EventBus<E> {
86        self.bus.clone()
87    }
88
89    /// Explicitly register a subscriber.
90    #[must_use]
91    pub fn register_subscriber(&self) -> Subscriber<E> {
92        self.bus.subscribe()
93    }
94}
95
96impl<E: Event> Default for EventRegistry<E> {
97    fn default() -> Self {
98        Self::new(EventBusConfig::default())
99    }
100}
101
102/// A typed event subscriber.
103pub struct Subscriber<E: Event> {
104    receiver: broadcast::Receiver<Arc<E>>,
105}
106
107impl<E: Event> Subscriber<E> {
108    /// Receive the next event.
109    ///
110    /// Returns a typed error when the sender is closed or this subscriber lagged
111    /// behind the bounded bus capacity.
112    pub async fn recv(&mut self) -> AppResult<Arc<E>> {
113        self.receiver.recv().await.map_err(|error| match error {
114            broadcast::error::RecvError::Closed => {
115                AppError::new(ErrorCode::Cancelled, "event bus publisher closed")
116            }
117            broadcast::error::RecvError::Lagged(skipped) => AppError::new(
118                ErrorCode::RateLimited,
119                format!("event subscriber lagged by {skipped} messages"),
120            ),
121        })
122    }
123}
124
125impl<E: Event> std::fmt::Debug for EventBus<E> {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        f.debug_struct("EventBus")
128            .field("event_type", &std::any::type_name::<E>())
129            .field("receiver_count", &self.sender.receiver_count())
130            .finish()
131    }
132}
133
134impl<E: Event> std::fmt::Debug for EventRegistry<E> {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        f.debug_struct("EventRegistry")
137            .field("event_type", &std::any::type_name::<E>())
138            .finish_non_exhaustive()
139    }
140}
141
142impl<E: Event> std::fmt::Debug for Subscriber<E> {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        let _phantom = PhantomData::<E>;
145        f.debug_struct("Subscriber")
146            .field("event_type", &std::any::type_name::<E>())
147            .finish_non_exhaustive()
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::{EventBus, EventBusConfig, EventRegistry};
154    use crate::{Event, EventType};
155
156    #[derive(Debug)]
157    struct Ping(u32);
158
159    impl Event for Ping {
160        fn event_type(&self) -> EventType {
161            EventType::new("ping")
162        }
163    }
164
165    #[tokio::test]
166    async fn bus_publishes_to_registered_subscriber() {
167        let bus = EventBus::<Ping>::new(EventBusConfig { capacity: 4 });
168        let mut subscriber = bus.subscribe();
169
170        assert_eq!(bus.publish(Ping(7)).expect("publish"), 1);
171        let event = subscriber.recv().await.expect("receive");
172        assert_eq!(event.0, 7);
173    }
174
175    #[tokio::test]
176    async fn publish_without_subscribers_succeeds() {
177        let bus = EventBus::<Ping>::new(EventBusConfig { capacity: 4 });
178        assert_eq!(bus.publish(Ping(1)).expect("publish"), 0);
179    }
180
181    #[tokio::test]
182    async fn subscriber_reports_lag() {
183        let bus = EventBus::<Ping>::new(EventBusConfig { capacity: 1 });
184        let mut subscriber = bus.subscribe();
185
186        let _ = bus.publish(Ping(1)).expect("first");
187        let _ = bus.publish(Ping(2)).expect("second");
188        let err = subscriber.recv().await.expect_err("lag should be reported");
189        assert_eq!(err.code(), rskit_errors::ErrorCode::RateLimited);
190    }
191
192    #[test]
193    fn event_registry_registers_subscribers_explicitly() {
194        let registry = EventRegistry::<Ping>::new(EventBusConfig { capacity: 4 });
195        let _subscriber = registry.register_subscriber();
196        let publisher = registry.publisher();
197        assert_eq!(publisher.publish(Ping(1)).expect("publish"), 1);
198    }
199}