snix_castore/directoryservice/traversal/
descend_to.rs

1use crate::{Node, Path, directoryservice::DirectoryService};
2use tracing::{instrument, warn};
3
4use super::Error;
5
6/// This descends from a (root) node to the given (sub)path, returning the Node
7/// at that path, or none, if there's nothing at that path.
8#[instrument(skip(directory_service, path), fields(%path))]
9pub async fn descend_to<DS>(
10    directory_service: DS,
11    root_node: Node,
12    path: impl AsRef<Path> + std::fmt::Display,
13) -> Result<Option<Node>, Error>
14where
15    DS: DirectoryService,
16{
17    let mut parent_node = root_node;
18    for component in path.as_ref().components_bytes() {
19        match parent_node {
20            Node::File { .. } | Node::Symlink { .. } => {
21                // There's still some path left, but the parent node is no directory.
22                // This means the path doesn't exist, as we can't reach it.
23                return Ok(None);
24            }
25            Node::Directory { digest, .. } => {
26                // fetch the linked node from the directory_service.
27                let directory = directory_service
28                    .get(&digest)
29                    .await
30                    .map_err(|e| Error::GetFailure(digest, e))?
31                    .ok_or_else(|| {
32                        // If we didn't get the directory node that's linked, that's a store inconsistency, bail out!
33                        warn!(directory.digest = %digest, "directory does not exist");
34
35                        Error::NotFound(digest)
36                    })?;
37
38                // look for the component in the [Directory].
39                if let Some((_child_name, child_node)) = directory
40                    .into_nodes()
41                    .find(|(name, _node)| name.as_ref() == component)
42                {
43                    // child node found, update prev_node to that and continue.
44                    parent_node = child_node.clone();
45                } else {
46                    // child node not found means there's no such element inside the directory.
47                    return Ok(None);
48                };
49            }
50        }
51    }
52
53    // We traversed the entire path, so this must be the node.
54    Ok(Some(parent_node))
55}
56
57#[cfg(test)]
58mod tests {
59    use super::descend_to;
60    use crate::{
61        Node, PathBuf,
62        directoryservice::DirectoryService,
63        fixtures::{DIRECTORY_COMPLICATED, DIRECTORY_WITH_KEEP, EMPTY_BLOB_DIGEST},
64        utils::gen_test_directory_service,
65    };
66
67    #[tokio::test]
68    async fn test_descend_to() {
69        let directory_service = gen_test_directory_service();
70
71        let mut handle = directory_service.put_multiple_start();
72        handle
73            .put(DIRECTORY_WITH_KEEP.clone())
74            .await
75            .expect("must succeed");
76        handle
77            .put(DIRECTORY_COMPLICATED.clone())
78            .await
79            .expect("must succeed");
80
81        handle.close().await.expect("must upload");
82
83        // construct the node for DIRECTORY_COMPLICATED
84        let node_directory_complicated = Node::Directory {
85            digest: DIRECTORY_COMPLICATED.digest(),
86            size: DIRECTORY_COMPLICATED.size(),
87        };
88
89        // construct the node for DIRECTORY_COMPLICATED
90        let node_directory_with_keep = Node::Directory {
91            digest: DIRECTORY_WITH_KEEP.digest(),
92            size: DIRECTORY_WITH_KEEP.size(),
93        };
94
95        // construct the node for the .keep file
96        let node_file_keep = Node::File {
97            digest: *EMPTY_BLOB_DIGEST,
98            size: 0,
99            executable: false,
100        };
101
102        // traversal to an empty subpath should return the root node.
103        {
104            let resp = descend_to(
105                &directory_service,
106                node_directory_complicated.clone(),
107                "".parse::<PathBuf>().unwrap(),
108            )
109            .await
110            .expect("must succeed");
111
112            assert_eq!(Some(node_directory_complicated.clone()), resp);
113        }
114
115        // traversal to `keep` should return the node for DIRECTORY_WITH_KEEP
116        {
117            let resp = descend_to(
118                &directory_service,
119                node_directory_complicated.clone(),
120                "keep".parse::<PathBuf>().unwrap(),
121            )
122            .await
123            .expect("must succeed");
124
125            assert_eq!(Some(node_directory_with_keep), resp);
126        }
127
128        // traversal to `keep/.keep` should return the node for the .keep file
129        {
130            let resp = descend_to(
131                &directory_service,
132                node_directory_complicated.clone(),
133                "keep/.keep".parse::<PathBuf>().unwrap(),
134            )
135            .await
136            .expect("must succeed");
137
138            assert_eq!(Some(node_file_keep.clone()), resp);
139        }
140
141        // traversal to `void` should return None (doesn't exist)
142        {
143            let resp = descend_to(
144                &directory_service,
145                node_directory_complicated.clone(),
146                "void".parse::<PathBuf>().unwrap(),
147            )
148            .await
149            .expect("must succeed");
150
151            assert_eq!(None, resp);
152        }
153
154        // traversal to `v/oid` should return None (doesn't exist)
155        {
156            let resp = descend_to(
157                &directory_service,
158                node_directory_complicated.clone(),
159                "v/oid".parse::<PathBuf>().unwrap(),
160            )
161            .await
162            .expect("must succeed");
163
164            assert_eq!(None, resp);
165        }
166
167        // traversal to `keep/.keep/404` should return None (the path can't be
168        // reached, as keep/.keep already is a file)
169        {
170            let resp = descend_to(
171                &directory_service,
172                node_directory_complicated.clone(),
173                "keep/.keep/foo".parse::<PathBuf>().unwrap(),
174            )
175            .await
176            .expect("must succeed");
177
178            assert_eq!(None, resp);
179        }
180    }
181}