1use crate::directory::entry::DirectoryEntry;
12use crate::directory::helper::DirectlyMutable;
13use crate::directory::immutable::Simple;
14
15use fidl_fuchsia_io as fio;
16use itertools::Itertools;
17use name::{Name, ParseNameError};
18use std::collections::HashMap;
19use std::collections::hash_map::Entry;
20use std::fmt;
21use std::marker::PhantomData;
22use std::slice::Iter;
23use std::sync::Arc;
24use thiserror::Error;
25
26pub struct Path<'components, Impl>
43where
44 Impl: AsRef<[&'components str]>,
45{
46 path: Impl,
47 _components: PhantomData<&'components str>,
48}
49
50impl<'components, Impl> Path<'components, Impl>
51where
52 Impl: AsRef<[&'components str]>,
53{
54 fn iter<'path>(&'path self) -> Iter<'path, &'components str>
55 where
56 'components: 'path,
57 {
58 self.path.as_ref().iter()
59 }
60}
61
62impl<'component> From<&'component str> for Path<'component, Vec<&'component str>> {
63 fn from(component: &'component str) -> Self {
64 Path { path: vec![component], _components: PhantomData }
65 }
66}
67
68impl<'components, Impl> From<Impl> for Path<'components, Impl>
69where
70 Impl: AsRef<[&'components str]>,
71{
72 fn from(path: Impl) -> Self {
73 Path { path, _components: PhantomData }
74 }
75}
76
77impl<'components, Impl> fmt::Display for Path<'components, Impl>
78where
79 Impl: AsRef<[&'components str]>,
80{
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 write!(f, "{}", self.iter().format("/"))
83 }
84}
85
86pub enum TreeBuilder {
87 Directory(HashMap<Name, TreeBuilder>),
88 Leaf(Arc<dyn DirectoryEntry>),
89}
90
91impl TreeBuilder {
101 pub fn empty_dir() -> Self {
104 TreeBuilder::Directory(HashMap::new())
105 }
106
107 pub fn add_entry<'components, P: 'components, PathImpl>(
111 &mut self,
112 path: P,
113 entry: Arc<dyn DirectoryEntry>,
114 ) -> Result<(), Error>
115 where
116 P: Into<Path<'components, PathImpl>>,
117 PathImpl: AsRef<[&'components str]>,
118 {
119 let path = path.into();
120 let traversed = vec![];
121 let mut rest = path.iter();
122 match rest.next() {
123 None => Err(Error::EmptyPath),
124 Some(name) => self.add_path(
125 &path,
126 traversed,
127 name,
128 rest,
129 |entries, name, full_path, _traversed| match entries
130 .insert(name, TreeBuilder::Leaf(entry))
131 {
132 None => Ok(()),
133 Some(TreeBuilder::Directory(_)) => {
134 Err(Error::LeafOverDirectory { path: full_path.to_string() })
135 }
136 Some(TreeBuilder::Leaf(_)) => {
137 Err(Error::LeafOverLeaf { path: full_path.to_string() })
138 }
139 },
140 ),
141 }
142 }
143
144 pub fn add_empty_dir<'components, P: 'components, PathImpl>(
172 &mut self,
173 path: P,
174 ) -> Result<(), Error>
175 where
176 P: Into<Path<'components, PathImpl>>,
177 PathImpl: AsRef<[&'components str]>,
178 {
179 let path = path.into();
180 let traversed = vec![];
181 let mut rest = path.iter();
182 match rest.next() {
183 None => Err(Error::EmptyPath),
184 Some(name) => self.add_path(
185 &path,
186 traversed,
187 name,
188 rest,
189 |entries, name, full_path, traversed| match entries
190 .entry(name)
191 .or_insert_with(|| TreeBuilder::Directory(HashMap::new()))
192 {
193 TreeBuilder::Directory(_) => Ok(()),
194 TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
195 path: full_path.to_string(),
196 traversed: traversed.iter().join("/"),
197 }),
198 },
199 ),
200 }
201 }
202
203 fn add_path<'path, 'components: 'path, PathImpl, Inserter>(
204 &mut self,
205 full_path: &'path Path<'components, PathImpl>,
206 mut traversed: Vec<&'components str>,
207 name: &'components str,
208 mut rest: Iter<'path, &'components str>,
209 inserter: Inserter,
210 ) -> Result<(), Error>
211 where
212 PathImpl: AsRef<[&'components str]>,
213 Inserter: FnOnce(
214 &mut HashMap<Name, TreeBuilder>,
215 Name,
216 &Path<'components, PathImpl>,
217 Vec<&'components str>,
218 ) -> Result<(), Error>,
219 {
220 let parsed_name =
221 Name::try_from(name.to_string()).map_err(|error| Error::InvalidComponent {
222 path: full_path.to_string(),
223 component: name.to_string(),
224 error,
225 })?;
226
227 match self {
228 TreeBuilder::Directory(entries) => match rest.next() {
229 None => inserter(entries, parsed_name, full_path, traversed),
230 Some(next_component) => {
231 traversed.push(name);
232 match entries.entry(parsed_name) {
233 Entry::Vacant(slot) => {
234 let mut child = TreeBuilder::Directory(HashMap::new());
235 child.add_path(full_path, traversed, next_component, rest, inserter)?;
236 slot.insert(child);
237 Ok(())
238 }
239 Entry::Occupied(mut slot) => slot.get_mut().add_path(
240 full_path,
241 traversed,
242 next_component,
243 rest,
244 inserter,
245 ),
246 }
247 }
248 },
249 TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
250 path: full_path.to_string(),
251 traversed: traversed.iter().join("/"),
252 }),
253 }
254 }
255
256 pub fn build(self) -> Arc<Simple> {
259 let mut generator = |_: &str| -> u64 { fio::INO_UNKNOWN };
260 self.build_with_inode_generator(&mut generator)
261 }
262
263 pub fn build_with_inode_generator<F>(self, get_inode: &mut F) -> Arc<Simple>
268 where
269 F: for<'a> FnMut(&'a str) -> u64,
270 {
271 match self {
272 TreeBuilder::Directory(mut entries) => {
273 let res = Simple::new_with_inode(get_inode("."));
274 for (name, child) in entries.drain() {
275 let child = child.build_dyn(&name, get_inode);
276 res.add_entry_impl(name, child, false)
277 .map_err(|status| format!("Status: {}", status))
278 .expect(
279 "Internal error. We have already checked all the entry names. \
280 There should be no collisions.",
281 );
282 }
283 res
284 }
285 TreeBuilder::Leaf(_) => {
286 panic!("Leaf nodes should not be buildable through the public API.")
287 }
288 }
289 }
290
291 fn build_dyn<F>(self, dir: &str, get_inode: &mut F) -> Arc<dyn DirectoryEntry>
292 where
293 F: for<'a> FnMut(&'a str) -> u64,
294 {
295 match self {
296 TreeBuilder::Directory(mut entries) => {
297 let res = Simple::new_with_inode(get_inode(dir));
298 for (name, child) in entries.drain() {
299 let child = child.build_dyn(&name, get_inode);
300 res.add_entry(name, child)
301 .map_err(|status| format!("Status: {}", status))
302 .expect(
303 "Internal error. We have already checked all the entry names. \
304 There should be no collisions, nor overly long names.",
305 );
306 }
307 res
308 }
309 TreeBuilder::Leaf(entry) => entry,
310 }
311 }
312}
313
314#[derive(Debug, Error, PartialEq, Eq)]
315pub enum Error {
316 #[error("`add_entry` requires a non-empty path")]
317 EmptyPath,
318
319 #[error(
320 "Path component is invalid.\n\
321 Path: {}\n\
322 Component: '{}'\n\
323 Error: '{}'",
324 path,
325 component,
326 error
327 )]
328 InvalidComponent { path: String, component: String, error: ParseNameError },
329
330 #[error(
331 "Trying to insert a leaf over an existing directory.\n\
332 Path: {}",
333 path
334 )]
335 LeafOverDirectory { path: String },
336
337 #[error(
338 "Trying to overwrite one leaf with another.\n\
339 Path: {}",
340 path
341 )]
342 LeafOverLeaf { path: String },
343
344 #[error(
345 "Trying to insert an entry inside a leaf.\n\
346 Leaf path: {}\n\
347 Path been inserted: {}",
348 path,
349 traversed
350 )]
351 EntryInsideLeaf { path: String, traversed: String },
352}
353
354#[cfg(test)]
355mod tests {
356 use super::{Error, Simple, TreeBuilder};
357
358 use crate::{assert_close, assert_read};
360
361 use crate::directory::serve;
362 use crate::file;
363
364 use fidl_fuchsia_io as fio;
365 use fuchsia_fs::directory::{DirEntry, DirentKind, open_directory, readdir};
366 use vfs_macros::pseudo_directory;
367
368 async fn assert_open_file_contents(
369 root: &fio::DirectoryProxy,
370 path: &str,
371 flags: fio::Flags,
372 expected_contents: &str,
373 ) {
374 let file = fuchsia_fs::directory::open_file(&root, path, flags).await.unwrap();
375 assert_read!(file, expected_contents);
376 assert_close!(file);
377 }
378
379 async fn get_id_of_path(root: &fio::DirectoryProxy, path: &str) -> u64 {
380 let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
381 root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
382 .expect("failed to call open");
383 let (_, immutable_attrs) = proxy
384 .get_attributes(fio::NodeAttributesQuery::ID)
385 .await
386 .expect("FIDL call failed")
387 .expect("GetAttributes failed");
388 immutable_attrs.id.expect("ID missing from GetAttributes response")
389 }
390
391 #[fuchsia::test]
392 async fn vfs_with_custom_inodes() {
393 let mut tree = TreeBuilder::empty_dir();
394 tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
395 tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
396
397 let mut get_inode = |name: &str| -> u64 {
398 match name {
399 "a" => 1,
400 "b" => 2,
401 "c" => 3,
402 _ => fio::INO_UNKNOWN,
403 }
404 };
405 let root = tree.build_with_inode_generator(&mut get_inode);
406 let root = serve(root, fio::PERM_READABLE);
407 assert_eq!(get_id_of_path(&root, "a").await, 1);
408 assert_eq!(get_id_of_path(&root, "a/b").await, 2);
409 assert_eq!(get_id_of_path(&root, "a/c").await, 3);
410 }
411
412 #[fuchsia::test]
413 async fn two_files() {
414 let mut tree = TreeBuilder::empty_dir();
415 tree.add_entry("a", file::read_only(b"A content")).unwrap();
416 tree.add_entry("b", file::read_only(b"B content")).unwrap();
417
418 let root = tree.build();
419 let root = serve(root, fio::PERM_READABLE);
420
421 assert_eq!(
422 readdir(&root).await.unwrap(),
423 vec![
424 DirEntry { name: String::from("a"), kind: DirentKind::File },
425 DirEntry { name: String::from("b"), kind: DirentKind::File },
426 ]
427 );
428 assert_open_file_contents(&root, "a", fio::PERM_READABLE, "A content").await;
429 assert_open_file_contents(&root, "b", fio::PERM_READABLE, "B content").await;
430
431 assert_close!(root);
432 }
433
434 #[fuchsia::test]
435 async fn overlapping_paths() {
436 let mut tree = TreeBuilder::empty_dir();
437 tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
438 tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
439 tree.add_entry("four", file::read_only(b"C")).unwrap();
440
441 let root = tree.build();
442 let root = serve(root, fio::PERM_READABLE);
443
444 assert_eq!(
445 readdir(&root).await.unwrap(),
446 vec![
447 DirEntry { name: String::from("four"), kind: DirentKind::File },
448 DirEntry { name: String::from("one"), kind: DirentKind::Directory },
449 ]
450 );
451 let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
452 assert_eq!(
453 readdir(&one_dir).await.unwrap(),
454 vec![
455 DirEntry { name: String::from("three"), kind: DirentKind::File },
456 DirEntry { name: String::from("two"), kind: DirentKind::File },
457 ]
458 );
459 assert_close!(one_dir);
460
461 assert_open_file_contents(&root, "one/two", fio::PERM_READABLE, "A").await;
462 assert_open_file_contents(&root, "one/three", fio::PERM_READABLE, "B").await;
463 assert_open_file_contents(&root, "four", fio::PERM_READABLE, "C").await;
464
465 assert_close!(root);
466 }
467
468 #[fuchsia::test]
469 async fn directory_leaf() {
470 let etc = pseudo_directory! {
471 "fstab" => file::read_only(b"/dev/fs /"),
472 "ssh" => pseudo_directory! {
473 "sshd_config" => file::read_only(b"# Empty"),
474 },
475 };
476
477 let mut tree = TreeBuilder::empty_dir();
478 tree.add_entry("etc", etc).unwrap();
479 tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
480
481 let root = tree.build();
482 let root = serve(root, fio::PERM_READABLE);
483
484 assert_eq!(
485 readdir(&root).await.unwrap(),
486 vec![
487 DirEntry { name: String::from("etc"), kind: DirentKind::Directory },
488 DirEntry { name: String::from("uname"), kind: DirentKind::File },
489 ]
490 );
491 let etc_dir = open_directory(&root, "etc", fio::PERM_READABLE).await.unwrap();
492 assert_eq!(
493 readdir(&etc_dir).await.unwrap(),
494 vec![
495 DirEntry { name: String::from("fstab"), kind: DirentKind::File },
496 DirEntry { name: String::from("ssh"), kind: DirentKind::Directory },
497 ]
498 );
499 assert_close!(etc_dir);
500 let ssh_dir = open_directory(&root, "etc/ssh", fio::PERM_READABLE).await.unwrap();
501 assert_eq!(
502 readdir(&ssh_dir).await.unwrap(),
503 vec![DirEntry { name: String::from("sshd_config"), kind: DirentKind::File }]
504 );
505 assert_close!(ssh_dir);
506
507 assert_open_file_contents(&root, "etc/fstab", fio::PERM_READABLE, "/dev/fs /").await;
508 assert_open_file_contents(&root, "etc/ssh/sshd_config", fio::PERM_READABLE, "# Empty")
509 .await;
510 assert_open_file_contents(&root, "uname", fio::PERM_READABLE, "Fuchsia").await;
511
512 assert_close!(root);
513 }
514
515 #[fuchsia::test]
516 async fn add_empty_dir_populate_later() {
517 let mut tree = TreeBuilder::empty_dir();
518 tree.add_empty_dir(&["one", "two"]).unwrap();
519 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
520
521 let root = tree.build();
522 let root = serve(root, fio::PERM_READABLE);
523
524 assert_eq!(
525 readdir(&root).await.unwrap(),
526 vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
527 );
528 let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
529 assert_eq!(
530 readdir(&one_dir).await.unwrap(),
531 vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
532 );
533 assert_close!(one_dir);
534 let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
535 assert_eq!(
536 readdir(&two_dir).await.unwrap(),
537 vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
538 );
539 assert_close!(two_dir);
540
541 assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
542
543 assert_close!(root);
544 }
545
546 #[fuchsia::test]
547 async fn add_empty_dir_already_exists() {
548 let mut tree = TreeBuilder::empty_dir();
549 tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
550 tree.add_empty_dir(&["one", "two"]).unwrap();
551
552 let root = tree.build();
553 let root = serve(root, fio::PERM_READABLE);
554
555 assert_eq!(
556 readdir(&root).await.unwrap(),
557 vec![DirEntry { name: String::from("one"), kind: DirentKind::Directory }]
558 );
559
560 let one_dir = open_directory(&root, "one", fio::PERM_READABLE).await.unwrap();
561 assert_eq!(
562 readdir(&one_dir).await.unwrap(),
563 vec![DirEntry { name: String::from("two"), kind: DirentKind::Directory }]
564 );
565 assert_close!(one_dir);
566
567 let two_dir = open_directory(&root, "one/two", fio::PERM_READABLE).await.unwrap();
568 assert_eq!(
569 readdir(&two_dir).await.unwrap(),
570 vec![DirEntry { name: String::from("three"), kind: DirentKind::File }]
571 );
572 assert_close!(two_dir);
573
574 assert_open_file_contents(&root, "one/two/three", fio::PERM_READABLE, "B").await;
575
576 assert_close!(root);
577 }
578
579 #[fuchsia::test]
580 async fn lone_add_empty_dir() {
581 let mut tree = TreeBuilder::empty_dir();
582 tree.add_empty_dir(&["just-me"]).unwrap();
583
584 let root = tree.build();
585 let root = serve(root, fio::PERM_READABLE);
586
587 assert_eq!(
588 readdir(&root).await.unwrap(),
589 vec![DirEntry { name: String::from("just-me"), kind: DirentKind::Directory }]
590 );
591 let just_me_dir = open_directory(&root, "just-me", fio::PERM_READABLE).await.unwrap();
592 assert_eq!(readdir(&just_me_dir).await.unwrap(), Vec::new());
593
594 assert_close!(just_me_dir);
595 assert_close!(root);
596 }
597
598 #[fuchsia::test]
599 async fn add_empty_dir_inside_add_empty_dir() {
600 let mut tree = TreeBuilder::empty_dir();
601 tree.add_empty_dir(&["container"]).unwrap();
602 tree.add_empty_dir(&["container", "nested"]).unwrap();
603
604 let root = tree.build();
605 let root = serve(root, fio::PERM_READABLE);
606
607 assert_eq!(
608 readdir(&root).await.unwrap(),
609 vec![DirEntry { name: String::from("container"), kind: DirentKind::Directory }]
610 );
611
612 let container_dir = open_directory(&root, "container", fio::PERM_READABLE).await.unwrap();
613 assert_eq!(
614 readdir(&container_dir).await.unwrap(),
615 vec![DirEntry { name: String::from("nested"), kind: DirentKind::Directory }]
616 );
617 assert_close!(container_dir);
618
619 let nested_dir =
620 open_directory(&root, "container/nested", fio::PERM_READABLE).await.unwrap();
621 assert_eq!(readdir(&nested_dir).await.unwrap(), Vec::new());
622 assert_close!(nested_dir);
623
624 assert_close!(root);
625 }
626
627 #[fuchsia::test]
628 fn error_empty_path_in_add_entry() {
629 let mut tree = TreeBuilder::empty_dir();
630 let err = tree
631 .add_entry(&[], file::read_only(b"Invalid"))
632 .expect_err("Empty paths are not allowed.");
633 assert_eq!(err, Error::EmptyPath);
634 }
635
636 #[fuchsia::test]
637 fn error_empty_first_component() {
638 let mut tree = TreeBuilder::empty_dir();
639 let err = tree
640 .add_entry(&[""], file::read_only(b"Invalid"))
641 .expect_err("Empty paths are not allowed.");
642 assert_eq!(
643 err,
644 Error::InvalidComponent {
645 path: String::new(),
646 component: String::new(),
647 error: name::ParseNameError::Empty
648 }
649 );
650 }
651
652 #[fuchsia::test]
653 fn error_empty_component() {
654 let mut tree = TreeBuilder::empty_dir();
655 let err = tree
656 .add_entry(&["a", "", "c"], file::read_only(b"Invalid"))
657 .expect_err("Empty paths are not allowed.");
658 assert_eq!(
659 err,
660 Error::InvalidComponent {
661 path: "a//c".to_string(),
662 component: String::new(),
663 error: name::ParseNameError::Empty
664 }
665 );
666 }
667
668 #[fuchsia::test]
669 fn error_slash_in_component() {
670 let mut tree = TreeBuilder::empty_dir();
671 let err = tree
672 .add_entry("a/b", file::read_only(b"Invalid"))
673 .expect_err("Slash in path component name.");
674 assert_eq!(
675 err,
676 Error::InvalidComponent {
677 path: "a/b".to_string(),
678 component: "a/b".to_string(),
679 error: name::ParseNameError::Slash
680 }
681 );
682 }
683
684 #[fuchsia::test]
685 fn error_slash_in_second_component() {
686 let mut tree = TreeBuilder::empty_dir();
687 let err = tree
688 .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
689 .expect_err("Slash in path component name.");
690 assert_eq!(
691 err,
692 Error::InvalidComponent {
693 path: "a/b/c".to_string(),
694 component: "b/c".to_string(),
695 error: name::ParseNameError::Slash
696 }
697 );
698 }
699
700 #[fuchsia::test]
701 fn error_component_name_too_long() {
702 let mut tree = TreeBuilder::empty_dir();
703
704 let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
705
706 let path: &[&str] = &["a", &long_component, "b"];
707 let err = tree
708 .add_entry(path, file::read_only(b"Invalid"))
709 .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
710 assert_eq!(
711 err,
712 Error::InvalidComponent {
713 path: format!("a/{}/b", long_component),
714 component: long_component.clone(),
715 error: name::ParseNameError::TooLong(long_component)
716 }
717 );
718 }
719
720 #[fuchsia::test]
721 fn error_dot_in_component() {
722 let mut tree = TreeBuilder::empty_dir();
723 let err = tree
724 .add_entry(&["a", "."], file::read_only(b"Invalid"))
725 .expect_err("Dot in path component name.");
726 assert_eq!(
727 err,
728 Error::InvalidComponent {
729 path: "a/.".to_string(),
730 component: ".".to_string(),
731 error: name::ParseNameError::Dot
732 }
733 );
734 }
735
736 #[fuchsia::test]
737 fn error_dot_dot_in_component() {
738 let mut tree = TreeBuilder::empty_dir();
739 let err = tree
740 .add_entry(&["a", ".."], file::read_only(b"Invalid"))
741 .expect_err("Dot dot in path component name.");
742 assert_eq!(
743 err,
744 Error::InvalidComponent {
745 path: "a/..".to_string(),
746 component: "..".to_string(),
747 error: name::ParseNameError::DotDot
748 }
749 );
750 }
751
752 #[fuchsia::test]
753 fn error_null_in_component() {
754 let mut tree = TreeBuilder::empty_dir();
755 let err = tree
756 .add_entry(&["a", "foo\0bar"], file::read_only(b"Invalid"))
757 .expect_err("Embedded null in component.");
758 assert_eq!(
759 err,
760 Error::InvalidComponent {
761 path: "a/foo\0bar".to_string(),
762 component: "foo\0bar".to_string(),
763 error: name::ParseNameError::EmbeddedNul
764 }
765 );
766 }
767
768 #[fuchsia::test]
769 fn error_leaf_over_directory() {
770 let mut tree = TreeBuilder::empty_dir();
771
772 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
773 let err = tree
774 .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
775 .expect_err("A leaf may not be constructed over a directory.");
776 assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
777 }
778
779 #[fuchsia::test]
780 fn error_leaf_over_leaf() {
781 let mut tree = TreeBuilder::empty_dir();
782
783 tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
784 let err = tree
785 .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
786 .expect_err("A leaf may not be constructed over another leaf.");
787 assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
788 }
789
790 #[fuchsia::test]
791 fn error_entry_inside_leaf() {
792 let mut tree = TreeBuilder::empty_dir();
793
794 tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
795 let err = tree
796 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
797 .expect_err("A leaf may not be constructed over another leaf.");
798 assert_eq!(
799 err,
800 Error::EntryInsideLeaf {
801 path: "top/file/nested".to_string(),
802 traversed: "top/file".to_string()
803 }
804 );
805 }
806
807 #[fuchsia::test]
808 fn error_entry_inside_leaf_directory() {
809 let mut tree = TreeBuilder::empty_dir();
810
811 tree.add_entry(&["top", "file"], Simple::new()).unwrap();
813 let err = tree
814 .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
815 .expect_err("A leaf may not be constructed over another leaf.");
816 assert_eq!(
817 err,
818 Error::EntryInsideLeaf {
819 path: "top/file/nested".to_string(),
820 traversed: "top/file".to_string()
821 }
822 );
823 }
824}