1use std::path::{Path, PathBuf};
2
3use async_trait::async_trait;
4
5use crate::error::Result;
6
7#[derive(Debug, Clone)]
9pub struct FsDirEntry {
10 pub name: String,
11 pub path: PathBuf,
12 pub is_dir: bool,
13}
14
15#[async_trait]
18pub trait Fs: Send + Sync {
19 async fn read_to_string(&self, path: &Path) -> Result<String>;
21
22 async fn read_dir(&self, path: &Path) -> Result<Vec<FsDirEntry>>;
24
25 async fn is_file(&self, path: &Path) -> bool;
27
28 async fn is_dir(&self, path: &Path) -> bool;
30
31 async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
35}
36
37pub async fn exists(fs: &dyn Fs, path: &Path) -> bool {
41 fs.is_file(path).await || fs.is_dir(path).await
42}
43
44pub async fn walk_dir(fs: &dyn Fs, root: &Path) -> Result<Vec<PathBuf>> {
47 let mut result = Vec::new();
48 let mut stack = vec![root.to_path_buf()];
49
50 while let Some(dir) = stack.pop() {
51 if !fs.is_dir(&dir).await {
52 continue;
53 }
54 let entries = fs.read_dir(&dir).await?;
55 for entry in entries {
56 if entry.is_dir {
57 stack.push(entry.path.clone());
58 } else {
59 result.push(entry.path);
60 }
61 }
62 }
63
64 Ok(result)
65}
66
67#[cfg(feature = "tokio")]
73#[derive(Debug, Clone, Copy, Default)]
74pub struct OsFs;
75
76#[cfg(feature = "tokio")]
77#[async_trait]
78impl Fs for OsFs {
79 async fn read_to_string(&self, path: &Path) -> Result<String> {
80 Ok(tokio::fs::read_to_string(path).await?)
81 }
82
83 async fn read_dir(&self, path: &Path) -> Result<Vec<FsDirEntry>> {
84 let mut entries = Vec::new();
85 let mut rd = tokio::fs::read_dir(path).await?;
86 while let Some(entry) = rd.next_entry().await? {
87 let metadata = entry.metadata().await?;
88 entries.push(FsDirEntry {
89 name: entry.file_name().to_string_lossy().into_owned(),
90 path: entry.path(),
91 is_dir: metadata.is_dir(),
92 });
93 }
94 Ok(entries)
95 }
96
97 async fn is_file(&self, path: &Path) -> bool {
98 tokio::fs::metadata(path)
99 .await
100 .map(|m| m.is_file())
101 .unwrap_or(false)
102 }
103
104 async fn is_dir(&self, path: &Path) -> bool {
105 tokio::fs::metadata(path)
106 .await
107 .map(|m| m.is_dir())
108 .unwrap_or(false)
109 }
110
111 async fn canonicalize(&self, path: &Path) -> Result<PathBuf> {
112 Ok(tokio::fs::canonicalize(path).await?)
113 }
114}