looprs_core/adapters/
channel_broker.rs1use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9
10use tokio::sync::broadcast;
11
12use crate::ports::{Message, MessageBroker};
13
14const CHANNEL_CAPACITY: usize = 64;
15
16#[derive(Clone)]
30pub struct ChannelBroker {
31 inner: Arc<BrokerInner>,
32}
33
34struct BrokerInner {
35 topics: Mutex<HashMap<String, broadcast::Sender<Message>>>,
37 closed: Mutex<bool>,
38}
39
40impl ChannelBroker {
41 pub fn new() -> Self {
43 Self {
44 inner: Arc::new(BrokerInner {
45 topics: Mutex::new(HashMap::new()),
46 closed: Mutex::new(false),
47 }),
48 }
49 }
50
51 fn sender_for(&self, topic: &str) -> Option<broadcast::Sender<Message>> {
53 let closed = self.inner.closed.lock().expect("closed lock poisoned");
54 if *closed {
55 return None;
56 }
57 drop(closed);
58
59 let mut topics = self.inner.topics.lock().expect("topics lock poisoned");
60 Some(
61 topics
62 .entry(topic.to_owned())
63 .or_insert_with(|| broadcast::channel(CHANNEL_CAPACITY).0)
64 .clone(),
65 )
66 }
67}
68
69impl Default for ChannelBroker {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl MessageBroker for ChannelBroker {
76 fn publish(&self, msg: Message) -> usize {
77 let topic = msg.topic.clone();
78 match self.sender_for(&topic) {
79 None => 0,
80 Some(tx) => tx.send(msg).unwrap_or_default(),
81 }
82 }
83
84 fn subscribe(&self, topic: &str) -> broadcast::Receiver<Message> {
85 match self.sender_for(topic) {
86 Some(tx) => tx.subscribe(),
87 None => {
88 let (tx, rx) = broadcast::channel(1);
90 drop(tx);
91 rx
92 }
93 }
94 }
95
96 fn close(&self) {
97 let mut closed = self.inner.closed.lock().expect("closed lock poisoned");
98 *closed = true;
99 let mut topics = self.inner.topics.lock().expect("topics lock poisoned");
101 topics.clear();
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108 use tokio::sync::broadcast::error::TryRecvError;
109
110 #[test]
111 fn publish_to_subscriber() {
112 let broker = ChannelBroker::new();
113 let mut rx = broker.subscribe("a");
114
115 let msg = Message::new("src", "a", 1, serde_json::json!({"x": 1}));
116 let n = broker.publish(msg.clone());
117
118 assert_eq!(n, 1);
119 let received = rx.try_recv().expect("should have message");
120 assert_eq!(received.topic, "a");
121 assert_eq!(received.source, "src");
122 }
123
124 #[test]
125 fn no_subscribers_returns_zero() {
126 let broker = ChannelBroker::new();
127 let n = broker.publish(Message::new(
128 "src",
129 "unsubscribed",
130 1,
131 serde_json::Value::Null,
132 ));
133 assert_eq!(n, 0);
134 }
135
136 #[test]
137 fn multiple_subscribers_same_topic() {
138 let broker = ChannelBroker::new();
139 let mut rx1 = broker.subscribe("t");
140 let mut rx2 = broker.subscribe("t");
141
142 let n = broker.publish(Message::new("src", "t", 1, serde_json::Value::Null));
143 assert_eq!(n, 2);
144 assert!(rx1.try_recv().is_ok());
145 assert!(rx2.try_recv().is_ok());
146 }
147
148 #[test]
149 fn independent_topics_do_not_cross() {
150 let broker = ChannelBroker::new();
151 let mut rx_a = broker.subscribe("a");
152 let mut _rx_b = broker.subscribe("b");
153
154 broker.publish(Message::new("src", "a", 1, serde_json::Value::Null));
155
156 assert!(rx_a.try_recv().is_ok());
157 assert!(matches!(_rx_b.try_recv(), Err(TryRecvError::Empty)));
159 }
160
161 #[test]
162 fn close_makes_receiver_see_closed() {
163 let broker = ChannelBroker::new();
164 let mut rx = broker.subscribe("c");
165 broker.close();
166
167 let n = broker.publish(Message::new("src", "c", 1, serde_json::Value::Null));
169 assert_eq!(n, 0);
170
171 assert!(matches!(rx.try_recv(), Err(TryRecvError::Closed)));
173 }
174
175 #[test]
176 fn clone_shares_state() {
177 let broker = ChannelBroker::new();
178 let broker2 = broker.clone();
179 let mut rx = broker.subscribe("shared");
180
181 broker2.publish(Message::new("b2", "shared", 1, serde_json::Value::Null));
182 assert!(rx.try_recv().is_ok());
183 }
184
185 #[test]
186 fn conformance() {
187 crate::ports::test_contracts::assert_message_broker_contract(ChannelBroker::new());
188 }
189}