use std::path::Path;
use crate::error::FileOpsError;
pub trait FileOps {
fn read_to_string(&self, path: &Path) -> Result<String, FileOpsError>;
fn write(&self, path: &Path, contents: &[u8]) -> Result<(), FileOpsError>;
fn create_dir_all(&self, path: &Path) -> Result<(), FileOpsError>;
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SystemFs;
impl FileOps for SystemFs {
fn read_to_string(&self, path: &Path) -> Result<String, FileOpsError> {
#[allow(clippy::disallowed_methods)]
std::fs::read_to_string(path).map_err(FileOpsError::from)
}
fn write(&self, path: &Path, contents: &[u8]) -> Result<(), FileOpsError> {
#[allow(clippy::disallowed_methods)]
std::fs::write(path, contents).map_err(FileOpsError::from)
}
fn create_dir_all(&self, path: &Path) -> Result<(), FileOpsError> {
#[allow(clippy::disallowed_methods)]
std::fs::create_dir_all(path).map_err(FileOpsError::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io;
use std::path::PathBuf;
#[derive(Default)]
struct MemoryFs {
files: RefCell<HashMap<PathBuf, Vec<u8>>>,
}
impl FileOps for MemoryFs {
fn read_to_string(&self, path: &Path) -> Result<String, FileOpsError> {
self.files
.borrow()
.get(path)
.map(|bytes| String::from_utf8(bytes.clone()).expect("non-utf8 in MemoryFs"))
.ok_or_else(|| {
FileOpsError::from(io::Error::new(io::ErrorKind::NotFound, "not found"))
})
}
fn write(&self, path: &Path, contents: &[u8]) -> Result<(), FileOpsError> {
self.files
.borrow_mut()
.insert(path.to_path_buf(), contents.to_vec());
Ok(())
}
fn create_dir_all(&self, _path: &Path) -> Result<(), FileOpsError> {
Ok(())
}
}
#[test]
fn memory_fs_round_trips() {
let fs = MemoryFs::default();
fs.write(Path::new("a.txt"), b"hello").unwrap();
assert_eq!(fs.read_to_string(Path::new("a.txt")).unwrap(), "hello");
}
#[test]
fn memory_fs_missing_file_is_not_found() {
let fs = MemoryFs::default();
let err = fs.read_to_string(Path::new("missing.txt")).unwrap_err();
assert_eq!(err.0.kind(), io::ErrorKind::NotFound);
}
#[test]
fn system_fs_round_trips_a_real_file() {
let dir = std::env::temp_dir().join("tangible-io-roundtrip");
SystemFs.create_dir_all(&dir).unwrap();
let path = dir.join("hello.txt");
SystemFs.write(&path, b"hi from tangible").unwrap();
assert_eq!(SystemFs.read_to_string(&path).unwrap(), "hi from tangible");
}
}