#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::MySerialize;
use std::collections::{BTreeMap, HashMap};
use std::fmt::Debug;
use std::hash::Hash;
pub trait Changeable {
fn is_changed(&self) -> bool;
fn is_unchanged(obj: &Self) -> bool {
!obj.is_changed()
}
}
pub trait Diffable {
type Repr: Changeable + Debug + for<'de> MySerialize<'de>;
fn diff(&self, b: &Self) -> Self::Repr;
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(rename_all = "lowercase", tag = "type", content = "value")
)]
pub enum PrimitiveDiff<T>
where
T: Diffable,
{
Changed {
old: T,
new: T,
},
#[default]
Unchanged,
}
impl<T> PartialEq for PrimitiveDiff<T>
where
T: Diffable + PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::Changed {
old: l_old,
new: l_new,
},
Self::Changed {
old: r_old,
new: r_new,
},
) => l_old == r_old && l_new == r_new,
(Self::Unchanged, Self::Unchanged) => true,
_ => false,
}
}
}
impl<T> Changeable for PrimitiveDiff<T>
where
T: Diffable,
{
fn is_changed(&self) -> bool {
!matches!(self, Self::Unchanged)
}
}
#[doc(hidden)]
macro_rules! impl_ints {
($ty:ty) => {
impl Diffable for $ty {
type Repr = PrimitiveDiff<$ty>;
fn diff(&self, b: &Self) -> Self::Repr {
if self == b {
PrimitiveDiff::Unchanged
} else {
PrimitiveDiff::Changed { old: *self, new: *b }
}
}
}
};
($($ty:ty),*) => {
$(impl_ints!($ty);)*
};
}
impl_ints!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, bool);
#[doc(hidden)]
macro_rules! impl_floats {
($ty:ty) => {
impl Diffable for $ty {
type Repr = PrimitiveDiff<$ty>;
fn diff(&self, b: &Self) -> Self::Repr {
if (b - self).abs() <= <$ty>::EPSILON {
PrimitiveDiff::Unchanged
} else {
PrimitiveDiff::Changed { old: *self, new: *b }
}
}
}
};
($($ty:ty),*) => {
$(impl_floats!($ty);)*
};
}
impl_floats!(f32, f64);
impl Diffable for String {
type Repr = PrimitiveDiff<String>;
fn diff(&self, b: &Self) -> Self::Repr {
if self == b {
PrimitiveDiff::Unchanged
} else {
PrimitiveDiff::Changed {
old: self.clone(),
new: b.clone(),
}
}
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(rename_all = "lowercase", tag = "type", content = "value")
)]
pub enum CollectionDiffEntry<T: Diffable> {
Removed(T),
Added(T),
Changed(<T as Diffable>::Repr),
#[default]
Unchanged,
}
impl<T> PartialEq for CollectionDiffEntry<T>
where
T: Diffable + PartialEq,
<T as Diffable>::Repr: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Removed(l), Self::Removed(r)) => l == r,
(Self::Added(l), Self::Added(r)) => l == r,
(Self::Changed(l), Self::Changed(r)) => l == r,
(Self::Unchanged, Self::Unchanged) => true,
_ => false,
}
}
}
impl<T: Diffable> Changeable for CollectionDiffEntry<T> {
fn is_changed(&self) -> bool {
!matches!(self, Self::Unchanged)
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HashMapDiff<K, T>(pub HashMap<K, CollectionDiffEntry<T>>)
where
K: Hash + Eq,
T: Diffable;
impl<K, T> Changeable for HashMapDiff<K, T>
where
K: Hash + Eq,
T: Diffable,
{
fn is_changed(&self) -> bool {
self.0.values().any(|v| v.is_changed())
}
}
impl<K, T> Diffable for HashMap<K, T>
where
K: Hash + Eq + Debug + Clone,
T: Diffable + Debug + Clone,
for<'de> T: MySerialize<'de>,
for<'de> K: MySerialize<'de>,
{
type Repr = HashMapDiff<K, T>;
fn diff(&self, b: &Self) -> Self::Repr {
let mut out = HashMap::new();
for (k, v) in self {
let other = b.get(k);
match other {
Some(other) => {
let diff = v.diff(other);
if diff.is_changed() {
out.insert(k.clone(), CollectionDiffEntry::Changed(diff))
} else {
out.insert(k.clone(), CollectionDiffEntry::Unchanged)
}
}
None => out.insert(k.clone(), CollectionDiffEntry::Removed(v.clone())),
};
}
for (k, v) in b {
if out.contains_key(k) {
continue;
}
out.insert(k.clone(), CollectionDiffEntry::Added(v.clone()));
}
HashMapDiff(out)
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct BTreeMapDiff<K, T>(pub BTreeMap<K, CollectionDiffEntry<T>>)
where
K: Hash + Eq + Ord,
T: Diffable;
impl<K, T> Changeable for BTreeMapDiff<K, T>
where
K: Hash + Eq + Ord,
T: Diffable,
{
fn is_changed(&self) -> bool {
self.0.values().any(|v| v.is_changed())
}
}
impl<K, T> Diffable for BTreeMap<K, T>
where
K: Hash + Eq + Ord + Debug + Clone,
T: Diffable + Debug + Clone,
for<'de> T: MySerialize<'de>,
for<'de> K: MySerialize<'de>,
{
type Repr = BTreeMapDiff<K, T>;
fn diff(&self, b: &Self) -> Self::Repr {
let mut out = BTreeMap::new();
for (k, v) in self {
let other = b.get(k);
match other {
Some(other) => {
let diff = v.diff(other);
if diff.is_changed() {
out.insert(k.clone(), CollectionDiffEntry::Changed(diff))
} else {
out.insert(k.clone(), CollectionDiffEntry::Unchanged)
}
}
None => out.insert(k.clone(), CollectionDiffEntry::Removed(v.clone())),
};
}
for (k, v) in b {
if out.contains_key(k) {
continue;
}
out.insert(k.clone(), CollectionDiffEntry::Added(v.clone()));
}
BTreeMapDiff(out)
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct VecDiff<T: Diffable>(pub Vec<CollectionDiffEntry<T>>);
impl<'de, T> PartialEq for VecDiff<T>
where
T: Diffable + PartialEq + MySerialize<'de>,
<T as Diffable>::Repr: PartialEq + MySerialize<'de>,
{
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Changeable for VecDiff<T>
where
T: Diffable + PartialEq,
{
fn is_changed(&self) -> bool {
self.0.iter().any(|d| d.is_changed())
}
}
impl<T> Diffable for Vec<T>
where
T: Diffable + Debug + Clone + PartialEq,
for<'de> T: MySerialize<'de>,
{
type Repr = VecDiff<T>;
fn diff(&self, b: &Self) -> Self::Repr {
let mut out = vec![];
let len = self.len().max(b.len());
for i in 0..len {
let old = self.get(i);
let new = b.get(i);
match (old, new) {
(Some(a), None) => out.push(CollectionDiffEntry::Removed(a.clone())),
(Some(a), Some(b)) => {
let diff = a.diff(b);
if diff.is_changed() {
out.push(CollectionDiffEntry::Changed(diff))
} else {
out.push(CollectionDiffEntry::Unchanged)
}
}
(None, None) => out.push(CollectionDiffEntry::Unchanged),
(None, Some(b)) => out.push(CollectionDiffEntry::Added(b.clone())),
}
}
VecDiff(out)
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(rename_all = "lowercase", tag = "type", content = "value")
)]
pub enum OptionDiff<T: Diffable> {
Removed(T),
Added(T),
Changed(<T as Diffable>::Repr),
#[default]
Unchanged,
}
impl<T> PartialEq for OptionDiff<T>
where
T: Diffable + PartialEq,
<T as Diffable>::Repr: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Removed(l), Self::Removed(r)) => l == r,
(Self::Added(l), Self::Added(r)) => l == r,
(Self::Changed(l), Self::Changed(r)) => l == r,
(Self::Unchanged, Self::Unchanged) => true,
_ => false,
}
}
}
impl<T: Diffable> Changeable for OptionDiff<T> {
fn is_changed(&self) -> bool {
!matches!(self, Self::Unchanged)
}
}
impl<T> Diffable for Option<T>
where
T: Diffable + Clone + Debug,
for<'de> T: MySerialize<'de>,
{
type Repr = OptionDiff<T>;
fn diff(&self, b: &Self) -> Self::Repr {
match (self, b) {
(Some(a), Some(b)) => {
let diffed = a.diff(b);
if diffed.is_changed() {
OptionDiff::Changed(diffed)
} else {
OptionDiff::Unchanged
}
}
(Some(a), None) => OptionDiff::Removed(a.clone()),
(None, Some(a)) => OptionDiff::Added(a.clone()),
(None, None) => OptionDiff::Unchanged,
}
}
}