mod app_fs;
pub use app_fs::*;
use std::path::PathBuf;
use async_trait::async_trait;
use tokio::io::AsyncWriteExt;
#[allow(dead_code)]
pub trait StorageFile: AsyncWriteExt + Unpin + Send + Sync {}
#[allow(unused)]
#[async_trait]
pub trait Storage: Sync + Send + Clone + 'static {
type File: StorageFile;
async fn exists(&self, path: &str) -> std::io::Result<bool>;
async fn read(&self, path: &str) -> std::io::Result<Option<Vec<u8>>>;
async fn write(&self, path: &str, content: impl AsRef<[u8]> + Send) -> std::io::Result<()>;
async fn create(&self, path: &str) -> std::io::Result<Self::File>;
async fn list(&self, path: &str) -> std::io::Result<Vec<String>>;
async fn mv(&self, from: &str, to: &str) -> std::io::Result<()>;
async fn rm(&self, path: &str) -> std::io::Result<bool>;
async fn rm_rf(&self, path: &str) -> std::io::Result<bool>;
fn path_buf(&self, path: &str) -> PathBuf {
PathBuf::from(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
pub async fn test_storage<S: Storage>(s: S) -> std::io::Result<()> {
s.write("foo/bar.txt", "test content").await?;
assert!(s.exists("foo/bar.txt").await?);
assert!(!s.exists("foo/NON_EXISTING.txt").await?);
assert!(!s.exists("NON_EXISTING/bar.txt").await?);
let content = s.read("foo/NON_EXISTING.txt").await?;
assert!(content.is_none());
let content = s.read("foo/bar.txt").await?;
assert!(content.is_some());
let content = content.unwrap();
assert_eq!(String::from_utf8_lossy(&content), "test content");
s.mv("foo/bar.txt", "bar/foo.txt").await?;
assert!(!s.exists("foo/bar.txt").await?);
assert!(s.exists("bar/foo.txt").await?);
let content = s.read("bar/foo.txt").await?;
assert!(content.is_some());
let content = content.unwrap();
assert_eq!(String::from_utf8_lossy(&content), "test content");
for i in 0..3 {
let mut file = s.create(&format!("list/{i}.txt")).await?;
file.write_all(format!("{i}").as_bytes()).await?;
}
let list = s.list("list/").await?;
assert_eq!(list, vec!["list/0.txt", "list/1.txt", "list/2.txt"]);
let list = s.list("list").await?;
assert_eq!(list, vec!["list/0.txt", "list/1.txt", "list/2.txt"]);
let list = s.list("NON_EXISTING/").await?;
assert_eq!(list, Vec::<String>::new());
s.write("to_remove/foo.txt", "foo").await?;
assert!(s.exists("to_remove/foo.txt").await?);
let is_removed = s.rm("to_remove/foo.txt").await?;
assert!(is_removed);
assert!(!s.exists("to_remove/foo.txt").await?);
let is_removed = s.rm("NON_EXISTING/foo.txt").await?;
assert!(!is_removed);
for i in 0..3 {
let mut file = s.create(&format!("to_remove/{i}.txt")).await?;
file.write_all(format!("{i}").as_bytes()).await?;
}
for i in 0..3 {
assert!(s.exists(&format!("to_remove/{i}.txt")).await?);
}
let is_removed = s.rm_rf("to_remove").await?;
assert!(is_removed);
for i in 0..3 {
assert!(!s.exists(&format!("to_remove/{i}.txt")).await?);
}
Ok(())
}
}