#![warn(missing_docs)]
extern crate self as delta_struct;
pub mod seq;
pub use delta_struct_macros::Delta;
pub use seq::{SeqDelta, Splice};
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::*;
#[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)]
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> {
#[delta_struct(
field_type = "unordered",
delta_leader = "/// This the foo type on the delta struct."
)]
foo: Vec<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: Vec<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: Vec<i32>,
}
#[derive(Clone, Debug, Delta, PartialEq)]
#[allow(dead_code)] struct DeviceConfig {
#[delta_struct(field_type = "unordered")]
pub services: Vec<String>,
#[delta_struct(field_type = "unordered")]
pub settings: Vec<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],
bar: false,
};
let new = SimpleCollectionWithGeneric {
foo: vec![3, 4, 5],
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 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();
if let Some(NewTypeDelta { field_0: Some(6) }) = delta.foo {
} else {
panic!();
}
assert_eq!(delta.bar, Some(true));
}
#[test]
fn default_type_respected() {
let old = AttributeTest {
foo: 5,
bar: 4,
baz: vec![],
};
let new = AttributeTest {
foo: 5,
bar: 4,
baz: vec![9, 4, 5],
};
let delta = Delta::delta(old, new).unwrap();
assert!(delta.foo.is_none());
assert!(delta.bar.is_none());
assert_eq!(delta.baz_add, vec![9, 4, 5]);
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],
}]
);
}
#[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().field_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, 3],
};
let new = AllFieldTypes {
scalar: 2,
delta: NewType(4),
unordered: vec![3, 4, 5],
};
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);
}
}