snix_castore/directoryservice/
traverse.rs

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