rivet-logger 0.1.0

Rivet framework crates and adapters.
Documentation
use std::io;
use std::sync::Arc;

use super::Handler;

pub struct Stack {
    children: Vec<Arc<dyn Handler>>,
}

impl Stack {
    pub fn new(children: Vec<Arc<dyn Handler>>) -> Self {
        Self { children }
    }

    pub fn push(&mut self, child: Arc<dyn Handler>) {
        self.children.push(child);
    }

    pub fn children(&self) -> &[Arc<dyn Handler>] {
        &self.children
    }
}

impl Handler for Stack {
    fn log(&self, message: &str) -> io::Result<()> {
        let mut first_err: Option<io::Error> = None;
        let mut has_success = false;

        for child in &self.children {
            match child.log(message) {
                Ok(()) => has_success = true,
                Err(err) if first_err.is_none() => first_err = Some(err),
                Err(_) => {}
            }
        }

        if has_success {
            return Ok(());
        }

        Err(first_err.unwrap_or_else(|| io::Error::other("stack handler has no child handlers")))
    }
}

#[cfg(test)]
mod tests {
    use std::io;
    use std::sync::{Arc, Mutex};

    use super::{Handler, Stack};

    struct Recording {
        id: &'static str,
        calls: Arc<Mutex<Vec<&'static str>>>,
        should_fail: bool,
    }

    impl Recording {
        fn new(id: &'static str, calls: Arc<Mutex<Vec<&'static str>>>, should_fail: bool) -> Self {
            Self {
                id,
                calls,
                should_fail,
            }
        }
    }

    impl Handler for Recording {
        fn log(&self, _message: &str) -> io::Result<()> {
            self.calls
                .lock()
                .expect("calls lock poisoned")
                .push(self.id);
            if self.should_fail {
                return Err(io::Error::other(format!("handler {} failed", self.id)));
            }
            Ok(())
        }
    }

    #[test]
    fn fans_out_to_all_children_in_order() {
        let calls = Arc::new(Mutex::new(Vec::new()));
        let first = Arc::new(Recording::new("first", Arc::clone(&calls), false));
        let second = Arc::new(Recording::new("second", Arc::clone(&calls), false));

        let stack = Stack::new(vec![first, second]);
        stack.log("hello").expect("stack should write");

        let got = calls.lock().expect("calls lock poisoned").clone();
        assert_eq!(got, vec!["first", "second"]);
    }

    #[test]
    fn succeeds_when_at_least_one_child_succeeds() {
        let calls = Arc::new(Mutex::new(Vec::new()));
        let fail = Arc::new(Recording::new("fail", Arc::clone(&calls), true));
        let ok = Arc::new(Recording::new("ok", Arc::clone(&calls), false));

        let stack = Stack::new(vec![fail, ok]);
        stack
            .log("hello")
            .expect("stack should succeed when one child succeeds");
    }

    #[test]
    fn fails_when_all_children_fail() {
        let calls = Arc::new(Mutex::new(Vec::new()));
        let first = Arc::new(Recording::new("first", Arc::clone(&calls), true));
        let second = Arc::new(Recording::new("second", Arc::clone(&calls), true));

        let stack = Stack::new(vec![first, second]);
        let err = stack.log("hello").expect_err("stack should fail");
        assert!(err.to_string().contains("handler first failed"));
    }
}