rivet-logger 0.1.0

Rivet framework crates and adapters.
Documentation
use std::fs::{self, File, OpenOptions};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::sync::Mutex;

use super::Handler;

pub struct Single {
    path: PathBuf,
    file: Mutex<File>,
}

impl Single {
    pub fn new(path: impl Into<PathBuf>) -> io::Result<Self> {
        let path = path.into();

        if let Some(parent) = path
            .parent()
            .filter(|parent| !parent.as_os_str().is_empty())
        {
            fs::create_dir_all(parent)?;
        }

        let file = OpenOptions::new().create(true).append(true).open(&path)?;

        Ok(Self {
            path,
            file: Mutex::new(file),
        })
    }

    pub fn path(&self) -> &Path {
        &self.path
    }
}

impl Handler for Single {
    fn log(&self, message: &str) -> io::Result<()> {
        let mut file = self
            .file
            .lock()
            .map_err(|_| io::Error::other("single log lock poisoned"))?;

        writeln!(file, "{message}")?;
        file.flush()?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::fs;
    use std::time::{SystemTime, UNIX_EPOCH};

    use super::{Handler, Single};

    #[test]
    fn writes_message_to_file() {
        let stamp = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("system time should be after unix epoch")
            .as_nanos();
        let path = std::env::temp_dir().join(format!("rivet-single-{stamp}.log"));

        let logger = Single::new(&path).expect("single handler should initialize");
        logger
            .log("hello from single")
            .expect("single handler should write");

        let output = fs::read_to_string(&path).expect("single log should be readable");
        assert_eq!(output, "hello from single\n");

        let _ = fs::remove_file(path);
    }
}