vortex-fs 0.1.0

Virtual file system with fault injection for Vortex simulation
Documentation
//! `RealFs` — production pass-through to `std::fs`.
//!
//! Implements [`VortexFs`] by delegating directly to the OS. Zero overhead.

use crate::traits::{FileMetadata, FileType, VortexFs, VortexFsError, VortexFsResult};

/// Production filesystem implementation — delegates to `std::fs`.
pub struct RealFs;

impl RealFs {
    pub fn new() -> Self {
        Self
    }
}

impl Default for RealFs {
    fn default() -> Self {
        Self::new()
    }
}

impl VortexFs for RealFs {
    fn read_file(&self, path: &str) -> VortexFsResult<Vec<u8>> {
        std::fs::read(path).map_err(|e| map_io_error(path, e))
    }

    fn write_file(&mut self, path: &str, data: &[u8]) -> VortexFsResult<()> {
        // Ensure parent directory exists
        if let Some(parent) = std::path::Path::new(path).parent()
            && !parent.as_os_str().is_empty()
        {
            std::fs::create_dir_all(parent).map_err(|e| map_io_error(path, e))?;
        }
        std::fs::write(path, data).map_err(|e| map_io_error(path, e))
    }

    fn append_file(&mut self, path: &str, data: &[u8]) -> VortexFsResult<()> {
        use std::io::Write;
        let mut file = std::fs::OpenOptions::new()
            .create(true)
            .append(true)
            .open(path)
            .map_err(|e| map_io_error(path, e))?;
        file.write_all(data).map_err(|e| map_io_error(path, e))
    }

    fn remove_file(&mut self, path: &str) -> VortexFsResult<()> {
        std::fs::remove_file(path).map_err(|e| map_io_error(path, e))
    }

    fn rename(&mut self, from: &str, to: &str) -> VortexFsResult<()> {
        std::fs::rename(from, to).map_err(|e| map_io_error(from, e))
    }

    fn create_dir_all(&mut self, path: &str) -> VortexFsResult<()> {
        std::fs::create_dir_all(path).map_err(|e| map_io_error(path, e))
    }

    fn remove_dir(&mut self, path: &str) -> VortexFsResult<()> {
        std::fs::remove_dir(path).map_err(|e| map_io_error(path, e))
    }

    fn read_dir(&self, path: &str) -> VortexFsResult<Vec<String>> {
        let entries = std::fs::read_dir(path).map_err(|e| map_io_error(path, e))?;
        let mut names = Vec::new();
        for entry in entries {
            let entry = entry.map_err(|e| map_io_error(path, e))?;
            if let Some(name) = entry.file_name().to_str() {
                names.push(name.to_string());
            }
        }
        names.sort(); // Deterministic ordering
        Ok(names)
    }

    fn metadata(&self, path: &str) -> VortexFsResult<FileMetadata> {
        let meta = std::fs::metadata(path).map_err(|e| map_io_error(path, e))?;
        Ok(FileMetadata {
            file_type: if meta.is_dir() {
                FileType::Directory
            } else {
                FileType::File
            },
            size: meta.len(),
        })
    }

    fn exists(&self, path: &str) -> bool {
        std::path::Path::new(path).exists()
    }

    fn fsync(&mut self, path: &str) -> VortexFsResult<()> {
        let file = std::fs::File::open(path).map_err(|e| map_io_error(path, e))?;
        file.sync_all().map_err(|e| map_io_error(path, e))
    }
}

fn map_io_error(path: &str, err: std::io::Error) -> VortexFsError {
    match err.kind() {
        std::io::ErrorKind::NotFound => VortexFsError::NotFound(path.into()),
        std::io::ErrorKind::PermissionDenied => VortexFsError::PermissionDenied(path.into()),
        std::io::ErrorKind::AlreadyExists => VortexFsError::AlreadyExists(path.into()),
        _ => VortexFsError::IoError(format!("{path}: {err}")),
    }
}