use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Result;
use bytes::Bytes;
use futures::future::BoxFuture;
use futures::stream::BoxStream;
use futures::{FutureExt, StreamExt};
use parking_lot::RwLock;
use tokio::sync::broadcast;
use tokio_stream::wrappers::BroadcastStream;
use super::{Message, Publisher, Subscriber, Subscription};
#[derive(Clone)]
pub struct StubBus {
inner: Arc<StubBusInner>,
}
struct StubBusInner {
channels: RwLock<HashMap<String, broadcast::Sender<Message>>>,
capacity: usize,
}
impl Default for StubBus {
fn default() -> Self {
Self::new(256)
}
}
impl StubBus {
pub fn new(capacity: usize) -> Self {
Self {
inner: Arc::new(StubBusInner {
channels: RwLock::new(HashMap::new()),
capacity,
}),
}
}
pub fn publisher(&self) -> StubPublisher {
StubPublisher { bus: self.clone() }
}
pub fn subscriber(&self) -> StubSubscriber {
StubSubscriber { bus: self.clone() }
}
fn get_or_create_channel(&self, subject: &str) -> broadcast::Sender<Message> {
let channels = self.inner.channels.read();
if let Some(tx) = channels.get(subject) {
return tx.clone();
}
drop(channels);
let mut channels = self.inner.channels.write();
if let Some(tx) = channels.get(subject) {
return tx.clone();
}
let (tx, _) = broadcast::channel(self.inner.capacity);
channels.insert(subject.to_string(), tx.clone());
tx
}
}
pub struct StubPublisher {
bus: StubBus,
}
impl StubPublisher {
pub fn new() -> (Self, StubSubscriber) {
let bus = StubBus::default();
(bus.publisher(), bus.subscriber())
}
}
impl Publisher for StubPublisher {
fn publish(&self, subject: &str, payload: Bytes) -> Result<()> {
let tx = self.bus.get_or_create_channel(subject);
let msg = Message {
subject: subject.to_string(),
payload,
};
let _ = tx.send(msg);
Ok(())
}
fn flush(&self) -> BoxFuture<'static, Result<()>> {
async { Ok(()) }.boxed()
}
}
pub struct StubSubscriber {
bus: StubBus,
}
impl Subscriber for StubSubscriber {
fn subscribe(&self, subject: &str) -> BoxFuture<'static, Result<Subscription>> {
let tx = self.bus.get_or_create_channel(subject);
let rx = tx.subscribe();
let stream: BoxStream<'static, Message> = BroadcastStream::new(rx)
.filter_map(|result| async move { result.ok() })
.boxed();
async move { Ok(stream) }.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
#[tokio::test]
async fn test_stub_pubsub() {
let bus = StubBus::default();
let publisher = bus.publisher();
let subscriber = bus.subscriber();
let mut sub = subscriber.subscribe("test.subject").await.unwrap();
publisher
.publish("test.subject", Bytes::from("hello"))
.unwrap();
let msg = sub.next().await.unwrap();
assert_eq!(msg.subject, "test.subject");
assert_eq!(msg.payload.as_ref(), b"hello");
}
#[tokio::test]
async fn test_stub_multiple_subscribers() {
let bus = StubBus::default();
let publisher = bus.publisher();
let mut sub1 = bus.subscriber().subscribe("multi").await.unwrap();
let mut sub2 = bus.subscriber().subscribe("multi").await.unwrap();
publisher
.publish("multi", Bytes::from("broadcast"))
.unwrap();
let msg1 = sub1.next().await.unwrap();
let msg2 = sub2.next().await.unwrap();
assert_eq!(msg1.payload.as_ref(), b"broadcast");
assert_eq!(msg2.payload.as_ref(), b"broadcast");
}
}