Skip to main content

delta_struct/
lib.rs

1//! Compute the difference (delta) between two instances of a type, and apply
2//! that difference to a third.
3//!
4//! Deriving [`Delta`] on a struct generates a companion "delta struct" holding
5//! only what changed, plus an implementation of the [`Delta`] trait that knows
6//! how to produce one and how to apply it. Pair it with `serde` and you can
7//! send updates over the wire without resending state that both sides already
8//! agree on.
9//!
10//! # Quick start
11//!
12//! ```
13//! use delta_struct::Delta;
14//!
15//! #[derive(Delta)]
16//! struct Config {
17//!     host: String,
18//!     port: u16,
19//! }
20//!
21//! let old = Config { host: "localhost".to_string(), port: 80 };
22//! let new = Config { host: "localhost".to_string(), port: 8080 };
23//!
24//! // `Config` gained a companion struct named `ConfigDelta`.
25//! let delta = Delta::delta(old, new).expect("the port changed");
26//! assert_eq!(delta.host, None);          // unchanged fields are `None`
27//! assert_eq!(delta.port, Some(8080));
28//!
29//! // Applying the delta to an older copy brings it up to date.
30//! let mut current = Config { host: "localhost".to_string(), port: 80 };
31//! current.apply_delta(delta);
32//! assert_eq!(current.port, 8080);
33//! ```
34//!
35//! Note that a single `use delta_struct::Delta;` imports both the trait and
36//! the derive macro. The trait has to be in scope wherever you derive it — the
37//! generated code refers to `Delta` by that name.
38//!
39//! [`Delta::delta`] returns [`None`] when nothing changed, so
40//! `if let Some(delta) = Delta::delta(old, new)` is the usual way to skip
41//! sending an empty update.
42//!
43//! # Field types
44//!
45//! Every field is diffed according to a *field type*, chosen with
46//! `#[delta_struct(field_type = "...")]`. The default is `"scalar"`, which can
47//! be changed per struct — see [Container attributes](#container-attributes).
48//!
49//! ## `scalar` (the default)
50//!
51//! The field is compared with `!=` and replaced wholesale. In the delta struct
52//! it becomes `Option<T>`: `Some(new_value)` when the two differ, [`None`]
53//! when they don't. Requires `T: PartialEq`.
54//!
55//! ## `unordered`
56//!
57//! The field is treated as a bag of elements whose order carries no meaning,
58//! so the delta records only which elements came and went: a [`BagDelta`],
59//! holding an `add` and a `remove`, both `Vec<Item>`.
60//!
61//! ```
62//! use delta_struct::Delta;
63//! use std::collections::HashSet;
64//!
65//! #[derive(Delta)]
66//! struct Device {
67//!     #[delta_struct(field_type = "unordered")]
68//!     services: HashSet<String>,
69//! }
70//!
71//! let device = |services: &[&str]| Device {
72//!     services: services.iter().map(|s| s.to_string()).collect(),
73//! };
74//!
75//! let delta = Delta::delta(device(&["ssh", "http"]), device(&["http", "mqtt"])).unwrap();
76//! assert_eq!(delta.services.add, vec!["mqtt".to_string()]);
77//! assert_eq!(delta.services.remove, vec!["ssh".to_string()]);
78//! ```
79//!
80//! The field has to be a **set** — a [`HashSet`](std::collections::HashSet) or
81//! a [`BTreeSet`](std::collections::BTreeSet). Formally it needs [`Extend`]
82//! and [`TryIndex`], this crate's fallible answer to
83//! [`Index`](std::ops::Index); the two std sets implement it, and you can
84//! implement it for your own collection. A [`Vec`] deliberately does not
85//! qualify — see [Limitations](#limitations).
86//!
87//! Every element of the old collection is looked up in the new one exactly
88//! once, so the cost of a diff is the cost of n lookups in whichever
89//! collection you picked: **O(n)** for a `HashSet`, O(n log n) for a
90//! `BTreeSet`. Applying one costs the same, since each removal is a lookup
91//! rather than a rebuild.
92//!
93//! `apply_delta` preserves membership but not position — additions land
94//! wherever the collection decides to put them. Use `ordered` where that
95//! matters.
96//!
97//! ## `unordered-delta`
98//!
99//! Like `unordered`, but for a collection of key/value entries whose values
100//! are worth diffing rather than resending. An entry whose key is on both
101//! sides is not a removal plus an addition: the two values are handed to
102//! [`Delta::delta`] and only the difference is recorded. The delta is a
103//! [`MapDelta`], holding an `add`, a `remove`, and a `change`.
104//!
105//! ```
106//! use delta_struct::Delta;
107//! use std::collections::HashMap;
108//!
109//! #[derive(Delta)]
110//! struct Service {
111//!     port: u16,
112//!     healthy: bool,
113//! }
114//!
115//! #[derive(Delta)]
116//! struct Cluster {
117//!     #[delta_struct(field_type = "unordered-delta")]
118//!     services: HashMap<String, Service>,
119//! }
120//!
121//! let cluster = |port| Cluster {
122//!     services: vec![("web".to_string(), Service { port, healthy: true })]
123//!         .into_iter()
124//!         .collect(),
125//! };
126//!
127//! let delta = Delta::delta(cluster(80), cluster(8080)).unwrap();
128//! // `web` stayed put, so all that travels is the one field that moved.
129//! assert!(delta.services.add.is_empty());
130//! assert!(delta.services.remove.is_empty());
131//! assert_eq!(delta.services.change[0].key, "web");
132//! assert_eq!(delta.services.change[0].delta.port, Some(8080));
133//! assert_eq!(delta.services.change[0].delta.healthy, None);
134//! ```
135//!
136//! The key is the collection's own — the `K` of a `HashMap<K, V>` — not
137//! something you nominate. The field has to be a **map**: a
138//! [`HashMap`](std::collections::HashMap) or a
139//! [`BTreeMap`](std::collections::BTreeMap). Formally it needs [`Extend`] and
140//! [`TryIndexMut`], its entry type needs [`MapEntry`] (implemented for
141//! `(K, V)`, which is what every std map iterates as), and its value type
142//! needs [`Delta`].
143//!
144//! [`TryIndexMut`] rather than [`TryIndex`] is what excludes sets here, and
145//! correctly so: applying a delta means mutating a value where it sits, which
146//! a set cannot allow without letting you invalidate the hash or ordering it
147//! filed the element under.
148//!
149//! Every key of the old collection is looked up in the new one exactly once,
150//! so as with `unordered` the cost is n lookups — **O(n)** for a `HashMap`,
151//! O(n log n) for a `BTreeMap`. Applying one preserves membership rather than
152//! position, also the same as `unordered`.
153//!
154//! ## `ordered`
155//!
156//! The field is diffed positionally with Myers' algorithm, and the delta is a
157//! minimal edit script: a [`SeqDelta`] holding [`Splice`]s that each say
158//! "at this index, drop this many items and put these in their place".
159//!
160//! ```
161//! use delta_struct::{Delta, Splice};
162//!
163//! #[derive(Delta)]
164//! struct Playlist {
165//!     #[delta_struct(field_type = "ordered")]
166//!     tracks: Vec<String>,
167//! }
168//!
169//! let old = Playlist { tracks: vec!["intro".to_string(), "b".to_string(), "outro".to_string()] };
170//! let new = Playlist { tracks: vec!["intro".to_string(), "x".to_string(), "outro".to_string()] };
171//!
172//! let delta = Delta::delta(old, new).unwrap();
173//! assert_eq!(
174//!     delta.tracks.splices,
175//!     vec![Splice { at: 1, remove: 1, insert: vec!["x".to_string()] }],
176//! );
177//! ```
178//!
179//! Splice positions index the *old* sequence and arrive sorted and
180//! non-overlapping, so applying one is a single forward pass. Reordering is a
181//! real change here where `unordered` would see none, and applying a delta
182//! reproduces the new sequence exactly, position included.
183//!
184//! The collection needs `IntoIterator` and `FromIterator`, and its items need
185//! `Hash + Eq`, because that is what indexing the sequences for Myers
186//! requires. This is the one field type that takes a [`Vec`], and so the only
187//! one that will diff a sequence at all — but `f64` is neither `Hash` nor
188//! `Eq`, so a `Vec<f64>` still has nowhere to go but `scalar`.
189//!
190//! ## `delta`
191//!
192//! The field is itself diffed recursively, which keeps a nested change from
193//! resending the whole subtree. Requires the field's type to implement
194//! [`Delta`]; the delta struct holds `Option<<T as Delta>::Output>`.
195//!
196//! ```
197//! use delta_struct::Delta;
198//!
199//! #[derive(Delta)]
200//! struct Inner {
201//!     a: i32,
202//!     b: i32,
203//! }
204//!
205//! #[derive(Delta)]
206//! struct Outer {
207//!     #[delta_struct(field_type = "delta")]
208//!     inner: Inner,
209//!     name: String,
210//! }
211//!
212//! let old = Outer { inner: Inner { a: 1, b: 2 }, name: "x".to_string() };
213//! let new = Outer { inner: Inner { a: 1, b: 3 }, name: "x".to_string() };
214//!
215//! let delta = Delta::delta(old, new).unwrap();
216//! let inner_delta = delta.inner.expect("`b` changed");
217//! assert_eq!(inner_delta.a, None);
218//! assert_eq!(inner_delta.b, Some(3));
219//! ```
220//!
221//! # Container attributes
222//!
223//! `#[delta_struct(...)]` on the struct itself accepts:
224//!
225//! - `default = "..."` — the field type used for fields without their own
226//!   `field_type`. Defaults to `"scalar"`.
227//! - `delta_leader = "..."` — tokens to emit immediately above the generated
228//!   struct. This is how you attach derives, doc comments, or any other
229//!   attribute to a type you never get to write by hand.
230//!
231//! ```
232//! use delta_struct::Delta;
233//! use std::collections::HashSet;
234//!
235//! #[derive(Delta)]
236//! #[delta_struct(
237//!     default = "unordered",
238//!     delta_leader = "/// The changes to a `Tags`.\n#[derive(Debug)]"
239//! )]
240//! struct Tags {
241//!     labels: HashSet<String>,
242//!     // Opt an individual field back out of the container default.
243//!     #[delta_struct(field_type = "scalar")]
244//!     revision: u32,
245//! }
246//!
247//! let old = Tags { labels: HashSet::new(), revision: 1 };
248//! let new = Tags {
249//!     labels: vec!["new".to_string()].into_iter().collect(),
250//!     revision: 2,
251//! };
252//! let delta = Delta::delta(old, new).unwrap();
253//! assert_eq!(format!("{:?}", delta.labels.add), r#"["new"]"#);
254//! assert_eq!(delta.revision, Some(2));
255//! ```
256//!
257//! `delta_leader` also works on individual fields, where it decorates the
258//! generated field instead of the generated struct.
259//!
260//! ```
261//! # use delta_struct::Delta;
262//! #[derive(Delta)]
263//! struct Host {
264//!     #[delta_struct(delta_leader = "/// The new port, if it moved.")]
265//!     port: u16,
266//! }
267//! ```
268//!
269//! # Working with serde
270//!
271//! For `scalar` and `delta` fields there is no serde integration to enable;
272//! `delta_leader` is the whole story. Put the derives on the generated struct
273//! and it serializes like anything else:
274//!
275//! ```
276//! use delta_struct::Delta;
277//!
278//! #[derive(Delta)]
279//! #[delta_struct(delta_leader = "#[derive(serde::Serialize, serde::Deserialize)]")]
280//! struct Config {
281//!     host: String,
282//!     port: u16,
283//! }
284//!
285//! let old = Config { host: "localhost".to_string(), port: 80 };
286//! let new = Config { host: "localhost".to_string(), port: 8080 };
287//!
288//! // Sender: there is no message to send at all when nothing changed.
289//! let payload = Delta::delta(old, new).map(|delta| serde_json::to_string(&delta).unwrap());
290//! assert_eq!(payload.as_deref(), Some(r#"{"host":null,"port":8080}"#));
291//!
292//! // Receiver applies it to whatever it already had.
293//! let mut config = Config { host: "localhost".to_string(), port: 80 };
294//! config.apply_delta(serde_json::from_str::<ConfigDelta>(&payload.unwrap()).unwrap());
295//! assert_eq!(config.port, 8080);
296//! ```
297//!
298//! Field-level `delta_leader` carries serde attributes just as well, so
299//! `skip_serializing_if` can keep unchanged fields out of the payload
300//! entirely rather than sending them as `null`:
301//!
302//! ```
303//! use delta_struct::Delta;
304//!
305//! #[derive(Delta)]
306//! #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
307//! struct Config {
308//!     #[delta_struct(delta_leader = "#[serde(skip_serializing_if = \"Option::is_none\")]")]
309//!     host: String,
310//!     #[delta_struct(delta_leader = "#[serde(skip_serializing_if = \"Option::is_none\")]")]
311//!     port: u16,
312//! }
313//!
314//! let old = Config { host: "localhost".to_string(), port: 80 };
315//! let new = Config { host: "localhost".to_string(), port: 8080 };
316//!
317//! let delta = Delta::delta(old, new).unwrap();
318//! assert_eq!(serde_json::to_string(&delta).unwrap(), r#"{"port":8080}"#);
319//! ```
320//!
321//! # Checking that a delta belongs
322//!
323//! [`Delta::apply_delta`] assumes the value it is handed equals the `old` the
324//! delta came from, and checks nothing. Over an unreliable transport that assumption breaks:
325//! a message is dropped, delivered twice, or arrives at a receiver whose state
326//! drifted for some other reason, and the two sides diverge in silence.
327//!
328//! [`Versioned`] is the opt-in fix. It pairs a value with a version counter
329//! and a [`Fingerprint`] of its contents, and refuses any delta that does not
330//! belong.
331//!
332//! ```
333//! use delta_struct::{Applied, Delta, Fingerprint, Mismatch, Versioned};
334//!
335//! #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
336//! #[delta_struct(delta_leader = "#[derive(Clone)]")]
337//! struct Config {
338//!     host: String,
339//!     port: u16,
340//! }
341//!
342//! let config = |port| Config { host: "localhost".to_string(), port };
343//!
344//! let mut sender = Versioned::new(config(80));
345//! let mut receiver = Versioned::new(config(80));
346//!
347//! let first = sender.commit(config(8080)).expect("the port changed");
348//! let second = sender.commit(config(9090)).expect("the port changed again");
349//!
350//! // Delivered twice: recognised and ignored.
351//! assert_eq!(receiver.apply(first.clone()), Ok(Applied::Updated));
352//! assert_eq!(receiver.apply(first), Ok(Applied::Stale));
353//! assert_eq!(receiver.apply(second), Ok(Applied::Updated));
354//! assert_eq!(receiver.get(), sender.get());
355//!
356//! // A delta from a stream this receiver never joined is refused rather than
357//! // half-applied.
358//! let mut stranger = Versioned::new(config(80));
359//! let orphan = Versioned::new(config(1)).commit(config(2)).unwrap();
360//! assert!(matches!(stranger.apply(orphan), Err(Mismatch::Base { .. })));
361//! ```
362//!
363//! Every [`VersionedDelta`] carries four numbers, each catching a failure the
364//! others cannot:
365//!
366//! | Field | Catches |
367//! | --- | --- |
368//! | `from`, `to` | A message dropped, reordered, or replayed. |
369//! | `base` | A receiver whose state drifted for any reason, including one that never came through this stream. |
370//! | `result` | The delta itself being wrong — mismatched schema versions, or a bug. |
371//!
372//! A rejected delta leaves the receiver untouched and its version unmoved, so
373//! a later delta in the same stream fails too rather than papering over the
374//! hole. The answer to any [`Mismatch`] is to resend the whole [`Versioned`],
375//! which serializes as a unit and carries the version the receiver resumes
376//! from.
377//!
378//! None of this touches the [`Delta`] trait, the derive, or any generated
379//! struct. If you are diffing locally rather than over a wire, you never name
380//! anything in this section and pay for none of it.
381//!
382//! ## `Fingerprint`
383//!
384//! [`Fingerprint`] is a separate derive because [`std::hash::Hash`] cannot do
385//! the job: it is not implemented for [`HashSet`](std::collections::HashSet)
386//! or [`HashMap`](std::collections::HashMap) — exactly the collections the
387//! `unordered` field types require — and its standard hasher is allowed to
388//! change between Rust releases, which would make a toolchain upgrade on one
389//! side of a connection look like corruption.
390//!
391//! So sets and maps fold commutatively, iteration order cannot reach the
392//! result, and the hash is pinned to FNV-1a constants written down in the
393//! source. The same value fingerprints identically on any platform and any
394//! Rust version. Unlike [`Delta`], it derives on enums too.
395//!
396//! Checking costs a full traversal of the state on each `commit` and each
397//! `apply` — cheaper than serializing it, but not free, which is the price of
398//! the `base` and `result` guarantees.
399//!
400//! # What gets generated
401//!
402//! For `struct Foo`, deriving [`Delta`] emits `struct FooDelta` with the same
403//! visibility as `Foo` and the same generic parameters, carrying over their
404//! bounds and `where` clause as written. All of its fields are
405//! `pub`, and by default it derives nothing at all — reach for `delta_leader`
406//! whenever you need `Debug`, `Clone`, or serde on it. (Likewise if your crate
407//! sets `#![deny(missing_docs)]`: the generated struct and its fields need doc
408//! comments supplied through `delta_leader`.)
409//!
410//! Every field type maps one source field onto exactly one delta field, so a
411//! delta struct always has the same fields in the same order as the struct it
412//! came from — only their types differ. A tuple struct's delta is a tuple
413//! struct in turn, so its fields keep their positions:
414//!
415//! ```
416//! use delta_struct::Delta;
417//!
418//! #[derive(Delta)]
419//! struct Meters(i32);
420//!
421//! let delta = Delta::delta(Meters(3), Meters(4)).unwrap();
422//! assert_eq!(delta.0, Some(4));
423//! ```
424//!
425//! # Limitations
426//!
427//! - **Structs only.** Enums and unions are rejected; there is no obvious
428//!   delta for a value that changed variant.
429//! - **Every type parameter gets a `PartialEq` bound** on the generated impl,
430//!   whether or not the field that uses it needs one.
431//! - **A unit struct's delta is always [`None`]**, as is that of a struct with
432//!   no fields — there is nothing that could differ.
433//! - **`ordered` items need `Hash + Eq`**, so float sequences are out. See
434//!   that section above.
435//! - **A [`Vec`] cannot be an `unordered` field.** Membership diffing goes
436//!   through [`TryIndex`], and a `Vec` has no sub-linear lookup to offer —
437//!   implementing it would only hide a quadratic scan behind an O(1)-looking
438//!   call. Use a [`HashSet`](std::collections::HashSet) or a
439//!   [`BTreeSet`](std::collections::BTreeSet), or `ordered` if position
440//!   matters.
441//! - **`unordered-delta` keys are the collection's own.** There is no way to
442//!   nominate a field of the value as the key, so a `Vec<Record>` has to
443//!   become a `HashMap<Id, Record>` to use it.
444//! - **[`Versioned`] assumes one writer per stream.** Two senders committing
445//!   against the same base both produce `from: 0`, and the second is rejected
446//!   rather than merged. Divergence is detected, not reconciled — reach for a
447//!   CRDT if you need concurrent writers.
448
449#![warn(missing_docs)]
450
451// The derive emits `::delta_struct::…` paths for the runtime items an
452// `ordered` field needs. That path has to resolve inside this crate too, or
453// the crate's own tests could not use its own derive.
454extern crate self as delta_struct;
455
456pub mod bag;
457pub mod fingerprint;
458pub mod index;
459pub mod map;
460pub mod seq;
461pub mod version;
462
463pub use bag::BagDelta;
464pub use delta_struct_macros::{Delta, Fingerprint};
465pub use fingerprint::{fingerprint_of, Fingerprint};
466pub use index::{TryIndex, TryIndexMut};
467pub use map::{KeyedDelta, MapDelta, MapEntry};
468pub use seq::{SeqDelta, Splice};
469pub use version::{Applied, Mismatch, Versioned, VersionedDelta};
470
471/// Computing the difference between two values, and applying it to a third.
472///
473/// You will normally derive this rather than implement it — see the
474/// [crate documentation](crate) for the derive's attributes and the shape of
475/// the type it generates. Implement it by hand when you want custom diffing
476/// for a type that other structs then reference with
477/// `#[delta_struct(field_type = "delta")]`.
478pub trait Delta {
479    /// The type describing a difference between two `Self` values.
480    ///
481    /// The derive sets this to the generated `{Self}Delta` struct.
482    type Output;
483
484    /// Computes what it would take to turn `old` into `new`.
485    ///
486    /// Returns [`None`] when the two are equivalent, which lets callers skip
487    /// sending or storing an update that would do nothing. Both values are
488    /// consumed: the delta takes ownership of whatever it needs from `new`.
489    fn delta(old: Self, new: Self) -> Option<Self::Output>;
490
491    /// Applies a delta in place.
492    ///
493    /// Applying the delta from `delta(old, new)` to a value equal to `old`
494    /// yields a value equal to `new` — with the caveat that `unordered` fields
495    /// preserve membership rather than order.
496    fn apply_delta(&mut self, delta: Self::Output);
497}
498#[cfg(test)]
499mod tests {
500    use super::*;
501    use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
502
503    #[derive(Delta)]
504    #[allow(dead_code)] // The derive is itself the test
505    struct UnitType;
506
507    #[derive(Delta, Clone, Debug, PartialEq, Eq)]
508    #[delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq, Eq)]")]
509    struct NewType(i32);
510
511    #[derive(Delta)]
512    #[allow(dead_code)] // The derive is itself the test
513    struct NewTypeWithGeneric<T>(T);
514
515    // A tuple struct's delta is a tuple struct, which puts its `where` clause
516    // after the fields rather than before them. Both spellings of a bound have
517    // to survive that.
518    #[derive(Delta)]
519    #[allow(dead_code)] // The derive is itself the test
520    struct InlineBoundNewType<T: Clone>(T);
521
522    #[derive(Delta)]
523    #[allow(dead_code)] // The derive is itself the test
524    struct WhereClauseNewType<T>(T)
525    where
526        T: Clone;
527
528    #[derive(Clone, Debug, Delta, PartialEq)]
529    #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
530    struct Reading(
531        #[delta_struct(field_type = "unordered")] BTreeSet<i32>,
532        #[delta_struct(field_type = "ordered")] Vec<String>,
533        #[delta_struct(field_type = "delta")] NewType,
534        bool,
535    );
536
537    #[test]
538    fn tuple_struct_delta_keeps_field_positions() {
539        let old = Reading(
540            vec![1, 2].into_iter().collect(),
541            vec!["a".to_string()],
542            NewType(7),
543            false,
544        );
545        let new = Reading(
546            vec![2, 3].into_iter().collect(),
547            vec!["a".to_string(), "b".to_string()],
548            NewType(8),
549            true,
550        );
551        let delta = Delta::delta(old.clone(), new.clone()).unwrap();
552        assert_eq!(delta.0.add, vec![3]);
553        assert_eq!(delta.0.remove, vec![1]);
554        assert_eq!(
555            delta.1.splices,
556            vec![Splice {
557                at: 1,
558                remove: 0,
559                insert: vec!["b".to_string()],
560            }]
561        );
562        assert_eq!(delta.2, Some(NewTypeDelta(Some(8))));
563        assert_eq!(delta.3, Some(true));
564
565        let mut applied = old;
566        applied.apply_delta(delta);
567        assert_eq!(applied, new);
568    }
569
570    #[cfg(feature = "serde")]
571    #[test]
572    fn tuple_struct_delta_serializes_as_a_sequence() {
573        #[derive(Delta)]
574        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
575        struct Meters(i32, i32);
576
577        let delta = Delta::delta(Meters(1, 2), Meters(1, 3)).unwrap();
578        assert_eq!(serde_json::to_string(&delta).unwrap(), "[null,3]");
579    }
580
581    #[derive(Delta)]
582    struct InlineBoundGeneric<T: Clone> {
583        foo: T,
584        bar: bool,
585    }
586
587    #[derive(Delta)]
588    struct WhereClauseGeneric<T>
589    where
590        T: Clone,
591    {
592        foo: T,
593        bar: bool,
594    }
595
596    #[derive(Delta)]
597    struct InlineBoundDeltaField<T: Delta> {
598        #[delta_struct(field_type = "delta")]
599        foo: T,
600    }
601
602    #[derive(Delta)]
603    struct WhereClauseDeltaField<T>
604    where
605        T: Delta,
606    {
607        #[delta_struct(field_type = "delta")]
608        foo: T,
609    }
610
611    #[derive(Delta)]
612    struct SimpleType {
613        #[delta_struct(delta_leader = "/// This is foo.")]
614        foo: i32,
615        bar: bool,
616    }
617
618    #[derive(Delta)]
619    #[allow(dead_code)] // The derive is itself the test
620    struct SimpleTypeWithGeneric<T> {
621        foo: T,
622        bar: bool,
623    }
624
625    #[derive(Delta)]
626    struct SimpleCollectionWithGeneric<T: Ord> {
627        #[delta_struct(
628            field_type = "unordered",
629            delta_leader = "/// This the foo type on the delta struct."
630        )]
631        foo: BTreeSet<T>,
632        bar: bool,
633    }
634
635    #[derive(Delta)]
636    struct DeltaRecursion {
637        #[delta_struct(field_type = "delta")]
638        foo: NewType,
639        bar: bool,
640    }
641
642    #[derive(Delta)]
643    #[delta_struct(default = "unordered")]
644    struct AttributeTest {
645        #[delta_struct(field_type = "scalar")]
646        foo: i32,
647        #[delta_struct(field_type = "scalar")]
648        bar: i32,
649        baz: BTreeSet<i32>,
650    }
651
652    #[derive(Delta, Clone, Debug, PartialEq, Eq)]
653    struct AllFieldTypes {
654        #[delta_struct(field_type = "scalar")]
655        scalar: i32,
656        #[delta_struct(field_type = "delta")]
657        delta: NewType,
658        #[delta_struct(field_type = "unordered")]
659        unordered: HashSet<i32>,
660    }
661
662    #[derive(Clone, Debug, Delta, PartialEq)]
663    #[allow(dead_code)] // The derive is itself the test
664    struct DeviceConfig {
665        #[delta_struct(field_type = "unordered")]
666        pub services: HashSet<String>,
667        #[delta_struct(field_type = "unordered")]
668        pub settings: HashSet<String>,
669        pub thumbnail_request: i32,
670        pub speedtest_request: i32,
671        #[delta_struct(field_type = "delta")]
672        pub features: AllFieldTypes,
673        pub deprovision: bool,
674    }
675
676    #[test]
677    fn unordered_with_scalar() {
678        let old = SimpleCollectionWithGeneric {
679            foo: vec![1, 2, 3].into_iter().collect(),
680            bar: false,
681        };
682        let new = SimpleCollectionWithGeneric {
683            foo: vec![3, 4, 5].into_iter().collect(),
684            bar: true,
685        };
686        let delta = Delta::delta(old, new).unwrap();
687        assert_eq!(delta.foo.add, vec![4, 5]);
688        assert_eq!(delta.foo.remove, vec![1, 2]);
689        assert_eq!(delta.bar, Some(true));
690    }
691
692    #[test]
693    fn unordered_apply_round_trips() {
694        #[derive(Clone, Debug, Delta, PartialEq)]
695        struct Tags {
696            #[delta_struct(field_type = "unordered")]
697            labels: HashSet<i32>,
698        }
699
700        let tags = |labels: &[i32]| Tags {
701            labels: labels.iter().copied().collect(),
702        };
703        let cases: &[(&[i32], &[i32])] = &[
704            (&[1, 2, 3], &[3, 4, 5]),
705            (&[1, 2], &[1, 2, 3]),
706            (&[1, 2, 3], &[1, 2]),
707            (&[], &[1, 2, 3]),
708            (&[1, 2, 3], &[]),
709            (&[1, 2], &[3, 4]),
710        ];
711        for (old, new) in cases {
712            let mut applied = tags(old);
713            let delta = Delta::delta(tags(old), tags(new)).unwrap();
714            applied.apply_delta(delta);
715            assert_eq!(applied, tags(new), "{:?} -> {:?}", old, new);
716        }
717    }
718
719    #[test]
720    fn unordered_apply_ignores_absent_removals() {
721        // `apply` drops each removal by lookup rather than rebuilding, so a
722        // key that isn't there is a no-op — which makes applying the same
723        // delta twice harmless.
724        let old = AllFieldTypes {
725            scalar: 1,
726            delta: NewType(1),
727            unordered: vec![1, 2].into_iter().collect(),
728        };
729        let new = AllFieldTypes {
730            scalar: 1,
731            delta: NewType(1),
732            unordered: vec![2, 3].into_iter().collect(),
733        };
734        let mut applied = old.clone();
735        applied.apply_delta(Delta::delta(old.clone(), new.clone()).unwrap());
736        applied.apply_delta(Delta::delta(old, new.clone()).unwrap());
737        assert_eq!(applied, new);
738    }
739
740    #[cfg(feature = "serde")]
741    #[test]
742    fn unordered_delta_serializes_nested() {
743        #[derive(Delta)]
744        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
745        struct Device {
746            #[delta_struct(field_type = "unordered")]
747            services: BTreeSet<String>,
748        }
749
750        let device = |services: &[&str]| Device {
751            services: services.iter().map(|s| s.to_string()).collect(),
752        };
753        let delta = Delta::delta(device(&["ssh"]), device(&["mqtt"])).unwrap();
754        assert_eq!(
755            serde_json::to_string(&delta).unwrap(),
756            r#"{"services":{"add":["mqtt"],"remove":["ssh"]}}"#
757        );
758    }
759
760    #[test]
761    fn delta_false_positive_check() {
762        let old = NewType(5);
763        let new = NewType(5);
764        let delta = Delta::delta(old, new);
765        assert!(delta.is_none());
766    }
767
768    #[test]
769    fn scalar_delta_false_positive_check() {
770        let old = SimpleType { foo: 5, bar: false };
771        let new = SimpleType { foo: 5, bar: true };
772        let delta = Delta::delta(old, new).unwrap();
773        assert!(delta.foo.is_none());
774        assert_eq!(delta.bar, Some(true));
775    }
776
777    #[test]
778    fn delta_field() {
779        let old = DeltaRecursion {
780            foo: NewType(5),
781            bar: false,
782        };
783        let new = DeltaRecursion {
784            foo: NewType(6),
785            bar: true,
786        };
787        let delta = Delta::delta(old, new).unwrap();
788        assert_eq!(delta.foo, Some(NewTypeDelta(Some(6))));
789        assert_eq!(delta.bar, Some(true));
790    }
791
792    #[test]
793    fn default_type_respected() {
794        let old = AttributeTest {
795            foo: 5,
796            bar: 4,
797            baz: BTreeSet::new(),
798        };
799        let new = AttributeTest {
800            foo: 5,
801            bar: 4,
802            baz: vec![9, 4, 5].into_iter().collect(),
803        };
804        let delta = Delta::delta(old, new).unwrap();
805        assert!(delta.foo.is_none());
806        assert!(delta.bar.is_none());
807        assert_eq!(delta.baz.add, vec![4, 5, 9]);
808        assert_eq!(delta.baz.remove, Vec::<i32>::new());
809    }
810
811    #[derive(Clone, Debug, Delta, PartialEq)]
812    struct Playlist {
813        #[delta_struct(field_type = "ordered")]
814        tracks: Vec<String>,
815        shuffle: bool,
816    }
817
818    #[derive(Delta)]
819    #[delta_struct(default = "ordered")]
820    struct OrderedByDefault {
821        a: Vec<i32>,
822        b: Vec<i32>,
823    }
824
825    fn playlist(tracks: &[&str], shuffle: bool) -> Playlist {
826        Playlist {
827            tracks: tracks.iter().map(|t| t.to_string()).collect(),
828            shuffle,
829        }
830    }
831
832    #[test]
833    fn ordered_records_position() {
834        let delta = Delta::delta(
835            playlist(&["a", "b", "c"], false),
836            playlist(&["a", "x", "c"], false),
837        )
838        .unwrap();
839        assert_eq!(
840            delta.tracks.splices,
841            vec![Splice {
842                at: 1,
843                remove: 1,
844                insert: vec!["x".to_string()],
845            }]
846        );
847        assert_eq!(delta.shuffle, None);
848    }
849
850    #[test]
851    fn ordered_distinguishes_reorder_from_unordered() {
852        // Reordering is invisible to `unordered` but not to `ordered`.
853        let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["b", "a"], false));
854        assert!(delta.is_some());
855    }
856
857    #[test]
858    fn ordered_false_positive_check() {
859        let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "b"], false));
860        assert!(delta.is_none());
861    }
862
863    #[test]
864    fn ordered_apply_round_trips() {
865        let cases: &[(&[&str], &[&str])] = &[
866            (&["a", "b", "c"], &["a", "x", "c"]),
867            (&["a", "b"], &["a", "b", "c"]),
868            (&["b", "c"], &["a", "b", "c"]),
869            (&["a", "b", "c"], &[]),
870            (&[], &["a", "b", "c"]),
871            (&["a", "b", "c", "d", "e"], &["a", "x", "c", "y", "e"]),
872            (&["a", "a", "a", "b"], &["a", "b", "a", "a"]),
873            (&["a", "b", "c"], &["c", "b", "a"]),
874        ];
875        for (old, new) in cases {
876            let mut applied = playlist(old, true);
877            let delta = Delta::delta(playlist(old, false), playlist(new, true)).unwrap();
878            applied.apply_delta(delta);
879            assert_eq!(applied, playlist(new, true), "{:?} -> {:?}", old, new);
880        }
881    }
882
883    #[cfg(feature = "serde")]
884    #[test]
885    fn ordered_delta_serializes() {
886        let delta =
887            Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "c"], false)).unwrap();
888        let json = serde_json::to_string(&delta.tracks).unwrap();
889        assert_eq!(json, r#"{"splices":[{"at":1,"remove":1,"insert":["c"]}]}"#);
890        let round_tripped: SeqDelta<String> = serde_json::from_str(&json).unwrap();
891        let mut target = playlist(&["a", "b"], false);
892        seq::apply(&mut target.tracks, round_tripped);
893        assert_eq!(target.tracks, vec!["a".to_string(), "c".to_string()]);
894    }
895
896    #[test]
897    fn ordered_as_container_default() {
898        let delta = Delta::delta(
899            OrderedByDefault {
900                a: vec![1, 2],
901                b: vec![3],
902            },
903            OrderedByDefault {
904                a: vec![1, 2],
905                b: vec![3, 4],
906            },
907        )
908        .unwrap();
909        assert!(delta.a.is_empty());
910        assert_eq!(
911            delta.b.splices,
912            vec![Splice {
913                at: 1,
914                remove: 0,
915                insert: vec![4],
916            }]
917        );
918    }
919
920    #[derive(Clone, Debug, Delta, PartialEq, serde::Serialize)]
921    #[delta_struct(delta_leader = "#[derive(Debug, serde::Serialize)]")]
922    struct Service {
923        port: u16,
924        healthy: bool,
925    }
926
927    #[derive(Clone, Debug, Delta, PartialEq)]
928    struct Cluster {
929        #[delta_struct(field_type = "unordered-delta")]
930        services: HashMap<String, Service>,
931        region: String,
932    }
933
934    #[derive(Delta)]
935    #[delta_struct(default = "unordered-delta")]
936    #[allow(dead_code)] // The derive is itself the test
937    struct UnorderedDeltaByDefault {
938        a: HashMap<u8, NewType>,
939        b: BTreeMap<u8, NewType>,
940    }
941
942    #[derive(Delta)]
943    #[allow(dead_code)] // The derive is itself the test
944    struct UnorderedDeltaWithGeneric<K: std::hash::Hash + Eq, V: Delta> {
945        #[delta_struct(
946            field_type = "unordered-delta",
947            delta_leader = "/// One part of the change to `foo`."
948        )]
949        foo: HashMap<K, V>,
950    }
951
952    /// A cluster's services in the compact `(name, port, healthy)` form the
953    /// tests below are written in.
954    type Services<'a> = &'a [(&'a str, u16, bool)];
955
956    fn cluster(services: Services, region: &str) -> Cluster {
957        Cluster {
958            services: services
959                .iter()
960                .map(|(name, port, healthy)| {
961                    (
962                        name.to_string(),
963                        Service {
964                            port: *port,
965                            healthy: *healthy,
966                        },
967                    )
968                })
969                .collect(),
970            region: region.to_string(),
971        }
972    }
973
974    #[test]
975    fn unordered_delta_diffs_values_in_place() {
976        let delta = Delta::delta(
977            cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
978            cluster(&[("web", 8080, true), ("db", 5432, true)], "us"),
979        )
980        .unwrap();
981        // `db` is untouched and `web` only moved its port, so neither entry is
982        // resent in full.
983        assert!(delta.services.add.is_empty());
984        assert!(delta.services.remove.is_empty());
985        assert_eq!(delta.services.change.len(), 1);
986        assert_eq!(delta.services.change[0].key, "web");
987        assert_eq!(delta.services.change[0].delta.port, Some(8080));
988        assert_eq!(delta.services.change[0].delta.healthy, None);
989        assert_eq!(delta.region, None);
990    }
991
992    #[test]
993    fn unordered_delta_adds_and_removes_by_key() {
994        let delta = Delta::delta(
995            cluster(&[("web", 80, true)], "us"),
996            cluster(&[("db", 5432, false)], "us"),
997        )
998        .unwrap();
999        assert_eq!(
1000            delta.services.add,
1001            vec![(
1002                "db".to_string(),
1003                Service {
1004                    port: 5432,
1005                    healthy: false
1006                }
1007            )]
1008        );
1009        assert_eq!(delta.services.remove, vec!["web".to_string()]);
1010        assert!(delta.services.change.is_empty());
1011    }
1012
1013    #[test]
1014    fn unordered_delta_false_positive_check() {
1015        let delta = Delta::delta(
1016            cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
1017            cluster(&[("db", 5432, true), ("web", 80, true)], "us"),
1018        );
1019        assert!(delta.is_none());
1020    }
1021
1022    #[test]
1023    fn unordered_delta_apply_round_trips() {
1024        let cases: &[(Services, Services)] = &[
1025            // A value changed under a stable key.
1026            (&[("web", 80, true)], &[("web", 8080, true)]),
1027            // Pure addition, pure removal, and both at once.
1028            (&[("web", 80, true)], &[("web", 80, true), ("db", 1, false)]),
1029            (&[("web", 80, true), ("db", 1, false)], &[("web", 80, true)]),
1030            (&[("web", 80, true)], &[("db", 1, false)]),
1031            // Every kind of change in one go.
1032            (
1033                &[("web", 80, true), ("db", 1, false), ("gone", 9, true)],
1034                &[("web", 8080, true), ("db", 1, false), ("new", 7, false)],
1035            ),
1036            (&[], &[("web", 80, true)]),
1037            (&[("web", 80, true)], &[]),
1038        ];
1039        for (old, new) in cases {
1040            let mut applied = cluster(old, "us");
1041            let delta = Delta::delta(cluster(old, "us"), cluster(new, "eu")).unwrap();
1042            applied.apply_delta(delta);
1043            assert_eq!(applied, cluster(new, "eu"), "{:?} -> {:?}", old, new);
1044        }
1045    }
1046
1047    #[test]
1048    fn unordered_delta_over_a_btree_map() {
1049        // The field need not be a `HashMap`: any collection with a
1050        // `TryIndexMut` impl works, and a `BTreeMap`'s ordering makes the
1051        // three lists deterministic.
1052        #[derive(Clone, Debug, Delta, PartialEq)]
1053        struct Pairs {
1054            #[delta_struct(field_type = "unordered-delta")]
1055            entries: BTreeMap<u8, NewType>,
1056        }
1057
1058        let pairs = |entries: &[(u8, i32)]| Pairs {
1059            entries: entries.iter().map(|(k, v)| (*k, NewType(*v))).collect(),
1060        };
1061
1062        let mut applied = pairs(&[(1, 10), (2, 20)]);
1063        let delta = Delta::delta(pairs(&[(1, 10), (2, 20)]), pairs(&[(2, 21), (3, 30)])).unwrap();
1064        assert_eq!(delta.entries.add, vec![(3, NewType(30))]);
1065        assert_eq!(delta.entries.remove, vec![1]);
1066        assert_eq!(delta.entries.change.len(), 1);
1067        applied.apply_delta(delta);
1068        assert_eq!(applied, pairs(&[(2, 21), (3, 30)]));
1069    }
1070
1071    #[cfg(feature = "serde")]
1072    #[test]
1073    fn unordered_delta_serializes() {
1074        #[derive(Delta)]
1075        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
1076        struct Fleet {
1077            #[delta_struct(field_type = "unordered-delta")]
1078            services: BTreeMap<String, Service>,
1079        }
1080
1081        let fleet = |port| Fleet {
1082            services: vec![(
1083                "web".to_string(),
1084                Service {
1085                    port,
1086                    healthy: true,
1087                },
1088            )]
1089            .into_iter()
1090            .collect(),
1091        };
1092        let delta = Delta::delta(fleet(80), fleet(8080)).unwrap();
1093        assert_eq!(
1094            serde_json::to_string(&delta).unwrap(),
1095            r#"{"services":{"add":[],"remove":[],"change":[{"key":"web","delta":{"port":8080,"healthy":null}}]}}"#
1096        );
1097    }
1098
1099    #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
1100    #[delta_struct(delta_leader = "#[derive(Clone, Debug)]")]
1101    struct Tracked {
1102        name: String,
1103        #[delta_struct(field_type = "unordered")]
1104        tags: HashSet<String>,
1105        revision: u32,
1106    }
1107
1108    fn tracked(name: &str, tags: &[&str], revision: u32) -> Tracked {
1109        Tracked {
1110            name: name.to_string(),
1111            tags: tags.iter().map(|t| t.to_string()).collect(),
1112            revision,
1113        }
1114    }
1115
1116    #[test]
1117    fn fingerprint_ignores_set_iteration_order() {
1118        // The whole point: two `HashSet`s built in different orders are the
1119        // same state and must fingerprint the same.
1120        let forwards = tracked("a", &["x", "y", "z"], 1);
1121        let backwards = tracked("a", &["z", "y", "x"], 1);
1122        assert_eq!(fingerprint_of(&forwards), fingerprint_of(&backwards));
1123        assert_ne!(
1124            fingerprint_of(&forwards),
1125            fingerprint_of(&tracked("a", &["x", "y"], 1))
1126        );
1127        assert_ne!(
1128            fingerprint_of(&forwards),
1129            fingerprint_of(&tracked("b", &["x", "y", "z"], 1))
1130        );
1131    }
1132
1133    #[test]
1134    fn fingerprint_derives_on_enums_and_tuple_structs() {
1135        #[derive(Fingerprint)]
1136        enum Shape {
1137            Empty,
1138            Circle(u32),
1139            Rect { w: u32, h: u32 },
1140        }
1141
1142        #[derive(Fingerprint)]
1143        struct Pair(u8, bool);
1144
1145        assert_ne!(
1146            fingerprint_of(&Shape::Empty),
1147            fingerprint_of(&Shape::Circle(0))
1148        );
1149        // Same payload, different variant, so the discriminant has to count.
1150        assert_ne!(
1151            fingerprint_of(&Shape::Circle(1)),
1152            fingerprint_of(&Shape::Rect { w: 1, h: 0 })
1153        );
1154        assert_eq!(
1155            fingerprint_of(&Shape::Rect { w: 2, h: 3 }),
1156            fingerprint_of(&Shape::Rect { w: 2, h: 3 })
1157        );
1158        assert_ne!(
1159            fingerprint_of(&Pair(1, true)),
1160            fingerprint_of(&Pair(1, false))
1161        );
1162    }
1163
1164    #[test]
1165    fn fingerprint_is_stable_across_runs() {
1166        // Pinned literals: if these ever change, every deployed sender and
1167        // receiver disagree until both are rebuilt.
1168        assert_eq!(fingerprint_of(&0u8), 0xaf63bd4c8601b7df);
1169        assert_eq!(fingerprint_of(&true), 0xaf63bc4c8601b62c);
1170        assert_eq!(fingerprint_of(&"delta"), 0x3035df3ae9e50ee6);
1171    }
1172
1173    #[test]
1174    fn versioned_round_trips() {
1175        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1176        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1177
1178        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1179        assert_eq!((message.from, message.to), (0, 1));
1180        assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1181        assert_eq!(receiver.get(), sender.get());
1182        assert_eq!(receiver.version(), sender.version());
1183    }
1184
1185    #[test]
1186    fn versioned_no_change_burns_nothing() {
1187        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1188        assert!(sender.commit(tracked("a", &["x"], 1)).is_none());
1189        assert_eq!(sender.version(), 0);
1190    }
1191
1192    #[test]
1193    fn versioned_ignores_a_replayed_delta() {
1194        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1195        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1196
1197        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1198        assert_eq!(receiver.apply(message.clone()), Ok(Applied::Updated));
1199        // Duplicate delivery is a no-op rather than a corruption.
1200        assert_eq!(receiver.apply(message), Ok(Applied::Stale));
1201        assert_eq!(receiver.get(), sender.get());
1202    }
1203
1204    #[test]
1205    fn versioned_catches_a_dropped_delta() {
1206        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1207        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1208
1209        let _lost = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1210        let second = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
1211
1212        assert_eq!(
1213            receiver.apply(second),
1214            Err(Mismatch::Gap {
1215                expected: 0,
1216                found: 1
1217            })
1218        );
1219        // A rejected delta leaves the receiver untouched.
1220        assert_eq!(receiver.version(), 0);
1221        assert_eq!(receiver.get(), &tracked("a", &["x"], 1));
1222    }
1223
1224    #[test]
1225    fn versioned_catches_drift_from_outside_the_stream() {
1226        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1227        // The receiver starts at the right version but the wrong contents,
1228        // which no sequence number could notice.
1229        let mut receiver = Versioned::new(tracked("a", &["tampered"], 1));
1230
1231        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1232        match receiver.apply(message) {
1233            Err(Mismatch::Base { expected, found }) => assert_ne!(expected, found),
1234            other => panic!("expected a base mismatch, got {:?}", other),
1235        }
1236        assert_eq!(receiver.version(), 0);
1237    }
1238
1239    #[test]
1240    fn versioned_resync_recovers() {
1241        // The documented answer to any `Mismatch`: send the whole thing.
1242        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1243        let mut receiver = Versioned::new(tracked("a", &["wrong"], 1));
1244
1245        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1246        assert!(receiver.apply(message).is_err());
1247
1248        receiver = sender.clone();
1249        let next = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
1250        assert_eq!(receiver.apply(next), Ok(Applied::Updated));
1251        assert_eq!(receiver.get(), sender.get());
1252    }
1253
1254    #[test]
1255    fn versioned_catches_a_wrong_result() {
1256        // A hand-built delta whose `result` does not describe what applying it
1257        // actually does — the case only the second fingerprint can catch.
1258        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1259        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1260
1261        let mut message = sender.commit(tracked("a", &["x"], 2)).unwrap();
1262        message.result ^= 1;
1263
1264        match receiver.apply(message) {
1265            Err(Mismatch::Result { expected, found }) => assert_ne!(expected, found),
1266            other => panic!("expected a result mismatch, got {:?}", other),
1267        }
1268        // Version not advanced, so the corruption cannot be mistaken for
1269        // healthy state by the next delta either.
1270        assert_eq!(receiver.version(), 0);
1271    }
1272
1273    #[cfg(feature = "serde")]
1274    #[test]
1275    fn versioned_delta_serializes() {
1276        #[derive(Clone, Delta, Fingerprint)]
1277        #[delta_struct(delta_leader = "#[derive(serde::Serialize, serde::Deserialize)]")]
1278        struct Config {
1279            port: u16,
1280        }
1281
1282        let mut sender = Versioned::new(Config { port: 80 });
1283        let mut receiver = Versioned::new(Config { port: 80 });
1284
1285        let payload =
1286            serde_json::to_string(&sender.commit(Config { port: 8080 }).unwrap()).unwrap();
1287        let message: VersionedDelta<ConfigDelta> = serde_json::from_str(&payload).unwrap();
1288        assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1289        assert_eq!(receiver.get().port, 8080);
1290    }
1291
1292    #[test]
1293    fn bounded_generics() {
1294        let delta = Delta::delta(
1295            InlineBoundGeneric { foo: 1, bar: false },
1296            InlineBoundGeneric { foo: 2, bar: false },
1297        )
1298        .unwrap();
1299        assert_eq!(delta.foo, Some(2));
1300        assert_eq!(delta.bar, None);
1301
1302        let delta = Delta::delta(
1303            WhereClauseGeneric { foo: 1, bar: false },
1304            WhereClauseGeneric { foo: 2, bar: false },
1305        )
1306        .unwrap();
1307        assert_eq!(delta.foo, Some(2));
1308        assert_eq!(delta.bar, None);
1309    }
1310
1311    #[test]
1312    fn bounded_generics_with_delta_field() {
1313        let delta = Delta::delta(
1314            InlineBoundDeltaField { foo: NewType(1) },
1315            InlineBoundDeltaField { foo: NewType(2) },
1316        )
1317        .unwrap();
1318        assert_eq!(delta.foo.unwrap().0, Some(2));
1319
1320        let mut applied = WhereClauseDeltaField { foo: NewType(1) };
1321        let delta = Delta::delta(
1322            WhereClauseDeltaField { foo: NewType(1) },
1323            WhereClauseDeltaField { foo: NewType(2) },
1324        )
1325        .unwrap();
1326        applied.apply_delta(delta);
1327        assert_eq!(applied.foo, NewType(2));
1328    }
1329
1330    #[test]
1331    fn apply_delta_all_field_types() {
1332        let old = AllFieldTypes {
1333            scalar: 1,
1334            delta: NewType(3),
1335            unordered: vec![1, 2, 3].into_iter().collect(),
1336        };
1337        let new = AllFieldTypes {
1338            scalar: 2,
1339            delta: NewType(4),
1340            unordered: vec![3, 4, 5].into_iter().collect(),
1341        };
1342        let new_clone = new.clone();
1343        let mut old_delta_applied = old.clone();
1344        let delta = Delta::delta(old, new);
1345        old_delta_applied.apply_delta(delta.unwrap());
1346        assert_eq!(new_clone, old_delta_applied);
1347    }
1348}