diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 9f45ac3..012b2f7 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -2,11 +2,11 @@ use crate::fs::{DirItem, DirItemIterator, Result}; use super::{ - path::{DirG, PathReal}, - FileG, PathG, + path::{DirMarker, PathReal}, + Error, FileMarker, PathMarker, }; -impl<'base, 'path> PathReal<'base, 'path, DirG> { +impl<'base, 'path> PathReal<'base, 'path, DirMarker> { /// Creates a new, empty directory at the path /// /// Wrapper for [std::fs::create_dir] @@ -22,7 +22,7 @@ impl<'base, 'path> PathReal<'base, 'path, DirG> { /// ``` pub fn create(&self) -> Result<()> { self.check_error()?; - std::fs::create_dir(self.as_pathbuf()).map_err(Into::into) + std::fs::create_dir(self.as_pathbuf()).map_err(Error::Io) } /// Recursively create a directory and all of its parent components if they are missing. @@ -40,7 +40,7 @@ impl<'base, 'path> PathReal<'base, 'path, DirG> { /// ``` pub fn create_all(&self) -> Result<()> { self.check_error()?; - std::fs::create_dir_all(self.as_pathbuf()).map_err(Into::into) + std::fs::create_dir_all(self.as_pathbuf()).map_err(Error::Io) } /// Returns an iterator over the entries within a directory. @@ -58,14 +58,18 @@ impl<'base, 'path> PathReal<'base, 'path, DirG> { /// ``` pub fn read(&self) -> Result>>> { self.check_error()?; - let read_dir = std::fs::read_dir(self.as_pathbuf())?; + let read_dir = std::fs::read_dir(self.as_pathbuf()).map_err(Error::Io)?; Ok(Box::new(DirItemIterator::new(read_dir))) } } -impl<'base, 'path> TryFrom> for PathReal<'base, 'path, FileG> { +impl<'base, 'path> TryFrom> + for PathReal<'base, 'path, FileMarker> +{ type Error = crate::fs::Error; - fn try_from(path: PathReal<'base, 'path, PathG>) -> std::result::Result { + fn try_from( + path: PathReal<'base, 'path, PathMarker>, + ) -> std::result::Result { match path.as_file() { Ok(Some(dir)) => Ok(dir.clone()), Ok(None) => Err(crate::fs::Error::NotADirectory { @@ -75,10 +79,14 @@ impl<'base, 'path> TryFrom> for PathReal<'base, 'p } } } -impl<'base, 'path> TryFrom> for PathReal<'base, 'path, DirG> { +impl<'base, 'path> TryFrom> + for PathReal<'base, 'path, DirMarker> +{ type Error = crate::fs::Error; - fn try_from(path: PathReal<'base, 'path, PathG>) -> std::result::Result { + fn try_from( + path: PathReal<'base, 'path, PathMarker>, + ) -> std::result::Result { match path.as_dir() { Ok(Some(dir)) => Ok(dir.clone()), Ok(None) => Err(crate::fs::Error::NotADirectory { diff --git a/src/fs/dir_item.rs b/src/fs/dir_item.rs index 79cb157..c3bad62 100644 --- a/src/fs/dir_item.rs +++ b/src/fs/dir_item.rs @@ -4,6 +4,8 @@ use std::{ path::PathBuf, }; +use super::Error; + /// Represents an item in a directory #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum DirItem { @@ -26,8 +28,8 @@ impl Iterator for DirItemIterator { } fn map_dir_item(item: std::io::Result) -> super::Result { - let item = item?; - let file_type = item.file_type()?; + let item = item.map_err(Error::Io)?; + let file_type = item.file_type().map_err(Error::Io)?; if file_type.is_dir() { Ok(DirItem::Dir(item.path())) } else if file_type.is_file() { diff --git a/src/fs/file.rs b/src/fs/file.rs index 957fe97..5b7c7b4 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -2,11 +2,12 @@ use crate::fs::Result; use super::{ - path::{FileG, PathReal}, + path::{FileMarker, PathReal}, reader::Reader, + Error, }; -impl<'base, 'path> PathReal<'base, 'path, FileG> { +impl<'base, 'path> PathReal<'base, 'path, FileMarker> { /// Returns a [Reader] for the file. /// /// ``` @@ -38,6 +39,6 @@ impl<'base, 'path> PathReal<'base, 'path, FileG> { /// ``` pub fn write>(&self, contents: C) -> Result<()> { self.check_error()?; - std::fs::write(self.as_pathbuf(), contents).map_err(Into::into) + std::fs::write(self.as_pathbuf(), contents).map_err(Error::Io) } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index fc187c6..4c27148 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,10 +1,31 @@ -/// 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`. +//! 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; +//! # use kxio::fs::FileSystem; +//! # use kxio::fs::PathReal; +//! # use kxio::fs::DirHandle; +//! # use kxio::fs::FileHandle; +//! # fn try_main() -> kxio::fs::Result<()> { +//! let fs = kxio::fs::temp()?; +//! let fs: FileSystem = kxio::fs::new(fs.base().to_path_buf()); +//! let dir_path: PathBuf = fs.base().join("foo"); +//! let dir: DirHandle = fs.dir(&dir_path); +//! dir.create()?; +//! let file_path = dir_path.join("bar.txt"); +//! let file: FileHandle = fs.file(&file_path); +//! file.write("new file contents")?; +//! let reader = file.reader()?; +//! assert_eq!(reader.to_string(), "new file contents"); +//! # Ok(()) +//! # } +//! ``` use std::path::PathBuf; mod dir; @@ -20,7 +41,7 @@ pub use dir_item::{DirItem, DirItemIterator}; pub use path::*; pub use reader::Reader; pub use result::{Error, Result}; -pub use system::FileSystem; +pub use system::{DirHandle, FileHandle, FileSystem, PathHandle}; /// Creates a new `FileSystem` for the path. /// diff --git a/src/fs/path.rs b/src/fs/path.rs index dc3459c..50169d5 100644 --- a/src/fs/path.rs +++ b/src/fs/path.rs @@ -6,19 +6,23 @@ use std::{ use crate::fs::{Error, Result}; +/// Marker trait for the type of [PathReal]. pub trait PathType {} +/// Path marker for the type of [PathReal]. #[derive(Clone, Debug)] -pub struct PathG; -impl PathType for PathG {} +pub struct PathMarker; +impl PathType for PathMarker {} +/// File marker for the type of [PathReal]. #[derive(Clone, Debug)] -pub struct FileG; -impl PathType for FileG {} +pub struct FileMarker; +impl PathType for FileMarker {} +/// Dir marker for the type of [PathReal]. #[derive(Clone, Debug)] -pub struct DirG; -impl PathType for DirG {} +pub struct DirMarker; +impl PathType for DirMarker {} /// Represents a path in the filesystem. /// @@ -63,7 +67,7 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> { } 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) { @@ -166,7 +170,7 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> { /// # Ok(()) /// # } /// ``` - pub fn as_dir(&self) -> Result>> { + pub fn as_dir(&self) -> Result>> { self.check_error()?; if self.as_pathbuf().is_dir() { Ok(Some(PathReal::new(self.base, self.path))) @@ -189,7 +193,7 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> { /// # Ok(()) /// # } /// ``` - pub fn as_file(&self) -> Result>> { + pub fn as_file(&self) -> Result>> { self.check_error()?; if self.as_pathbuf().is_file() { Ok(Some(PathReal::new(self.base, self.path))) @@ -198,8 +202,8 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> { } } } -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/reader.rs b/src/fs/reader.rs index a9b4d46..1b64784 100644 --- a/src/fs/reader.rs +++ b/src/fs/reader.rs @@ -3,13 +3,15 @@ use std::{fmt::Display, path::Path, str::Lines}; use crate::fs::Result; +use super::Error; + /// A reader for a file. pub struct Reader { contents: String, } impl Reader { pub(super) fn new(path: &Path) -> Result { - let contents = std::fs::read_to_string(path)?; + let contents = std::fs::read_to_string(path).map_err(Error::Io)?; Ok(Self { contents }) } diff --git a/src/fs/result.rs b/src/fs/result.rs index bd38f1d..9e924b5 100644 --- a/src/fs/result.rs +++ b/src/fs/result.rs @@ -1,14 +1,12 @@ // 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)] +#[derive(Debug, derive_more::Display)] pub enum Error { Io(std::io::Error), @@ -24,6 +22,11 @@ pub enum Error { NotADirectory { path: PathBuf, }, + + #[display("Path must be a file: {path:?}")] + NotAFile { + path: PathBuf, + }, } impl std::error::Error for Error {} impl Clone for Error { @@ -36,6 +39,7 @@ impl Clone for Error { path: path.clone(), }, Error::NotADirectory { path } => Error::NotADirectory { path: path.clone() }, + Error::NotAFile { path } => Error::NotAFile { path: path.clone() }, } } } diff --git a/src/fs/system.rs b/src/fs/system.rs index 1fbe9c8..7398d63 100644 --- a/src/fs/system.rs +++ b/src/fs/system.rs @@ -3,22 +3,34 @@ use std::path::{Path, PathBuf}; use crate::fs::{Error, Result}; -use super::path::{DirG, FileG, PathG, PathReal}; +use super::path::{DirMarker, FileMarker, PathMarker, PathReal}; /// Represents to base of a section of a file system. #[derive(Clone, Debug)] pub struct FileSystem { base: PathBuf, } + +/// Represents a directory path in the filesystem. +pub type DirHandle<'base, 'path> = PathReal<'base, 'path, DirMarker>; + +/// Represents a file path in the filesystem. +pub type FileHandle<'base, 'path> = PathReal<'base, 'path, FileMarker>; + +/// Represents a path in the filesystem. +pub type PathHandle<'base, 'path> = PathReal<'base, 'path, PathMarker>; + impl FileSystem { pub const fn new(base: PathBuf) -> Self { Self { base } } + /// Returns the base of the [FileSystem]. pub fn base(&self) -> &Path { &self.base } + /// Returns a [PathBuf] for the path. pub fn path_of(&self, path: PathBuf) -> Result { let path_of = self.base.as_path().join(path); self.validate(&path_of)?; @@ -26,7 +38,21 @@ impl FileSystem { } /// Access the path as a directory. - pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, DirG> { + /// + /// The path must exist and be a directory. + /// + /// If the path does not exist, or is not a directory, an error is returned. + /// + /// ``` + /// # fn try_main() -> kxio::fs::Result<()> { + /// let fs = kxio::fs::temp()?; + /// let path = fs.base().join("foo"); + /// let dir = fs.dir(&path); + /// dir.create()?; + /// # Ok(()) + /// # } + /// ``` + pub fn dir<'base, 'path>(&'base self, path: &'path Path) -> DirHandle<'base, 'path> { let mut dir = PathReal::new(self.base(), path); if dir.error.is_none() { @@ -46,11 +72,56 @@ impl FileSystem { dir } - pub fn file<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, FileG> { - PathReal::new(self.base(), path) + /// Access the path as a file. + /// + /// The path must exist and be a file. + /// + /// If the path does not exist, or is not a file, an error is returned. + /// + /// ``` + /// # fn try_main() -> kxio::fs::Result<()> { + /// let fs = kxio::fs::temp()?; + /// let path = fs.base().join("foo"); + /// let file = fs.file(&path); + /// file.write("new file contents")?; + /// # Ok(()) + /// # } + /// ``` + pub fn file<'base, 'path>(&'base self, path: &'path Path) -> FileHandle<'base, 'path> { + let mut file = PathReal::new(self.base(), path); + + if file.error.is_none() { + if let Ok(exists) = file.exists() { + if exists { + if let Ok(is_file) = file.is_file() { + if !is_file { + file.put(Error::NotAFile { + path: file.as_pathbuf(), + }) + } + } + } + } + } + + file } - pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathReal<'base, 'path, PathG> { + /// Access the path as a path. + /// + /// The path must exist. + /// + /// If the path does not exist, an error is returned. + /// + /// ``` + /// # fn try_main() -> kxio::fs::Result<()> { + /// let fs = kxio::fs::temp()?; + /// let path = fs.base().join("foo"); + /// let path_handle = fs.path(&path); + /// # Ok(()) + /// # } + /// ``` + pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathHandle<'base, 'path> { PathReal::new(self.base(), path) } @@ -71,7 +142,7 @@ impl FileSystem { let abs_path = if path.is_absolute() { path.to_path_buf() } else { - std::env::current_dir()?.join(path) + std::env::current_dir().map_err(Error::Io)?.join(path) } .clean(); Ok(abs_path) diff --git a/src/fs/temp.rs b/src/fs/temp.rs index a93e024..3d550f4 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::FileSystem; +use super::{Error, FileSystem}; #[derive(Clone, Debug)] pub struct TempFileSystem { @@ -11,7 +11,7 @@ pub struct TempFileSystem { } impl TempFileSystem { pub fn new() -> super::Result { - let temp_dir = tempfile::tempdir()?; + let temp_dir = tempfile::tempdir().map_err(Error::Io)?; let base = temp_dir.path().to_path_buf(); let temp_dir = Arc::new(Mutex::new(temp_dir)); let real = super::new(base);