use crate::context::Receiver;
use crate::error::Result;
use std::future::Future;
pub async fn forward_echo<T, P, Fut>(mut incoming: Receiver<T>, seed: Option<T>, mut publish: P) -> Result<()>
where
P: FnMut(T) -> Fut,
Fut: Future<Output = Result<()>>,
{
if let Some(seed) = seed {
publish(seed).await?;
}
while let Some(message) = incoming.recv().await {
publish(message).await?;
}
Ok(())
}
#[cfg(feature = "messaging")]
mod subscribe {
use super::forward_echo;
use crate::context::{Context, Receiver};
use crate::error::Result;
use crate::messages::Message;
use crate::topics::Topic;
use serde::de::DeserializeOwned;
use serde::Serialize;
pub async fn echo_topic<T>(
ctx: &Context,
subscribe_topic: Topic<T>,
publish_topic: Topic<T>,
seed: Option<T>,
) -> Result<Receiver<T>>
where
T: Message + DeserializeOwned + Serialize + Send + Clone + 'static,
{
let incoming = ctx.subscribe(subscribe_topic).await?;
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<T>();
let ctx = ctx.clone();
tokio::spawn(async move {
let result = forward_echo(incoming, seed, move |message: T| {
let ctx = ctx.clone();
let tx = tx.clone();
async move {
ctx.publish_to(publish_topic, &message).await?;
let _ = tx.send(message);
Ok(())
}
})
.await;
if let Err(error) = result {
tracing::warn!(
subscribe_topic = subscribe_topic.path(),
publish_topic = publish_topic.path(),
%error,
"echo_topic forwarding task stopped due to a publish error"
);
}
});
Ok(Receiver::from(rx))
}
}
#[cfg(feature = "messaging")]
pub use subscribe::echo_topic;
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Mecha10Error;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Reading(i32);
fn channel<T>() -> (tokio::sync::mpsc::UnboundedSender<T>, Receiver<T>) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
(tx, Receiver::from(rx))
}
#[tokio::test]
async fn forwards_a_single_received_message_unchanged() {
let (tx, rx) = channel::<Reading>();
let published = Arc::new(Mutex::new(Vec::new()));
let published_clone = Arc::clone(&published);
tx.send(Reading(42)).unwrap();
drop(tx);
forward_echo(rx, None, move |message: Reading| {
let published = Arc::clone(&published_clone);
async move {
published.lock().unwrap().push(message);
Ok(())
}
})
.await
.unwrap();
assert_eq!(*published.lock().unwrap(), vec![Reading(42)]);
}
#[tokio::test]
async fn forwards_multiple_messages_in_order() {
let (tx, rx) = channel::<Reading>();
let published = Arc::new(Mutex::new(Vec::new()));
let published_clone = Arc::clone(&published);
for value in 0..5 {
tx.send(Reading(value)).unwrap();
}
drop(tx);
forward_echo(rx, None, move |message: Reading| {
let published = Arc::clone(&published_clone);
async move {
published.lock().unwrap().push(message);
Ok(())
}
})
.await
.unwrap();
let expected: Vec<Reading> = (0..5).map(Reading).collect();
assert_eq!(*published.lock().unwrap(), expected);
}
#[tokio::test]
async fn publishes_seed_before_any_real_message() {
let (tx, rx) = channel::<Reading>();
let published = Arc::new(Mutex::new(Vec::new()));
let published_clone = Arc::clone(&published);
tx.send(Reading(1)).unwrap();
drop(tx);
forward_echo(rx, Some(Reading(-1)), move |message: Reading| {
let published = Arc::clone(&published_clone);
async move {
published.lock().unwrap().push(message);
Ok(())
}
})
.await
.unwrap();
assert_eq!(*published.lock().unwrap(), vec![Reading(-1), Reading(1)]);
}
#[tokio::test]
async fn publishes_nothing_extra_when_no_seed_and_no_messages() {
let (tx, rx) = channel::<Reading>();
let published = Arc::new(Mutex::new(Vec::new()));
let published_clone = Arc::clone(&published);
drop(tx);
forward_echo(rx, None, move |message: Reading| {
let published = Arc::clone(&published_clone);
async move {
published.lock().unwrap().push(message);
Ok(())
}
})
.await
.unwrap();
assert!(published.lock().unwrap().is_empty());
}
#[tokio::test]
async fn works_generically_over_an_arbitrary_message_type() {
#[derive(Debug, Clone, PartialEq)]
struct Pose {
x: f32,
y: f32,
}
let (tx, rx) = channel::<Pose>();
let published = Arc::new(Mutex::new(Vec::new()));
let published_clone = Arc::clone(&published);
tx.send(Pose { x: 1.0, y: 2.0 }).unwrap();
drop(tx);
forward_echo(rx, Some(Pose { x: 0.0, y: 0.0 }), move |message: Pose| {
let published = Arc::clone(&published_clone);
async move {
published.lock().unwrap().push(message);
Ok(())
}
})
.await
.unwrap();
assert_eq!(
*published.lock().unwrap(),
vec![Pose { x: 0.0, y: 0.0 }, Pose { x: 1.0, y: 2.0 }]
);
}
#[tokio::test]
async fn stops_and_returns_the_error_on_the_first_publish_failure() {
let (tx, rx) = channel::<Reading>();
let published = Arc::new(Mutex::new(Vec::new()));
let published_clone = Arc::clone(&published);
for value in 0..3 {
tx.send(Reading(value)).unwrap();
}
drop(tx);
let result = forward_echo(rx, None, move |message: Reading| {
let published = Arc::clone(&published_clone);
async move {
if message == Reading(1) {
return Err(Mecha10Error::MessagingError {
message: "boom".to_string(),
suggestion: "n/a".to_string(),
});
}
published.lock().unwrap().push(message);
Ok(())
}
})
.await;
assert!(result.is_err());
assert_eq!(*published.lock().unwrap(), vec![Reading(0)]);
}
}