From d2ee798f258985a464465220043ea345edaca080 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sat, 2 Nov 2024 07:34:08 +0000 Subject: [PATCH] refactor: use generics for path type --- src/fs/dir.rs | 22 +++++++++++ src/fs/dir_item.rs | 3 ++ src/fs/file.rs | 19 ++++++++++ src/fs/mod.rs | 38 ++++++++++++++++--- src/fs/{real => }/path.rs | 74 ++++++++++++++++++++++--------------- src/fs/{real => }/reader.rs | 0 src/fs/real/dir.rs | 64 -------------------------------- src/fs/real/file.rs | 46 ----------------------- src/fs/real/mod.rs | 8 ---- src/fs/result.rs | 10 +++++ src/fs/{real => }/system.rs | 29 ++++++++++++--- src/fs/temp.rs | 2 +- tests/fs.rs | 4 +- 13 files changed, 158 insertions(+), 161 deletions(-) create mode 100644 src/fs/dir.rs create mode 100644 src/fs/file.rs rename src/fs/{real => }/path.rs (61%) rename src/fs/{real => }/reader.rs (100%) delete mode 100644 src/fs/real/dir.rs delete mode 100644 src/fs/real/file.rs delete mode 100644 src/fs/real/mod.rs rename src/fs/{real => }/system.rs (64%) diff --git a/src/fs/dir.rs b/src/fs/dir.rs new file mode 100644 index 0000000..c65de01 --- /dev/null +++ b/src/fs/dir.rs @@ -0,0 +1,22 @@ +// +use crate::fs::{DirItem, DirItemIterator, Result}; + +use super::path::{DirG, PathReal}; + +impl<'base, 'path> PathReal<'base, 'path, DirG> { + pub fn create(&mut self) -> Result<()> { + self.check_error()?; + std::fs::create_dir(self.as_pathbuf()).map_err(Into::into) + } + + pub fn create_all(&mut self) -> Result<()> { + self.check_error()?; + std::fs::create_dir_all(self.as_pathbuf()).map_err(Into::into) + } + + pub fn read(&mut self) -> Result>>> { + self.check_error()?; + let read_dir = std::fs::read_dir(self.as_pathbuf())?; + Ok(Box::new(DirItemIterator::new(read_dir))) + } +} diff --git a/src/fs/dir_item.rs b/src/fs/dir_item.rs index fdd0eac..79cb157 100644 --- a/src/fs/dir_item.rs +++ b/src/fs/dir_item.rs @@ -1,8 +1,10 @@ +// use std::{ fs::{DirEntry, ReadDir}, path::PathBuf, }; +/// Represents an item in a directory #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum DirItem { File(PathBuf), @@ -12,6 +14,7 @@ pub enum DirItem { Unsupported(PathBuf), } +/// An iterator for items in a directory. #[derive(Debug, derive_more::Constructor)] pub struct DirItemIterator(ReadDir); impl Iterator for DirItemIterator { diff --git a/src/fs/file.rs b/src/fs/file.rs new file mode 100644 index 0000000..af11770 --- /dev/null +++ b/src/fs/file.rs @@ -0,0 +1,19 @@ +// +use crate::fs::Result; + +use super::{ + path::{FileG, PathReal}, + reader::ReaderReal, +}; + +impl<'base, 'path> PathReal<'base, 'path, FileG> { + pub fn reader(&mut self) -> Result { + self.check_error()?; + ReaderReal::new(&self.as_pathbuf()) + } + + pub fn write(&mut self, contents: &str) -> Result<()> { + self.check_error()?; + std::fs::write(self.as_pathbuf(), contents).map_err(Into::into) + } +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs index b8991bb..3d6df74 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,18 +1,46 @@ +/// Provides an injectable reference to part of the filesystem. +/// +/// Create a new `FileSystem` to access a directory using `kxio::fs::new(path)`. +/// Create a new `TempFileSystem` to access a temporary directory using `kxio::fs::temp()?`; +/// +/// `TempFileSystem` derefs automaticalyl to `FileSystem` so can be used anywhere +/// you would use `FileSystem`. use std::path::PathBuf; +mod dir; mod dir_item; -mod real; +mod file; +mod path; +mod reader; mod result; +mod system; mod temp; -pub use dir_item::DirItem; -pub use dir_item::DirItemIterator; +pub use dir_item::{DirItem, DirItemIterator}; pub use result::{Error, Result}; +pub use system::FileSystem; -pub const fn new(base: PathBuf) -> real::FileSystem { - real::FileSystem::new(base) +/// Creates a new `FileSystem` for the path. +/// +/// This will create a `FileSystem` that provides access to the +/// filesystem under the given path. +/// +/// Any attempt to access outside this base will result in a +/// `error::Error::PathTraversal` error when attempting the +/// opertation. +pub const fn new(base: PathBuf) -> FileSystem { + FileSystem::new(base) } +/// Creates a new `TempFileSystem` for a temporary directory. +/// +/// The `TempFileSystem` provides a `Deref` to a `FileSystem` for +/// the temporary directory. +/// +/// When the `TempFileSystem` is dropped, the temporary directory +/// is deleted. +/// +/// Returns an error if the temporary directory cannot be created. pub fn temp() -> Result { temp::TempFileSystem::new() } diff --git a/src/fs/real/path.rs b/src/fs/path.rs similarity index 61% rename from src/fs/real/path.rs rename to src/fs/path.rs index 772172b..0a5b6a4 100644 --- a/src/fs/real/path.rs +++ b/src/fs/path.rs @@ -1,27 +1,51 @@ // -use std::path::{Path, PathBuf}; +use std::{ + marker::PhantomData, + path::{Path, PathBuf}, +}; use crate::fs::{Error, Result}; -use super::{dir::DirReal, file::FileReal}; +pub trait PathType {} + +pub struct PathG; +impl PathType for PathG {} + +pub struct FileG; +impl PathType for FileG {} + +pub struct DirG; +impl PathType for DirG {} #[derive(Debug)] -pub struct PathReal<'base, 'path> { +pub struct PathReal<'base, 'path, T: PathType> { base: &'base Path, path: &'path Path, + _phanton: PhantomData, pub(super) error: Option, } -impl<'base, 'path> PathReal<'base, 'path> { - pub(super) fn full_path(&self) -> PathBuf { +impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> { + pub(super) fn new(base: &'base Path, path: &'path Path) -> Self { + Self { + base, + path, + _phanton: PhantomData::, + error: PathReal::::validate(base, path), + } + } + + pub fn as_pathbuf(&self) -> PathBuf { self.base.join(self.path) } + pub(super) fn put(&mut self, error: Error) { if self.error.is_none() { self.error.replace(error); } } + fn validate(base: &Path, path: &Path) -> Option { - match PathReal::clean_path(path) { + match PathReal::::clean_path(path) { Err(error) => Some(error), Ok(path) => { if !path.starts_with(base) { @@ -47,19 +71,6 @@ impl<'base, 'path> PathReal<'base, 'path> { Ok(abs_path) } - pub(super) fn new(base: &'base Path, path: &'path Path) -> Self { - Self { - base, - path, - error: PathReal::validate(base, path), - } - } - - pub fn exists(&mut self) -> Result { - self.check_error()?; - Ok(self.full_path().exists()) - } - pub(super) fn check_error(&mut self) -> Result<()> { if let Some(error) = self.error.take() { return Err(error); @@ -67,36 +78,41 @@ impl<'base, 'path> PathReal<'base, 'path> { Ok(()) } + pub fn exists(&mut self) -> Result { + self.check_error()?; + Ok(self.as_pathbuf().exists()) + } + pub fn is_dir(&mut self) -> Result { self.check_error()?; - Ok(self.full_path().is_dir()) + Ok(self.as_pathbuf().is_dir()) } pub fn is_file(&mut self) -> Result { self.check_error()?; - Ok(self.full_path().is_file()) + Ok(self.as_pathbuf().is_file()) } - pub fn as_dir(&mut self) -> Result>> { + pub fn as_dir(&mut self) -> Result>> { self.check_error()?; - if self.full_path().is_dir() { - Ok(Some(DirReal::new(self.base, self.path))) + if self.as_pathbuf().is_dir() { + Ok(Some(PathReal::new(self.base, self.path))) } else { Ok(None) } } - pub fn as_file(&mut self) -> Result>> { + pub fn as_file(&mut self) -> Result>> { self.check_error()?; - if self.full_path().is_file() { - Ok(Some(FileReal::new(self.base, self.path))) + if self.as_pathbuf().is_file() { + Ok(Some(PathReal::new(self.base, self.path))) } else { Ok(None) } } } -impl From> for PathBuf { - fn from(path: PathReal) -> Self { +impl From> for PathBuf { + fn from(path: PathReal) -> Self { path.base.join(path.path) } } diff --git a/src/fs/real/reader.rs b/src/fs/reader.rs similarity index 100% rename from src/fs/real/reader.rs rename to src/fs/reader.rs diff --git a/src/fs/real/dir.rs b/src/fs/real/dir.rs deleted file mode 100644 index eba0c4f..0000000 --- a/src/fs/real/dir.rs +++ /dev/null @@ -1,64 +0,0 @@ -// -use std::path::{Path, PathBuf}; - -use crate::fs::{DirItem, DirItemIterator, Error, Result}; - -use super::path::PathReal; - -pub struct DirReal<'base, 'path> { - path: PathReal<'base, 'path>, -} -impl<'base, 'path> DirReal<'base, 'path> { - pub(super) fn new(base: &'base Path, path: &'path Path) -> Self { - let mut path = PathReal::new(base, path); - if path.error.is_none() { - if let Ok(exists) = path.exists() { - if exists { - if let Ok(is_dir) = path.is_dir() { - if !is_dir { - path.put(Error::NotADirectory { - path: path.full_path(), - }) - } - } - } - } - } - Self { path } - } - - pub fn path(&self) -> PathBuf { - self.path.full_path() - } - - pub fn create(&mut self) -> Result<()> { - self.path.check_error()?; - std::fs::create_dir(self.path.full_path()).map_err(Into::into) - } - - pub fn create_all(&mut self) -> Result<()> { - self.path.check_error()?; - std::fs::create_dir_all(self.path.full_path()).map_err(Into::into) - } - - pub fn read(&mut self) -> Result>>> { - self.path.check_error()?; - let read_dir = std::fs::read_dir(self.path.full_path())?; - Ok(Box::new(DirItemIterator::new(read_dir))) - } - - pub fn exists(&mut self) -> Result { - self.path.check_error()?; - self.path.exists() - } - - pub fn is_dir(&mut self) -> Result { - self.path.check_error()?; - Ok(true) - } - - pub fn is_file(&mut self) -> Result { - self.path.check_error()?; - Ok(false) - } -} diff --git a/src/fs/real/file.rs b/src/fs/real/file.rs deleted file mode 100644 index da4a74e..0000000 --- a/src/fs/real/file.rs +++ /dev/null @@ -1,46 +0,0 @@ -// -use std::path::{Path, PathBuf}; - -use crate::fs::Result; - -use super::{path::PathReal, reader::ReaderReal}; - -pub struct FileReal<'base, 'path> { - path: PathReal<'base, 'path>, -} -impl<'base, 'path> FileReal<'base, 'path> { - pub(super) fn new(base: &'base Path, path: &'path Path) -> Self { - Self { - path: PathReal::new(base, path), - } - } - - pub fn path(&self) -> PathBuf { - self.path.full_path() - } - - pub fn reader(&mut self) -> Result { - self.path.check_error()?; - ReaderReal::new(&self.path.full_path()) - } - - pub fn write(&mut self, contents: &str) -> Result<()> { - self.path.check_error()?; - std::fs::write(self.path.full_path(), contents).map_err(Into::into) - } - - pub fn exists(&mut self) -> Result { - self.path.check_error()?; - self.path.exists() - } - - pub fn is_dir(&mut self) -> Result { - self.path.check_error()?; - Ok(false) - } - - pub fn is_file(&mut self) -> Result { - self.path.check_error()?; - Ok(true) - } -} diff --git a/src/fs/real/mod.rs b/src/fs/real/mod.rs deleted file mode 100644 index 10408cc..0000000 --- a/src/fs/real/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -// -mod dir; -mod file; -mod path; -mod reader; -mod system; - -pub use system::FileSystem; diff --git a/src/fs/result.rs b/src/fs/result.rs index 9e3e679..6629818 100644 --- a/src/fs/result.rs +++ b/src/fs/result.rs @@ -3,6 +3,11 @@ use std::path::PathBuf; use derive_more::From; +/// Represents a error accessing the file system. +/// +/// Any failure is related to `std::io`, a Path Traversal +/// (i.e. trying to escape the base of the `FileSystem`), +/// or attempting to use a file as a directory or /vise versa/. #[derive(Debug, From, derive_more::Display)] pub enum Error { Io(std::io::Error), @@ -20,4 +25,9 @@ pub enum Error { } impl std::error::Error for Error {} +/// Represents a success or a failure. +/// +/// Any failure is related to `std::io`, a Path Traversal +/// (i.e. trying to escape the base of the `FileSystem`), +/// or attempting to use a file as a directory or /vise versa/. pub type Result = core::result::Result; diff --git a/src/fs/real/system.rs b/src/fs/system.rs similarity index 64% rename from src/fs/real/system.rs rename to src/fs/system.rs index 829f1e7..820bbca 100644 --- a/src/fs/real/system.rs +++ b/src/fs/system.rs @@ -3,8 +3,9 @@ use std::path::{Path, PathBuf}; use crate::fs::{Error, Result}; -use super::{dir::DirReal, file::FileReal, path::PathReal}; +use super::path::{DirG, FileG, PathG, PathReal}; +/// Represents to base of a section of a file system. #[derive(Clone, Debug)] pub struct FileSystem { base: PathBuf, @@ -23,15 +24,31 @@ impl FileSystem { Ok(path_of) } - pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> DirReal<'base, 'path> { - DirReal::new(self.base(), path) + pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, DirG> { + let mut dir = PathReal::new(self.base(), path); + + if dir.error.is_none() { + if let Ok(exists) = dir.exists() { + if exists { + if let Ok(is_dir) = dir.is_dir() { + if !is_dir { + dir.put(Error::NotADirectory { + path: dir.as_pathbuf(), + }) + } + } + } + } + } + + dir } - pub fn file<'base, 'path>(&'base self, path: &'path Path) -> FileReal<'base, 'path> { - FileReal::new(self.base(), path) + pub fn file<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, FileG> { + PathReal::new(self.base(), path) } - pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path> { + pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, PathG> { PathReal::new(self.base(), path) } diff --git a/src/fs/temp.rs b/src/fs/temp.rs index eb1f3db..a93e024 100644 --- a/src/fs/temp.rs +++ b/src/fs/temp.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex}; use tempfile::TempDir; -use super::real::FileSystem; +use super::FileSystem; #[derive(Clone, Debug)] pub struct TempFileSystem { diff --git a/tests/fs.rs b/tests/fs.rs index 15dae46..4e80f82 100644 --- a/tests/fs.rs +++ b/tests/fs.rs @@ -45,7 +45,7 @@ mod path { let_assert!(Ok(Some(as_dir)) = fs.path(&path).as_dir()); - assert_eq!(dir.path(), as_dir.path()); + assert_eq!(dir.as_pathbuf(), as_dir.as_pathbuf()); Ok(()) } @@ -60,7 +60,7 @@ mod path { let_assert!(Ok(Some(mut as_file)) = fs.path(&path).as_file()); - assert_eq!(file.path(), as_file.path()); + assert_eq!(file.as_pathbuf(), as_file.as_pathbuf()); assert_eq!(as_file.reader().expect("reader").to_string(), "contents"); Ok(())