From f825aad327f9e53e90af7bc1dc9f9684da6bcae2 Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Sun, 3 Nov 2024 14:18:32 +0000 Subject: [PATCH] feat(fs): add .path(path).rename() --- README.md | 2 +- src/fs/dir.rs | 14 +++++++++----- src/fs/path.rs | 34 +++++++++++++++++++++++++++++++++- src/fs/system.rs | 12 +++++++++--- tests/fs.rs | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 052e79c..6b09e01 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Provides injectable Filesystem and Network resources to make code more testable. - [x] `std::fs::remove_dir` - `dir(path).remove()` - Removes an empty directory. - [x] `std::fs::remove_dir_all` - `dir(path).remove_all()` - Removes a directory at this path, after removing all its contents. Use carefully! - [x] `std::fs::remove_file` - `file(path).remove()` - Removes a file from the filesystem. -- [ ] `std::fs::rename` - `path(path).rename()` - Rename a file or directory to a new name, replacing the original file if to already exists. +- [x] `std::fs::rename` - `path(path).rename()` - Rename a file or directory to a new name, replacing the original file if to already exists. - [ ] `std::fs::set_permissions` - `path(path).set_permissions()` - Changes the permissions found on a file or a directory. - [ ] `std::fs::symlink_metadata` - `link(path).metadata()` - Query the metadata about a file without following symlinks. - [x] `std::fs::write` - `file(path).write()` - Write a slice as the entire contents of a file. diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 4e9efc0..27402b9 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -1,7 +1,7 @@ // use crate::fs::{DirItem, DirItemIterator, Result}; -use super::{DirHandle, Error, FileHandle, PathHandle}; +use super::{DirHandle, Error, FileHandle, PathHandle, PathMarker}; impl<'base, 'path> DirHandle<'base, 'path> { /// Creates a new, empty directory at the path @@ -93,10 +93,12 @@ impl<'base, 'path> DirHandle<'base, 'path> { std::fs::remove_dir_all(self.as_pathbuf()).map_err(Error::Io) } } -impl<'base, 'path> TryFrom> for FileHandle<'base, 'path> { +impl<'base, 'path> TryFrom> for FileHandle<'base, 'path> { type Error = crate::fs::Error; - fn try_from(path: PathHandle<'base, 'path>) -> std::result::Result { + fn try_from( + path: PathHandle<'base, 'path, PathMarker>, + ) -> std::result::Result { match path.as_file() { Ok(Some(dir)) => Ok(dir.clone()), Ok(None) => Err(crate::fs::Error::NotADirectory { @@ -106,10 +108,12 @@ impl<'base, 'path> TryFrom> for FileHandle<'base, 'path } } } -impl<'base, 'path> TryFrom> for DirHandle<'base, 'path> { +impl<'base, 'path> TryFrom> for DirHandle<'base, 'path> { type Error = crate::fs::Error; - fn try_from(path: PathHandle<'base, 'path>) -> std::result::Result { + fn try_from( + path: PathHandle<'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/path.rs b/src/fs/path.rs index 2e4e529..0acbe73 100644 --- a/src/fs/path.rs +++ b/src/fs/path.rs @@ -203,9 +203,41 @@ impl<'base, 'path, T: PathType> PathReal<'base, 'path, T> { Ok(None) } } + + /// Renames a path. + /// + /// Wrapper for [std::fs::rename] + /// + /// ``` + /// # use kxio::fs::Result; + /// # fn main() -> Result<()> { + /// let fs = kxio::fs::temp()?; + /// let src_path = fs.base().join("foo"); + /// let src = fs.file(&src_path); + /// # src.write("bar")?; + /// let dst_path = fs.base().join("bar"); + /// let dst = fs.file(&dst_path); + /// src.rename(&dst)?; + /// # Ok(()) + /// # } + /// ``` + pub fn rename(&self, dest: &PathHandle<'base, 'path, T>) -> Result<()> { + self.check_error()?; + std::fs::rename(self.as_pathbuf(), dest.as_pathbuf()).map_err(Error::Io) + } } -impl<'base, 'path> From> for PathBuf { +impl<'base, 'path> From> for PathBuf { fn from(path: PathReal) -> Self { path.base.join(path.path) } } +impl<'base, 'path> From> for PathHandle<'base, 'path, PathMarker> { + fn from(dir: DirHandle<'base, 'path>) -> Self { + PathReal::new(dir.base, dir.path) + } +} +impl<'base, 'path> From> for PathHandle<'base, 'path, PathMarker> { + fn from(file: FileHandle<'base, 'path>) -> Self { + PathReal::new(file.base, file.path) + } +} diff --git a/src/fs/system.rs b/src/fs/system.rs index 7398d63..217cced 100644 --- a/src/fs/system.rs +++ b/src/fs/system.rs @@ -3,7 +3,10 @@ use std::path::{Path, PathBuf}; use crate::fs::{Error, Result}; -use super::path::{DirMarker, FileMarker, PathMarker, PathReal}; +use super::{ + path::{DirMarker, FileMarker, PathReal}, + PathMarker, +}; /// Represents to base of a section of a file system. #[derive(Clone, Debug)] @@ -18,7 +21,7 @@ pub type DirHandle<'base, 'path> = PathReal<'base, 'path, DirMarker>; pub type FileHandle<'base, 'path> = PathReal<'base, 'path, FileMarker>; /// Represents a path in the filesystem. -pub type PathHandle<'base, 'path> = PathReal<'base, 'path, PathMarker>; +pub type PathHandle<'base, 'path, T> = PathReal<'base, 'path, T>; impl FileSystem { pub const fn new(base: PathBuf) -> Self { @@ -121,7 +124,10 @@ impl FileSystem { /// # Ok(()) /// # } /// ``` - pub fn path<'base, 'path>(&'base self, path: &'path Path) -> PathHandle<'base, 'path> { + pub fn path<'base, 'path>( + &'base self, + path: &'path Path, + ) -> PathHandle<'base, 'path, PathMarker> { PathReal::new(self.base(), path) } diff --git a/tests/fs.rs b/tests/fs.rs index ef5eb7f..99504aa 100644 --- a/tests/fs.rs +++ b/tests/fs.rs @@ -448,6 +448,40 @@ mod path_is_file { Ok(()) } } +mod path_rename { + use super::*; + #[test] + fn should_rename_a_file() -> TestResult { + let fs = fs::temp().expect("temp fs"); + let src_path = fs.base().join("foo"); + let src = fs.file(&src_path); + src.write("bar").expect("write"); + let src_contents = src.reader().expect("reader").to_string(); + let dst_path = fs.base().join("bar"); + let dst = fs.file(&dst_path); + src.rename(&dst).expect("rename"); + let dst_contents = dst.reader().expect("reader").to_string(); + assert_eq!(src_contents, dst_contents); + let src_exists = src.exists().expect("exists"); + assert!(!src_exists); + + Ok(()) + } + + #[test] + fn should_fail_on_path_traversal() -> TestResult { + let fs = fs::temp().expect("temp fs"); + let src_path = fs.base().join("..").join("foo"); + let_assert!( + Err(fs::Error::PathTraversal { + base: _base, + path: _path + }) = fs.file(&src_path).rename(&fs.file(&src_path)) + ); + + Ok(()) + } +} mod copy { use super::*; #[test]