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_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    async fn get_id_of_path(root: &fio::DirectoryProxy, path: &str) -> u64 {
387        let (proxy, server) = fidl::endpoints::create_proxy::<fio::NodeMarker>();
388        root.open(path, fio::PERM_READABLE, &Default::default(), server.into_channel())
389            .expect("failed to call open");
390        let (_, immutable_attrs) = proxy
391            .get_attributes(fio::NodeAttributesQuery::ID)
392            .await
393            .expect("FIDL call failed")
394            .expect("GetAttributes failed");
395        immutable_attrs.id.expect("ID missing from GetAttributes response")
396    }
397
398    #[test]
399    fn vfs_with_custom_inodes() {
400        let mut tree = TreeBuilder::empty_dir();
401        tree.add_entry(&["a", "b", "file"], file::read_only(b"A content")).unwrap();
402        tree.add_entry(&["a", "c", "file"], file::read_only(b"B content")).unwrap();
403
404        let mut get_inode = |name: String| -> u64 {
405            match &name[..] {
406                "a" => 1,
407                "b" => 2,
408                "c" => 3,
409                _ => fio::INO_UNKNOWN,
410            }
411        };
412        let root = tree.build_with_inode_generator(&mut get_inode);
413
414        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
415            assert_eq!(get_id_of_path(&root, "a").await, 1);
416            assert_eq!(get_id_of_path(&root, "a/b").await, 2);
417            assert_eq!(get_id_of_path(&root, "a/c").await, 3);
418        });
419    }
420
421    #[test]
422    fn two_files() {
423        let mut tree = TreeBuilder::empty_dir();
424        tree.add_entry("a", file::read_only(b"A content")).unwrap();
425        tree.add_entry("b", file::read_only(b"B content")).unwrap();
426
427        let root = tree.build();
428
429        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
430            assert_read_dirents_one_listing!(
431                root, 1000,
432                { DIRECTORY, b"." },
433                { FILE, b"a" },
434                { FILE, b"b" },
435            );
436            open_as_vmo_file_assert_content!(
437                &root,
438                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
439                "a",
440                "A content"
441            );
442            open_as_vmo_file_assert_content!(
443                &root,
444                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
445                "b",
446                "B content"
447            );
448
449            assert_close!(root);
450        });
451    }
452
453    #[test]
454    fn overlapping_paths() {
455        let mut tree = TreeBuilder::empty_dir();
456        tree.add_entry(&["one", "two"], file::read_only(b"A")).unwrap();
457        tree.add_entry(&["one", "three"], file::read_only(b"B")).unwrap();
458        tree.add_entry("four", file::read_only(b"C")).unwrap();
459
460        let root = tree.build();
461
462        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
463            assert_read_dirents_one_listing!(
464                root, 1000,
465                { DIRECTORY, b"." },
466                { FILE, b"four" },
467                { DIRECTORY, b"one" },
468            );
469            assert_read_dirents_path_one_listing!(
470                &root, "one", 1000,
471                { DIRECTORY, b"." },
472                { FILE, b"three" },
473                { FILE, b"two" },
474            );
475
476            open_as_vmo_file_assert_content!(
477                &root,
478                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
479                "one/two",
480                "A"
481            );
482            open_as_vmo_file_assert_content!(
483                &root,
484                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
485                "one/three",
486                "B"
487            );
488            open_as_vmo_file_assert_content!(
489                &root,
490                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
491                "four",
492                "C"
493            );
494
495            assert_close!(root);
496        });
497    }
498
499    #[test]
500    fn directory_leaf() {
501        let etc = pseudo_directory! {
502            "fstab" => file::read_only(b"/dev/fs /"),
503            "ssh" => pseudo_directory! {
504                "sshd_config" => file::read_only(b"# Empty"),
505            },
506        };
507
508        let mut tree = TreeBuilder::empty_dir();
509        tree.add_entry("etc", etc).unwrap();
510        tree.add_entry("uname", file::read_only(b"Fuchsia")).unwrap();
511
512        let root = tree.build();
513
514        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
515            assert_read_dirents_one_listing!(
516                root, 1000,
517                { DIRECTORY, b"." },
518                { DIRECTORY, b"etc" },
519                { FILE, b"uname" },
520            );
521            assert_read_dirents_path_one_listing!(
522                &root, "etc", 1000,
523                { DIRECTORY, b"." },
524                { FILE, b"fstab" },
525                { DIRECTORY, b"ssh" },
526            );
527            assert_read_dirents_path_one_listing!(
528                &root, "etc/ssh", 1000,
529                { DIRECTORY, b"." },
530                { FILE, b"sshd_config" },
531            );
532
533            open_as_vmo_file_assert_content!(
534                &root,
535                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
536                "etc/fstab",
537                "/dev/fs /"
538            );
539            open_as_vmo_file_assert_content!(
540                &root,
541                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
542                "etc/ssh/sshd_config",
543                "# Empty"
544            );
545            open_as_vmo_file_assert_content!(
546                &root,
547                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
548                "uname",
549                "Fuchsia"
550            );
551
552            assert_close!(root);
553        });
554    }
555
556    #[test]
557    fn add_empty_dir_populate_later() {
558        let mut tree = TreeBuilder::empty_dir();
559        tree.add_empty_dir(&["one", "two"]).unwrap();
560        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
561
562        let root = tree.build();
563
564        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
565            assert_read_dirents_one_listing!(
566                root, 1000,
567                { DIRECTORY, b"." },
568                { DIRECTORY, b"one" },
569            );
570            assert_read_dirents_path_one_listing!(
571                &root, "one", 1000,
572                { DIRECTORY, b"." },
573                { DIRECTORY, b"two" },
574            );
575            assert_read_dirents_path_one_listing!(
576                &root, "one/two", 1000,
577                { DIRECTORY, b"." },
578                { FILE, b"three" },
579            );
580
581            open_as_vmo_file_assert_content!(
582                &root,
583                fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::DESCRIBE,
584                "one/two/three",
585                "B"
586            );
587
588            assert_close!(root);
589        });
590    }
591
592    #[test]
593    fn add_empty_dir_already_exists() {
594        let mut tree = TreeBuilder::empty_dir();
595        tree.add_entry(&["one", "two", "three"], file::read_only(b"B")).unwrap();
596        tree.add_empty_dir(&["one", "two"]).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 lone_add_empty_dir() {
630        let mut tree = TreeBuilder::empty_dir();
631        tree.add_empty_dir(&["just-me"]).unwrap();
632
633        let root = tree.build();
634
635        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
636            assert_read_dirents_one_listing!(
637                root, 1000,
638                { DIRECTORY, b"." },
639                { DIRECTORY, b"just-me" },
640            );
641            assert_read_dirents_path_one_listing!(
642                &root, "just-me", 1000,
643                { DIRECTORY, b"." },
644            );
645            assert_close!(root);
646        });
647    }
648
649    #[test]
650    fn add_empty_dir_inside_add_empty_dir() {
651        let mut tree = TreeBuilder::empty_dir();
652        tree.add_empty_dir(&["container"]).unwrap();
653        tree.add_empty_dir(&["container", "nested"]).unwrap();
654
655        let root = tree.build();
656
657        run_server_client(fio::OpenFlags::RIGHT_READABLE, root, |root| async move {
658            assert_read_dirents_one_listing!(
659                root, 1000,
660                { DIRECTORY, b"." },
661                { DIRECTORY, b"container" },
662            );
663            assert_read_dirents_path_one_listing!(
664                &root, "container", 1000,
665                { DIRECTORY, b"." },
666                { DIRECTORY, b"nested" },
667            );
668            assert_read_dirents_path_one_listing!(
669                &root, "container/nested", 1000,
670                { DIRECTORY, b"." },
671            );
672            assert_close!(root);
673        });
674    }
675
676    #[test]
677    fn error_empty_path_in_add_entry() {
678        let mut tree = TreeBuilder::empty_dir();
679        let err = tree
680            .add_entry(vec![], file::read_only(b"Invalid"))
681            .expect_err("Empty paths are not allowed.");
682        assert_eq!(err, Error::EmptyPath);
683    }
684
685    #[test]
686    fn error_slash_in_component() {
687        let mut tree = TreeBuilder::empty_dir();
688        let err = tree
689            .add_entry("a/b", file::read_only(b"Invalid"))
690            .expect_err("Slash in path component name.");
691        assert_eq!(
692            err,
693            Error::SlashInComponent { path: "a/b".to_string(), component: "a/b".to_string() }
694        );
695    }
696
697    #[test]
698    fn error_slash_in_second_component() {
699        let mut tree = TreeBuilder::empty_dir();
700        let err = tree
701            .add_entry(&["a", "b/c"], file::read_only(b"Invalid"))
702            .expect_err("Slash in path component name.");
703        assert_eq!(
704            err,
705            Error::SlashInComponent { path: "a/b/c".to_string(), component: "b/c".to_string() }
706        );
707    }
708
709    #[test]
710    fn error_component_name_too_long() {
711        let mut tree = TreeBuilder::empty_dir();
712
713        let long_component = "abcdefghij".repeat(fio::MAX_NAME_LENGTH as usize / 10 + 1);
714
715        let path: &[&str] = &["a", &long_component, "b"];
716        let err = tree
717            .add_entry(path, file::read_only(b"Invalid"))
718            .expect_err("Individual component names may not exceed MAX_FILENAME bytes.");
719        assert_eq!(
720            err,
721            Error::ComponentNameTooLong {
722                path: format!("a/{}/b", long_component),
723                component: long_component.clone(),
724                component_len: long_component.len(),
725                max_len: (fio::MAX_NAME_LENGTH - 1) as usize,
726            }
727        );
728    }
729
730    #[test]
731    fn error_leaf_over_directory() {
732        let mut tree = TreeBuilder::empty_dir();
733
734        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
735        let err = tree
736            .add_entry(&["top", "nested"], file::read_only(b"Invalid"))
737            .expect_err("A leaf may not be constructed over a directory.");
738        assert_eq!(err, Error::LeafOverDirectory { path: "top/nested".to_string() });
739    }
740
741    #[test]
742    fn error_leaf_over_leaf() {
743        let mut tree = TreeBuilder::empty_dir();
744
745        tree.add_entry(&["top", "nested", "file"], file::read_only(b"Content")).unwrap();
746        let err = tree
747            .add_entry(&["top", "nested", "file"], file::read_only(b"Invalid"))
748            .expect_err("A leaf may not be constructed over another leaf.");
749        assert_eq!(err, Error::LeafOverLeaf { path: "top/nested/file".to_string() });
750    }
751
752    #[test]
753    fn error_entry_inside_leaf() {
754        let mut tree = TreeBuilder::empty_dir();
755
756        tree.add_entry(&["top", "file"], file::read_only(b"Content")).unwrap();
757        let err = tree
758            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
759            .expect_err("A leaf may not be constructed over another leaf.");
760        assert_eq!(
761            err,
762            Error::EntryInsideLeaf {
763                path: "top/file/nested".to_string(),
764                traversed: "top/file".to_string()
765            }
766        );
767    }
768
769    #[test]
770    fn error_entry_inside_leaf_directory() {
771        let mut tree = TreeBuilder::empty_dir();
772
773        // Even when a leaf is itself a directory the tree builder cannot insert a nested entry.
774        tree.add_entry(&["top", "file"], Simple::new()).unwrap();
775        let err = tree
776            .add_entry(&["top", "file", "nested"], file::read_only(b"Invalid"))
777            .expect_err("A leaf may not be constructed over another leaf.");
778        assert_eq!(
779            err,
780            Error::EntryInsideLeaf {
781                path: "top/file/nested".to_string(),
782                traversed: "top/file".to_string()
783            }
784        );
785    }
786}