snix_castore/directoryservice/traversal/
descend_to.rs1use crate::{Node, Path, directoryservice::DirectoryService};
2use tracing::{instrument, warn};
3
4use super::Error;
5
6#[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 return Ok(None);
24 }
25 Node::Directory { digest, .. } => {
26 let directory = directory_service
28 .get(&digest)
29 .await
30 .map_err(|e| Error::GetFailure(digest, e))?
31 .ok_or_else(|| {
32 warn!(directory.digest = %digest, "directory does not exist");
34
35 Error::NotFound(digest)
36 })?;
37
38 if let Some((_child_name, child_node)) = directory
40 .into_nodes()
41 .find(|(name, _node)| name.as_ref() == component)
42 {
43 parent_node = child_node.clone();
45 } else {
46 return Ok(None);
48 };
49 }
50 }
51 }
52
53 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 let node_directory_complicated = Node::Directory {
85 digest: DIRECTORY_COMPLICATED.digest(),
86 size: DIRECTORY_COMPLICATED.size(),
87 };
88
89 let node_directory_with_keep = Node::Directory {
91 digest: DIRECTORY_WITH_KEEP.digest(),
92 size: DIRECTORY_WITH_KEEP.size(),
93 };
94
95 let node_file_keep = Node::File {
97 digest: *EMPTY_BLOB_DIGEST,
98 size: 0,
99 executable: false,
100 };
101
102 {
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 {
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 {
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 {
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 {
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 {
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}