use crate::CoreError;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct WatcherId(u64);
impl WatcherId {
pub fn new(value: u64) -> Self {
Self(value)
}
pub fn as_u64(&self) -> u64 {
self.0
}
}
type ReplySend<R> = Box<dyn FnOnce(R) -> Result<(), CoreError> + Send>;
type WatcherPush<U> = Box<dyn FnMut(&U) -> Result<(), CoreError> + Send>;
pub struct ReplySink<R> {
send: ReplySend<R>,
}
impl<R> ReplySink<R> {
pub fn new(send: impl FnOnce(R) -> Result<(), CoreError> + Send + 'static) -> Self {
Self {
send: Box::new(send),
}
}
pub fn send(self, reply: R) -> Result<(), CoreError> {
(self.send)(reply)
}
}
impl<R> std::fmt::Debug for ReplySink<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReplySink").finish_non_exhaustive()
}
}
pub struct WatcherSink<U> {
push: WatcherPush<U>,
}
impl<U> WatcherSink<U> {
pub fn new(push: impl FnMut(&U) -> Result<(), CoreError> + Send + 'static) -> Self {
Self {
push: Box::new(push),
}
}
pub fn push(&mut self, update: &U) -> Result<(), CoreError> {
(self.push)(update)
}
}
impl<U> std::fmt::Debug for WatcherSink<U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WatcherSink").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub enum TransportEvent<C, R> {
Command {
cmd: C,
calling_uid: Option<u32>,
reply: ReplySink<R>,
},
WatcherClosed(WatcherId),
WatcherDied(WatcherId),
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
#[test]
fn watcher_id_round_trips_value() {
let id = WatcherId::new(7);
assert_eq!(id.as_u64(), 7);
assert_eq!(WatcherId::new(7), id);
assert_ne!(WatcherId::new(8), id);
}
#[test]
fn reply_sink_delivers_exactly_once() {
let delivered = Arc::new(AtomicU32::new(0));
let captured = Arc::clone(&delivered);
let sink = ReplySink::new(move |reply: u32| {
captured.fetch_add(reply, Ordering::SeqCst);
Ok(())
});
sink.send(42).unwrap();
assert_eq!(delivered.load(Ordering::SeqCst), 42);
}
#[test]
fn reply_sink_propagates_backend_error() {
let sink = ReplySink::new(|_: u32| Err(CoreError::sys(libc::EPIPE, "reply")));
let err = sink.send(1).unwrap_err();
assert_eq!(err.raw_os_error(), Some(libc::EPIPE));
}
#[test]
fn watcher_sink_pushes_borrowed_updates() {
let seen = Arc::new(std::sync::Mutex::new(Vec::new()));
let captured = Arc::clone(&seen);
let mut sink = WatcherSink::new(move |update: &u32| {
captured.lock().unwrap().push(*update);
Ok(())
});
sink.push(&1).unwrap();
sink.push(&2).unwrap();
assert_eq!(*seen.lock().unwrap(), vec![1, 2]);
}
#[test]
fn watcher_sink_propagates_push_error() {
let mut sink = WatcherSink::new(|_: &u32| Err(CoreError::sys(libc::EPIPE, "push")));
let err = sink.push(&1).unwrap_err();
assert_eq!(err.raw_os_error(), Some(libc::EPIPE));
}
#[derive(Debug)]
enum Cmd {
Ping,
}
#[derive(Debug)]
enum Reply {
Pong,
}
#[test]
fn transport_event_carries_command_and_identity() {
let answered = Arc::new(AtomicUsize::new(0));
let captured = Arc::clone(&answered);
let event = TransportEvent::Command {
cmd: Cmd::Ping,
calling_uid: Some(1000),
reply: ReplySink::new(move |r: Reply| {
assert!(matches!(r, Reply::Pong));
captured.fetch_add(1, Ordering::SeqCst);
Ok(())
}),
};
match event {
TransportEvent::Command {
cmd,
calling_uid,
reply,
} => {
assert!(matches!(cmd, Cmd::Ping));
assert_eq!(calling_uid, Some(1000));
reply.send(Reply::Pong).unwrap();
}
_ => panic!("expected command event"),
}
assert_eq!(answered.load(Ordering::SeqCst), 1);
}
#[test]
fn transport_event_carries_watcher_close_and_died() {
let id = WatcherId::new(3);
match TransportEvent::<Cmd, Reply>::WatcherClosed(id) {
TransportEvent::WatcherClosed(got) => assert_eq!(got, id),
_ => panic!("expected watcher closed"),
}
match TransportEvent::<Cmd, Reply>::WatcherDied(id) {
TransportEvent::WatcherDied(got) => assert_eq!(got, id),
_ => panic!("expected watcher died"),
}
}
#[test]
fn sinks_are_debug_but_opaque() {
let sink: ReplySink<u32> = ReplySink::new(|_| Ok(()));
let text = format!("{sink:?}");
assert!(text.starts_with("ReplySink"));
let watcher: WatcherSink<u32> = WatcherSink::new(|_| Ok(()));
let text = format!("{watcher:?}");
assert!(text.starts_with("WatcherSink"));
}
}