monofs/filesystem/dir/
find.rs1use std::fmt::Debug;
2
3use monoutils_store::IpldStore;
4use typed_path::{Utf8UnixComponent, Utf8UnixPath};
5
6use crate::filesystem::{entity::Entity, FsError, FsResult};
7
8use super::{Dir, Utf8UnixPathSegment};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
16pub enum FindResult<T> {
17 Found {
19 dir: T,
21 },
22
23 NotFound {
25 dir: T,
27
28 depth: usize,
30 },
31
32 NotADir {
34 depth: usize,
36 },
37}
38
39pub type FindResultDir<'a, S> = FindResult<&'a Dir<S>>;
41
42pub type FindResultDirMut<'a, S> = FindResult<&'a mut Dir<S>>;
44
45pub async fn find_dir<S>(mut dir: &Dir<S>, path: impl AsRef<str>) -> FsResult<FindResultDir<S>>
80where
81 S: IpldStore + Send + Sync,
82{
83 let path = Utf8UnixPath::new(path.as_ref());
84
85 let components = path
87 .components()
88 .map(|ref c| match c {
89 Utf8UnixComponent::RootDir => Err(FsError::InvalidSearchPath(path.to_string())),
90 Utf8UnixComponent::CurDir => Err(FsError::InvalidSearchPath(path.to_string())),
91 Utf8UnixComponent::ParentDir => Err(FsError::InvalidSearchPath(path.to_string())),
92 _ => Ok(Utf8UnixPathSegment::try_from(c)?),
93 })
94 .collect::<Result<Vec<_>, _>>()?;
95
96 for (depth, segment) in components.iter().enumerate() {
98 match dir.get_entity(segment).await? {
99 Some(Entity::Dir(d)) => {
100 dir = d;
101 }
102 Some(Entity::SoftLink(_)) => {
103 return Err(FsError::SoftLinkNotSupportedYet(components.clone()));
105 }
106 Some(_) => {
107 return Ok(FindResult::NotADir { depth });
110 }
111 None => {
112 return Ok(FindResult::NotFound { dir, depth });
115 }
116 }
117 }
118
119 Ok(FindResult::Found { dir })
120}
121
122pub async fn find_dir_mut<S>(
153 mut dir: &mut Dir<S>,
154 path: impl AsRef<str>,
155) -> FsResult<FindResultDirMut<S>>
156where
157 S: IpldStore + Send + Sync,
158{
159 let path = Utf8UnixPath::new(path.as_ref());
160
161 let components = path
163 .components()
164 .map(|ref c| match c {
165 Utf8UnixComponent::RootDir => Err(FsError::InvalidSearchPath(path.to_string())),
166 Utf8UnixComponent::CurDir => Err(FsError::InvalidSearchPath(path.to_string())),
167 Utf8UnixComponent::ParentDir => Err(FsError::InvalidSearchPath(path.to_string())),
168 _ => Ok(Utf8UnixPathSegment::try_from(c)?),
169 })
170 .collect::<Result<Vec<_>, _>>()?;
171
172 for (depth, segment) in components.iter().enumerate() {
174 match dir.get_entity(segment).await? {
175 Some(Entity::Dir(_)) => {
176 dir = dir.get_dir_mut(segment).await?.unwrap();
178 }
179 Some(Entity::SoftLink(_)) => {
180 return Err(FsError::SoftLinkNotSupportedYet(components.clone()));
182 }
183 Some(_) => {
184 return Ok(FindResult::NotADir { depth });
187 }
188 None => {
189 return Ok(FindResult::NotFound { dir, depth });
192 }
193 }
194 }
195
196 Ok(FindResult::Found { dir })
197}
198
199pub async fn find_or_create_dir<S>(dir: &mut Dir<S>, path: impl AsRef<str>) -> FsResult<&mut Dir<S>>
221where
222 S: IpldStore + Send + Sync,
223{
224 let path = Utf8UnixPath::new(path.as_ref());
225
226 match find_dir_mut(dir, path).await {
227 Ok(FindResult::Found { dir }) => Ok(dir),
228 Ok(FindResult::NotFound { mut dir, depth }) => {
229 for component in path.components().skip(depth) {
230 let new_dir = Dir::new(dir.get_store().clone());
231 let segment = Utf8UnixPathSegment::try_from(&component)?;
232
233 dir.put_dir(segment.clone(), new_dir)?;
234 dir = dir.get_dir_mut(&segment).await?.unwrap();
235 }
236
237 Ok(dir)
238 }
239 Ok(FindResult::NotADir { depth }) => {
240 let components = path
241 .components()
242 .take(depth + 1)
243 .map(|c| c.to_string())
244 .collect::<Vec<_>>();
245
246 Err(FsError::NotADirectory(components.join("/")))
247 }
248 Err(e) => Err(e),
249 }
250}
251
252#[cfg(test)]
257mod tests {
258 use monoutils_store::MemoryStore;
259
260 use crate::filesystem::File;
261
262 use super::*;
263
264 mod fixtures {
265 use monoutils_store::Storable;
266
267 use super::*;
268
269 pub(super) async fn setup_test_filesystem() -> anyhow::Result<Dir<MemoryStore>> {
270 let store = MemoryStore::default();
271 let mut root = Dir::new(store.clone());
272
273 let mut subdir1 = Dir::new(store.clone());
274 let mut subdir2 = Dir::new(store.clone());
275
276 let file1 = File::new(store.clone());
277 let file2 = File::new(store.clone());
278
279 let file1_cid = file1.store().await?;
280 subdir1.put_entry("file1.txt", file1_cid.into())?;
281
282 let file2_cid = file2.store().await?;
283 subdir2.put_entry("file2.txt", file2_cid.into())?;
284
285 let subdir2_cid = subdir2.store().await?;
286 subdir1.put_entry("subdir2", subdir2_cid.into())?;
287
288 let subdir1_cid = subdir1.store().await?;
289 root.put_entry("subdir1", subdir1_cid.into())?;
290
291 Ok(root)
292 }
293 }
294
295 #[tokio::test]
296 async fn test_find_dir() -> anyhow::Result<()> {
297 let root = fixtures::setup_test_filesystem().await?;
298
299 let result = find_dir(&root, "subdir1").await?;
301 assert!(matches!(result, FindResult::Found { .. }));
302
303 let result = find_dir(&root, "subdir1/subdir2").await?;
304 assert!(matches!(result, FindResult::Found { .. }));
305
306 let result = find_dir(&root, "nonexistent").await?;
308 assert!(matches!(result, FindResult::NotFound { depth: 0, .. }));
309
310 let result = find_dir(&root, "subdir1/nonexistent").await?;
311 assert!(matches!(result, FindResult::NotFound { depth: 1, .. }));
312
313 let result = find_dir(&root, "subdir1/file1.txt/invalid").await?;
315 assert!(matches!(result, FindResult::NotADir { depth: 1 }));
316
317 let result = find_dir(&root, "/invalid/path").await;
319 assert!(matches!(result, Err(FsError::InvalidSearchPath(_))));
320
321 let result = find_dir(&root, "invalid/../path").await;
322 assert!(matches!(result, Err(FsError::InvalidSearchPath(_))));
323
324 let result = find_dir(&root, "./invalid/path").await;
325 assert!(matches!(result, Err(FsError::InvalidSearchPath(_))));
326
327 Ok(())
328 }
329
330 #[tokio::test]
331 async fn test_find_dir_mut() -> anyhow::Result<()> {
332 let mut root = fixtures::setup_test_filesystem().await?;
333
334 let result = find_dir_mut(&mut root, "subdir1").await?;
336 assert!(matches!(result, FindResult::Found { .. }));
337
338 let result = find_dir_mut(&mut root, "subdir1/subdir2").await?;
339 assert!(matches!(result, FindResult::Found { .. }));
340
341 let result = find_dir_mut(&mut root, "nonexistent").await?;
343 assert!(matches!(result, FindResult::NotFound { depth: 0, .. }));
344
345 let result = find_dir_mut(&mut root, "subdir1/nonexistent").await?;
346 assert!(matches!(result, FindResult::NotFound { depth: 1, .. }));
347
348 let result = find_dir_mut(&mut root, "subdir1/file1.txt/invalid").await?;
350 assert!(matches!(result, FindResult::NotADir { depth: 1 }));
351
352 Ok(())
353 }
354
355 #[tokio::test]
356 async fn test_find_or_create_dir() -> anyhow::Result<()> {
357 let mut root = fixtures::setup_test_filesystem().await?;
358
359 let new_dir = find_or_create_dir(&mut root, "new_dir").await?;
361 assert!(new_dir.is_empty());
362
363 let result = find_dir(&root, "new_dir").await?;
365 assert!(matches!(result, FindResult::Found { .. }));
366
367 let nested_dir = find_or_create_dir(&mut root, "parent/child/grandchild").await?;
369 assert!(nested_dir.is_empty());
370
371 let result = find_dir(&root, "parent/child/grandchild").await?;
373 assert!(matches!(result, FindResult::Found { .. }));
374
375 let existing_dir = find_or_create_dir(&mut root, "subdir1").await?;
377 assert!(!existing_dir.is_empty());
378
379 let result = find_or_create_dir(&mut root, "subdir1/file1.txt").await;
381 assert!(matches!(result, Err(FsError::NotADirectory(_))));
382
383 Ok(())
384 }
385}