use crate::traits::{DiffType, Entity, EntityKey, EntityKind};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Collection<E: Entity> {
entities: Vec<E>,
index: HashMap<EntityKey, usize>,
}
impl<E: Entity> Default for Collection<E> {
fn default() -> Self {
Self::new()
}
}
impl<E: Entity> Collection<E> {
pub fn new() -> Self {
Self {
entities: Vec::new(),
index: HashMap::new(),
}
}
pub fn push(&mut self, entity: E) -> bool {
let key = entity.key();
if self.index.contains_key(&key) {
return false;
}
let idx = self.entities.len();
self.entities.push(entity);
self.index.insert(key, idx);
true
}
pub fn get(&self, key: &EntityKey) -> Option<&E> {
self.index.get(key).map(|&idx| &self.entities[idx])
}
pub fn contains(&self, key: &EntityKey) -> bool {
self.index.contains_key(key)
}
pub fn delete(&mut self, key: &EntityKey) -> Option<E> {
if let Some(&idx) = self.index.get(key) {
self.index.remove(key);
let removed = self.entities.swap_remove(idx);
if idx < self.entities.len() {
let swapped_key = self.entities[idx].key();
self.index.insert(swapped_key, idx);
}
Some(removed)
} else {
None
}
}
pub fn list(&self) -> &[E] {
&self.entities
}
pub fn list_mut(&mut self) -> &mut Vec<E> {
&mut self.entities
}
pub fn rebuild_index(&mut self) {
self.index.clear();
for (idx, entity) in self.entities.iter().enumerate() {
self.index.insert(entity.key(), idx);
}
}
pub fn is_empty(&self) -> bool {
self.entities.is_empty()
}
pub fn len(&self) -> usize {
self.entities.len()
}
pub fn iter(&self) -> impl Iterator<Item = &E> {
self.entities.iter()
}
pub fn into_vec(self) -> Vec<E> {
self.entities
}
pub fn update_where<P, F>(&mut self, predicate: P, mut transform: F)
where
P: Fn(&E) -> bool,
F: FnMut(&mut E),
{
for entity in self.entities.iter_mut() {
if predicate(entity) {
transform(entity);
}
}
self.rebuild_index();
}
pub fn filter<P>(&self, predicate: P) -> Vec<&E>
where
P: Fn(&E) -> bool,
{
self.entities.iter().filter(|e| predicate(*e)).collect()
}
}
#[derive(Debug, Clone)]
pub struct EntityDiff<E: Entity> {
pub diff_type: DiffType,
pub key: EntityKey,
pub left: Option<E>,
pub right: Option<E>,
}
impl<E: Entity> EntityDiff<E> {
pub fn kind(&self) -> EntityKind {
E::KIND
}
}
pub fn diff_collections<E: Entity>(
left: &Collection<E>,
right: &Collection<E>,
) -> Vec<EntityDiff<E>> {
let mut diffs = Vec::new();
for entity in left.iter() {
let key = entity.key();
if !right.contains(&key) {
diffs.push(EntityDiff {
diff_type: DiffType::Drop,
key,
left: Some(entity.clone()),
right: None,
});
}
}
for entity in right.iter() {
let key = entity.key();
if !left.contains(&key) {
diffs.push(EntityDiff {
diff_type: DiffType::Create,
key,
left: None,
right: Some(entity.clone()),
});
}
}
for left_entity in left.iter() {
let key = left_entity.key();
if let Some(right_entity) = right.get(&key)
&& left_entity != right_entity
{
diffs.push(EntityDiff {
diff_type: DiffType::Alter,
key,
left: Some(left_entity.clone()),
right: Some(right_entity.clone()),
});
}
}
diffs
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone, Debug, PartialEq)]
struct TestEntity {
name: String,
value: i32,
}
impl Entity for TestEntity {
const KIND: EntityKind = EntityKind::Table;
fn key(&self) -> EntityKey {
EntityKey::simple(&self.name)
}
}
#[test]
fn test_collection_push_and_get() {
let mut col: Collection<TestEntity> = Collection::new();
let e1 = TestEntity {
name: "foo".into(),
value: 1,
};
assert!(col.push(e1.clone()));
let e1_dup = TestEntity {
name: "foo".into(),
value: 2,
};
assert!(!col.push(e1_dup));
let key = EntityKey::simple("foo");
let got = col.get(&key).unwrap();
assert_eq!(got.value, 1);
}
#[test]
fn test_collection_delete() {
let mut col: Collection<TestEntity> = Collection::new();
col.push(TestEntity {
name: "a".into(),
value: 1,
});
col.push(TestEntity {
name: "b".into(),
value: 2,
});
col.push(TestEntity {
name: "c".into(),
value: 3,
});
let key_b = EntityKey::simple("b");
let removed = col.delete(&key_b);
assert!(removed.is_some());
assert_eq!(removed.unwrap().name, "b");
assert_eq!(col.len(), 2);
assert!(!col.contains(&key_b));
assert!(col.contains(&EntityKey::simple("a")));
assert!(col.contains(&EntityKey::simple("c")));
}
#[test]
fn test_diff_collections() {
let mut left: Collection<TestEntity> = Collection::new();
left.push(TestEntity {
name: "keep".into(),
value: 1,
});
left.push(TestEntity {
name: "drop".into(),
value: 2,
});
left.push(TestEntity {
name: "alter".into(),
value: 3,
});
let mut right: Collection<TestEntity> = Collection::new();
right.push(TestEntity {
name: "keep".into(),
value: 1,
});
right.push(TestEntity {
name: "create".into(),
value: 4,
});
right.push(TestEntity {
name: "alter".into(),
value: 99,
});
let diffs = diff_collections(&left, &right);
assert_eq!(diffs.len(), 3);
let dropped: Vec<_> = diffs
.iter()
.filter(|d| d.diff_type == DiffType::Drop)
.collect();
assert_eq!(dropped.len(), 1);
assert_eq!(dropped[0].left.as_ref().unwrap().name, "drop");
let created: Vec<_> = diffs
.iter()
.filter(|d| d.diff_type == DiffType::Create)
.collect();
assert_eq!(created.len(), 1);
assert_eq!(created[0].right.as_ref().unwrap().name, "create");
let altered: Vec<_> = diffs
.iter()
.filter(|d| d.diff_type == DiffType::Alter)
.collect();
assert_eq!(altered.len(), 1);
assert_eq!(altered[0].left.as_ref().unwrap().value, 3);
assert_eq!(altered[0].right.as_ref().unwrap().value, 99);
}
}