vfs/
tree_builder.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A helper to build a tree of directory nodes.  It is useful in case when a nested tree is
6//! desired, with specific nodes to be inserted as the leafs of this tree.  It is similar to the
7//! functionality provided by the [`vfs_macros::pseudo_directory!`] macro, except that the macro
8//! expects the tree structure to be defined at compile time, while this helper allows the tree
9//! structure to be dynamic.
10
11use 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 std::collections::HashMap;
18use std::fmt;
19use std::marker::PhantomData;
20use std::slice::Iter;
21use std::sync::Arc;
22use thiserror::Error;
23
24/// Represents a paths provided to [`TreeBuilder::add_entry()`].  See [`TreeBuilder`] for details.
25// I think it would be a bit more straightforward to have two different types that implement a
26// `Path` trait, `OwnedPath` and `SharedPath`.  But, `add_entry` then needs two type variables: one
27// for the type of the value passed in, and one for the type of the `Path` trait (either
28// `OwnedPath` or `SharedPath`).  Type inference fails with two variables requiring explicit type
29// annotation.  And that defeats the whole purpose of the overloading in the API.
30//
31//     pub fn add_entry<'path, 'components: 'path, F, P: 'path>(
32//         &mut self,
33//         path: F,
34//         entry: Arc<dyn DirectoryEntry>,
35//     ) -> Result<(), Error>
36//
37// Instead we capture the underlying implementation of the path in the `Impl` type and just wrap
38// our type around it.  `'components` and `AsRef` constraints on the struct itself are not actually
39// needed, but it makes it more the usage a bit easier to understand.
40pub struct Path<'components, Impl>
41where
42    Impl: AsRef<[&'components str]>,
43{
44    path: Impl,
45    _components: PhantomData<&'components str>,
46}
47
48impl<'components, Impl> Path<'components, Impl>
49where
50    Impl: AsRef<[&'components str]>,
51{
52    fn iter<'path>(&'path self) -> Iter<'path, &'components str>
53    where
54        'components: 'path,
55    {
56        self.path.as_ref().iter()
57    }
58}
59
60impl<'component> From<&'component str> for Path<'component, Vec<&'component str>> {
61    fn from(component: &'component str) -> Self {
62        Path { path: vec![component], _components: PhantomData }
63    }
64}
65
66impl<'components, Impl> From<Impl> for Path<'components, Impl>
67where
68    Impl: AsRef<[&'components str]>,
69{
70    fn from(path: Impl) -> Self {
71        Path { path, _components: PhantomData }
72    }
73}
74
75impl<'components, Impl> fmt::Display for Path<'components, Impl>
76where
77    Impl: AsRef<[&'components str]>,
78{
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{}", self.iter().format("/"))
81    }
82}
83
84pub enum TreeBuilder {
85    Directory(HashMap<String, TreeBuilder>),
86    Leaf(Arc<dyn DirectoryEntry>),
87}
88
89/// Collects a number of [`DirectoryEntry`] nodes and corresponding paths and the constructs a tree
90/// of [`crate::directory::immutable::simple::Simple`] directories that hold these nodes.  This is a
91/// companion tool, related to the [`vfs_macros::pseudo_directory!`] macro, except that it is
92/// collecting the paths dynamically, while the [`vfs_macros::pseudo_directory!`] expects the tree
93/// to be specified at compilation time.
94///
95/// Note that the final tree is build as a result of the [`Self::build()`] method that consumes the
96/// builder.  You would need to use the [`crate::directory::helper::DirectlyMutable::add_entry()`]
97/// interface to add any new nodes afterwards (a [`crate::directory::watchers::Controller`] APIs).
98impl TreeBuilder {
99    /// Constructs an empty builder.  It is always an empty [`crate::directory::immutable::Simple`]
100    /// directory.
101    pub fn empty_dir() -> Self {
102        TreeBuilder::Directory(HashMap::new())
103    }
104
105    /// Adds a [`DirectoryEntry`] at the specified path.  It can be either a file or a directory.
106    /// In case it is a directory, this builder cannot add new child nodes inside of the added
107    /// directory.  Any `entry` is treated as an opaque "leaf" as far as the builder is concerned.
108    pub fn add_entry<'components, P: 'components, PathImpl>(
109        &mut self,
110        path: P,
111        entry: Arc<dyn DirectoryEntry>,
112    ) -> Result<(), Error>
113    where
114        P: Into<Path<'components, PathImpl>>,
115        PathImpl: AsRef<[&'components str]>,
116    {
117        let path = path.into();
118        let traversed = vec![];
119        let mut rest = path.iter();
120        match rest.next() {
121            None => Err(Error::EmptyPath),
122            Some(name) => self.add_path(
123                &path,
124                traversed,
125                name,
126                rest,
127                |entries, name, full_path, _traversed| match entries
128                    .insert(name.to_string(), TreeBuilder::Leaf(entry))
129                {
130                    None => Ok(()),
131                    Some(TreeBuilder::Directory(_)) => {
132                        Err(Error::LeafOverDirectory { path: full_path.to_string() })
133                    }
134                    Some(TreeBuilder::Leaf(_)) => {
135                        Err(Error::LeafOverLeaf { path: full_path.to_string() })
136                    }
137                },
138            ),
139        }
140    }
141
142    /// Adds an empty directory into the generated tree at the specified path.  The difference with
143    /// the [`crate::directory::helper::DirectlyMutable::add_entry`] that adds an entry that is a directory is that the builder can can only
144    /// add leaf nodes.  In other words, code like this will fail:
145    ///
146    /// ```should_panic
147    /// use crate::{
148    ///     directory::immutable::Simple,
149    ///     file::vmo::read_only,
150    /// };
151    ///
152    /// let mut tree = TreeBuilder::empty_dir();
153    /// tree.add_entry(&["dir1"], Simple::new());
154    /// tree.add_entry(&["dir1", "nested"], read_only(b"A file"));
155    /// ```
156    ///
157    /// The problem is that the builder does not see "dir1" as a directory, but as a leaf node that
158    /// it cannot descend into.
159    ///
160    /// If you use `add_empty_dir()` instead, it would work:
161    ///
162    /// ```
163    /// use crate::file::vmo::read_only;
164    ///
165    /// let mut tree = TreeBuilder::empty_dir();
166    /// tree.add_empty_dir(&["dir1"]);
167    /// tree.add_entry(&["dir1", "nested"], read_only(b"A file"));
168    /// ```
169    pub fn add_empty_dir<'components, P: 'components, PathImpl>(
170        &mut self,
171        path: P,
172    ) -> Result<(), Error>
173    where
174        P: Into<Path<'components, PathImpl>>,
175        PathImpl: AsRef<[&'components str]>,
176    {
177        let path = path.into();
178        let traversed = vec![];
179        let mut rest = path.iter();
180        match rest.next() {
181            None => Err(Error::EmptyPath),
182            Some(name) => self.add_path(
183                &path,
184                traversed,
185                name,
186                rest,
187                |entries, name, full_path, traversed| match entries
188                    .entry(name.to_string())
189                    .or_insert_with(|| TreeBuilder::Directory(HashMap::new()))
190                {
191                    TreeBuilder::Directory(_) => Ok(()),
192                    TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
193                        path: full_path.to_string(),
194                        traversed: traversed.iter().join("/"),
195                    }),
196                },
197            ),
198        }
199    }
200
201    fn add_path<'path, 'components: 'path, PathImpl, Inserter>(
202        &mut self,
203        full_path: &'path Path<'components, PathImpl>,
204        mut traversed: Vec<&'components str>,
205        name: &'components str,
206        mut rest: Iter<'path, &'components str>,
207        inserter: Inserter,
208    ) -> Result<(), Error>
209    where
210        PathImpl: AsRef<[&'components str]>,
211        Inserter: FnOnce(
212            &mut HashMap<String, TreeBuilder>,
213            &str,
214            &Path<'components, PathImpl>,
215            Vec<&'components str>,
216        ) -> Result<(), Error>,
217    {
218        if name.len() as u64 >= fio::MAX_NAME_LENGTH {
219            return Err(Error::ComponentNameTooLong {
220                path: full_path.to_string(),
221                component: name.to_string(),
222                component_len: name.len(),
223                max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
224            });
225        }
226
227        if name.contains('/') {
228            return Err(Error::SlashInComponent {
229                path: full_path.to_string(),
230                component: name.to_string(),
231            });
232        }
233
234        match self {
235            TreeBuilder::Directory(entries) => match rest.next() {
236                None => inserter(entries, name, full_path, traversed),
237                Some(next_component) => {
238                    traversed.push(name);
239                    match entries.get_mut(name) {
240                        None => {
241                            let mut child = TreeBuilder::Directory(HashMap::new());
242                            child.add_path(full_path, traversed, next_component, rest, inserter)?;
243                            let existing = entries.insert(name.to_string(), child);
244                            assert!(existing.is_none());
245                            Ok(())
246                        }
247                        Some(children) => {
248                            children.add_path(full_path, traversed, next_component, rest, inserter)
249                        }
250                    }
251                }
252            },
253            TreeBuilder::Leaf(_) => Err(Error::EntryInsideLeaf {
254                path: full_path.to_string(),
255                traversed: traversed.iter().join("/"),
256            }),
257        }
258    }
259
260    // Helper function for building a tree with a default inode generator. Use if you don't
261    // care about directory inode values.
262    pub fn build(self) -> Arc<Simple> {
263        let mut generator = |_| -> u64 { fio::INO_UNKNOWN };
264        self.build_with_inode_generator(&mut generator)
265    }
266
267    /// Consumes the builder, producing a tree with all the nodes provided to
268    /// [`crate::directory::helper::DirectlyMutable::add_entry()`] at their respective locations.
269    /// The tree itself is built using [`crate::directory::immutable::Simple`]
270    /// nodes, and the top level is a directory.
271    pub fn build_with_inode_generator(
272        self,
273        get_inode: &mut impl FnMut(String) -> u64,
274    ) -> Arc<Simple> {
275        match self {
276            TreeBuilder::Directory(mut entries) => {
277                let res = Simple::new_with_inode(get_inode(".".to_string()));
278                for (name, child) in entries.drain() {
279                    res.clone()
280                        .add_entry(&name, child.build_dyn(name.clone(), get_inode))
281                        .map_err(|status| format!("Status: {}", status))
282                        .expect(
283                            "Internal error.  We have already checked all the entry names. \
284                             There should be no collisions, nor overly long names.",
285                        );
286                }
287                res
288            }
289            TreeBuilder::Leaf(_) => {
290                panic!("Leaf nodes should not be buildable through the public API.")
291            }
292        }
293    }
294
295    fn build_dyn(
296        self,
297        dir: String,
298        get_inode: &mut impl FnMut(String) -> u64,
299    ) -> Arc<dyn DirectoryEntry> {
300        match self {
301            TreeBuilder::Directory(mut entries) => {
302                let res = Simple::new_with_inode(get_inode(dir));
303                for (name, child) in entries.drain() {
304                    res.clone()
305                        .add_entry(&name, child.build_dyn(name.clone(), get_inode))
306                        .map_err(|status| format!("Status: {}", status))
307                        .expect(
308                            "Internal error.  We have already checked all the entry names. \
309                             There should be no collisions, nor overly long names.",
310                        );
311                }
312                res
313            }
314            TreeBuilder::Leaf(entry) => entry,
315        }
316    }
317}
318
319#[derive(Debug, Error, PartialEq, Eq)]
320pub enum Error {
321    #[error("`add_entry` requires a non-empty path")]
322    EmptyPath,
323
324    #[error(
325        "Path component contains a forward slash.\n\
326                   Path: {}\n\
327                   Component: '{}'",
328        path,
329        component
330    )]
331    SlashInComponent { path: String, component: String },
332
333    #[error(
334        "Path component name is too long - {} characters.  Maximum is {}.\n\
335                   Path: {}\n\
336                   Component: '{}'",
337        component_len,
338        max_len,
339        path,
340        component
341    )]
342    ComponentNameTooLong { path: String, component: String, component_len: usize, max_len: usize },
343
344    #[error(
345        "Trying to insert a leaf over an existing directory.\n\
346                   Path: {}",
347        path
348    )]
349    LeafOverDirectory { path: String },
350
351    #[error(
352        "Trying to overwrite one leaf with another.\n\
353                   Path: {}",
354        path
355    )]
356    LeafOverLeaf { path: String },
357
358    #[error(
359        "Trying to insert an entry inside a leaf.\n\
360                   Leaf path: {}\n\
361                   Path been inserted: {}",
362        path,
363        traversed
364    )]
365    EntryInsideLeaf { path: String, traversed: String },
366}
367
368#[cfg(test)]
369mod tests {
370    use super::{Error, Simple, TreeBuilder};
371
372    // Macros are exported into the root of the crate.
373    use crate::{
374        assert_close, assert_event, assert_get_attr_path, assert_read, assert_read_dirents,
375        assert_read_dirents_one_listing, assert_read_dirents_path_one_listing,
376        open_as_vmo_file_assert_content, open_get_directory_proxy_assert_ok, open_get_proxy_assert,
377        open_get_vmo_file_proxy_assert_ok,
378    };
379
380    use crate::directory::test_utils::run_server_client;
381    use crate::file;
382
383    use fidl_fuchsia_io as fio;
384    use vfs_macros::pseudo_directory;
385
386    #[test]
387    fn vfs_with_custom_inodes() {
388        let mut tree = TreeBuilder::empty_dir();
389        tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
390        tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
391
392        let mut get_inode = |name: String| -> u64 {
393            match &name[..] {
394                "a" => 1,
395                "b" => 2,
396                "c" => 3,
397                _ => fio::INO_UNKNOWN,
398            }
399        };
400        let root = tree.build_with_inode_generator(&mut get_inode);
401
402        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
403            assert_get_attr_path!(
404                &root,
405                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
406                "a",
407                fio::NodeAttributes {
408                    mode: fio::MODE_TYPE_DIRECTORY
409                        | crate::common::rights_to_posix_mode_bits(
410                            /*r*/ true, /*w*/ false, /*x*/ true
411                        ),
412                    id: 1,
413                    content_size: 0,
414                    storage_size: 0,
415                    link_count: 1,
416                    creation_time: 0,
417                    modification_time: 0,
418                }
419            );
420            assert_get_attr_path!(
421                &root,
422                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
423                "a/b",
424                fio::NodeAttributes {
425                    mode: fio::MODE_TYPE_DIRECTORY
426                        | crate::common::rights_to_posix_mode_bits(
427                            /*r*/ true, /*w*/ false, /*x*/ true
428                        ),
429                    id: 2,
430                    content_size: 0,
431                    storage_size: 0,
432                    link_count: 1,
433                    creation_time: 0,
434                    modification_time: 0,
435                }
436            );
437            assert_get_attr_path!(
438                &root,
439                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
440                "a/c",
441                fio::NodeAttributes {
442                    mode: fio::MODE_TYPE_DIRECTORY
443                        | crate::common::rights_to_posix_mode_bits(
444                            /*r*/ true, /*w*/ false, /*x*/ true
445                        ),
446                    id: 3,
447                    content_size: 0,
448                    storage_size: 0,
449                    link_count: 1,
450                    creation_time: 0,
451                    modification_time: 0,
452                }
453            );
454        });
455    }
456
457    #[test]
458    fn two_files() {
459        let mut tree = TreeBuilder::empty_dir();
460        tree.add_entry("a", file::read_only(b"A content")).unwrap();
461        tree.add_entry("b", file::read_only(b"B content")).unwrap();
462
463        let root = tree.build();
464
465        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
466            assert_read_dirents_one_listing!(
467                root, 1000,
468                { DIRECTORY, b"." },
469                { FILE, b"a" },
470                { FILE, b"b" },
471            );
472            open_as_vmo_file_assert_content!(
473                &root,
474                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
475                "a",
476                "A content"
477            );
478            open_as_vmo_file_assert_content!(
479                &root,
480                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
481                "b",
482                "B content"
483            );
484
485            assert_close!(root);
486        });
487    }
488
489    #[test]
490    fn overlapping_paths() {
491        let mut tree = TreeBuilder::empty_dir();
492        tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
493        tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
494        tree.add_entry("four", file::read_only(b"C")).unwrap();
495
496        let root = tree.build();
497
498        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
499            assert_read_dirents_one_listing!(
500                root, 1000,
501                { DIRECTORY, b"." },
502                { FILE, b"four" },
503                { DIRECTORY, b"one" },
504            );
505            assert_read_dirents_path_one_listing!(
506                &root, "one", 1000,
507                { DIRECTORY, b"." },
508                { FILE, b"three" },
509                { FILE, b"two" },
510            );
511
512            open_as_vmo_file_assert_content!(
513                &root,
514                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
515                "one/two",
516                "A"
517            );
518            open_as_vmo_file_assert_content!(
519                &root,
520                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
521                "one/three",
522                "B"
523            );
524            open_as_vmo_file_assert_content!(
525                &root,
526                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
527                "four",
528                "C"
529            );
530
531            assert_close!(root);
532        });
533    }
534
535    #[test]
536    fn directory_leaf() {
537        let etc = pseudo_directory! {
538            "fstab" => file::read_only(b"/dev/fs /"),
539            "ssh" => pseudo_directory! {
540                "sshd_config" => file::read_only(b"# Empty"),
541            },
542        };
543
544        let mut tree = TreeBuilder::empty_dir();
545        tree.add_entry("etc", etc).unwrap();
546        tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
547
548        let root = tree.build();
549
550        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
551            assert_read_dirents_one_listing!(
552                root, 1000,
553                { DIRECTORY, b"." },
554                { DIRECTORY, b"etc" },
555                { FILE, b"uname" },
556            );
557            assert_read_dirents_path_one_listing!(
558                &root, "etc", 1000,
559                { DIRECTORY, b"." },
560                { FILE, b"fstab" },
561                { DIRECTORY, b"ssh" },
562            );
563            assert_read_dirents_path_one_listing!(
564                &root, "etc/ssh", 1000,
565                { DIRECTORY, b"." },
566                { FILE, b"sshd_config" },
567            );
568
569            open_as_vmo_file_assert_content!(
570                &root,
571                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
572                "etc/fstab",
573                "/dev/fs /"
574            );
575            open_as_vmo_file_assert_content!(
576                &root,
577                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
578                "etc/ssh/sshd_config",
579                "# Empty"
580            );
581            open_as_vmo_file_assert_content!(
582                &root,
583                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
584                "uname",
585                "Fuchsia"
586            );
587
588            assert_close!(root);
589        });
590    }
591
592    #[test]
593    fn add_empty_dir_populate_later() {
594        let mut tree = TreeBuilder::empty_dir();
595        tree.add_empty_dir(&["one", "two"]).unwrap();
596        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
597
598        let root = tree.build();
599
600        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
601            assert_read_dirents_one_listing!(
602                root, 1000,
603                { DIRECTORY, b"." },
604                { DIRECTORY, b"one" },
605            );
606            assert_read_dirents_path_one_listing!(
607                &root, "one", 1000,
608                { DIRECTORY, b"." },
609                { DIRECTORY, b"two" },
610            );
611            assert_read_dirents_path_one_listing!(
612                &root, "one/two", 1000,
613                { DIRECTORY, b"." },
614                { FILE, b"three" },
615            );
616
617            open_as_vmo_file_assert_content!(
618                &root,
619                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
620                "one/two/three",
621                "B"
622            );
623
624            assert_close!(root);
625        });
626    }
627
628    #[test]
629    fn add_empty_dir_already_exists() {
630        let mut tree = TreeBuilder::empty_dir();
631        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
632        tree.add_empty_dir(&["one", "two"]).unwrap();
633
634        let root = tree.build();
635
636        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
637            assert_read_dirents_one_listing!(
638                root, 1000,
639                { DIRECTORY, b"." },
640                { DIRECTORY, b"one" },
641            );
642            assert_read_dirents_path_one_listing!(
643                &root, "one", 1000,
644                { DIRECTORY, b"." },
645                { DIRECTORY, b"two" },
646            );
647            assert_read_dirents_path_one_listing!(
648                &root, "one/two", 1000,
649                { DIRECTORY, b"." },
650                { FILE, b"three" },
651            );
652
653            open_as_vmo_file_assert_content!(
654                &root,
655                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
656                "one/two/three",
657                "B"
658            );
659
660            assert_close!(root);
661        });
662    }
663
664    #[test]
665    fn lone_add_empty_dir() {
666        let mut tree = TreeBuilder::empty_dir();
667        tree.add_empty_dir(&["just-me"]).unwrap();
668
669        let root = tree.build();
670
671        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
672            assert_read_dirents_one_listing!(
673                root, 1000,
674                { DIRECTORY, b"." },
675                { DIRECTORY, b"just-me" },
676            );
677            assert_read_dirents_path_one_listing!(
678                &root, "just-me", 1000,
679                { DIRECTORY, b"." },
680            );
681            assert_close!(root);
682        });
683    }
684
685    #[test]
686    fn add_empty_dir_inside_add_empty_dir() {
687        let mut tree = TreeBuilder::empty_dir();
688        tree.add_empty_dir(&["container"]).unwrap();
689        tree.add_empty_dir(&["container", "nested"]).unwrap();
690
691        let root = tree.build();
692
693        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
694            assert_read_dirents_one_listing!(
695                root, 1000,
696                { DIRECTORY, b"." },
697                { DIRECTORY, b"container" },
698            );
699            assert_read_dirents_path_one_listing!(
700                &root, "container", 1000,
701                { DIRECTORY, b"." },
702                { DIRECTORY, b"nested" },
703            );
704            assert_read_dirents_path_one_listing!(
705                &root, "container/nested", 1000,
706                { DIRECTORY, b"." },
707            );
708            assert_close!(root);
709        });
710    }
711
712    #[test]
713    fn error_empty_path_in_add_entry() {
714        let mut tree = TreeBuilder::empty_dir();
715        let err = tree
716            .add_entry(vec![], file::read_only(b"Invalid"))
717            .expect_err("Empty paths are not allowed.");
718        assert_eq!(err, Error::EmptyPath);
719    }
720
721    #[test]
722    fn error_slash_in_component() {
723        let mut tree = TreeBuilder::empty_dir();
724        let err = tree
725            .add_entry("a/b", file::read_only(b"Invalid"))
726            .expect_err("Slash in path component name.");
727        assert_eq!(
728            err,
729            Error::SlashInComponent { path: "a/b".to_string(), component: "a/b".to_string() }
730        );
731    }
732
733    #[test]
734    fn error_slash_in_second_component() {
735        let mut tree = TreeBuilder::empty_dir();
736        let err = tree
737            .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
738            .expect_err("Slash in path component name.");
739        assert_eq!(
740            err,
741            Error::SlashInComponent { path: "a/b/c".to_string(), component: "b/c".to_string() }
742        );
743    }
744
745    #[test]
746    fn error_component_name_too_long() {
747        let mut tree = TreeBuilder::empty_dir();
748
749        let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
750
751        let path: &[&str] = &["a", &long_component, "b"];
752        let err = tree
753            .add_entry(path, file::read_only(b"Invalid"))
754            .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
755        assert_eq!(
756            err,
757            Error::ComponentNameTooLong {
758                path: format!("a/{}/b", long_component),
759                component: long_component.clone(),
760                component_len: long_component.len(),
761                max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
762            }
763        );
764    }
765
766    #[test]
767    fn error_leaf_over_directory() {
768        let mut tree = TreeBuilder::empty_dir();
769
770        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
771        let err = tree
772            .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
773            .expect_err("A leaf may not be constructed over a directory.");
774        assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
775    }
776
777    #[test]
778    fn error_leaf_over_leaf() {
779        let mut tree = TreeBuilder::empty_dir();
780
781        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
782        let err = tree
783            .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
784            .expect_err("A leaf may not be constructed over another leaf.");
785        assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
786    }
787
788    #[test]
789    fn error_entry_inside_leaf() {
790        let mut tree = TreeBuilder::empty_dir();
791
792        tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
793        let err = tree
794            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
795            .expect_err("A leaf may not be constructed over another leaf.");
796        assert_eq!(
797            err,
798            Error::EntryInsideLeaf {
799                path: "top/file/nested".to_string(),
800                traversed: "top/file".to_string()
801            }
802        );
803    }
804
805    #[test]
806    fn error_entry_inside_leaf_directory() {
807        let mut tree = TreeBuilder::empty_dir();
808
809        // Even when a leaf is itself a directory the tree builder cannot insert a nested entry.
810        tree.add_entry(&["top", "file"], Simple::new()).unwrap();
811        let err = tree
812            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
813            .expect_err("A leaf may not be constructed over another leaf.");
814        assert_eq!(
815            err,
816            Error::EntryInsideLeaf {
817                path: "top/file/nested".to_string(),
818                traversed: "top/file".to_string()
819            }
820        );
821    }
822}