use crate::ProgressEntry;
use bytes::Bytes;
pub type ProgressListener = Box<dyn FnMut(ProgressEntry) + Send + 'static>;
pub trait Pusher: Send + 'static {
type Error: std::error::Error + Send + Sync + Unpin + 'static;
#[allow(clippy::missing_errors_doc)]
fn push(&mut self, range: &ProgressEntry, content: Bytes) -> Result<(), (Self::Error, Bytes)>;
#[allow(clippy::missing_errors_doc)]
fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
#[allow(clippy::needless_pass_by_value)]
#[allow(unused_variables)]
fn set_listener(&mut self, cb: ProgressListener) {}
}
pub trait AnyError: std::error::Error + Send + Sync + Unpin + 'static {}
impl<T: std::error::Error + Send + Sync + Unpin + 'static> AnyError for T {}
impl std::error::Error for Box<dyn AnyError> {}
#[allow(missing_debug_implementations)]
pub struct BoxPusher {
pub pusher: Box<dyn Pusher<Error = Box<dyn AnyError>>>,
}
impl Pusher for BoxPusher {
type Error = Box<dyn AnyError>;
fn set_listener(&mut self, cb: ProgressListener) {
self.pusher.set_listener(cb);
}
fn push(&mut self, range: &ProgressEntry, content: Bytes) -> Result<(), (Self::Error, Bytes)> {
self.pusher.push(range, content)
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.pusher.flush()
}
}
struct PusherAdapter<P: Pusher> {
inner: P,
}
impl<P: Pusher> Pusher for PusherAdapter<P> {
type Error = Box<dyn AnyError>;
fn set_listener(&mut self, cb: ProgressListener) {
self.inner.set_listener(cb);
}
fn push(&mut self, range: &ProgressEntry, content: Bytes) -> Result<(), (Self::Error, Bytes)> {
self.inner
.push(range, content)
.map_err(|(e, b)| (BoxPusher::upcast(e), b))
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.inner.flush().map_err(|e| BoxPusher::upcast(e))
}
}
impl BoxPusher {
pub fn new<P: Pusher>(pusher: P) -> Self {
Self {
pusher: Box::new(PusherAdapter { inner: pusher }),
}
}
pub fn upcast<E: AnyError>(e: E) -> Box<dyn AnyError> {
Box::new(e)
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
struct DummyPusher;
impl Pusher for DummyPusher {
type Error = std::io::Error;
fn push(
&mut self,
_range: &ProgressEntry,
_content: Bytes,
) -> Result<(), (Self::Error, Bytes)> {
Ok(())
}
}
#[test]
fn default_set_listener_is_noop() {
let mut p = DummyPusher;
p.set_listener(Box::new(|_| {}));
p.push(&(0..0), Bytes::new()).unwrap();
}
#[test]
fn upcast_boxes_any_error() {
let boxed: Box<dyn AnyError> = BoxPusher::upcast(std::io::Error::other("boom"));
let _ = boxed;
}
#[derive(Clone)]
struct RecordingPusher {
pushes: Arc<Mutex<Vec<(ProgressEntry, Bytes)>>>,
fail_push: Arc<AtomicBool>,
fail_flush: Arc<AtomicBool>,
listener_set: Arc<AtomicBool>,
listener: Arc<Mutex<Option<ProgressListener>>>,
}
impl RecordingPusher {
fn new() -> Self {
Self {
pushes: Arc::new(Mutex::new(Vec::new())),
fail_push: Arc::new(AtomicBool::new(false)),
fail_flush: Arc::new(AtomicBool::new(false)),
listener_set: Arc::new(AtomicBool::new(false)),
listener: Arc::new(Mutex::new(None)),
}
}
}
impl Pusher for RecordingPusher {
type Error = std::io::Error;
fn set_listener(&mut self, cb: ProgressListener) {
self.listener_set.store(true, Ordering::SeqCst);
*self.listener.lock().unwrap() = Some(cb);
}
fn push(
&mut self,
range: &ProgressEntry,
bytes: Bytes,
) -> Result<(), (Self::Error, Bytes)> {
if self.fail_push.swap(false, Ordering::SeqCst) {
return Err((std::io::Error::other("push"), bytes));
}
if let Some(cb) = self.listener.lock().unwrap().as_mut() {
cb(range.clone());
}
self.pushes.lock().unwrap().push((range.clone(), bytes));
Ok(())
}
fn flush(&mut self) -> Result<(), Self::Error> {
if self.fail_flush.swap(false, Ordering::SeqCst) {
Err(std::io::Error::other("flush"))
} else {
Ok(())
}
}
}
#[test]
fn box_pusher_forwards_push_flush_and_listener() {
let inner = RecordingPusher::new();
let mut bp = BoxPusher::new(inner.clone());
bp.set_listener(Box::new(|_| {}));
assert!(inner.listener_set.load(Ordering::SeqCst));
bp.push(&(0..3), Bytes::copy_from_slice(b"abc")).unwrap();
bp.flush().unwrap();
let pushes = inner.pushes.lock().unwrap();
assert_eq!(pushes.len(), 1);
assert_eq!(pushes[0].0, 0..3);
drop(pushes);
}
#[test]
fn box_pusher_upcasts_push_error() {
let inner = RecordingPusher::new();
inner.fail_push.store(true, Ordering::SeqCst);
let mut bp = BoxPusher::new(inner);
let res = bp.push(&(0..3), Bytes::copy_from_slice(b"abc"));
assert!(res.is_err());
let _ = res.unwrap_err();
}
#[test]
fn box_pusher_upcasts_flush_error() {
let inner = RecordingPusher::new();
inner.fail_flush.store(true, Ordering::SeqCst);
let mut bp = BoxPusher::new(inner);
assert!(bp.flush().is_err());
}
#[test]
fn box_pusher_listener_fires_on_successful_push() {
let inner = RecordingPusher::new();
let seen = Arc::new(Mutex::new(Vec::<ProgressEntry>::new()));
let seen2 = seen.clone();
let mut bp = BoxPusher::new(inner);
bp.set_listener(Box::new(move |r| seen2.lock().unwrap().push(r)));
bp.push(&(0..3), Bytes::copy_from_slice(b"abc")).unwrap();
let s = seen.lock().unwrap();
assert_eq!(s.len(), 1);
assert_eq!(s[0], 0..3);
}
#[test]
fn box_pusher_push_error_preserves_unwritten_bytes() {
let inner = RecordingPusher::new();
inner.fail_push.store(true, Ordering::SeqCst);
let mut bp = BoxPusher::new(inner);
let payload = Bytes::copy_from_slice(b"hello");
let res = bp.push(&(0..5), payload.clone());
let (_, unwritten) = res.unwrap_err();
assert_eq!(unwritten, payload);
}
#[test]
fn box_pusher_default_flush_is_ok() {
let mut bp = BoxPusher::new(DummyPusher);
assert!(bp.flush().is_ok());
}
}