use rand::RngCore;
use std::collections::HashMap;
use std::sync::Mutex;
use tokio::sync::oneshot;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CorrelationId(pub [u8; 16]);
impl CorrelationId {
#[must_use]
pub fn new_random() -> Self {
let mut id = [0u8; 16];
rand::thread_rng().fill_bytes(&mut id);
Self(id)
}
#[must_use]
pub fn hex(&self) -> String {
hex::encode(self.0)
}
}
#[derive(Default)]
pub struct ReplyWaiter {
waiters: Mutex<HashMap<CorrelationId, oneshot::Sender<Vec<u8>>>>,
}
impl ReplyWaiter {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register(&self, id: CorrelationId) -> oneshot::Receiver<Vec<u8>> {
let (tx, rx) = oneshot::channel();
self.waiters
.lock()
.expect("reply waiter poisoned")
.insert(id, tx);
rx
}
pub fn deliver(&self, id: CorrelationId, payload: Vec<u8>) -> bool {
if let Some(tx) = self
.waiters
.lock()
.expect("reply waiter poisoned")
.remove(&id)
{
tx.send(payload).is_ok()
} else {
false
}
}
pub fn cancel(&self, id: &CorrelationId) {
self.waiters
.lock()
.expect("reply waiter poisoned")
.remove(id);
}
#[must_use]
pub fn pending(&self) -> usize {
self.waiters.lock().expect("reply waiter poisoned").len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn correlation_ids_are_distinct() {
let a = CorrelationId::new_random();
let b = CorrelationId::new_random();
assert_ne!(a, b, "two random ids must not collide");
assert_eq!(a.hex().len(), 32);
}
#[test]
fn correlation_id_hex_is_deterministic() {
let id = CorrelationId([0xab; 16]);
assert_eq!(id.hex(), "abababababababababababababababab");
}
#[tokio::test]
async fn register_returns_receiver_completed_on_deliver() {
let w = ReplyWaiter::new();
let id = CorrelationId::new_random();
let rx = w.register(id);
assert_eq!(w.pending(), 1);
assert!(w.deliver(id, b"reply".to_vec()));
assert_eq!(rx.await.unwrap(), b"reply");
assert_eq!(w.pending(), 0, "deliver removes the waiter");
}
#[test]
fn deliver_unknown_id_returns_false() {
let w = ReplyWaiter::new();
assert!(!w.deliver(CorrelationId::new_random(), b"orphan".to_vec()));
}
#[tokio::test]
async fn cancel_removes_waiter() {
let w = ReplyWaiter::new();
let id = CorrelationId::new_random();
let rx = w.register(id);
assert_eq!(w.pending(), 1);
w.cancel(&id);
assert_eq!(w.pending(), 0);
assert!(rx.await.is_err());
assert!(!w.deliver(id, b"late".to_vec()));
}
#[tokio::test]
async fn deliver_twice_only_first_wins() {
let w = ReplyWaiter::new();
let id = CorrelationId::new_random();
let rx = w.register(id);
assert!(w.deliver(id, b"first".to_vec()));
assert!(!w.deliver(id, b"second".to_vec()));
assert_eq!(rx.await.unwrap(), b"first");
}
}