pamoja_loopback/
transport.rs1use std::sync::{Arc, Mutex};
4
5use tokio::sync::mpsc::{self, UnboundedReceiver};
6
7use pamoja_core::{Error, Result, Transport};
8
9use crate::broker::LoopbackBroker;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
13pub struct Message {
14 pub topic: String,
16 pub payload: Vec<u8>,
18}
19
20pub struct LoopbackTransport {
49 broker: LoopbackBroker,
50 filters: Arc<Mutex<Vec<String>>>,
51 incoming: Option<UnboundedReceiver<Message>>,
52}
53
54impl LoopbackTransport {
55 pub fn new(broker: LoopbackBroker) -> Self {
65 Self {
66 broker,
67 filters: Arc::new(Mutex::new(Vec::new())),
68 incoming: None,
69 }
70 }
71
72 pub fn is_connected(&self) -> bool {
79 self.incoming.is_some()
80 }
81
82 pub async fn recv(&mut self) -> Result<Option<Message>> {
94 let incoming = self.incoming.as_mut().ok_or(Error::Closed)?;
95 Ok(incoming.recv().await)
96 }
97
98 pub fn disconnect(&mut self) {
102 self.incoming = None;
103 }
104}
105
106impl Transport for LoopbackTransport {
107 async fn connect(&mut self) -> Result<()> {
108 let (sender, receiver) = mpsc::unbounded_channel();
109 self.broker.register(Arc::clone(&self.filters), sender);
110 self.incoming = Some(receiver);
111 Ok(())
112 }
113
114 async fn send(&mut self, topic: &str, payload: &[u8]) -> Result<()> {
115 if self.incoming.is_none() {
116 return Err(Error::Closed);
117 }
118 self.broker.publish(&Message {
119 topic: topic.to_owned(),
120 payload: payload.to_vec(),
121 });
122 Ok(())
123 }
124
125 async fn subscribe(&mut self, topic: &str) -> Result<()> {
126 if self.incoming.is_none() {
127 return Err(Error::Closed);
128 }
129 self.filters
130 .lock()
131 .expect("filters lock")
132 .push(topic.to_owned());
133 Ok(())
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[tokio::test]
142 async fn publish_and_subscribe_round_trip() {
143 let broker = LoopbackBroker::new();
144 let mut subscriber = LoopbackTransport::new(broker.clone());
145 let mut publisher = LoopbackTransport::new(broker);
146 subscriber.connect().await.expect("connect");
147 publisher.connect().await.expect("connect");
148
149 subscriber
150 .subscribe("sensors/+/temperature")
151 .await
152 .expect("subscribe");
153 publisher
154 .send("sensors/1/temperature", b"21.5")
155 .await
156 .expect("send");
157
158 let message = subscriber.recv().await.expect("recv").expect("a message");
159 assert_eq!(message.topic, "sensors/1/temperature");
160 assert_eq!(message.payload, b"21.5");
161 }
162
163 #[tokio::test]
164 async fn non_matching_topics_are_not_delivered() {
165 let broker = LoopbackBroker::new();
166 let mut subscriber = LoopbackTransport::new(broker.clone());
167 let mut publisher = LoopbackTransport::new(broker);
168 subscriber.connect().await.expect("connect");
169 publisher.connect().await.expect("connect");
170
171 subscriber
172 .subscribe("sensors/1/#")
173 .await
174 .expect("subscribe");
175 publisher
176 .send("sensors/2/temperature", b"x")
177 .await
178 .expect("send");
179 publisher
180 .send("sensors/1/humidity", b"y")
181 .await
182 .expect("send");
183
184 let message = subscriber.recv().await.expect("recv").expect("a message");
185 assert_eq!(message.topic, "sensors/1/humidity");
186 }
187
188 #[tokio::test]
189 async fn operations_before_connect_report_closed() {
190 let broker = LoopbackBroker::new();
191 let mut transport = LoopbackTransport::new(broker);
192 assert!(matches!(
193 transport.send("t", b"x").await,
194 Err(Error::Closed)
195 ));
196 assert!(matches!(transport.subscribe("t").await, Err(Error::Closed)));
197 assert!(matches!(transport.recv().await, Err(Error::Closed)));
198 assert!(!transport.is_connected());
199 }
200}