phab_lib/storage/
storage_fs.rs1use std::collections::HashMap;
2use std::fs;
3use std::path::Path;
4use std::path::PathBuf;
5
6use anyhow::Error;
7use slugify::slugify;
8use thiserror::Error;
9
10use crate::dto::Task;
11use crate::dto::Watchlist;
12use crate::storage::storage::PhabStorage;
13use crate::types::ResultAnyError;
14
15type Table = HashMap<String, Watchlist>;
16type FileDB = HashMap<String, Table>;
17
18pub struct PhabStorageFilesystem {
19 pub filepath: PathBuf,
20 db_content: FileDB,
21}
22
23impl PhabStorageFilesystem {
24 pub fn new(filepath: impl AsRef<Path>) -> ResultAnyError<PhabStorageFilesystem> {
25 let mut storage = PhabStorageFilesystem {
26 db_content: HashMap::new(),
27 filepath: PathBuf::from(filepath.as_ref()),
28 };
29
30 storage.reload()?;
31
32 return Ok(storage);
33 }
34}
35
36impl PhabStorageFilesystem {
37 fn watchlist_table(&mut self) -> &mut Table {
38 return self
39 .db_content
40 .entry("watchlists".to_owned())
41 .or_insert(HashMap::new());
42 }
43
44 fn reload(&mut self) -> ResultAnyError<()> {
45 if !self.filepath.exists() {
46 let _ = self.watchlist_table();
47
48 self.persist()?;
49 }
50
51 let str = fs::read_to_string(&self.filepath)?;
52 self.db_content = serde_json::from_str(&str)?;
53
54 return Ok(());
55 }
56
57 fn persist(&self) -> ResultAnyError<()> {
58 if !self.filepath.exists() {
60 let mut cloned_filepath = self.filepath.clone();
61 cloned_filepath.pop();
62
63 fs::create_dir_all(&cloned_filepath)?;
64 }
65
66 let content = serde_json::to_string(&self.db_content)?;
68 fs::write(&self.filepath, content)?;
69
70 return Ok(());
71 }
72}
73
74#[derive(Debug, Error)]
75enum PhabStorageFilesystemError {
76 #[error("PhabStorageFilesystemError err: {message:?}")]
77 QueryError { message: String },
78}
79
80impl PhabStorageFilesystemError {
81 fn query_error(message: &str) -> Error {
82 return PhabStorageFilesystemError::QueryError {
83 message: message.to_owned(),
84 }
85 .into();
86 }
87}
88
89impl PhabStorage for PhabStorageFilesystem {
90 fn add_to_watchlist(&mut self, watchlist_id: &str, task: &Task) -> ResultAnyError<()> {
91 self
92 .watchlist_table()
93 .get_mut(watchlist_id)
94 .unwrap()
95 .tasks
96 .push(task.clone());
97
98 self.persist()?;
99
100 return Ok(());
101 }
102
103 fn create_watchlist(&mut self, watchlist: &Watchlist) -> ResultAnyError<Watchlist> {
104 let watchlists = self.db_content.get_mut("watchlists").unwrap();
105 let watchlist_id = slugify!(&watchlist.name);
106 let mut watchlist = watchlist.clone();
107
108 watchlist.id = Some(watchlist_id.clone());
109 watchlists.insert(watchlist_id, watchlist.to_owned());
110
111 self.persist()?;
112
113 return Ok(watchlist);
114 }
115
116 fn get_watchlists(&mut self) -> ResultAnyError<Vec<Watchlist>> {
117 let watchlists: Vec<Watchlist> = self.watchlist_table().values().cloned().collect();
118
119 return Ok(watchlists);
120 }
121
122 fn get_watchlist_by_id(&mut self, watchlist_id: &str) -> ResultAnyError<Option<Watchlist>> {
123 let watchlist = self.watchlist_table().get(watchlist_id).map(Clone::clone);
124
125 return Ok(watchlist);
126 }
127}
128
129#[cfg(test)]
130mod test {
131 use super::*;
132 use std::fs;
133
134 struct DirCleaner {
135 dir: PathBuf,
136 }
137
138 impl Drop for DirCleaner {
139 fn drop(&mut self) {
140 if self.dir.exists() {
141 fs::remove_dir_all(&self.dir).unwrap();
142 }
143 }
144 }
145
146 mod reload {
147 use super::*;
148 use fake::Fake;
149 use fake::Faker;
150
151 fn create_new(db_dir_path: PathBuf) -> ResultAnyError<PhabStorageFilesystem> {
152 let mut storage = PhabStorageFilesystem {
153 db_content: HashMap::new(),
154 filepath: PathBuf::from(format!(
155 "{}/{}",
156 db_dir_path.into_os_string().into_string().unwrap(),
157 "yo.json"
158 )),
159 };
160
161 storage.reload()?;
162
163 return Ok(storage);
164 }
165
166 fn test_db_dir(fn_name: &str) -> PathBuf {
167 return PathBuf::from(format!("/tmp/__phab_for_testing/db_{}", fn_name));
168 }
169
170 #[test]
171 fn it_should_create_dir_and_load_data_for_first_time() -> ResultAnyError<()> {
172 let db_dir_path = test_db_dir(function_name!());
173 let _dir_cleaner = DirCleaner {
174 dir: db_dir_path.clone(),
175 };
176 let mut storage = test::reload::create_new(db_dir_path)?;
177
178 let watchlists = storage.get_watchlists()?;
179
180 assert_eq!(watchlists.len(), 0);
181
182 return Ok(());
183 }
184
185 #[test]
186 fn it_should_insert_data() -> ResultAnyError<()> {
187 let db_dir_path = test_db_dir(function_name!());
188 let _dir_cleaner = DirCleaner {
189 dir: db_dir_path.clone(),
190 };
191 let mut storage = test::reload::create_new(db_dir_path)?;
192 let watchlist = Watchlist {
193 id: None,
194 name: String::from("hey ho test watchlist"),
195 tasks: vec![],
196 };
197
198 storage.create_watchlist(&watchlist)?;
199 let watchlists = storage.get_watchlists()?;
200
201 assert_eq!(watchlists.len(), 1);
202 assert_eq!(
203 watchlists.get(0).unwrap().id.as_ref().unwrap(),
204 "hey-ho-test-watchlist"
205 );
206
207 storage.reload()?;
209 assert_eq!(watchlists.len(), 1);
210 assert_eq!(
211 watchlists.get(0).unwrap().id.as_ref().unwrap(),
212 "hey-ho-test-watchlist"
213 );
214
215 return Ok(());
216 }
217
218 #[test]
219 fn it_should_add_to_watchlist() -> ResultAnyError<()> {
220 let db_dir_path = test_db_dir(function_name!());
221 let _dir_cleaner = DirCleaner {
222 dir: db_dir_path.clone(),
223 };
224 let mut storage = test::reload::create_new(db_dir_path)?;
225 let watchlist = Watchlist {
226 id: None,
227 name: String::from("hey ho test watchlist"),
228 tasks: vec![],
229 };
230
231 let mut task_1: Task = Faker.fake();
232 task_1.id = "foo".to_owned();
233
234 let mut task_2: Task = Faker.fake();
235 task_2.id = "Bar".to_owned();
236
237 let watchlist = storage.create_watchlist(&watchlist)?;
238 let watchlist_id = watchlist.id.unwrap();
239 storage.add_to_watchlist(&watchlist_id, &task_1)?;
240 storage.add_to_watchlist(&watchlist_id, &task_2)?;
241
242 let watchlist = storage.get_watchlist_by_id(&watchlist_id)?;
243
244 assert!(watchlist.is_some());
245
246 let tasks: Vec<Task> = watchlist.unwrap().tasks;
247
248 assert_eq!(tasks.len(), 2);
249 assert_eq!(tasks.get(0).unwrap().id, "foo");
250 assert_eq!(tasks.get(1).unwrap().id, "Bar");
251
252 return Ok(());
253 }
254 }
255}