Compare commits
41 commits
Author | SHA1 | Date | |
---|---|---|---|
|
d90a1c42c8 | ||
|
9943a0fe4e | ||
|
a2e91a871b | ||
|
ca7be5c484 | ||
|
7335e78552 | ||
|
9c819fd856 | ||
|
3f805b3ad5 | ||
274f70e485 | |||
d0aee99c83 | |||
|
93c74487a1 | ||
|
f261474ed0 | ||
faa9f291d4 | |||
a84248eca3 | |||
595a376a62 | |||
6995dbedcb | |||
a83fe8b581 | |||
d58e46d65e | |||
b32c10f080 | |||
a9057a8831 | |||
12d55b98e5 | |||
a77ed422eb | |||
9c76ddc3e1 | |||
483d274e5b | |||
396d4b77bc | |||
68f419459f | |||
9339958996 | |||
8d506131ca | |||
2e3827e0e0 | |||
36070f59cc | |||
8d3f5df378 | |||
a917b21f50 | |||
a65689a51c | |||
6354681ac1 | |||
edb21d67df | |||
07eaa09d11 | |||
ff69ed2f23 | |||
939230038c | |||
fac1b38828 | |||
281271a82d | |||
72158c9c94 | |||
9c46af2e22 |
19 changed files with 704 additions and 67 deletions
6
.git-next.toml
Normal file
6
.git-next.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[branches]
|
||||||
|
main = "main"
|
||||||
|
next = "next"
|
||||||
|
dev = "dev"
|
||||||
|
|
||||||
|
[options]
|
|
@ -1,9 +1,8 @@
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
update-builder-image:
|
update-builder-image:
|
||||||
when:
|
when:
|
||||||
- event: cron
|
- event: cron
|
||||||
image: docker.io/woodpeckerci/plugin-docker-buildx:3.2
|
image: docker.io/woodpeckerci/plugin-docker-buildx:4.2
|
||||||
settings:
|
settings:
|
||||||
username: kemitix
|
username: kemitix
|
||||||
repo: git.kemitix.net/kemitix/kxio-builder
|
repo: git.kemitix.net/kemitix/kxio-builder
|
||||||
|
@ -13,74 +12,61 @@ steps:
|
||||||
registry: git.kemitix.net
|
registry: git.kemitix.net
|
||||||
password:
|
password:
|
||||||
from_secret: woodpecker-docker-push
|
from_secret: woodpecker-docker-push
|
||||||
|
|
||||||
todo_check:
|
todo_check:
|
||||||
# INFO: https://woodpecker-ci.org/plugins/TODO-Checker
|
# INFO: https://woodpecker-ci.org/plugins/TODO-Checker
|
||||||
image: codeberg.org/epsilon_02/todo-checker:1.1
|
image: codeberg.org/epsilon_02/todo-checker:1.1
|
||||||
when:
|
when:
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: [main, next]
|
||||||
settings:
|
settings:
|
||||||
# kxio-woodpecker-todo-checker - read:issue
|
# kxio-woodpecker-todo-checker - read:issue
|
||||||
repository_token: '4acf14f93747e044aa2d1397367741b53f3d4f8f'
|
repository_token: "4acf14f93747e044aa2d1397367741b53f3d4f8f"
|
||||||
prefix_regex: "(#|//) (TODO|FIXME): "
|
prefix_regex: "(#|//) (TODO|FIXME): "
|
||||||
debug: false
|
debug: false
|
||||||
|
|
||||||
build:
|
lint_and_build:
|
||||||
when:
|
when:
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: [main, next]
|
||||||
- event: tag
|
- event: tag
|
||||||
ref: refs/tags/v*
|
|
||||||
image: git.kemitix.net/kemitix/kxio-builder:latest
|
|
||||||
environment:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
lint:
|
|
||||||
when:
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
- event: tag
|
|
||||||
ref: refs/tags/v*
|
|
||||||
image: git.kemitix.net/kemitix/kxio-builder:latest
|
image: git.kemitix.net/kemitix/kxio-builder:latest
|
||||||
environment:
|
environment:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
commands:
|
commands:
|
||||||
- ls -l /usr/local/cargo/bin/
|
- ls -l /usr/local/cargo/bin/
|
||||||
- cargo fmt --all -- --check
|
- cargo fmt --all -- --check
|
||||||
- cargo clippy -- -D warnings -W clippy::nursery -W clippy::unwrap_used -W clippy::expect_used
|
- cargo clippy --features "fs,network" -- -D warnings -W clippy::nursery -W clippy::unwrap_used -W clippy::expect_used
|
||||||
|
- cargo build --features "fs,network"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
when:
|
when:
|
||||||
- event: push
|
- event: push
|
||||||
branch: main
|
branch: [main, next]
|
||||||
- event: tag
|
- event: tag
|
||||||
ref: refs/tags/v*
|
|
||||||
image: git.kemitix.net/kemitix/kxio-builder:latest
|
image: git.kemitix.net/kemitix/kxio-builder:latest
|
||||||
environment:
|
environment:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
commands:
|
commands:
|
||||||
- cargo test
|
- cargo test --features "fs,network"
|
||||||
|
|
||||||
|
publish_to_crates_io:
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
image: docker.io/rust:1.81
|
||||||
|
commands:
|
||||||
|
- cargo login "$CARGO_REGISTRY_TOKEN"
|
||||||
|
- cargo publish --registry crates-io --no-verify
|
||||||
|
secrets: [cargo_registry_token]
|
||||||
|
|
||||||
publish_to_forgejo:
|
publish_to_forgejo:
|
||||||
when:
|
when:
|
||||||
- event: tag
|
- event: tag
|
||||||
ref: refs/tags/v*
|
|
||||||
# INFO: https://woodpecker-ci.org/plugins/Gitea%20Release
|
# INFO: https://woodpecker-ci.org/plugins/Gitea%20Release
|
||||||
image: docker.io/woodpeckerci/plugin-gitea-release:0.3
|
image: docker.io/woodpeckerci/plugin-gitea-release:latest
|
||||||
settings:
|
settings:
|
||||||
base_url: https://git.kemitix.net
|
base_url: https://git.kemitix.net
|
||||||
api_key:
|
api_key:
|
||||||
from_secret: FORGEJO_RELEASE_PLUGIN
|
from_secret: FORGEJO_RELEASE_PLUGIN
|
||||||
target: main
|
target: main
|
||||||
prerelease: true
|
prerelease: false
|
||||||
|
|
||||||
publish_to_crates_io:
|
|
||||||
when:
|
|
||||||
- event: tag
|
|
||||||
ref: refs/tags/v*
|
|
||||||
image: docker.io/rust:1.77
|
|
||||||
commands:
|
|
||||||
- cargo login "$CARGO_REGISTRY_TOKEN"
|
|
||||||
- cargo publish --registry crates-io --no-verify
|
|
||||||
secrets: [cargo_registry_token]
|
|
||||||
|
|
17
Cargo.toml
17
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "kxio"
|
name = "kxio"
|
||||||
version = "0.1.0"
|
version = "1.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Paul Campbell <pcampbell@kemitix.net>"]
|
authors = ["Paul Campbell <pcampbell@kemitix.net>"]
|
||||||
description = "Provides injectable Filesystem and Network resources to make code more testable"
|
description = "Provides injectable Filesystem and Network resources to make code more testable"
|
||||||
|
@ -8,6 +8,11 @@ license = "MIT"
|
||||||
repository = "https://git.kemitix.net/kemitix/kxio"
|
repository = "https://git.kemitix.net/kemitix/kxio"
|
||||||
exclude = [".cargo_home"]
|
exclude = [".cargo_home"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["fs", "network"]
|
||||||
|
fs = []
|
||||||
|
network = []
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -18,7 +23,7 @@ tracing = "0.1"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
http = "1.1"
|
http = "1.1"
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
secrecy = "0.8"
|
secrecy = "0.10"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde-xml-rs = "0.6"
|
serde-xml-rs = "0.6"
|
||||||
|
@ -26,6 +31,14 @@ thiserror = "1.0"
|
||||||
|
|
||||||
# fs
|
# fs
|
||||||
tempfile = "3.10"
|
tempfile = "3.10"
|
||||||
|
path-clean = "1.0"
|
||||||
|
|
||||||
|
# boilerplate
|
||||||
|
derive_more = { version = "1.0.0-beta", features = [
|
||||||
|
"from",
|
||||||
|
"display",
|
||||||
|
"constructor",
|
||||||
|
] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# testing
|
# testing
|
||||||
|
|
35
README.md
35
README.md
|
@ -3,3 +3,38 @@
|
||||||
[![status-badge](https://ci.kemitix.net/api/badges/53/status.svg)](https://ci.kemitix.net/repos/53)
|
[![status-badge](https://ci.kemitix.net/api/badges/53/status.svg)](https://ci.kemitix.net/repos/53)
|
||||||
|
|
||||||
Provides injectable Filesystem and Network resources to make code more testable.
|
Provides injectable Filesystem and Network resources to make code more testable.
|
||||||
|
|
||||||
|
### FileSystem
|
||||||
|
|
||||||
|
There are two FileSystem implementation: [filesystem] and [fs].
|
||||||
|
|
||||||
|
- [filesystem] is the legacy implementation and will be removed in a future version.
|
||||||
|
- [fs] is the current version and is intended to stand-in for and extend the [std::fs] module from the Standard Library.
|
||||||
|
|
||||||
|
#### std::fs alternatives
|
||||||
|
|
||||||
|
| To Do | [std::fs] | [kxio::fs::FileSystem] | |
|
||||||
|
| ----- | ---------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| [ ] | canonicalize | path_canonicalize | Returns the canonical, absolute form of a path with all intermediate components normalized and symbolic links resolved. |
|
||||||
|
| [ ] | copy | file_copy | Copies the contents of one file to another. This function will also copy the permission bits of the original file to the destination file. |
|
||||||
|
| [ ] | create_dir | dir_create | Creates a new, empty directory at the provided path |
|
||||||
|
| [ ] | create_dir_all | dir_create_all | Recursively create a directory and all of its parent components if they are missing. |
|
||||||
|
| [ ] | hard_link | link_create | Creates a new hard link on the filesystem. |
|
||||||
|
| [ ] | metadata | path_metadata | Given a path, query the file system to get information about a file, directory, etc. |
|
||||||
|
| [ ] | read | file_read | Read the entire contents of a file into a bytes vector. |
|
||||||
|
| [ ] | read_dir | dir_read | Returns an iterator over the entries within a directory. |
|
||||||
|
| [ ] | read_link | link_read | Reads a symbolic link, returning the file that the link points to. |
|
||||||
|
| [x] | read_to_string | file_read_to_string | Read the entire contents of a file into a string. |
|
||||||
|
| [ ] | remove_dir | dir_remove | Removes an empty directory. |
|
||||||
|
| [ ] | remove_dir_all | dir_remove_all | Removes a directory at this path, after removing all its contents. Use carefully! |
|
||||||
|
| [ ] | remove_file | file_remove | Removes a file from the filesystem. |
|
||||||
|
| [ ] | rename | path_rename | Rename a file or directory to a new name, replacing the original file if to already exists. |
|
||||||
|
| [ ] | set_permissions | path_set_permissions | Changes the permissions found on a file or a directory. |
|
||||||
|
| [ ] | symlink_metadata | link_metadata | Query the metadata about a file without following symlinks. |
|
||||||
|
| [x] | write | file_write | Write a slice as the entire contents of a file. |
|
||||||
|
|
||||||
|
### Network
|
||||||
|
|
||||||
|
The entire [network] module needs to be completly rewritten
|
||||||
|
It's use is strongly discouraged.
|
||||||
|
A new [net] module will likely be its replacement.
|
||||||
|
|
5
justfile
5
justfile
|
@ -1,3 +1,8 @@
|
||||||
install-hooks:
|
install-hooks:
|
||||||
@echo "Installing git hooks"
|
@echo "Installing git hooks"
|
||||||
git config core.hooksPath .git-hooks
|
git config core.hooksPath .git-hooks
|
||||||
|
|
||||||
|
validate-dev-branch:
|
||||||
|
git rebase -i origin/main -x 'cargo build --features "fs,network"'
|
||||||
|
git rebase -i origin/main -x 'cargo test --features "fs,network"'
|
||||||
|
git rebase -i origin/main -x 'cargo clippy --features "fs,network" -- -D warnings -W clippy::nursery -W clippy::unwrap_used -W clippy::expect_used'
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["config:recommended"],
|
||||||
"config:recommended"
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchManagers": ["cargo"],
|
||||||
|
"rangeStrategy": "replace"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
#![allow(unused)]
|
#![allow(deprecated)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
|
@ -7,24 +7,34 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
use tracing::info;
|
use tracing::{debug, info};
|
||||||
|
|
||||||
|
pub fn real(cwd: Option<PathBuf>) -> FileSystem {
|
||||||
|
let cwd = cwd.unwrap_or_default();
|
||||||
|
FileSystem::Real(RealFileSystem::new(cwd))
|
||||||
|
}
|
||||||
|
pub fn temp() -> std::io::Result<FileSystem> {
|
||||||
|
TempFileSystem::new().map(FileSystem::Temp)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
#[deprecated(since = "1.1.0", note = "Use [kxio::fs::FileSystem] instead")]
|
||||||
pub enum FileSystem {
|
pub enum FileSystem {
|
||||||
Real(RealFileSystemEnv),
|
Real(RealFileSystem),
|
||||||
Temp(TempFileSystemEnv),
|
Temp(TempFileSystem),
|
||||||
}
|
}
|
||||||
impl FileSystem {
|
impl FileSystem {
|
||||||
|
#[deprecated(since = "1.1.0", note = "Use [kxio::filesystem::real()] instead")]
|
||||||
pub fn new_real(cwd: Option<PathBuf>) -> Self {
|
pub fn new_real(cwd: Option<PathBuf>) -> Self {
|
||||||
let cwd = cwd.unwrap_or_default();
|
real(cwd)
|
||||||
Self::Real(RealFileSystemEnv::new(cwd))
|
|
||||||
}
|
}
|
||||||
|
#[deprecated(since = "1.1.0", note = "Use [kxio::filesystem::temp()] instead")]
|
||||||
pub fn new_temp() -> std::io::Result<Self> {
|
pub fn new_temp() -> std::io::Result<Self> {
|
||||||
TempFileSystemEnv::new().map(Self::Temp)
|
temp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Deref for FileSystem {
|
impl Deref for FileSystem {
|
||||||
type Target = dyn FileSystemEnv;
|
type Target = dyn FileSystemLike;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
match self {
|
match self {
|
||||||
|
@ -34,7 +44,7 @@ impl Deref for FileSystem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileSystemEnv: Sync + Send + std::fmt::Debug {
|
pub trait FileSystemLike: Sync + Send + std::fmt::Debug {
|
||||||
fn cwd(&self) -> &PathBuf;
|
fn cwd(&self) -> &PathBuf;
|
||||||
|
|
||||||
fn in_cwd(&self, name: &str) -> PathBuf {
|
fn in_cwd(&self, name: &str) -> PathBuf {
|
||||||
|
@ -46,7 +56,7 @@ pub trait FileSystemEnv: Sync + Send + std::fmt::Debug {
|
||||||
use std::io::{LineWriter, Write};
|
use std::io::{LineWriter, Write};
|
||||||
|
|
||||||
let path = self.in_cwd(file_name);
|
let path = self.in_cwd(file_name);
|
||||||
info!("writing to {:?}", path);
|
debug!("writing to {:?}", path);
|
||||||
let file = File::create(path.clone())?;
|
let file = File::create(path.clone())?;
|
||||||
let mut file = LineWriter::new(file);
|
let mut file = LineWriter::new(file);
|
||||||
file.write_all(content.as_bytes())?;
|
file.write_all(content.as_bytes())?;
|
||||||
|
@ -63,7 +73,7 @@ pub trait FileSystemEnv: Sync + Send + std::fmt::Debug {
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
let path = self.in_cwd(file_name);
|
let path = self.in_cwd(file_name);
|
||||||
info!("reading from {:?}", path);
|
debug!("reading from {:?}", path);
|
||||||
let mut file = File::open(path)?;
|
let mut file = File::open(path)?;
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
file.read_to_string(&mut content)?;
|
file.read_to_string(&mut content)?;
|
||||||
|
@ -72,41 +82,47 @@ pub trait FileSystemEnv: Sync + Send + std::fmt::Debug {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct RealFileSystemEnv {
|
pub struct RealFileSystem {
|
||||||
cwd: PathBuf,
|
cwd: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TempFileSystemEnv {
|
pub struct TempFileSystem {
|
||||||
cwd: PathBuf,
|
cwd: PathBuf,
|
||||||
temp_dir: Arc<Mutex<TempDir>>,
|
|
||||||
|
// Handle to the temporary directory
|
||||||
|
// When this handle is dropped the directory is deleted
|
||||||
|
_temp_dir: Arc<Mutex<TempDir>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystemEnv for TempFileSystemEnv {
|
impl FileSystemLike for TempFileSystem {
|
||||||
fn cwd(&self) -> &PathBuf {
|
fn cwd(&self) -> &PathBuf {
|
||||||
&self.cwd
|
&self.cwd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSystemEnv for RealFileSystemEnv {
|
impl FileSystemLike for RealFileSystem {
|
||||||
fn cwd(&self) -> &PathBuf {
|
fn cwd(&self) -> &PathBuf {
|
||||||
&self.cwd
|
&self.cwd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealFileSystemEnv {
|
impl RealFileSystem {
|
||||||
const fn new(cwd: PathBuf) -> Self {
|
const fn new(cwd: PathBuf) -> Self {
|
||||||
Self { cwd }
|
Self { cwd }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TempFileSystemEnv {
|
impl TempFileSystem {
|
||||||
fn new() -> std::io::Result<Self> {
|
fn new() -> std::io::Result<Self> {
|
||||||
let temp_dir = tempdir()?;
|
let temp_dir = tempdir()?;
|
||||||
info!("temp dir: {:?}", temp_dir.path());
|
info!("temp dir: {:?}", temp_dir.path());
|
||||||
let cwd = temp_dir.path().to_path_buf();
|
let cwd = temp_dir.path().to_path_buf();
|
||||||
let temp_dir = Arc::new(Mutex::new(temp_dir));
|
let temp_dir = Arc::new(Mutex::new(temp_dir));
|
||||||
Ok(Self { cwd, temp_dir })
|
Ok(Self {
|
||||||
|
cwd,
|
||||||
|
_temp_dir: temp_dir,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,13 +136,13 @@ mod tests {
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn test_cwd() {
|
fn test_cwd() {
|
||||||
let cwd = PathBuf::from("/tmp");
|
let cwd = PathBuf::from("/tmp");
|
||||||
let env = RealFileSystemEnv::new(cwd.clone());
|
let env = RealFileSystem::new(cwd.clone());
|
||||||
assert_eq!(env.cwd(), &cwd);
|
assert_eq!(env.cwd(), &cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn test_create_on_temp_fs() -> std::io::Result<()> {
|
fn test_create_on_temp_fs() -> std::io::Result<()> {
|
||||||
let env = TempFileSystemEnv::new()?;
|
let env = TempFileSystem::new()?;
|
||||||
assert!(env.cwd().exists());
|
assert!(env.cwd().exists());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -134,13 +150,13 @@ mod tests {
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn test_create_on_real_fs() {
|
fn test_create_on_real_fs() {
|
||||||
let cwd = PathBuf::from("/tmp");
|
let cwd = PathBuf::from("/tmp");
|
||||||
let env = RealFileSystemEnv::new(cwd.clone());
|
let env = RealFileSystem::new(cwd.clone());
|
||||||
assert_eq!(env.cwd(), &cwd);
|
assert_eq!(env.cwd(), &cwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
fn test_write_and_read_file() -> std::io::Result<()> {
|
fn test_write_and_read_file() -> std::io::Result<()> {
|
||||||
let env = TempFileSystemEnv::new()?;
|
let env = TempFileSystem::new()?;
|
||||||
let file_name = "test.txt";
|
let file_name = "test.txt";
|
||||||
let content = "Hello, World!";
|
let content = "Hello, World!";
|
||||||
let path = env.write_file(file_name, content)?;
|
let path = env.write_file(file_name, content)?;
|
||||||
|
|
37
src/fs/dir_item.rs
Normal file
37
src/fs/dir_item.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use std::{
|
||||||
|
fs::{DirEntry, ReadDir},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum DirItem {
|
||||||
|
File(PathBuf),
|
||||||
|
Dir(PathBuf),
|
||||||
|
SymLink(PathBuf),
|
||||||
|
Fifo(PathBuf),
|
||||||
|
Unsupported(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Constructor)]
|
||||||
|
pub struct DirItemIterator(ReadDir);
|
||||||
|
impl Iterator for DirItemIterator {
|
||||||
|
type Item = super::Result<DirItem>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.0.next().map(map_dir_item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_dir_item(item: std::io::Result<DirEntry>) -> super::Result<DirItem> {
|
||||||
|
let item = item?;
|
||||||
|
let file_type = item.file_type()?;
|
||||||
|
if file_type.is_dir() {
|
||||||
|
Ok(DirItem::Dir(item.path()))
|
||||||
|
} else if file_type.is_file() {
|
||||||
|
Ok(DirItem::File(item.path()))
|
||||||
|
} else if file_type.is_symlink() {
|
||||||
|
Ok(DirItem::SymLink(item.path()))
|
||||||
|
} else {
|
||||||
|
Ok(DirItem::Unsupported(item.path()))
|
||||||
|
}
|
||||||
|
}
|
23
src/fs/like.rs
Normal file
23
src/fs/like.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::fs::DirItem;
|
||||||
|
|
||||||
|
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<()>;
|
||||||
|
|
||||||
|
/// Reads the items in a directory and returns them as an iterator.
|
||||||
|
fn dir_read(&self, path: &Path) -> Result<Box<dyn Iterator<Item = Result<DirItem>>>>;
|
||||||
|
|
||||||
|
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>;
|
||||||
|
}
|
56
src/fs/mod.rs
Normal file
56
src/fs/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use derive_more::From;
|
||||||
|
|
||||||
|
use crate::fs::like::FileSystemLike;
|
||||||
|
|
||||||
|
mod like;
|
||||||
|
mod real;
|
||||||
|
mod temp;
|
||||||
|
|
||||||
|
mod dir_item;
|
||||||
|
pub use dir_item::DirItem;
|
||||||
|
pub use dir_item::DirItemIterator;
|
||||||
|
|
||||||
|
#[derive(Debug, From, derive_more::Display)]
|
||||||
|
pub enum Error {
|
||||||
|
Io(std::io::Error),
|
||||||
|
|
||||||
|
#[display("Path access attempted outside of base ({base:?}): {path:?}")]
|
||||||
|
PathTraversal {
|
||||||
|
base: PathBuf,
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[display("Path must be a directory: {path:?}")]
|
||||||
|
NotADirectory {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
impl std::error::Error for Error {}
|
||||||
|
|
||||||
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
pub const fn new(base: PathBuf) -> FileSystem {
|
||||||
|
FileSystem::Real(real::new(base))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn temp() -> Result<FileSystem> {
|
||||||
|
temp::new().map(FileSystem::Temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum FileSystem {
|
||||||
|
Real(real::RealFileSystem),
|
||||||
|
Temp(temp::TempFileSystem),
|
||||||
|
}
|
||||||
|
impl std::ops::Deref for FileSystem {
|
||||||
|
type Target = dyn FileSystemLike;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
Self::Real(fs) => fs,
|
||||||
|
Self::Temp(fs) => fs.deref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
src/fs/real.rs
Normal file
98
src/fs/real.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::fs::{DirItem, DirItemIterator};
|
||||||
|
|
||||||
|
pub const fn new(base: PathBuf) -> RealFileSystem {
|
||||||
|
RealFileSystem { base }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RealFileSystem {
|
||||||
|
base: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::FileSystemLike for RealFileSystem {
|
||||||
|
fn base(&self) -> &Path {
|
||||||
|
&self.base
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dir_create(&self, path: &Path) -> super::Result<()> {
|
||||||
|
self.validate(path)?;
|
||||||
|
std::fs::create_dir(path).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dir_create_all(&self, path: &Path) -> super::Result<()> {
|
||||||
|
self.validate(path)?;
|
||||||
|
std::fs::create_dir_all(path).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dir_read(
|
||||||
|
&self,
|
||||||
|
path: &Path,
|
||||||
|
) -> super::Result<Box<dyn Iterator<Item = super::Result<DirItem>>>> {
|
||||||
|
self.validate(path)?;
|
||||||
|
if !self.path_is_dir(path)? {
|
||||||
|
return Err(super::Error::NotADirectory {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let read_dir = std::fs::read_dir(path)?;
|
||||||
|
Ok(Box::new(DirItemIterator::new(read_dir)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
self.validate(path)?;
|
||||||
|
std::fs::write(path, contents).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_exists(&self, path: &Path) -> super::Result<bool> {
|
||||||
|
self.validate(path)?;
|
||||||
|
Ok(path.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
self.validate(path)?;
|
||||||
|
Ok(path.is_file())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_of(&self, path: PathBuf) -> super::Result<PathBuf> {
|
||||||
|
let path_of = self.base.as_path().join(path);
|
||||||
|
self.validate(&path_of)?;
|
||||||
|
Ok(path_of)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealFileSystem {
|
||||||
|
fn validate(&self, path: &Path) -> super::Result<()> {
|
||||||
|
let path = self.clean_path(path)?;
|
||||||
|
if !path.starts_with(&self.base) {
|
||||||
|
return Err(super::Error::PathTraversal {
|
||||||
|
base: self.base.clone(),
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
29
src/fs/temp.rs
Normal file
29
src/fs/temp.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
pub(super) fn new() -> super::Result<TempFileSystem> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let base = temp_dir.path().to_path_buf();
|
||||||
|
let temp_dir = Arc::new(Mutex::new(temp_dir));
|
||||||
|
let real = super::real::new(base);
|
||||||
|
|
||||||
|
Ok(TempFileSystem {
|
||||||
|
real,
|
||||||
|
_temp_dir: temp_dir,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TempFileSystem {
|
||||||
|
real: super::real::RealFileSystem,
|
||||||
|
_temp_dir: Arc<Mutex<TempDir>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for TempFileSystem {
|
||||||
|
type Target = dyn super::FileSystemLike;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.real
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,11 @@
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
pub mod filesystem;
|
pub mod filesystem;
|
||||||
|
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
pub mod fs;
|
||||||
|
|
||||||
|
#[cfg(feature = "network")]
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl NetRequest {
|
||||||
.url(net_url)
|
.url(net_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn new(
|
pub const fn new(
|
||||||
method: RequestMethod,
|
method: RequestMethod,
|
||||||
url: NetUrl,
|
url: NetUrl,
|
||||||
headers: NetRequestHeaders,
|
headers: NetRequestHeaders,
|
||||||
|
|
|
@ -36,3 +36,9 @@ impl<T> NetResponse<T> {
|
||||||
self.response_body
|
self.response_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NetResponse<()>> for () {
|
||||||
|
fn from(_value: NetResponse<()>) -> Self {
|
||||||
|
// ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -169,7 +169,7 @@ impl NetworkTrait for RealNetwork {
|
||||||
&self,
|
&self,
|
||||||
net_request: NetRequest,
|
net_request: NetRequest,
|
||||||
) -> Result<NetResponse<T>, NetworkError> {
|
) -> Result<NetResponse<T>, NetworkError> {
|
||||||
tracing::info!("RealNetworkEnv::get({:?})", net_request);
|
tracing::debug!("RealNetworkEnv::get({:?})", net_request);
|
||||||
let url = net_request.url();
|
let url = net_request.url();
|
||||||
let auth = net_request.auth();
|
let auth = net_request.auth();
|
||||||
let response_type = net_request.response_type();
|
let response_type = net_request.response_type();
|
||||||
|
|
12
src/tests/filesystem.rs
Normal file
12
src/tests/filesystem.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
type TestResult = Result<(), Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn write_read_file_exists() -> TestResult {
|
||||||
|
let fs = crate::filesystem::temp()?;
|
||||||
|
let pathbuf = fs.write_file("foo", "content")?;
|
||||||
|
let c = fs.read_file("foo")?;
|
||||||
|
assert_eq!(c, "content");
|
||||||
|
assert!(fs.file_exists(&pathbuf));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
294
src/tests/fs.rs
Normal file
294
src/tests/fs.rs
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
use assert2::let_assert;
|
||||||
|
|
||||||
|
use crate::fs;
|
||||||
|
|
||||||
|
type TestResult = Result<(), fs::Error>;
|
||||||
|
|
||||||
|
mod path_of {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn validate_fails_on_path_traversal() -> TestResult {
|
||||||
|
let fs = fs::temp()?;
|
||||||
|
|
||||||
|
let_assert!(Err(fs::Error::PathTraversal { base, path: _path }) = fs.path_of("..".into()));
|
||||||
|
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 dir_dir_read {
|
||||||
|
use crate::fs::DirItem;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_return_dir_items() -> TestResult {
|
||||||
|
let fs = fs::temp()?;
|
||||||
|
let file1 = fs.base().join("file-1");
|
||||||
|
let dir = fs.base().join("dir");
|
||||||
|
let file2 = dir.join("file-2");
|
||||||
|
fs.file_write(&file1, "file-1")?;
|
||||||
|
fs.dir_create(&dir)?;
|
||||||
|
fs.file_write(&file2, "file-2")?;
|
||||||
|
|
||||||
|
let items = fs
|
||||||
|
.dir_read(fs.base())?
|
||||||
|
.filter_map(|i| i.ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(items.len(), 2);
|
||||||
|
assert!(items.contains(&DirItem::File(file1)));
|
||||||
|
assert!(items.contains(&DirItem::Dir(dir)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn should_fail_on_not_a_dir() -> TestResult {
|
||||||
|
let fs = fs::temp()?;
|
||||||
|
let path = fs.base().join("file");
|
||||||
|
fs.file_write(&path, "contents")?;
|
||||||
|
|
||||||
|
let_assert!(Err(fs::Error::NotADirectory { path: _path }) = fs.dir_read(&path));
|
||||||
|
|
||||||
|
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_read(&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(())
|
||||||
|
}
|
||||||
|
}
|
8
src/tests/mod.rs
Normal file
8
src/tests/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
pub mod filesystem;
|
||||||
|
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
pub mod fs;
|
||||||
|
|
||||||
|
// #[cfg(feature = "network")]
|
||||||
|
// pub mod network;
|
Loading…
Reference in a new issue