use std::borrow::Cow;
use flowscope::Timestamp;
use crate::anomaly::Severity;
use crate::anomaly::sink::AnomalySink;
use crate::layer::Layer;
pub struct Tee {
secondary: TeeSecondary,
}
enum TeeSecondary {
Owned(Box<dyn AnomalySink>),
Factory(Box<dyn Fn() -> Box<dyn AnomalySink> + Send + Sync>),
}
impl Tee {
pub fn into(secondary: impl AnomalySink + 'static) -> Self {
Self {
secondary: TeeSecondary::Owned(Box::new(secondary)),
}
}
pub fn factory<F>(factory: F) -> Self
where
F: Fn() -> Box<dyn AnomalySink> + Send + Sync + 'static,
{
Self {
secondary: TeeSecondary::Factory(Box::new(factory)),
}
}
}
impl Layer for Tee {
fn wrap(self: Box<Self>, inner: Box<dyn AnomalySink>) -> Box<dyn AnomalySink> {
let secondary = match self.secondary {
TeeSecondary::Owned(b) => b,
TeeSecondary::Factory(f) => f(),
};
Box::new(TeeLayered { inner, secondary })
}
}
pub struct TeeLayered {
inner: Box<dyn AnomalySink>,
secondary: Box<dyn AnomalySink>,
}
impl AnomalySink for TeeLayered {
fn write(
&mut self,
kind: &'static str,
severity: Severity,
ts: Timestamp,
key: Option<&dyn crate::anomaly::Key>,
observations: &[(&'static str, Cow<'_, str>)],
metrics: &[(&'static str, f64)],
) {
self.inner
.write(kind, severity, ts, key, observations, metrics);
self.secondary
.write(kind, severity, ts, key, observations, metrics);
}
fn flush(&mut self) -> Result<(), std::io::Error> {
let r1 = self.inner.flush();
let r2 = self.secondary.flush();
r1.and(r2)
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use super::*;
#[derive(Default)]
struct Capture(Arc<Mutex<u32>>);
impl Capture {
fn n(&self) -> Arc<Mutex<u32>> {
Arc::clone(&self.0)
}
}
impl AnomalySink for Capture {
fn write(
&mut self,
_: &'static str,
_: Severity,
_: Timestamp,
_: Option<&dyn crate::anomaly::Key>,
_: &[(&'static str, Cow<'_, str>)],
_: &[(&'static str, f64)],
) {
*self.0.lock().unwrap() += 1;
}
}
#[test]
fn writes_to_both_sinks() {
let primary = Capture::default();
let secondary = Capture::default();
let p_calls = primary.n();
let s_calls = secondary.n();
let mut sink: Box<dyn AnomalySink> = Box::new(Tee::into(secondary)).wrap(Box::new(primary));
for _ in 0..3 {
sink.begin("X", Severity::Info, Timestamp::new(0, 0)).emit();
}
assert_eq!(*p_calls.lock().unwrap(), 3);
assert_eq!(*s_calls.lock().unwrap(), 3);
}
#[test]
fn factory_mints_fresh_secondary_per_wrap() {
let counter = Arc::new(Mutex::new(0u32));
let factory_counter = Arc::clone(&counter);
let factory =
move || -> Box<dyn AnomalySink> { Box::new(Capture(Arc::clone(&factory_counter))) };
let primary1 = Capture::default();
let s1 = primary1.n();
let mut sink1: Box<dyn AnomalySink> =
Box::new(Tee::factory(factory.clone())).wrap(Box::new(primary1));
sink1
.begin("X", Severity::Info, Timestamp::new(0, 0))
.emit();
let primary2 = Capture::default();
let s2 = primary2.n();
let mut sink2: Box<dyn AnomalySink> =
Box::new(Tee::factory(factory.clone())).wrap(Box::new(primary2));
sink2
.begin("X", Severity::Info, Timestamp::new(0, 0))
.emit();
sink2
.begin("X", Severity::Info, Timestamp::new(0, 0))
.emit();
assert_eq!(*s1.lock().unwrap(), 1);
assert_eq!(*s2.lock().unwrap(), 2);
assert_eq!(*counter.lock().unwrap(), 3);
}
}