naru-config 0.7.0

A security-first configuration manager with encryption and audit logging
Documentation
use std::fs;
use std::path::{Path, PathBuf};

#[allow(dead_code)]
pub struct FileStorage {
    base_path: PathBuf,
}

#[allow(dead_code)]
impl FileStorage {
    pub fn new(base_path: impl Into<PathBuf>) -> Self {
        Self {
            base_path: base_path.into(),
        }
    }

    pub fn ensure_dir(&self) -> std::io::Result<()> {
        fs::create_dir_all(&self.base_path)
    }

    pub fn exists(&self, filename: &str) -> bool {
        self.resolve_path(filename).exists()
    }

    pub fn read(&self, filename: &str) -> std::io::Result<String> {
        fs::read_to_string(self.resolve_path(filename))
    }

    pub fn write(&self, filename: &str, content: &str) -> std::io::Result<()> {
        let path = self.resolve_path(filename);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)?;
        }
        fs::write(path, content)
    }

    pub fn delete(&self, filename: &str) -> std::io::Result<()> {
        let path = self.resolve_path(filename);
        if path.exists() {
            fs::remove_file(path)
        } else {
            Ok(())
        }
    }

    pub fn list_files(&self, extension: Option<&str>) -> std::io::Result<Vec<String>> {
        let mut files = Vec::new();
        if self.base_path.is_dir() {
            for entry in fs::read_dir(&self.base_path)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_file() {
                    if let Some(ext) = extension {
                        if let Some(path_ext) = path.extension() {
                            if path_ext.to_str() == Some(ext) {
                                if let Some(name) = path.file_name() {
                                    files.push(name.to_string_lossy().to_string());
                                }
                            }
                        }
                    } else if let Some(name) = path.file_name() {
                        files.push(name.to_string_lossy().to_string());
                    }
                }
            }
        }
        Ok(files)
    }

    pub fn copy(&self, source: &str, dest: &str) -> std::io::Result<()> {
        fs::copy(self.resolve_path(source), self.resolve_path(dest))?;
        Ok(())
    }

    pub fn move_file(&self, source: &str, dest: &str) -> std::io::Result<()> {
        fs::rename(self.resolve_path(source), self.resolve_path(dest))
    }

    pub fn get_size(&self, filename: &str) -> std::io::Result<u64> {
        let metadata = fs::metadata(self.resolve_path(filename))?;
        Ok(metadata.len())
    }

    fn resolve_path(&self, filename: &str) -> PathBuf {
        self.base_path.join(filename)
    }

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

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_file_storage_new() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path());
        assert!(storage.path().exists());
    }

    #[test]
    fn test_file_storage_write_read() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path());

        storage.write("test.txt", "hello world").unwrap();
        assert!(storage.exists("test.txt"));

        let content = storage.read("test.txt").unwrap();
        assert_eq!(content, "hello world");
    }

    #[test]
    fn test_file_storage_delete() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path());

        storage.write("delete_me.txt", "content").unwrap();
        assert!(storage.exists("delete_me.txt"));

        storage.delete("delete_me.txt").unwrap();
        assert!(!storage.exists("delete_me.txt"));
    }

    #[test]
    fn test_file_storage_list_files() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path());

        storage.write("file1.txt", "content1").unwrap();
        storage.write("file2.txt", "content2").unwrap();
        storage.write("file3.json", "content3").unwrap();

        let txt_files = storage.list_files(Some("txt")).unwrap();
        assert_eq!(txt_files.len(), 2);

        let json_files = storage.list_files(Some("json")).unwrap();
        assert_eq!(json_files.len(), 1);

        let all_files = storage.list_files(None).unwrap();
        assert_eq!(all_files.len(), 3);
    }

    #[test]
    fn test_file_storage_get_size() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path());

        storage.write("size_test.txt", "hello").unwrap();
        let size = storage.get_size("size_test.txt").unwrap();
        assert_eq!(size, 5);
    }

    #[test]
    fn test_file_storage_nested_path() {
        let temp_dir = TempDir::new().unwrap();
        let storage = FileStorage::new(temp_dir.path());

        storage
            .write("nested/dir/test.txt", "nested content")
            .unwrap();
        let content = storage.read("nested/dir/test.txt").unwrap();
        assert_eq!(content, "nested content");
    }
}