use crate::protocol::ServerMessage;
use crate::pubsub::PubSub;
use doido_core::Result;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tokio::task::JoinHandle;
pub struct ChannelContext {
pub identifier: String,
tx: mpsc::UnboundedSender<String>,
pubsub: Arc<dyn PubSub>,
streams: Arc<Mutex<Vec<JoinHandle<()>>>>,
}
impl ChannelContext {
pub(crate) fn new(
identifier: String,
tx: mpsc::UnboundedSender<String>,
pubsub: Arc<dyn PubSub>,
) -> Self {
Self {
identifier,
tx,
pubsub,
streams: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn for_test(identifier: impl Into<String>) -> (Self, mpsc::UnboundedReceiver<String>) {
let (tx, rx) = mpsc::unbounded_channel();
let ctx = Self::new(
identifier.into(),
tx,
Arc::new(crate::pubsub::MemoryPubSub::new()),
);
(ctx, rx)
}
pub fn params(&self) -> serde_json::Value {
serde_json::from_str(&self.identifier).unwrap_or(serde_json::Value::Null)
}
pub fn transmit(&self, message: serde_json::Value) {
let frame = ServerMessage::new(self.identifier.clone(), message);
if let Ok(json) = frame.to_json() {
let _ = self.tx.send(json);
}
}
pub async fn stream_from(&self, stream: &str) {
if let Ok(mut rx) = self.pubsub.subscribe(stream).await {
let tx = self.tx.clone();
let handle = tokio::spawn(async move {
while let Ok(msg) = rx.recv().await {
if tx.send(msg).is_err() {
break;
}
}
});
self.streams.lock().await.push(handle);
}
}
pub async fn stop_all_streams(&self) {
let mut streams = self.streams.lock().await;
for handle in streams.drain(..) {
handle.abort();
}
}
}
#[async_trait::async_trait]
pub trait Channel: Send + Sync {
async fn subscribed(&self, ctx: &ChannelContext) -> Result<()>;
async fn unsubscribed(&self, ctx: &ChannelContext) -> Result<()>;
async fn received(&self, ctx: &ChannelContext, data: serde_json::Value) -> Result<()>;
}
pub trait ChannelName {
fn channel_name() -> &'static str;
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn params_parses_the_identifier_json() {
let (ctx, _rx) = ChannelContext::for_test(r#"{"channel":"ChatChannel","room":"42"}"#);
assert_eq!(ctx.params()["channel"], "ChatChannel");
assert_eq!(ctx.params()["room"], "42");
}
#[tokio::test]
async fn transmit_sends_a_server_message_to_this_connection() {
let (ctx, mut rx) = ChannelContext::for_test(r#"{"channel":"ChatChannel"}"#);
ctx.transmit(serde_json::json!({ "hello": "world" }));
let raw = rx.recv().await.unwrap();
let msg = ServerMessage::parse(&raw).unwrap();
assert_eq!(msg.identifier, r#"{"channel":"ChatChannel"}"#);
assert_eq!(msg.message["hello"], "world");
}
#[tokio::test]
async fn stream_from_forwards_published_messages_and_stops() {
let pubsub: Arc<dyn PubSub> = Arc::new(crate::pubsub::MemoryPubSub::new());
let (tx, mut rx) = mpsc::unbounded_channel();
let ctx = ChannelContext::new("id".into(), tx, pubsub.clone());
ctx.stream_from("room:1").await;
tokio::task::yield_now().await;
pubsub.publish("room:1", "hi").await.unwrap();
assert_eq!(rx.recv().await.unwrap(), "hi");
ctx.stop_all_streams().await;
tokio::task::yield_now().await;
pubsub.publish("room:1", "after-stop").await.unwrap();
assert!(rx.try_recv().is_err());
}
}