Skip to main content

hd_watch/
pathmap.rs

1use std::collections::HashMap;
2use hd_cas::ContentHash;
3
4/// Bidirectional mapping between host filesystem paths and DAG node hashes.
5pub struct PathMap {
6    path_to_hash: HashMap<String, ContentHash>,
7    hash_to_path: HashMap<ContentHash, String>,
8}
9
10impl PathMap {
11    pub fn new() -> Self {
12        PathMap {
13            path_to_hash: HashMap::new(),
14            hash_to_path: HashMap::new(),
15        }
16    }
17
18    pub fn insert(&mut self, path: &str, hash: ContentHash) {
19        // Remove old mapping if path existed
20        if let Some(old_hash) = self.path_to_hash.remove(path) {
21            self.hash_to_path.remove(&old_hash);
22        }
23        self.path_to_hash.insert(path.to_string(), hash);
24        self.hash_to_path.insert(hash, path.to_string());
25    }
26
27    pub fn get_hash(&self, path: &str) -> Option<&ContentHash> {
28        self.path_to_hash.get(path)
29    }
30
31    pub fn get_path(&self, hash: &ContentHash) -> Option<&str> {
32        self.hash_to_path.get(hash).map(|s| s.as_str())
33    }
34
35    pub fn remove(&mut self, path: &str) {
36        if let Some(hash) = self.path_to_hash.remove(path) {
37            self.hash_to_path.remove(&hash);
38        }
39    }
40
41    pub fn all_paths(&self) -> Vec<&str> {
42        self.path_to_hash.keys().map(|s| s.as_str()).collect()
43    }
44}
45
46impl Default for PathMap {
47    fn default() -> Self {
48        Self::new()
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use hd_cas::ContentHash;
56
57    #[test]
58    fn insert_and_lookup_by_path() {
59        let mut map = PathMap::new();
60        let hash = ContentHash::from_bytes(b"file1");
61        map.insert("src/main.rs", hash);
62        assert_eq!(map.get_hash("src/main.rs"), Some(&hash));
63    }
64
65    #[test]
66    fn insert_and_lookup_by_hash() {
67        let mut map = PathMap::new();
68        let hash = ContentHash::from_bytes(b"file1");
69        map.insert("src/main.rs", hash);
70        assert_eq!(map.get_path(&hash), Some("src/main.rs"));
71    }
72
73    #[test]
74    fn update_replaces_mapping() {
75        let mut map = PathMap::new();
76        let h1 = ContentHash::from_bytes(b"v1");
77        let h2 = ContentHash::from_bytes(b"v2");
78        map.insert("file.rs", h1);
79        map.insert("file.rs", h2);
80        assert_eq!(map.get_hash("file.rs"), Some(&h2));
81        assert!(map.get_path(&h1).is_none()); // old hash removed
82    }
83
84    #[test]
85    fn remove_by_path() {
86        let mut map = PathMap::new();
87        let hash = ContentHash::from_bytes(b"data");
88        map.insert("file.rs", hash);
89        map.remove("file.rs");
90        assert!(map.get_hash("file.rs").is_none());
91        assert!(map.get_path(&hash).is_none());
92    }
93
94    #[test]
95    fn all_paths() {
96        let mut map = PathMap::new();
97        map.insert("a.rs", ContentHash::from_bytes(b"a"));
98        map.insert("b.rs", ContentHash::from_bytes(b"b"));
99        let paths = map.all_paths();
100        assert_eq!(paths.len(), 2);
101    }
102}