1use std::collections::{HashMap, HashSet};
2
3pub struct Overlay {
6 files: HashMap<String, Vec<u8>>,
8 deleted: HashSet<String>,
10}
11
12impl Overlay {
13 pub fn new() -> Self {
14 Overlay {
15 files: HashMap::new(),
16 deleted: HashSet::new(),
17 }
18 }
19
20 pub fn write(&mut self, path: &str, data: Vec<u8>) {
22 self.deleted.remove(path);
23 self.files.insert(path.to_string(), data);
24 }
25
26 pub fn read(&self, path: &str) -> Option<&[u8]> {
28 if self.deleted.contains(path) {
29 return None;
30 }
31 self.files.get(path).map(|v| v.as_slice())
32 }
33
34 pub fn delete(&mut self, path: &str) {
36 self.files.remove(path);
37 self.deleted.insert(path.to_string());
38 }
39
40 pub fn is_deleted(&self, path: &str) -> bool {
42 self.deleted.contains(path)
43 }
44
45 pub fn modified_paths(&self) -> Vec<&str> {
47 let mut paths: Vec<&str> = self.files.keys().map(|s| s.as_str()).collect();
48 paths.extend(self.deleted.iter().map(|s| s.as_str()));
49 paths.sort();
50 paths
51 }
52
53 pub fn clear(&mut self) {
55 self.files.clear();
56 self.deleted.clear();
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.files.is_empty() && self.deleted.is_empty()
62 }
63}
64
65impl Default for Overlay {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn write_and_read_back() {
77 let mut overlay = Overlay::new();
78 overlay.write("/app/new.txt", b"hello".to_vec());
79 assert_eq!(overlay.read("/app/new.txt"), Some(b"hello".as_slice()));
80 }
81
82 #[test]
83 fn read_nonexistent_returns_none() {
84 let overlay = Overlay::new();
85 assert!(overlay.read("/missing").is_none());
86 }
87
88 #[test]
89 fn overwrite_replaces_data() {
90 let mut overlay = Overlay::new();
91 overlay.write("/file", b"v1".to_vec());
92 overlay.write("/file", b"v2".to_vec());
93 assert_eq!(overlay.read("/file"), Some(b"v2".as_slice()));
94 }
95
96 #[test]
97 fn delete_marks_as_deleted() {
98 let mut overlay = Overlay::new();
99 overlay.write("/file", b"data".to_vec());
100 overlay.delete("/file");
101 assert!(overlay.is_deleted("/file"));
102 assert!(overlay.read("/file").is_none());
103 }
104
105 #[test]
106 fn list_modified_paths() {
107 let mut overlay = Overlay::new();
108 overlay.write("/a", b"1".to_vec());
109 overlay.write("/b", b"2".to_vec());
110 overlay.delete("/c");
111 let paths = overlay.modified_paths();
112 assert_eq!(paths.len(), 3);
113 }
114
115 #[test]
116 fn clear_resets_state() {
117 let mut overlay = Overlay::new();
118 overlay.write("/file", b"data".to_vec());
119 overlay.clear();
120 assert!(overlay.read("/file").is_none());
121 assert!(overlay.modified_paths().is_empty());
122 }
123}