about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/filesystem.rs197
-rw-r--r--src/lib.rs1
2 files changed, 198 insertions, 0 deletions
diff --git a/src/filesystem.rs b/src/filesystem.rs
new file mode 100644
index 0000000..0f6b9b8
--- /dev/null
+++ b/src/filesystem.rs
@@ -0,0 +1,197 @@
+use anyhow::{Context, Result};
+use std::{
+    fs::{File, OpenOptions},
+    io::{Read, Seek, Write},
+    path::Path,
+};
+
+pub trait FileSystem {
+    type F;
+
+    fn create_file(&mut self, path: &Path) -> Result<Self::F>;
+    fn open_readable_file(&mut self, path: &Path) -> Result<Self::F>;
+    fn open_writable_file(&mut self, path: &Path) -> Result<Self::F>;
+
+    fn write_to_file(&mut self, file: &mut Self::F, buffer: Vec<u8>) -> Result<()>;
+    fn read_from_file(&mut self, file: &mut Self::F) -> Result<Vec<u8>>;
+}
+
+pub struct FileSystemImpl {}
+
+impl FileSystem for FileSystemImpl {
+    type F = File;
+
+    fn create_file(&mut self, path: &Path) -> Result<Self::F> {
+        OpenOptions::new()
+            .create(true)
+            .read(true)
+            .write(true)
+            .open(path)
+            .with_context(|| format!("Failed creating '{}'.", path.display()))
+    }
+
+    fn open_readable_file(&mut self, path: &Path) -> Result<Self::F> {
+        File::open(path)
+            .with_context(|| format!("Failed opening '{}' for reading.", path.display()))
+    }
+
+    fn open_writable_file(&mut self, path: &Path) -> Result<Self::F> {
+        OpenOptions::new()
+            .read(true)
+            .write(true)
+            .open(path)
+            .with_context(|| {
+                format!(
+                    "Failed opening '{}' for reading and writing.",
+                    path.display()
+                )
+            })
+    }
+
+    fn write_to_file(&mut self, file: &mut Self::F, buffer: Vec<u8>) -> Result<()> {
+        file.rewind()?;
+        file.set_len(0)?;
+        file.write_all(&buffer)?;
+        Ok(())
+    }
+
+    fn read_from_file(&mut self, file: &mut Self::F) -> Result<Vec<u8>> {
+        let mut buffer = Vec::new();
+        file.read_to_end(&mut buffer)?;
+        Ok(buffer)
+    }
+}
+
+#[cfg(test)]
+pub mod mock {
+    use std::path::{Path, PathBuf};
+    use anyhow::{Error, Result};
+
+    use super::FileSystem;
+
+    pub struct FileSystemMock {
+        expected_calls: Vec<ExpectedCall>,
+        received_calls: Vec<ReceivedCall>,
+    }
+
+    impl FileSystemMock {
+        pub fn new() -> Self {
+            FileSystemMock {
+                expected_calls: Vec::new(),
+                received_calls: Vec::new(),
+            }
+        }
+    }
+
+    impl FileSystem for FileSystemMock {
+        type F = FileMock;
+
+        fn create_file(&mut self, path: &Path) -> Result<Self::F> {
+            let call = ReceivedCall {
+                affected_file: path.to_path_buf(),
+                variant: ReceivedCallVariant::FileCreated,
+            };
+
+            self.received_calls.push(call);
+
+            Ok(FileMock {
+                path: path.to_path_buf(),
+                writable: true,
+            })
+        }
+
+        fn open_readable_file(&mut self, path: &Path) -> Result<Self::F> {
+            let call = ReceivedCall {
+                affected_file: path.to_path_buf(),
+                variant: ReceivedCallVariant::ReadableFileOpened,
+            };
+
+            self.received_calls.push(call);
+
+            Ok(FileMock {
+                path: path.to_path_buf(),
+                writable: false,
+            })
+        }
+
+        fn open_writable_file(&mut self, path: &Path) -> Result<Self::F> {
+            let call = ReceivedCall {
+                affected_file: path.to_path_buf(),
+                variant: ReceivedCallVariant::WritableFileOpened,
+            };
+
+            self.received_calls.push(call);
+
+            Ok(FileMock {
+                path: path.to_path_buf(),
+                writable: true,
+            })
+        }
+
+        fn write_to_file(&mut self, file: &mut Self::F, buffer: Vec<u8>) -> Result<()> {
+            let call = ReceivedCall {
+                affected_file: file.path.clone(),
+                variant: ReceivedCallVariant::FileWritten {
+                    written_content: buffer,
+                },
+            };
+
+            // TODO: Check whether file was opened with write flag.
+
+            self.received_calls.push(call);
+
+            Ok(())
+        }
+
+        fn read_from_file(&mut self, file: &mut Self::F) -> Result<Vec<u8>> {
+            let call = ReceivedCall {
+                affected_file: file.path.clone(),
+                variant: ReceivedCallVariant::FileRead,
+            };
+
+            self.received_calls.push(call);
+
+            let expected_call_option = self.expected_calls.get(self.received_calls.len() - 1);
+
+            if let Some(expected_call) = expected_call_option {
+                if let ExpectedCallVariant::ReadFile { read_content } = &expected_call.variant {
+                    return Ok(read_content.clone());
+                }
+            }
+
+            Err(Error::msg("No content to read."))
+        }
+    }
+
+    pub struct FileMock {
+        path: PathBuf,
+        writable: bool,
+    }
+
+    // TODO: Do we need ways to return errors?
+    pub struct ExpectedCall {
+        pub affected_file: PathBuf,
+        pub variant: ExpectedCallVariant,
+    }
+
+    pub enum ExpectedCallVariant {
+        CreateFile,
+        OpenReadableFile,
+        OpenWritableFile,
+        ReadFile { read_content: Vec<u8> },
+        WriteToFile,
+    }
+
+    pub struct ReceivedCall {
+        pub affected_file: PathBuf,
+        pub variant: ReceivedCallVariant,
+    }
+
+    pub enum ReceivedCallVariant {
+        FileCreated,
+        ReadableFileOpened,
+        WritableFileOpened,
+        FileRead,
+        FileWritten { written_content: Vec<u8> },
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 152219e..3c489c5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,3 +3,4 @@ pub mod actions;
 mod history;
 mod diff;
 mod files;
+mod filesystem;