farmap/
user_collection.rs1use crate::fetch::DataReadError;
2use crate::has_tag::HasTag;
3use crate::user::InvalidInputError;
4use crate::user::User;
5use crate::user::UserError;
6use serde::Deserialize;
7use serde::Serialize;
8use std::collections::hash_map::Entry::Vacant;
9use std::collections::HashMap;
10use std::error::Error;
11use std::fs::File;
12use std::io::Read;
13use std::io::Write;
14use std::path::Path;
15use thiserror::Error;
16
17#[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
18pub struct UserCollection {
19 map: HashMap<usize, User>,
20}
21
22pub type CreateResult = Result<(UserCollection, Vec<DataCreationError>), DataCreationError>;
23
24impl UserCollection {
25 pub fn add_user_value_iter(
27 &mut self,
28 values: impl IntoIterator<Item = impl HasTag<u64>>,
29 ) -> Option<Vec<CollectionError>> {
30 let mut errors: Option<Vec<CollectionError>> = None;
31 for value in values {
32 if let Some(user) = self.user_mut(value.tag() as usize) {
33 let user_add_result = user
34 .add_user_value(value.untag().0)
35 .map_err(|_| CollectionError::UserValueCollisionError);
36 if let Err(add_result) = user_add_result {
37 if let Some(errors) = &mut errors {
38 errors.push(add_result);
39 } else {
40 errors = Some(vec![add_result])
41 }
42 }
43 } else {
44 let mut user = User::new_without_labels(value.tag() as usize);
45 user.add_user_value(value.untag().0)
46 .expect("new user cannot collide");
47 self.add_user(user).expect("new user cannot collide");
48 }
49 }
50 errors
51 }
52
53 pub fn user_mut(&mut self, fid: usize) -> Option<&mut User> {
54 self.map.get_mut(&fid)
55 }
56
57 #[allow(unused)]
58 pub(crate) fn user_mut_unchecked(&mut self, fid: usize) -> &mut User {
59 self.map
60 .get_mut(&fid)
61 .expect("fid {fid} should exist in collection")
62 }
63
64 pub fn user(&self, fid: usize) -> Option<&User> {
65 self.map.get(&fid)
66 }
67
68 pub fn user_count(&self) -> usize {
69 self.map.len()
70 }
71
72 pub fn create_from_db(db: &Path) -> Result<Self, DbReadError> {
75 let collection = serde_json::from_str(&std::fs::read_to_string(db)?)?;
76
77 Ok(collection)
78 }
79
80 pub fn create_from_file(file: &mut std::fs::File) -> Result<Self, DbReadError> {
81 let mut result = String::new();
82 file.read_to_string(&mut result)?;
83 Ok(serde_json::from_str(&result)?)
84 }
85
86 pub fn save_to_db(&self, db: &Path) -> Result<(), Box<dyn Error>> {
87 let mut file = File::create(db)?;
88 let json_text = serde_json::to_string(self)?;
89 file.write_all(json_text.as_bytes())?;
90 Ok(())
91 }
92
93 pub fn apply_filter<F>(&mut self, filter: F)
96 where
97 F: Fn(&User) -> bool,
98 {
99 let old_map = std::mem::take(&mut self.map);
100 let new_map = old_map
101 .into_values()
102 .filter(|user| filter(user))
103 .map(|user| (user.fid(), user))
104 .collect::<HashMap<usize, User>>();
105 self.map = new_map;
106 }
107
108 pub fn iter(&self) -> impl Iterator<Item = &User> {
109 self.map.values()
110 }
111
112 pub fn data(&self) -> &HashMap<usize, User> {
113 &self.map
114 }
115
116 pub fn add_user(&mut self, user: User) -> Result<(), CollectionError> {
117 let fid = user.fid();
118 if let Vacant(v) = self.map.entry(fid) {
119 v.insert(user);
120 Ok(())
121 } else {
122 Err(CollectionError::DuplicateUserError)
123 }
124 }
125}
126
127#[derive(Error, Debug, PartialEq, Clone, Hash)]
128#[non_exhaustive]
129pub enum CollectionError {
130 #[error("Tried to add colliding value")]
131 UserValueCollisionError,
132
133 #[error("user already exists in collection")]
134 DuplicateUserError,
135}
136
137#[derive(Error, Debug, PartialEq)]
138pub enum DataCreationError {
139 #[error("Input data is invalid.")]
140 InvalidInputError(#[from] InvalidInputError),
141
142 #[error("UserError")]
143 UserError(#[from] UserError),
144
145 #[error("Input is not readable or accessible")]
146 DataReadError(#[from] DataReadError),
147
148 #[error("DuplicateUserError")]
149 DuplicateUserError,
150}
151
152#[derive(Error, Debug)]
153pub enum DbReadError {
154 #[error("fs error")]
155 FSError(#[from] std::io::Error),
156
157 #[error("json error")]
158 JSONError(#[from] serde_json::Error),
159}
160
161#[cfg(test)]
162pub mod tests {
163 use super::*;
164 use crate::user_with_spam_data::tests::create_user_with_m_spam_scores;
165 use crate::UserValue;
166 use chrono::NaiveDate;
167 use std::path::PathBuf;
168
169 #[test]
170 pub fn test_user_count_on_dir_with_new() {
171 assert_eq!(dummy_data().user_count(), 2);
172 }
173
174 impl<T> HasTag<u64> for TestUserValue<T>
175 where
176 T: UserValue,
177 {
178 fn tag(&self) -> u64 {
179 self.fid
180 }
181
182 #[allow(refining_impl_trait)]
183 fn untag(self) -> (T, u64) {
184 (self.value, self.fid)
185 }
186 }
187
188 struct TestUserValue<T: UserValue> {
189 pub value: T,
190 pub fid: u64,
191 }
192
193 #[track_caller]
194 pub fn collection_from_fidded<T: HasTag<u64>>(
195 values: impl IntoIterator<Item = T>,
196 ) -> UserCollection {
197 let mut collection = UserCollection::default();
198 collection.add_user_value_iter(values);
199 collection
200 }
201
202 pub fn new_collection_from_user_value_iter<T>(
203 values: impl IntoIterator<Item = T>,
204 ) -> UserCollection
205 where
206 T: UserValue,
207 {
208 let mut collection = UserCollection::default();
209 let res = collection.add_user_value_iter(values.into_iter().enumerate().map(|(n, x)| {
210 TestUserValue {
211 value: x,
212 fid: n as u64 + 1,
213 }
214 }));
215 assert!(res.is_none()); collection
217 }
218
219 pub fn empty_collection() -> UserCollection {
220 UserCollection::default()
221 }
222
223 pub fn basic_single_user_test_collection_with_n_spam_updates(n: u64) -> UserCollection {
224 let date = NaiveDate::parse_from_str("2020-1-1", "%Y-%m-%d").unwrap();
225 let user = create_user_with_m_spam_scores(1, n, date);
226
227 let mut user_collection = UserCollection::default();
228 user_collection
229 .add_user(user)
230 .expect("only one user in collection - cannot collide");
231 user_collection
232 }
233
234 pub fn dummy_data() -> UserCollection {
235 let db_path = PathBuf::from("data/dummy-data_db.json");
236 UserCollection::create_from_db(&db_path).unwrap()
237 }
238
239 mod add_user {
240
241 use super::*;
242
243 #[track_caller]
244 pub fn check_add_user(collection: &mut UserCollection, user: User) {
245 collection.add_user(user).unwrap()
246 }
247 }
248
249 mod user_count {
250 use super::add_user::check_add_user;
251 use crate::user::tests::create_user;
252
253 use super::*;
254
255 #[track_caller]
256 fn check_user_count(collection: &UserCollection, n: usize) {
257 assert_eq!(collection.user_count(), n)
258 }
259
260 #[test]
261 fn test_empty_user_count() {
262 let collection = empty_collection();
263 check_user_count(&collection, 0);
264 }
265
266 #[test]
267 fn test_non_empty_user_count() {
268 let mut collection = empty_collection();
269 check_add_user(&mut collection, create_user(1));
270 check_add_user(&mut collection, create_user(2));
271 check_user_count(&collection, 2);
272 }
273 }
274}