#![warn(missing_docs)]
extern crate self as delta_struct;
pub mod bag;
pub mod fingerprint;
pub mod index;
pub mod map;
pub mod seq;
pub mod version;
pub use bag::BagDelta;
pub use delta_struct_macros::{Delta, Fingerprint};
pub use fingerprint::{fingerprint_of, Fingerprint};
pub use index::{TryIndex, TryIndexMut};
pub use map::{KeyedDelta, MapDelta, MapEntry};
pub use seq::{SeqDelta, Splice};
pub use version::{Applied, Mismatch, Versioned, VersionedDelta};
pub trait Delta {
type Output;
fn delta(old: Self, new: Self) -> Option<Self::Output>;
fn apply_delta(&mut self, delta: Self::Output);
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
#[derive(Delta)]
#[allow(dead_code)] struct UnitType;
#[derive(Delta, Clone, Debug, PartialEq, Eq)]
#[delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq, Eq)]")]
struct NewType(i32);
#[derive(Delta)]
#[allow(dead_code)] struct NewTypeWithGeneric<T>(T);
#[derive(Delta)]
#[allow(dead_code)] struct InlineBoundNewType<T: Clone>(T);
#[derive(Delta)]
#[allow(dead_code)] struct WhereClauseNewType<T>(T)
where
T: Clone;
#[derive(Clone, Debug, Delta, PartialEq)]
#[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
struct Reading(
#[delta_struct(field_type = "unordered")] BTreeSet<i32>,
#[delta_struct(field_type = "ordered")] Vec<String>,
#[delta_struct(field_type = "delta")] NewType,
bool,
);
#[test]
fn tuple_struct_delta_keeps_field_positions() {
let old = Reading(
vec![1, 2].into_iter().collect(),
vec!["a".to_string()],
NewType(7),
false,
);
let new = Reading(
vec![2, 3].into_iter().collect(),
vec!["a".to_string(), "b".to_string()],
NewType(8),
true,
);
let delta = Delta::delta(old.clone(), new.clone()).unwrap();
assert_eq!(delta.0.add, vec![3]);
assert_eq!(delta.0.remove, vec![1]);
assert_eq!(
delta.1.splices,
vec![Splice {
at: 1,
remove: 0,
insert: vec!["b".to_string()],
}]
);
assert_eq!(delta.2, Some(NewTypeDelta(Some(8))));
assert_eq!(delta.3, Some(true));
let mut applied = old;
applied.apply_delta(delta);
assert_eq!(applied, new);
}
#[cfg(feature = "serde")]
#[test]
fn tuple_struct_delta_serializes_as_a_sequence() {
#[derive(Delta)]
#[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
struct Meters(i32, i32);
let delta = Delta::delta(Meters(1, 2), Meters(1, 3)).unwrap();
assert_eq!(serde_json::to_string(&delta).unwrap(), "[null,3]");
}
#[derive(Delta)]
struct InlineBoundGeneric<T: Clone> {
foo: T,
bar: bool,
}
#[derive(Delta)]
struct WhereClauseGeneric<T>
where
T: Clone,
{
foo: T,
bar: bool,
}
#[derive(Delta)]
struct InlineBoundDeltaField<T: Delta> {
#[delta_struct(field_type = "delta")]
foo: T,
}
#[derive(Delta)]
struct WhereClauseDeltaField<T>
where
T: Delta,
{
#[delta_struct(field_type = "delta")]
foo: T,
}
#[derive(Delta)]
struct SimpleType {
#[delta_struct(delta_leader = "/// This is foo.")]
foo: i32,
bar: bool,
}
#[derive(Delta)]
#[allow(dead_code)] struct SimpleTypeWithGeneric<T> {
foo: T,
bar: bool,
}
#[derive(Delta)]
struct SimpleCollectionWithGeneric<T: Ord> {
#[delta_struct(
field_type = "unordered",
delta_leader = "/// This the foo type on the delta struct."
)]
foo: BTreeSet<T>,
bar: bool,
}
#[derive(Delta)]
struct DeltaRecursion {
#[delta_struct(field_type = "delta")]
foo: NewType,
bar: bool,
}
#[derive(Delta)]
#[delta_struct(default = "unordered")]
struct AttributeTest {
#[delta_struct(field_type = "scalar")]
foo: i32,
#[delta_struct(field_type = "scalar")]
bar: i32,
baz: BTreeSet<i32>,
}
#[derive(Delta, Clone, Debug, PartialEq, Eq)]
struct AllFieldTypes {
#[delta_struct(field_type = "scalar")]
scalar: i32,
#[delta_struct(field_type = "delta")]
delta: NewType,
#[delta_struct(field_type = "unordered")]
unordered: HashSet<i32>,
}
#[derive(Clone, Debug, Delta, PartialEq)]
#[allow(dead_code)] struct DeviceConfig {
#[delta_struct(field_type = "unordered")]
pub services: HashSet<String>,
#[delta_struct(field_type = "unordered")]
pub settings: HashSet<String>,
pub thumbnail_request: i32,
pub speedtest_request: i32,
#[delta_struct(field_type = "delta")]
pub features: AllFieldTypes,
pub deprovision: bool,
}
#[test]
fn unordered_with_scalar() {
let old = SimpleCollectionWithGeneric {
foo: vec![1, 2, 3].into_iter().collect(),
bar: false,
};
let new = SimpleCollectionWithGeneric {
foo: vec![3, 4, 5].into_iter().collect(),
bar: true,
};
let delta = Delta::delta(old, new).unwrap();
assert_eq!(delta.foo.add, vec![4, 5]);
assert_eq!(delta.foo.remove, vec![1, 2]);
assert_eq!(delta.bar, Some(true));
}
#[test]
fn unordered_apply_round_trips() {
#[derive(Clone, Debug, Delta, PartialEq)]
struct Tags {
#[delta_struct(field_type = "unordered")]
labels: HashSet<i32>,
}
let tags = |labels: &[i32]| Tags {
labels: labels.iter().copied().collect(),
};
let cases: &[(&[i32], &[i32])] = &[
(&[1, 2, 3], &[3, 4, 5]),
(&[1, 2], &[1, 2, 3]),
(&[1, 2, 3], &[1, 2]),
(&[], &[1, 2, 3]),
(&[1, 2, 3], &[]),
(&[1, 2], &[3, 4]),
];
for (old, new) in cases {
let mut applied = tags(old);
let delta = Delta::delta(tags(old), tags(new)).unwrap();
applied.apply_delta(delta);
assert_eq!(applied, tags(new), "{:?} -> {:?}", old, new);
}
}
#[test]
fn unordered_apply_ignores_absent_removals() {
let old = AllFieldTypes {
scalar: 1,
delta: NewType(1),
unordered: vec![1, 2].into_iter().collect(),
};
let new = AllFieldTypes {
scalar: 1,
delta: NewType(1),
unordered: vec![2, 3].into_iter().collect(),
};
let mut applied = old.clone();
applied.apply_delta(Delta::delta(old.clone(), new.clone()).unwrap());
applied.apply_delta(Delta::delta(old, new.clone()).unwrap());
assert_eq!(applied, new);
}
#[cfg(feature = "serde")]
#[test]
fn unordered_delta_serializes_nested() {
#[derive(Delta)]
#[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
struct Device {
#[delta_struct(field_type = "unordered")]
services: BTreeSet<String>,
}
let device = |services: &[&str]| Device {
services: services.iter().map(|s| s.to_string()).collect(),
};
let delta = Delta::delta(device(&["ssh"]), device(&["mqtt"])).unwrap();
assert_eq!(
serde_json::to_string(&delta).unwrap(),
r#"{"services":{"add":["mqtt"],"remove":["ssh"]}}"#
);
}
#[test]
fn delta_false_positive_check() {
let old = NewType(5);
let new = NewType(5);
let delta = Delta::delta(old, new);
assert!(delta.is_none());
}
#[test]
fn scalar_delta_false_positive_check() {
let old = SimpleType { foo: 5, bar: false };
let new = SimpleType { foo: 5, bar: true };
let delta = Delta::delta(old, new).unwrap();
assert!(delta.foo.is_none());
assert_eq!(delta.bar, Some(true));
}
#[test]
fn delta_field() {
let old = DeltaRecursion {
foo: NewType(5),
bar: false,
};
let new = DeltaRecursion {
foo: NewType(6),
bar: true,
};
let delta = Delta::delta(old, new).unwrap();
assert_eq!(delta.foo, Some(NewTypeDelta(Some(6))));
assert_eq!(delta.bar, Some(true));
}
#[test]
fn default_type_respected() {
let old = AttributeTest {
foo: 5,
bar: 4,
baz: BTreeSet::new(),
};
let new = AttributeTest {
foo: 5,
bar: 4,
baz: vec![9, 4, 5].into_iter().collect(),
};
let delta = Delta::delta(old, new).unwrap();
assert!(delta.foo.is_none());
assert!(delta.bar.is_none());
assert_eq!(delta.baz.add, vec![4, 5, 9]);
assert_eq!(delta.baz.remove, Vec::<i32>::new());
}
#[derive(Clone, Debug, Delta, PartialEq)]
struct Playlist {
#[delta_struct(field_type = "ordered")]
tracks: Vec<String>,
shuffle: bool,
}
#[derive(Delta)]
#[delta_struct(default = "ordered")]
struct OrderedByDefault {
a: Vec<i32>,
b: Vec<i32>,
}
fn playlist(tracks: &[&str], shuffle: bool) -> Playlist {
Playlist {
tracks: tracks.iter().map(|t| t.to_string()).collect(),
shuffle,
}
}
#[test]
fn ordered_records_position() {
let delta = Delta::delta(
playlist(&["a", "b", "c"], false),
playlist(&["a", "x", "c"], false),
)
.unwrap();
assert_eq!(
delta.tracks.splices,
vec![Splice {
at: 1,
remove: 1,
insert: vec!["x".to_string()],
}]
);
assert_eq!(delta.shuffle, None);
}
#[test]
fn ordered_distinguishes_reorder_from_unordered() {
let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["b", "a"], false));
assert!(delta.is_some());
}
#[test]
fn ordered_false_positive_check() {
let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "b"], false));
assert!(delta.is_none());
}
#[test]
fn ordered_apply_round_trips() {
let cases: &[(&[&str], &[&str])] = &[
(&["a", "b", "c"], &["a", "x", "c"]),
(&["a", "b"], &["a", "b", "c"]),
(&["b", "c"], &["a", "b", "c"]),
(&["a", "b", "c"], &[]),
(&[], &["a", "b", "c"]),
(&["a", "b", "c", "d", "e"], &["a", "x", "c", "y", "e"]),
(&["a", "a", "a", "b"], &["a", "b", "a", "a"]),
(&["a", "b", "c"], &["c", "b", "a"]),
];
for (old, new) in cases {
let mut applied = playlist(old, true);
let delta = Delta::delta(playlist(old, false), playlist(new, true)).unwrap();
applied.apply_delta(delta);
assert_eq!(applied, playlist(new, true), "{:?} -> {:?}", old, new);
}
}
#[cfg(feature = "serde")]
#[test]
fn ordered_delta_serializes() {
let delta =
Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "c"], false)).unwrap();
let json = serde_json::to_string(&delta.tracks).unwrap();
assert_eq!(json, r#"{"splices":[{"at":1,"remove":1,"insert":["c"]}]}"#);
let round_tripped: SeqDelta<String> = serde_json::from_str(&json).unwrap();
let mut target = playlist(&["a", "b"], false);
seq::apply(&mut target.tracks, round_tripped);
assert_eq!(target.tracks, vec!["a".to_string(), "c".to_string()]);
}
#[test]
fn ordered_as_container_default() {
let delta = Delta::delta(
OrderedByDefault {
a: vec![1, 2],
b: vec![3],
},
OrderedByDefault {
a: vec![1, 2],
b: vec![3, 4],
},
)
.unwrap();
assert!(delta.a.is_empty());
assert_eq!(
delta.b.splices,
vec![Splice {
at: 1,
remove: 0,
insert: vec![4],
}]
);
}
#[derive(Clone, Debug, Delta, PartialEq, serde::Serialize)]
#[delta_struct(delta_leader = "#[derive(Debug, serde::Serialize)]")]
struct Service {
port: u16,
healthy: bool,
}
#[derive(Clone, Debug, Delta, PartialEq)]
struct Cluster {
#[delta_struct(field_type = "unordered-delta")]
services: HashMap<String, Service>,
region: String,
}
#[derive(Delta)]
#[delta_struct(default = "unordered-delta")]
#[allow(dead_code)] struct UnorderedDeltaByDefault {
a: HashMap<u8, NewType>,
b: BTreeMap<u8, NewType>,
}
#[derive(Delta)]
#[allow(dead_code)] struct UnorderedDeltaWithGeneric<K: std::hash::Hash + Eq, V: Delta> {
#[delta_struct(
field_type = "unordered-delta",
delta_leader = "/// One part of the change to `foo`."
)]
foo: HashMap<K, V>,
}
type Services<'a> = &'a [(&'a str, u16, bool)];
fn cluster(services: Services, region: &str) -> Cluster {
Cluster {
services: services
.iter()
.map(|(name, port, healthy)| {
(
name.to_string(),
Service {
port: *port,
healthy: *healthy,
},
)
})
.collect(),
region: region.to_string(),
}
}
#[test]
fn unordered_delta_diffs_values_in_place() {
let delta = Delta::delta(
cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
cluster(&[("web", 8080, true), ("db", 5432, true)], "us"),
)
.unwrap();
assert!(delta.services.add.is_empty());
assert!(delta.services.remove.is_empty());
assert_eq!(delta.services.change.len(), 1);
assert_eq!(delta.services.change[0].key, "web");
assert_eq!(delta.services.change[0].delta.port, Some(8080));
assert_eq!(delta.services.change[0].delta.healthy, None);
assert_eq!(delta.region, None);
}
#[test]
fn unordered_delta_adds_and_removes_by_key() {
let delta = Delta::delta(
cluster(&[("web", 80, true)], "us"),
cluster(&[("db", 5432, false)], "us"),
)
.unwrap();
assert_eq!(
delta.services.add,
vec![(
"db".to_string(),
Service {
port: 5432,
healthy: false
}
)]
);
assert_eq!(delta.services.remove, vec!["web".to_string()]);
assert!(delta.services.change.is_empty());
}
#[test]
fn unordered_delta_false_positive_check() {
let delta = Delta::delta(
cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
cluster(&[("db", 5432, true), ("web", 80, true)], "us"),
);
assert!(delta.is_none());
}
#[test]
fn unordered_delta_apply_round_trips() {
let cases: &[(Services, Services)] = &[
(&[("web", 80, true)], &[("web", 8080, true)]),
(&[("web", 80, true)], &[("web", 80, true), ("db", 1, false)]),
(&[("web", 80, true), ("db", 1, false)], &[("web", 80, true)]),
(&[("web", 80, true)], &[("db", 1, false)]),
(
&[("web", 80, true), ("db", 1, false), ("gone", 9, true)],
&[("web", 8080, true), ("db", 1, false), ("new", 7, false)],
),
(&[], &[("web", 80, true)]),
(&[("web", 80, true)], &[]),
];
for (old, new) in cases {
let mut applied = cluster(old, "us");
let delta = Delta::delta(cluster(old, "us"), cluster(new, "eu")).unwrap();
applied.apply_delta(delta);
assert_eq!(applied, cluster(new, "eu"), "{:?} -> {:?}", old, new);
}
}
#[test]
fn unordered_delta_over_a_btree_map() {
#[derive(Clone, Debug, Delta, PartialEq)]
struct Pairs {
#[delta_struct(field_type = "unordered-delta")]
entries: BTreeMap<u8, NewType>,
}
let pairs = |entries: &[(u8, i32)]| Pairs {
entries: entries.iter().map(|(k, v)| (*k, NewType(*v))).collect(),
};
let mut applied = pairs(&[(1, 10), (2, 20)]);
let delta = Delta::delta(pairs(&[(1, 10), (2, 20)]), pairs(&[(2, 21), (3, 30)])).unwrap();
assert_eq!(delta.entries.add, vec![(3, NewType(30))]);
assert_eq!(delta.entries.remove, vec![1]);
assert_eq!(delta.entries.change.len(), 1);
applied.apply_delta(delta);
assert_eq!(applied, pairs(&[(2, 21), (3, 30)]));
}
#[cfg(feature = "serde")]
#[test]
fn unordered_delta_serializes() {
#[derive(Delta)]
#[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
struct Fleet {
#[delta_struct(field_type = "unordered-delta")]
services: BTreeMap<String, Service>,
}
let fleet = |port| Fleet {
services: vec![(
"web".to_string(),
Service {
port,
healthy: true,
},
)]
.into_iter()
.collect(),
};
let delta = Delta::delta(fleet(80), fleet(8080)).unwrap();
assert_eq!(
serde_json::to_string(&delta).unwrap(),
r#"{"services":{"add":[],"remove":[],"change":[{"key":"web","delta":{"port":8080,"healthy":null}}]}}"#
);
}
#[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
#[delta_struct(delta_leader = "#[derive(Clone, Debug)]")]
struct Tracked {
name: String,
#[delta_struct(field_type = "unordered")]
tags: HashSet<String>,
revision: u32,
}
fn tracked(name: &str, tags: &[&str], revision: u32) -> Tracked {
Tracked {
name: name.to_string(),
tags: tags.iter().map(|t| t.to_string()).collect(),
revision,
}
}
#[test]
fn fingerprint_ignores_set_iteration_order() {
let forwards = tracked("a", &["x", "y", "z"], 1);
let backwards = tracked("a", &["z", "y", "x"], 1);
assert_eq!(fingerprint_of(&forwards), fingerprint_of(&backwards));
assert_ne!(
fingerprint_of(&forwards),
fingerprint_of(&tracked("a", &["x", "y"], 1))
);
assert_ne!(
fingerprint_of(&forwards),
fingerprint_of(&tracked("b", &["x", "y", "z"], 1))
);
}
#[test]
fn fingerprint_derives_on_enums_and_tuple_structs() {
#[derive(Fingerprint)]
enum Shape {
Empty,
Circle(u32),
Rect { w: u32, h: u32 },
}
#[derive(Fingerprint)]
struct Pair(u8, bool);
assert_ne!(
fingerprint_of(&Shape::Empty),
fingerprint_of(&Shape::Circle(0))
);
assert_ne!(
fingerprint_of(&Shape::Circle(1)),
fingerprint_of(&Shape::Rect { w: 1, h: 0 })
);
assert_eq!(
fingerprint_of(&Shape::Rect { w: 2, h: 3 }),
fingerprint_of(&Shape::Rect { w: 2, h: 3 })
);
assert_ne!(
fingerprint_of(&Pair(1, true)),
fingerprint_of(&Pair(1, false))
);
}
#[test]
fn fingerprint_is_stable_across_runs() {
assert_eq!(fingerprint_of(&0u8), 0xaf63bd4c8601b7df);
assert_eq!(fingerprint_of(&true), 0xaf63bc4c8601b62c);
assert_eq!(fingerprint_of(&"delta"), 0x3035df3ae9e50ee6);
}
#[test]
fn versioned_round_trips() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
let mut receiver = Versioned::new(tracked("a", &["x"], 1));
let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
assert_eq!((message.from, message.to), (0, 1));
assert_eq!(receiver.apply(message), Ok(Applied::Updated));
assert_eq!(receiver.get(), sender.get());
assert_eq!(receiver.version(), sender.version());
}
#[test]
fn versioned_no_change_burns_nothing() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
assert!(sender.commit(tracked("a", &["x"], 1)).is_none());
assert_eq!(sender.version(), 0);
}
#[test]
fn versioned_ignores_a_replayed_delta() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
let mut receiver = Versioned::new(tracked("a", &["x"], 1));
let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
assert_eq!(receiver.apply(message.clone()), Ok(Applied::Updated));
assert_eq!(receiver.apply(message), Ok(Applied::Stale));
assert_eq!(receiver.get(), sender.get());
}
#[test]
fn versioned_catches_a_dropped_delta() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
let mut receiver = Versioned::new(tracked("a", &["x"], 1));
let _lost = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
let second = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
assert_eq!(
receiver.apply(second),
Err(Mismatch::Gap {
expected: 0,
found: 1
})
);
assert_eq!(receiver.version(), 0);
assert_eq!(receiver.get(), &tracked("a", &["x"], 1));
}
#[test]
fn versioned_catches_drift_from_outside_the_stream() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
let mut receiver = Versioned::new(tracked("a", &["tampered"], 1));
let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
match receiver.apply(message) {
Err(Mismatch::Base { expected, found }) => assert_ne!(expected, found),
other => panic!("expected a base mismatch, got {:?}", other),
}
assert_eq!(receiver.version(), 0);
}
#[test]
fn versioned_resync_recovers() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
let mut receiver = Versioned::new(tracked("a", &["wrong"], 1));
let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
assert!(receiver.apply(message).is_err());
receiver = sender.clone();
let next = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
assert_eq!(receiver.apply(next), Ok(Applied::Updated));
assert_eq!(receiver.get(), sender.get());
}
#[test]
fn versioned_catches_a_wrong_result() {
let mut sender = Versioned::new(tracked("a", &["x"], 1));
let mut receiver = Versioned::new(tracked("a", &["x"], 1));
let mut message = sender.commit(tracked("a", &["x"], 2)).unwrap();
message.result ^= 1;
match receiver.apply(message) {
Err(Mismatch::Result { expected, found }) => assert_ne!(expected, found),
other => panic!("expected a result mismatch, got {:?}", other),
}
assert_eq!(receiver.version(), 0);
}
#[cfg(feature = "serde")]
#[test]
fn versioned_delta_serializes() {
#[derive(Clone, Delta, Fingerprint)]
#[delta_struct(delta_leader = "#[derive(serde::Serialize, serde::Deserialize)]")]
struct Config {
port: u16,
}
let mut sender = Versioned::new(Config { port: 80 });
let mut receiver = Versioned::new(Config { port: 80 });
let payload =
serde_json::to_string(&sender.commit(Config { port: 8080 }).unwrap()).unwrap();
let message: VersionedDelta<ConfigDelta> = serde_json::from_str(&payload).unwrap();
assert_eq!(receiver.apply(message), Ok(Applied::Updated));
assert_eq!(receiver.get().port, 8080);
}
#[test]
fn bounded_generics() {
let delta = Delta::delta(
InlineBoundGeneric { foo: 1, bar: false },
InlineBoundGeneric { foo: 2, bar: false },
)
.unwrap();
assert_eq!(delta.foo, Some(2));
assert_eq!(delta.bar, None);
let delta = Delta::delta(
WhereClauseGeneric { foo: 1, bar: false },
WhereClauseGeneric { foo: 2, bar: false },
)
.unwrap();
assert_eq!(delta.foo, Some(2));
assert_eq!(delta.bar, None);
}
#[test]
fn bounded_generics_with_delta_field() {
let delta = Delta::delta(
InlineBoundDeltaField { foo: NewType(1) },
InlineBoundDeltaField { foo: NewType(2) },
)
.unwrap();
assert_eq!(delta.foo.unwrap().0, Some(2));
let mut applied = WhereClauseDeltaField { foo: NewType(1) };
let delta = Delta::delta(
WhereClauseDeltaField { foo: NewType(1) },
WhereClauseDeltaField { foo: NewType(2) },
)
.unwrap();
applied.apply_delta(delta);
assert_eq!(applied.foo, NewType(2));
}
#[test]
fn apply_delta_all_field_types() {
let old = AllFieldTypes {
scalar: 1,
delta: NewType(3),
unordered: vec![1, 2, 3].into_iter().collect(),
};
let new = AllFieldTypes {
scalar: 2,
delta: NewType(4),
unordered: vec![3, 4, 5].into_iter().collect(),
};
let new_clone = new.clone();
let mut old_delta_applied = old.clone();
let delta = Delta::delta(old, new);
old_delta_applied.apply_delta(delta.unwrap());
assert_eq!(new_clone, old_delta_applied);
}
}