kxio 0.1.0

Provides injectable Filesystem and Network resources to make code more testable
Documentation
#![allow(unused)]

use std::{
    ops::Deref,
    path::PathBuf,
    sync::{Arc, Mutex},
};

use tempfile::{tempdir, TempDir};
use tracing::info;

#[derive(Clone, Debug)]
pub enum FileSystem {
    Real(RealFileSystemEnv),
    Temp(TempFileSystemEnv),
}
impl FileSystem {
    pub fn new_real(cwd: Option<PathBuf>) -> Self {
        let cwd = cwd.unwrap_or_default();
        Self::Real(RealFileSystemEnv::new(cwd))
    }
    pub fn new_temp() -> std::io::Result<Self> {
        TempFileSystemEnv::new().map(Self::Temp)
    }
}
impl Deref for FileSystem {
    type Target = dyn FileSystemEnv;

    fn deref(&self) -> &Self::Target {
        match self {
            Self::Real(env) => env,
            Self::Temp(env) => env,
        }
    }
}

pub trait FileSystemEnv: Sync + Send + std::fmt::Debug {
    fn cwd(&self) -> &PathBuf;

    fn in_cwd(&self, name: &str) -> PathBuf {
        self.cwd().join(name)
    }

    fn write_file(&self, file_name: &str, content: &str) -> std::io::Result<PathBuf> {
        use std::fs::File;
        use std::io::{LineWriter, Write};

        let path = self.in_cwd(file_name);
        info!("writing to {:?}", path);
        let file = File::create(path.clone())?;
        let mut file = LineWriter::new(file);
        file.write_all(content.as_bytes())?;
        Ok(path)
    }

    fn file_exists(&self, name: &PathBuf) -> bool {
        use std::fs::File;
        File::open(name).is_ok()
    }

    fn read_file(&self, file_name: &str) -> std::io::Result<String> {
        use std::fs::File;
        use std::io::Read;

        let path = self.in_cwd(file_name);
        info!("reading from {:?}", path);
        let mut file = File::open(path)?;
        let mut content = String::new();
        file.read_to_string(&mut content)?;
        Ok(content)
    }
}

#[derive(Clone, Debug, Default)]
pub struct RealFileSystemEnv {
    cwd: PathBuf,
}

#[derive(Clone, Debug)]
pub struct TempFileSystemEnv {
    cwd: PathBuf,
    temp_dir: Arc<Mutex<TempDir>>,
}

impl FileSystemEnv for TempFileSystemEnv {
    fn cwd(&self) -> &PathBuf {
        &self.cwd
    }
}

impl FileSystemEnv for RealFileSystemEnv {
    fn cwd(&self) -> &PathBuf {
        &self.cwd
    }
}

impl RealFileSystemEnv {
    const fn new(cwd: PathBuf) -> Self {
        Self { cwd }
    }
}

impl TempFileSystemEnv {
    fn new() -> std::io::Result<Self> {
        let temp_dir = tempdir()?;
        info!("temp dir: {:?}", temp_dir.path());
        let cwd = temp_dir.path().to_path_buf();
        let temp_dir = Arc::new(Mutex::new(temp_dir));
        Ok(Self { cwd, temp_dir })
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use std::path::PathBuf;

    #[test_log::test]
    fn test_cwd() {
        let cwd = PathBuf::from("/tmp");
        let env = RealFileSystemEnv::new(cwd.clone());
        assert_eq!(env.cwd(), &cwd);
    }

    #[test_log::test]
    fn test_create_on_temp_fs() -> std::io::Result<()> {
        let env = TempFileSystemEnv::new()?;
        assert!(env.cwd().exists());
        Ok(())
    }

    #[test_log::test]
    fn test_create_on_real_fs() {
        let cwd = PathBuf::from("/tmp");
        let env = RealFileSystemEnv::new(cwd.clone());
        assert_eq!(env.cwd(), &cwd);
    }

    #[test_log::test]
    fn test_write_and_read_file() -> std::io::Result<()> {
        let env = TempFileSystemEnv::new()?;
        let file_name = "test.txt";
        let content = "Hello, World!";
        let path = env.write_file(file_name, content)?;
        assert_eq!(env.read_file(file_name)?, content);
        assert!(path.exists());
        Ok(())
    }
}