Compare commits
No commits in common. "d58e46d65e6ce81652179cbe0837179ebb2a494d" and "396d4b77bc79d4bd54c727ffcd2d77e4811b73d2" have entirely different histories.
d58e46d65e
...
396d4b77bc
5 changed files with 35 additions and 301 deletions
|
@ -9,7 +9,6 @@ repository = "https://git.kemitix.net/kemitix/kxio"
|
||||||
exclude = [".cargo_home"]
|
exclude = [".cargo_home"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["fs", "network"]
|
|
||||||
fs = []
|
fs = []
|
||||||
network = []
|
network = []
|
||||||
|
|
||||||
|
@ -31,7 +30,6 @@ thiserror = "1.0"
|
||||||
|
|
||||||
# fs
|
# fs
|
||||||
tempfile = "3.10"
|
tempfile = "3.10"
|
||||||
path-clean = "1.0"
|
|
||||||
|
|
||||||
# error handling
|
# error handling
|
||||||
derive_more = { version = "1.0.0-beta.6", features = ["from", "display"] }
|
derive_more = { version = "1.0.0-beta.6", features = ["from", "display"] }
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
use super::Result;
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
pub trait FileSystemLike {
|
|
||||||
fn base(&self) -> &Path;
|
|
||||||
|
|
||||||
fn dir_create(&self, path: &Path) -> Result<()>;
|
|
||||||
fn dir_create_all(&self, path: &Path) -> Result<()>;
|
|
||||||
|
|
||||||
fn file_read_to_string(&self, path: &Path) -> Result<String>;
|
|
||||||
fn file_write(&self, path: &Path, contents: &str) -> Result<()>;
|
|
||||||
|
|
||||||
fn path_exists(&self, path: &Path) -> Result<bool>;
|
|
||||||
fn path_is_dir(&self, path: &Path) -> Result<bool>;
|
|
||||||
fn path_is_file(&self, path: &Path) -> Result<bool>;
|
|
||||||
fn path_of(&self, path: PathBuf) -> Result<PathBuf>;
|
|
||||||
}
|
|
|
@ -1,10 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use derive_more::From;
|
use derive_more::From;
|
||||||
|
|
||||||
use crate::fs::like::FileSystemLike;
|
|
||||||
|
|
||||||
mod like;
|
|
||||||
mod real;
|
mod real;
|
||||||
mod temp;
|
mod temp;
|
||||||
|
|
||||||
|
@ -32,6 +29,7 @@ pub enum FileSystem {
|
||||||
Real(real::RealFileSystem),
|
Real(real::RealFileSystem),
|
||||||
Temp(temp::TempFileSystem),
|
Temp(temp::TempFileSystem),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for FileSystem {
|
impl std::ops::Deref for FileSystem {
|
||||||
type Target = dyn FileSystemLike;
|
type Target = dyn FileSystemLike;
|
||||||
|
|
||||||
|
@ -42,3 +40,11 @@ impl std::ops::Deref for FileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub trait FileSystemLike {
|
||||||
|
fn base(&self) -> &Path;
|
||||||
|
fn path_of(&self, path: PathBuf) -> Result<PathBuf>;
|
||||||
|
fn file_write(&self, path: &Path, contents: &str) -> Result<()>;
|
||||||
|
fn file_read_to_string(&self, path: &Path) -> Result<String>;
|
||||||
|
fn path_exists(&self, path: &Path) -> Result<bool>;
|
||||||
|
fn path_is_file(&self, path: &Path) -> Result<bool>;
|
||||||
|
}
|
||||||
|
|
|
@ -12,20 +12,10 @@ impl super::FileSystemLike for RealFileSystem {
|
||||||
fn base(&self) -> &Path {
|
fn base(&self) -> &Path {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
fn path_of(&self, path: PathBuf) -> super::Result<PathBuf> {
|
||||||
fn dir_create(&self, path: &Path) -> super::Result<()> {
|
let path_of = self.base.as_path().join(path);
|
||||||
self.validate(path)?;
|
self.validate(&path_of)?;
|
||||||
std::fs::create_dir(path).map_err(Into::into)
|
Ok(path_of)
|
||||||
}
|
|
||||||
|
|
||||||
fn dir_create_all(&self, path: &Path) -> super::Result<()> {
|
|
||||||
self.validate(path)?;
|
|
||||||
std::fs::create_dir_all(path).map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_read_to_string(&self, path: &Path) -> super::Result<String> {
|
|
||||||
self.validate(path)?;
|
|
||||||
std::fs::read_to_string(path).map_err(Into::into)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_write(&self, path: &Path, contents: &str) -> super::Result<()> {
|
fn file_write(&self, path: &Path, contents: &str) -> super::Result<()> {
|
||||||
|
@ -33,14 +23,9 @@ impl super::FileSystemLike for RealFileSystem {
|
||||||
std::fs::write(path, contents).map_err(Into::into)
|
std::fs::write(path, contents).map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_exists(&self, path: &Path) -> super::Result<bool> {
|
fn file_read_to_string(&self, path: &Path) -> super::Result<String> {
|
||||||
self.validate(path)?;
|
self.validate(path)?;
|
||||||
Ok(path.exists())
|
std::fs::read_to_string(path).map_err(Into::into)
|
||||||
}
|
|
||||||
|
|
||||||
fn path_is_dir(&self, path: &Path) -> super::Result<bool> {
|
|
||||||
self.validate(path)?;
|
|
||||||
Ok(path.is_dir())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_is_file(&self, path: &Path) -> super::Result<bool> {
|
fn path_is_file(&self, path: &Path) -> super::Result<bool> {
|
||||||
|
@ -48,34 +33,20 @@ impl super::FileSystemLike for RealFileSystem {
|
||||||
Ok(path.is_file())
|
Ok(path.is_file())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_of(&self, path: PathBuf) -> super::Result<PathBuf> {
|
fn path_exists(&self, path: &Path) -> super::Result<bool> {
|
||||||
let path_of = self.base.as_path().join(path);
|
self.validate(path)?;
|
||||||
self.validate(&path_of)?;
|
Ok(path.exists())
|
||||||
Ok(path_of)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealFileSystem {
|
impl RealFileSystem {
|
||||||
fn validate(&self, path: &Path) -> super::Result<()> {
|
fn validate(&self, path: &std::path::Path) -> super::Result<()> {
|
||||||
let path = self.clean_path(path)?;
|
|
||||||
if !path.starts_with(&self.base) {
|
if !path.starts_with(&self.base) {
|
||||||
return Err(super::Error::PathTraversal {
|
return Err(super::Error::PathTraversal {
|
||||||
base: self.base.clone(),
|
base: self.base.clone(),
|
||||||
path,
|
path: path.to_path_buf(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_path(&self, path: &Path) -> super::Result<PathBuf> {
|
|
||||||
// let path = path.as_ref();
|
|
||||||
use path_clean::PathClean;
|
|
||||||
let abs_path = if path.is_absolute() {
|
|
||||||
path.to_path_buf()
|
|
||||||
} else {
|
|
||||||
std::env::current_dir()?.join(path)
|
|
||||||
}
|
|
||||||
.clean();
|
|
||||||
Ok(abs_path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
251
src/tests/fs.rs
251
src/tests/fs.rs
|
@ -1,243 +1,20 @@
|
||||||
use assert2::let_assert;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::fs;
|
use crate::fs;
|
||||||
|
|
||||||
type TestResult = Result<(), fs::Error>;
|
type TestResult = Result<(), crate::fs::Error>;
|
||||||
|
|
||||||
mod path_of {
|
#[test]
|
||||||
use super::*;
|
fn write_read_file_exists() -> TestResult {
|
||||||
#[test]
|
let temp_fs = fs::temp()?;
|
||||||
fn validate_fails_on_path_traversal() -> TestResult {
|
let name: PathBuf = temp_fs.path_of("foo".into())?;
|
||||||
let fs = fs::temp()?;
|
temp_fs.file_write(&name, "content")?;
|
||||||
|
let c = temp_fs.file_read_to_string(&name)?;
|
||||||
|
assert_eq!(c, "content");
|
||||||
|
let exists = temp_fs.path_exists(&name)?;
|
||||||
|
assert!(exists);
|
||||||
|
let is_file = temp_fs.path_is_file(&name)?;
|
||||||
|
assert!(is_file);
|
||||||
|
|
||||||
let_assert!(Err(fs::Error::PathTraversal { base, path: _path }) = fs.path_of("..".into()));
|
Ok(())
|
||||||
assert_eq!(base, fs.base());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod file {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
/// Write to a file, read it, verify it exists, is a file and has the expected contents
|
|
||||||
fn write_read_file_exists() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let pathbuf = fs.base().join("foo");
|
|
||||||
|
|
||||||
let_assert!(Ok(_) = fs.file_write(&pathbuf, "content"));
|
|
||||||
let_assert!(
|
|
||||||
Ok(c) = fs.file_read_to_string(&pathbuf),
|
|
||||||
"file_read_to_string"
|
|
||||||
);
|
|
||||||
assert_eq!(c, "content");
|
|
||||||
|
|
||||||
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
|
|
||||||
assert!(exists);
|
|
||||||
|
|
||||||
let_assert!(Ok(is_file) = fs.path_is_file(&pathbuf));
|
|
||||||
assert!(is_file);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod dir_create {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn should_create_a_dir() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let pathbuf = fs.base().join("subdir");
|
|
||||||
|
|
||||||
let_assert!(Ok(_) = fs.dir_create(&pathbuf));
|
|
||||||
|
|
||||||
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
|
|
||||||
assert!(exists);
|
|
||||||
|
|
||||||
let_assert!(Ok(is_dir) = fs.path_is_dir(&pathbuf));
|
|
||||||
assert!(is_dir);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_fail_on_path_traversal() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("..").join("foo");
|
|
||||||
let_assert!(
|
|
||||||
Err(fs::Error::PathTraversal {
|
|
||||||
base: _base,
|
|
||||||
path: _path
|
|
||||||
}) = fs.dir_create(&path)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod dir_create_all {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_create_a_dir() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let pathbuf = fs.base().join("subdir").join("child");
|
|
||||||
|
|
||||||
let_assert!(Ok(_) = fs.dir_create_all(&pathbuf));
|
|
||||||
|
|
||||||
let_assert!(Ok(exists) = fs.path_exists(&pathbuf));
|
|
||||||
assert!(exists, "path exists");
|
|
||||||
|
|
||||||
let_assert!(Ok(is_dir) = fs.path_is_dir(&pathbuf));
|
|
||||||
assert!(is_dir, "path is a directory");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_fail_on_path_traversal() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("..").join("foo");
|
|
||||||
let_assert!(
|
|
||||||
Err(fs::Error::PathTraversal {
|
|
||||||
base: _base,
|
|
||||||
path: _path
|
|
||||||
}) = fs.dir_create_all(&path)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod path_exists {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn should_be_true_when_it_exists() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
let_assert!(Ok(_) = fs.file_write(&path, "bar"));
|
|
||||||
let_assert!(Ok(exists) = fs.path_exists(&path));
|
|
||||||
assert!(exists);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_be_false_when_it_does_not_exist() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
let_assert!(Ok(exists) = fs.path_exists(&path));
|
|
||||||
assert!(!exists);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_fail_on_path_traversal() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("..").join("foo");
|
|
||||||
let_assert!(
|
|
||||||
Err(fs::Error::PathTraversal {
|
|
||||||
base: _base,
|
|
||||||
path: _path
|
|
||||||
}) = fs.path_exists(&path)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod path_is_dir {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn should_be_true_when_is_a_dir() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
let_assert!(Ok(_) = fs.dir_create(&path));
|
|
||||||
let_assert!(Ok(is_dir) = fs.path_is_dir(&path));
|
|
||||||
assert!(is_dir);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_be_false_when_is_a_file() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
let_assert!(Ok(_) = fs.file_write(&path, "bar"));
|
|
||||||
let_assert!(Ok(is_dir) = fs.path_is_dir(&path));
|
|
||||||
assert!(!is_dir);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn should_be_false_when_is_a_link() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
// TODO create a link
|
|
||||||
// let_assert!(Ok(_) = fs.file_write(&path, "bar"));
|
|
||||||
let_assert!(Ok(is_dir) = fs.path_is_dir(&path));
|
|
||||||
assert!(!is_dir);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_fail_on_path_traversal() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("..").join("foo");
|
|
||||||
let_assert!(
|
|
||||||
Err(fs::Error::PathTraversal {
|
|
||||||
base: _base,
|
|
||||||
path: _path
|
|
||||||
}) = fs.path_is_dir(&path)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod path_is_file {
|
|
||||||
use super::*;
|
|
||||||
#[test]
|
|
||||||
fn should_be_true_when_is_a_file() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
let_assert!(Ok(_) = fs.file_write(&path, "bar"));
|
|
||||||
|
|
||||||
let_assert!(Ok(is_file) = fs.path_is_file(&path));
|
|
||||||
assert!(is_file);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_be_false_when_is_a_dir() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
let_assert!(Ok(_) = fs.dir_create(&path));
|
|
||||||
let_assert!(Ok(is_file) = fs.path_is_file(&path));
|
|
||||||
assert!(!is_file);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
#[ignore]
|
|
||||||
fn should_be_false_when_is_a_link() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("foo");
|
|
||||||
// TODO create a link
|
|
||||||
// let_assert!(Ok(_) = fs.file_write(&path, "bar"));
|
|
||||||
let_assert!(Ok(is_file) = fs.path_is_file(&path));
|
|
||||||
assert!(!is_file);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
#[test]
|
|
||||||
fn should_fail_on_path_traversal() -> TestResult {
|
|
||||||
let fs = fs::temp()?;
|
|
||||||
let path = fs.base().join("..").join("foo");
|
|
||||||
let_assert!(
|
|
||||||
Err(fs::Error::PathTraversal {
|
|
||||||
base: _base,
|
|
||||||
path: _path
|
|
||||||
}) = fs.path_is_file(&path)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue