/* Copyright 2023, Savanni D'Gerinel This file is part of the Luminescent Dreams Tools. Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Lumeto. If not, see . */ use std::{ collections::{HashMap, HashSet}, hash::Hash, }; pub trait Constructable { fn new() -> Self; } #[derive(Clone, Debug, PartialEq)] pub enum Change { DeleteRecord(Key), UpdateRecord((Key, Value)), NewRecord(Value), } #[derive(Clone, Debug, Default)] pub struct Changeset { delete: HashSet, update: HashMap, new: HashMap, } impl Changeset { pub fn add(&mut self, r: Value) -> Key { let k = Key::new(); self.new.insert(k.clone(), r); k.clone() } pub fn update(&mut self, k: Key, v: Value) { match self.new.get_mut(&k) { Some(record) => *record = v, None => { let _ = self.update.insert(k, v); } } } pub fn delete(&mut self, k: Key) { let new_v = self.new.remove(&k); let updated_v = self.update.remove(&k); match (new_v.is_some(), updated_v.is_some()) { (false, false) => { let _ = self.delete.insert(k); } (true, false) => (), (false, true) => { let _ = self.delete.insert(k); } (true, true) => { panic!("state error: key exists in the new and updtade columns at once") } } } } impl From> for Vec> { fn from(changes: Changeset) -> Self { let Changeset { delete, update, new, } = changes; delete .into_iter() .map(|k| Change::DeleteRecord(k)) .chain( update .into_iter() .map(|(k, v)| Change::UpdateRecord((k, v))), ) .chain(new.into_values().map(|v| Change::NewRecord(v))) .collect() } } #[cfg(test)] mod tests { use super::*; use uuid::Uuid; #[derive(Clone, PartialEq, Eq, Hash, Default)] struct Id(Uuid); impl Constructable for Id { fn new() -> Self { Id(Uuid::new_v4()) } } #[test] fn it_generates_a_new_record() { let mut set: Changeset = Changeset::default(); set.add("efgh".to_string()); let changes = Vec::from(set.clone()); assert_eq!(changes.len(), 1); assert!(changes.contains(&Change::NewRecord("efgh".to_string()))); set.add("wxyz".to_string()); let changes = Vec::from(set); assert_eq!(changes.len(), 2); assert!(changes.contains(&Change::NewRecord("efgh".to_string()))); assert!(changes.contains(&Change::NewRecord("wxyz".to_string()))); } #[test] fn it_generates_a_delete_record() { let mut set: Changeset = Changeset::default(); let id1 = Id::new(); set.delete(id1.clone()); let changes = Vec::from(set.clone()); assert_eq!(changes.len(), 1); assert!(changes.contains(&Change::DeleteRecord(id1.clone()))); let id2 = Id::new(); set.delete(id2.clone()); let changes = Vec::from(set); assert_eq!(changes.len(), 2); assert!(changes.contains(&Change::DeleteRecord(id1))); assert!(changes.contains(&Change::DeleteRecord(id2))); } #[test] fn update_unrelated_records() { let mut set: Changeset = Changeset::default(); let id1 = Id::new(); let id2 = Id::new(); set.update(id1.clone(), "abcd".to_owned()); set.update(id2.clone(), "efgh".to_owned()); let changes = Vec::from(set); assert_eq!(changes.len(), 2); assert!(changes.contains(&Change::UpdateRecord((id1, "abcd".to_owned())))); assert!(changes.contains(&Change::UpdateRecord((id2, "efgh".to_owned())))); } #[test] fn delete_cancels_new() { let mut set: Changeset = Changeset::default(); let key = set.add("efgh".to_string()); set.delete(key); let changes = Vec::from(set); assert_eq!(changes.len(), 0); } #[test] fn delete_cancels_update() { let mut set: Changeset = Changeset::default(); let id = Id::new(); set.update(id.clone(), "efgh".to_owned()); set.delete(id.clone()); let changes = Vec::from(set); assert_eq!(changes.len(), 1); assert!(changes.contains(&Change::DeleteRecord(id))); } #[test] fn update_atop_new_is_new() { let mut set: Changeset = Changeset::default(); let key = set.add("efgh".to_owned()); set.update(key, "wxyz".to_owned()); let changes = Vec::from(set); assert_eq!(changes.len(), 1); assert!(changes.contains(&Change::NewRecord("wxyz".to_string()))); } #[test] fn updates_get_squashed() { let mut set: Changeset = Changeset::default(); let id1 = Id::new(); let id2 = Id::new(); set.update(id1.clone(), "efgh".to_owned()); set.update(id2.clone(), "efgh".to_owned()); let changes = Vec::from(set.clone()); assert_eq!(changes.len(), 2); assert!(changes.contains(&Change::UpdateRecord((id1.clone(), "efgh".to_string())))); assert!(changes.contains(&Change::UpdateRecord((id2.clone(), "efgh".to_string())))); set.update(id1.clone(), "wxyz".to_owned()); let changes = Vec::from(set); assert_eq!(changes.len(), 2); assert!(changes.contains(&Change::UpdateRecord((id1, "wxyz".to_string())))); assert!(changes.contains(&Change::UpdateRecord((id2, "efgh".to_string())))); } }