1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3use crate::{JoydbError, Model};
4
5#[derive(Debug)]
10pub struct Relation<M: Model> {
11 pub(crate) meta: RelationMeta,
14
15 pub(crate) records: Vec<M>,
17}
18
19impl<M> Default for Relation<M>
20where
21 M: Model,
22{
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl<M: Model> Relation<M> {
29 pub(crate) fn new() -> Self {
31 Relation {
32 meta: RelationMeta::default(),
33 records: Vec::new(),
34 }
35 }
36
37 pub fn new_with_records(records: Vec<M>) -> Self {
42 Relation {
43 meta: RelationMeta::default(),
44 records,
45 }
46 }
47
48 pub fn is_dirty(&self) -> bool {
52 self.meta.is_dirty
53 }
54
55 pub fn reset_dirty(&mut self) {
59 self.meta.is_dirty = false;
60 }
61
62 pub fn records(&self) -> &[M] {
65 &self.records
66 }
67
68 pub(crate) fn insert(&mut self, record: &M) -> Result<(), JoydbError> {
69 let id = record.id();
70 let is_duplicated = self.records.iter().any(|m| m.id() == id);
71 if is_duplicated {
72 Err(JoydbError::DuplicatedId {
73 id: format!("{:?}", id),
74 model: M::model_name().to_owned(),
75 })
76 } else {
77 self.records.push(record.clone());
78 self.meta.is_dirty = true;
79 Ok(())
80 }
81 }
82
83 pub(crate) fn get(&self, id: &M::Id) -> Result<Option<M>, JoydbError> {
84 let maybe_record = self.records.iter().find(|m| m.id() == id).cloned();
85 Ok(maybe_record)
86 }
87
88 pub(crate) fn get_all(&self) -> Result<Vec<M>, JoydbError> {
89 Ok(self.records.to_vec())
90 }
91
92 pub(crate) fn get_all_by<F>(&self, predicate: F) -> Result<Vec<M>, JoydbError>
94 where
95 F: Fn(&M) -> bool,
96 {
97 let filtered_records = self
98 .records
99 .iter()
100 .filter(|m| predicate(m))
101 .cloned()
102 .collect();
103 Ok(filtered_records)
104 }
105
106 pub(crate) fn count(&self) -> Result<usize, JoydbError> {
107 Ok(self.records.len())
108 }
109
110 pub(crate) fn update(&mut self, new_record: &M) -> Result<(), JoydbError> {
111 let id = new_record.id();
112
113 if let Some(m) = self.records.iter_mut().find(|m| m.id() == id) {
114 *m = new_record.clone();
115 self.meta.is_dirty = true;
116 Ok(())
117 } else {
118 Err(JoydbError::NotFound {
119 id: format!("{:?}", id),
120 model: M::model_name().to_owned(),
121 })
122 }
123 }
124
125 pub(crate) fn upsert(&mut self, record: &M) -> Result<(), JoydbError> {
126 let target_id = record.id();
127 let maybe_target_record = self.records.iter_mut().find(|m| m.id() == target_id);
128 if let Some(target_record) = maybe_target_record {
129 *target_record = record.clone();
130 } else {
131 self.records.push(record.clone());
132 }
133 self.meta.is_dirty = true;
134 Ok(())
135 }
136
137 pub(crate) fn delete(&mut self, id: &M::Id) -> Result<Option<M>, JoydbError> {
138 let index = self.records.iter().position(|m| m.id() == id);
139 if let Some(index) = index {
140 let record = self.records.remove(index);
141 self.meta.is_dirty = true;
142 Ok(Some(record))
143 } else {
144 Ok(None)
145 }
146 }
147
148 pub(crate) fn delete_all_by<F>(&mut self, predicate: F) -> Result<Vec<M>, JoydbError>
149 where
150 F: Fn(&M) -> bool,
151 {
152 let mut deleted_records = Vec::new();
153 let mut retained_records = Vec::with_capacity(self.records.len());
154
155 for record in self.records.drain(..) {
156 if predicate(&record) {
157 deleted_records.push(record);
158 self.meta.is_dirty = true;
159 } else {
160 retained_records.push(record);
161 }
162 }
163 self.records = retained_records;
164
165 Ok(deleted_records)
166 }
167}
168
169#[derive(Debug, Default)]
172pub struct RelationMeta {
173 pub(crate) is_dirty: bool,
174}
175
176impl<M: Model> Serialize for Relation<M> {
178 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179 where
180 S: Serializer,
181 {
182 self.records.serialize(serializer)
183 }
184}
185
186impl<'de, M: Model> Deserialize<'de> for Relation<M> {
188 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189 where
190 D: Deserializer<'de>,
191 {
192 let models = Vec::<M>::deserialize(deserializer)?;
193 Ok(Relation {
194 meta: RelationMeta::default(),
195 records: models,
196 })
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use serde::{Deserialize, Serialize};
204
205 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
206 struct Post {
207 id: u32,
208 title: String,
209 }
210
211 impl Model for Post {
212 type Id = u32;
213
214 fn id(&self) -> &Self::Id {
215 &self.id
216 }
217
218 fn model_name() -> &'static str {
219 "Post"
220 }
221 }
222
223 fn sample_posts() -> Vec<Post> {
224 vec![first_post(), second_post(), third_post()]
225 }
226
227 fn first_post() -> Post {
228 Post {
229 id: 1,
230 title: "First".to_string(),
231 }
232 }
233
234 fn second_post() -> Post {
235 Post {
236 id: 2,
237 title: "Second".to_string(),
238 }
239 }
240
241 fn third_post() -> Post {
242 Post {
243 id: 3,
244 title: "Third".to_string(),
245 }
246 }
247
248 fn sample_relation() -> Relation<Post> {
249 Relation {
250 meta: RelationMeta { is_dirty: false },
251 records: sample_posts(),
252 }
253 }
254
255 mod serialization_and_deserialization {
256 use super::*;
257
258 #[test]
259 fn test_serialize_relation() {
260 let relation = Relation {
261 meta: RelationMeta { is_dirty: false },
262 records: sample_posts(),
263 };
264
265 let json = serde_json::to_string(&relation).unwrap();
266 assert_eq!(
267 json,
268 r#"[{"id":1,"title":"First"},{"id":2,"title":"Second"},{"id":3,"title":"Third"}]"#
269 );
270 }
271
272 #[test]
273 fn test_deserialize_relation() {
274 let json = r#"[{"id":10,"title":"One"},{"id":20,"title":"Two"}]"#;
275
276 let relation: Relation<Post> = serde_json::from_str(json).unwrap();
277
278 assert_eq!(relation.records.len(), 2);
279 assert_eq!(relation.records[0].id, 10);
280 assert_eq!(relation.records[0].title, "One");
281 assert_eq!(relation.records[1].id, 20);
282 assert_eq!(relation.records[1].title, "Two");
283
284 assert_eq!(relation.meta.is_dirty, false);
286 }
287
288 #[test]
289 fn test_serialize_deserialize_roundtrip() {
290 let original = Relation {
291 meta: RelationMeta { is_dirty: true },
292 records: sample_posts(),
293 };
294
295 let json = serde_json::to_string(&original).unwrap();
296 let deserialized: Relation<Post> = serde_json::from_str(&json).unwrap();
297
298 assert_eq!(original.records, deserialized.records);
299 assert_eq!(deserialized.meta.is_dirty, false); }
301 }
302
303 mod insert {
304 use super::*;
305
306 #[test]
307 fn should_insert_new_record_and_mark_dirty() {
308 let mut relation = sample_relation();
309 assert_eq!(relation.records.len(), 3);
310 assert_eq!(relation.meta.is_dirty, false);
311
312 let post = Post {
313 id: 13,
314 title: "Thirteen".to_string(),
315 };
316 relation.insert(&post).unwrap();
317
318 assert_eq!(relation.records.len(), 4);
319 assert_eq!(relation.records[3], post);
320 assert_eq!(relation.meta.is_dirty, true);
321 }
322
323 #[test]
324 fn should_return_an_error_when_record_with_id_already_exists() {
325 let mut relation = Relation::new();
326 let post = Post {
327 id: 777,
328 title: "First".to_string(),
329 };
330 relation.insert(&post).unwrap();
331
332 let another_post = Post {
333 id: 777,
334 title: "Another First".to_string(),
335 };
336 let err = relation.insert(&another_post).unwrap_err();
337
338 assert!(matches!(err, JoydbError::DuplicatedId { .. }));
339 assert_eq!(
340 err.to_string(),
341 format!("Post with id = 777 already exists")
342 );
343 }
344 }
345
346 mod get {
347 use super::*;
348
349 #[test]
350 fn should_return_none_when_record_not_found() {
351 let relation = sample_relation();
352 let id = 999;
353 let maybe_post = relation.get(&id).unwrap();
354 assert!(maybe_post.is_none());
355 }
356
357 #[test]
358 fn should_return_record_when_found() {
359 let relation = sample_relation();
360 let id = 2;
361 let maybe_post = relation.get(&id).unwrap();
362 let post = maybe_post.unwrap();
363 assert_eq!(post, second_post());
364 }
365 }
366
367 mod get_all {
368 use super::*;
369
370 #[test]
371 fn should_return_all_records() {
372 let relation = sample_relation();
373 let all_posts = relation.get_all().unwrap();
374 assert_eq!(all_posts, sample_posts());
375 }
376 }
377
378 mod count {
379 use super::*;
380
381 #[test]
382 fn should_return_number_of_records() {
383 let relation = sample_relation();
384 let count = relation.count().unwrap();
385 assert_eq!(count, 3);
386 }
387 }
388
389 mod update {
390 use super::*;
391
392 #[test]
393 fn should_update_record_and_mark_dirty() {
394 let mut relation = sample_relation();
395 let new_post = Post {
396 id: 2,
397 title: "Updated Second".to_string(),
398 };
399 relation.update(&new_post).unwrap();
400
401 let updated_post = relation.get(&2).unwrap().unwrap();
402 assert_eq!(updated_post, new_post);
403 assert_eq!(relation.meta.is_dirty, true);
404 }
405
406 #[test]
407 fn should_return_error_when_record_not_found() {
408 let mut relation = sample_relation();
409 let new_post = Post {
410 id: 999,
411 title: "Updated Second".to_string(),
412 };
413 let err = relation.update(&new_post).unwrap_err();
414
415 assert!(matches!(err, JoydbError::NotFound { .. }));
416 assert_eq!(err.to_string(), format!("Post with id = 999 not found"));
417 }
418 }
419
420 mod delete {
421 use super::*;
422
423 #[test]
424 fn should_delete_record_and_mark_dirty() {
425 let mut relation = sample_relation();
426 let id = 1;
427 let deleted_post = relation.delete(&id).unwrap().unwrap();
428
429 assert_eq!(relation.records.len(), 2);
430 assert_eq!(relation.records[0], second_post());
431 assert_eq!(relation.meta.is_dirty, true);
432 assert_eq!(deleted_post, first_post());
433 }
434
435 #[test]
436 fn should_return_none_when_record_not_found() {
437 let mut relation = sample_relation();
438 let id = 555;
439 let maybe_post = relation.delete(&id).unwrap();
440 assert!(maybe_post.is_none());
441 assert_eq!(relation.records.len(), 3);
442 assert_eq!(relation.meta.is_dirty, false);
443 }
444 }
445
446 mod delete_all_by {
447 use super::*;
448
449 #[test]
450 fn should_delete_all_records_that_match_predicate() {
451 let mut relation = sample_relation();
452 assert_eq!(relation.records.len(), 3);
453
454 let deleted_records = relation.delete_all_by(|post: &Post| post.id >= 2).unwrap();
455
456 assert_eq!(deleted_records.len(), 2);
457 assert!(deleted_records.contains(&second_post()));
458 assert!(deleted_records.contains(&third_post()));
459
460 assert_eq!(relation.records.len(), 1);
461 assert_eq!(relation.records[0], first_post());
462 assert_eq!(relation.meta.is_dirty, true);
463 }
464
465 #[test]
466 fn should_not_delete_anything_if_no_record_matches_predicate() {
467 let mut relation = sample_relation();
468 assert_eq!(relation.records.len(), 3);
469
470 let deleted_records = relation.delete_all_by(|post: &Post| post.id > 777).unwrap();
471
472 assert_eq!(deleted_records.len(), 0);
473
474 assert_eq!(relation.records.len(), 3);
475 assert_eq!(relation.meta.is_dirty, false);
476 }
477 }
478
479 mod upsert {
480 use super::*;
481
482 #[test]
483 fn should_add_new_record_if_does_not_exist_yet() {
484 let mut relation = sample_relation();
485 assert_eq!(relation.records.len(), 3);
486
487 let post44 = Post {
488 id: 44,
489 title: "Forty Four".to_string(),
490 };
491 relation.upsert(&post44).unwrap();
492
493 assert_eq!(relation.records.len(), 4);
494 assert_eq!(relation.records[3], post44);
495 }
496
497 #[test]
498 fn should_update_existing_record_matched_by_id() {
499 let mut relation = sample_relation();
500 assert_eq!(relation.records.len(), 3);
501
502 let updated_post2 = Post {
503 id: 2,
504 title: "Updated Second!!!".to_string(),
505 };
506
507 relation.upsert(&updated_post2).unwrap();
508
509 assert_eq!(relation.records.len(), 3);
510 assert_eq!(relation.get(&2).unwrap(), Some(updated_post2));
511 }
512 }
513
514 #[test]
515 fn should_reset_dirty() {
516 let mut relation = sample_relation();
517 assert_eq!(relation.is_dirty(), false);
518
519 relation.delete(&1).unwrap();
520 assert_eq!(relation.is_dirty(), true);
521
522 relation.reset_dirty();
523 assert_eq!(relation.is_dirty(), false);
524 }
525}