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, ScalarDelta};
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, ScalarDelta::Unchanged);
27//! assert_eq!(delta.port, ScalarDelta::Changed(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).unwrap();
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//! [`Delta::apply_delta`] returns a [`Result`], and the `unwrap` above is safe
44//! rather than lazy: a struct's delta always fits the struct, so only an enum
45//! can fail — see [`Mismatch`]. Use `?` wherever an enum is in reach.
46//!
47//! # Field types
48//!
49//! Every field is diffed according to a *field type*, chosen with
50//! `#[delta_struct(field_type = "...")]`. The default is `"scalar"`, which can
51//! be changed per struct — see [Container attributes](#container-attributes).
52//!
53//! ## `scalar` (the default)
54//!
55//! The field is compared with `!=` and replaced wholesale. In the delta struct
56//! it becomes [`ScalarDelta<T>`]: `ScalarDelta::Changed(new_value)` when
57//! the two differ, `ScalarDelta::Unchanged` when they don't. Requires `T: PartialEq`.
58//!
59//! ## `unordered`
60//!
61//! The field is treated as a collection whose order carries no meaning, so the
62//! delta records only which elements came and went. A set's answer to that is
63//! a [`BagDelta`], holding an `add` and a `remove`, both `Vec<Item>`.
64//!
65//! ```
66//! use delta_struct::Delta;
67//! use std::collections::HashSet;
68//!
69//! #[derive(Delta)]
70//! struct Device {
71//!     #[delta_struct(field_type = "unordered")]
72//!     services: HashSet<String>,
73//! }
74//!
75//! let device = |services: &[&str]| Device {
76//!     services: services.iter().map(|s| s.to_string()).collect(),
77//! };
78//!
79//! let delta = Delta::delta(device(&["ssh", "http"]), device(&["http", "mqtt"])).unwrap();
80//! assert_eq!(delta.services.add, vec!["mqtt".to_string()]);
81//! assert_eq!(delta.services.remove, vec!["ssh".to_string()]);
82//! ```
83//!
84//! The field has to be a **set or a map** — a
85//! [`HashSet`](std::collections::HashSet), a
86//! [`BTreeSet`](std::collections::BTreeSet), a
87//! [`HashMap`](std::collections::HashMap), or a
88//! [`BTreeMap`](std::collections::BTreeMap). Formally it needs [`Unordered`],
89//! which all four implement and which you can implement for your own
90//! collection. A [`Vec`] deliberately does not qualify — see
91//! [Limitations](#limitations).
92//!
93//! Every element of the old collection is looked up in the new one exactly
94//! once, so the cost of a diff is the cost of n lookups in whichever
95//! collection you picked: **O(n)** for a `HashSet` or `HashMap`, O(n log n)
96//! for a `BTreeSet` or `BTreeMap`. Applying one costs the same, since each
97//! removal is a lookup rather than a rebuild.
98//!
99//! ### A map's membership diff is a different shape
100//!
101//! A map is a collection of entries, but one with a rule a set has no
102//! equivalent of: no two entries share a key. That rule earns a smaller delta,
103//! so a map field's is an [`EntryDelta`] rather than a [`BagDelta`] — `add`
104//! carries whole entries because the receiver needs to be told the value,
105//! while `remove` carries **bare keys**, since a key names an entry on its
106//! own.
107//!
108//! ```
109//! use delta_struct::Delta;
110//! use std::collections::BTreeMap;
111//!
112//! #[derive(Delta)]
113//! struct Deployment {
114//!     #[delta_struct(field_type = "unordered")]
115//!     labels: BTreeMap<String, String>,
116//! }
117//!
118//! let deployment = |labels: &[(&str, &str)]| Deployment {
119//!     labels: labels.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(),
120//! };
121//!
122//! let delta = Delta::delta(
123//!     deployment(&[("tier", "web"), ("zone", "a")]),
124//!     deployment(&[("tier", "edge")]),
125//! )
126//! .unwrap();
127//! // `tier` survived, so only what it holds now travels — the old value stays
128//! // where it already is. `zone` left, and its key alone says so.
129//! assert_eq!(delta.labels.add, vec![("tier".to_string(), "edge".to_string())]);
130//! assert_eq!(delta.labels.remove, vec!["zone".to_string()]);
131//! ```
132//!
133//! A key that survived with a new value is an *addition*, not a removal
134//! followed by one: applying an addition overwrites whatever the key held, so
135//! the removal would say nothing the addition does not already say.
136//!
137//! Which shape a field gets is the collection's business, not the field type's
138//! — the delta field is declared as `<T as Unordered>::Delta`, and the
139//! collection's [`Unordered`] impl picks. That is what lets one field type
140//! cover both without the derive needing to tell a map from a set.
141//!
142//! This — not `unordered-delta` — is what to reach for when the values are
143//! scalars with no [`Delta`] impl of their own, which is the usual shape of a
144//! map of labels, tags, or config. The trade against `unordered-delta` is that
145//! a changed value travels whole rather than as its own delta; in exchange
146//! this asks nothing of the value type but [`PartialEq`], and needs only
147//! [`TryIndex`] where `unordered-delta` needs [`TryIndexMut`].
148//!
149//! `apply_delta` preserves membership but not position — additions land
150//! wherever the collection decides to put them. Use `ordered` where that
151//! matters.
152//!
153//! ## `unordered-delta`
154//!
155//! Like `unordered`, but for a collection of key/value entries whose values
156//! are worth diffing rather than resending. An entry whose key is on both
157//! sides is not a removal plus an addition: the two values are handed to
158//! [`Delta::delta`] and only the difference is recorded. The delta is a
159//! [`MapDelta`], holding an `add`, a `remove`, and a `change`.
160//!
161//! ```
162//! use delta_struct::{Delta, ScalarDelta};
163//! use std::collections::HashMap;
164//!
165//! #[derive(Delta)]
166//! struct Service {
167//!     port: u16,
168//!     healthy: bool,
169//! }
170//!
171//! #[derive(Delta)]
172//! struct Cluster {
173//!     #[delta_struct(field_type = "unordered-delta")]
174//!     services: HashMap<String, Service>,
175//! }
176//!
177//! let cluster = |port| Cluster {
178//!     services: vec![("web".to_string(), Service { port, healthy: true })]
179//!         .into_iter()
180//!         .collect(),
181//! };
182//!
183//! let delta = Delta::delta(cluster(80), cluster(8080)).unwrap();
184//! // `web` stayed put, so all that travels is the one field that moved.
185//! assert!(delta.services.add.is_empty());
186//! assert!(delta.services.remove.is_empty());
187//! assert_eq!(delta.services.change[0].key, "web");
188//! assert_eq!(delta.services.change[0].delta.port, ScalarDelta::Changed(8080));
189//! assert_eq!(delta.services.change[0].delta.healthy, ScalarDelta::Unchanged);
190//! ```
191//!
192//! The key is the collection's own — the `K` of a `HashMap<K, V>` — not
193//! something you nominate. The field has to be a **map**: a
194//! [`HashMap`](std::collections::HashMap) or a
195//! [`BTreeMap`](std::collections::BTreeMap). Formally it needs [`Extend`] and
196//! [`TryIndexMut`], its entry type needs [`MapEntry`] (implemented for
197//! `(K, V)`, which is what every std map iterates as), and its value type
198//! needs [`Delta`].
199//!
200//! [`TryIndexMut`] rather than [`TryIndex`] is what excludes sets here, and
201//! correctly so: applying a delta means mutating a value where it sits, which
202//! a set cannot allow without letting you invalidate the hash or ordering it
203//! filed the element under.
204//!
205//! Every key of the old collection is looked up in the new one exactly once,
206//! so as with `unordered` the cost is n lookups — **O(n)** for a `HashMap`,
207//! O(n log n) for a `BTreeMap`. Applying one preserves membership rather than
208//! position, also the same as `unordered`.
209//!
210//! ## `ordered`
211//!
212//! The field is diffed positionally with Myers' algorithm, and the delta is a
213//! minimal edit script: a [`SeqDelta`] holding [`Splice`]s that each say
214//! "at this index, drop this many items and put these in their place".
215//!
216//! ```
217//! use delta_struct::{Delta, Splice};
218//!
219//! #[derive(Delta)]
220//! struct Playlist {
221//!     #[delta_struct(field_type = "ordered")]
222//!     tracks: Vec<String>,
223//! }
224//!
225//! let old = Playlist { tracks: vec!["intro".to_string(), "b".to_string(), "outro".to_string()] };
226//! let new = Playlist { tracks: vec!["intro".to_string(), "x".to_string(), "outro".to_string()] };
227//!
228//! let delta = Delta::delta(old, new).unwrap();
229//! assert_eq!(
230//!     delta.tracks.splices,
231//!     vec![Splice { at: 1, remove: 1, insert: vec!["x".to_string()] }],
232//! );
233//! ```
234//!
235//! Splice positions index the *old* sequence and arrive sorted and
236//! non-overlapping, so applying one is a single forward pass. Reordering is a
237//! real change here where `unordered` would see none, and applying a delta
238//! reproduces the new sequence exactly, position included.
239//!
240//! The collection needs `IntoIterator` and `FromIterator`, and its items need
241//! `Hash + Eq`, because that is what indexing the sequences for Myers
242//! requires. This is the one field type that takes a [`Vec`], and so the only
243//! one that will diff a sequence at all — but `f64` is neither `Hash` nor
244//! `Eq`, so a `Vec<f64>` still has nowhere to go but `scalar`.
245//!
246//! ## `delta`
247//!
248//! The field is itself diffed recursively, which keeps a nested change from
249//! resending the whole subtree. Requires the field's type to implement
250//! [`Delta`]; the delta struct holds `Option<<T as Delta>::Output>`.
251//!
252//! ```
253//! use delta_struct::{Delta, ScalarDelta};
254//!
255//! #[derive(Delta)]
256//! struct Inner {
257//!     a: i32,
258//!     b: i32,
259//! }
260//!
261//! #[derive(Delta)]
262//! struct Outer {
263//!     #[delta_struct(field_type = "delta")]
264//!     inner: Inner,
265//!     name: String,
266//! }
267//!
268//! let old = Outer { inner: Inner { a: 1, b: 2 }, name: "x".to_string() };
269//! let new = Outer { inner: Inner { a: 1, b: 3 }, name: "x".to_string() };
270//!
271//! let delta = Delta::delta(old, new).unwrap();
272//! let inner_delta = delta.inner.expect("`b` changed");
273//! assert_eq!(inner_delta.a, ScalarDelta::Unchanged);
274//! assert_eq!(inner_delta.b, ScalarDelta::Changed(3));
275//! ```
276//!
277//! # Enums
278//!
279//! An enum can change in two ways a struct cannot, and its delta says which.
280//! Two values in the *same* variant are diffed field by field exactly as a
281//! struct is. Two values in *different* variants have no difference to
282//! describe — the new one shares nothing with the old — so the whole value
283//! travels.
284//!
285//! That fork is [`EnumDelta`], and `Output` becomes
286//! `EnumDelta<Self, {Self}Delta>` rather than the bare companion type. The
287//! generated `{Self}Delta` carries one variant per *diffable* source variant;
288//! a field-less variant gets none, since two of those can never differ.
289//!
290//! ```
291//! use delta_struct::{Delta, EnumDelta, ScalarDelta};
292//!
293//! #[derive(Delta)]
294//! #[delta_struct(delta_leader = "#[derive(Debug)]")]
295//! enum Shape {
296//!     Empty,
297//!     Circle { r: u32 },
298//! }
299//!
300//! // Same variant: only the field that moved travels.
301//! let delta = Delta::delta(Shape::Circle { r: 1 }, Shape::Circle { r: 2 }).unwrap();
302//! match delta {
303//!     EnumDelta::Delta(ShapeDelta::Circle { r }) => assert_eq!(r, ScalarDelta::Changed(2)),
304//!     _ => panic!("same variant"),
305//! }
306//!
307//! // Different variant: a replacement, not a difference.
308//! let delta = Delta::delta(Shape::Empty, Shape::Circle { r: 3 }).unwrap();
309//! assert!(matches!(delta, EnumDelta::Became(Shape::Circle { r: 3 })));
310//! ```
311//!
312//! Keeping `Became` on a crate type rather than as an arm of the generated
313//! enum is what lets you have a variant of your own called `Became`.
314//!
315//! ## Why `apply_delta` returns a `Result`
316//!
317//! A delta built while a value was one variant can arrive at a value that is
318//! now another. That is divergence, and it is the one thing applying a delta
319//! can genuinely fail at — hence [`Mismatch`], which names the type, the
320//! variant the delta expected, and the variant it found.
321//!
322//! ```
323//! # use delta_struct::{Delta, Mismatch};
324//! # #[derive(Delta)]
325//! # enum Shape { Empty, Circle { r: u32 } }
326//! let delta = Delta::delta(Shape::Circle { r: 1 }, Shape::Circle { r: 2 }).unwrap();
327//! let mut diverged = Shape::Empty;
328//! assert_eq!(
329//!     diverged.apply_delta(delta),
330//!     Err(Mismatch { type_name: "Shape", expected: "Circle", found: "Empty" }),
331//! );
332//! ```
333//!
334//! Nested deltas propagate the innermost mismatch rather than wrapping it, so
335//! what you get names the enum that actually disagreed rather than the
336//! outermost struct you called `apply_delta` on.
337//!
338//! Only enums can produce this. A struct's `apply_delta` returns `Ok` unless
339//! one of its fields is an enum, which is why the `unwrap`s in the struct
340//! examples above are safe rather than sloppy.
341//!
342//! # Container attributes
343//!
344//! `#[delta_struct(...)]` on the struct itself accepts:
345//!
346//! - `default = "..."` — the field type used for fields without their own
347//!   `field_type`. Defaults to `"scalar"`.
348//! - `delta_leader = "..."` — tokens to emit immediately above the generated
349//!   struct. This is how you attach derives, doc comments, or any other
350//!   attribute to a type you never get to write by hand.
351//!
352//! ```
353//! use delta_struct::{Delta, ScalarDelta};
354//! use std::collections::HashSet;
355//!
356//! #[derive(Delta)]
357//! #[delta_struct(
358//!     default = "unordered",
359//!     delta_leader = "/// The changes to a `Tags`.\n#[derive(Debug)]"
360//! )]
361//! struct Tags {
362//!     labels: HashSet<String>,
363//!     // Opt an individual field back out of the container default.
364//!     #[delta_struct(field_type = "scalar")]
365//!     revision: u32,
366//! }
367//!
368//! let old = Tags { labels: HashSet::new(), revision: 1 };
369//! let new = Tags {
370//!     labels: vec!["new".to_string()].into_iter().collect(),
371//!     revision: 2,
372//! };
373//! let delta = Delta::delta(old, new).unwrap();
374//! assert_eq!(format!("{:?}", delta.labels.add), r#"["new"]"#);
375//! assert_eq!(delta.revision, ScalarDelta::Changed(2));
376//! ```
377//!
378//! `delta_leader` also works on individual fields, where it decorates the
379//! generated field instead of the generated struct.
380//!
381//! ```
382//! # use delta_struct::Delta;
383//! #[derive(Delta)]
384//! struct Host {
385//!     #[delta_struct(delta_leader = "/// The new port, if it moved.")]
386//!     port: u16,
387//! }
388//! ```
389//!
390//! # Working with serde
391//!
392//! For `scalar` and `delta` fields there is no serde integration to enable;
393//! `delta_leader` is the whole story. Put the derives on the generated struct
394//! and it serializes like anything else:
395//!
396//! ```
397//! use delta_struct::Delta;
398//!
399//! #[derive(Delta)]
400//! #[delta_struct(delta_leader = "#[derive(serde::Serialize, serde::Deserialize)]")]
401//! struct Config {
402//!     host: String,
403//!     port: u16,
404//! }
405//!
406//! let old = Config { host: "localhost".to_string(), port: 80 };
407//! let new = Config { host: "localhost".to_string(), port: 8080 };
408//!
409//! // Sender: there is no message to send at all when nothing changed.
410//! let payload = Delta::delta(old, new).map(|delta| serde_json::to_string(&delta).unwrap());
411//! assert_eq!(payload.as_deref(), Some(r#"{"host":"unchanged","port":{"changed":8080}}"#));
412//!
413//! // Receiver applies it to whatever it already had.
414//! let mut config = Config { host: "localhost".to_string(), port: 80 };
415//! config.apply_delta(serde_json::from_str::<ConfigDelta>(&payload.unwrap()).unwrap()).unwrap();
416//! assert_eq!(config.port, 8080);
417//! ```
418//!
419//! Field-level `delta_leader` carries serde attributes just as well, so
420//! `skip_serializing_if` can keep unchanged fields out of the payload
421//! entirely rather than sending them as `null`:
422//!
423//! ```
424//! use delta_struct::Delta;
425//!
426//! #[derive(Delta)]
427//! #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
428//! struct Config {
429//!     #[delta_struct(delta_leader = "#[serde(default, skip_serializing_if = \"::delta_struct::ScalarDelta::is_unchanged\")]")]
430//!     host: String,
431//!     #[delta_struct(delta_leader = "#[serde(default, skip_serializing_if = \"::delta_struct::ScalarDelta::is_unchanged\")]")]
432//!     port: u16,
433//! }
434//!
435//! let old = Config { host: "localhost".to_string(), port: 80 };
436//! let new = Config { host: "localhost".to_string(), port: 8080 };
437//!
438//! let delta = Delta::delta(old, new).unwrap();
439//! assert_eq!(serde_json::to_string(&delta).unwrap(), r#"{"port":{"changed":8080}}"#);
440//! ```
441//!
442//! # Checking that a delta belongs
443//!
444//! [`Delta::apply_delta`] assumes the value it is handed equals the `old` the
445//! delta came from, and checks nothing. Over an unreliable transport that assumption breaks:
446//! a message is dropped, delivered twice, or arrives at a receiver whose state
447//! drifted for some other reason, and the two sides diverge in silence.
448//!
449//! [`Versioned`] is the opt-in fix. It pairs a value with a version counter
450//! and a [`Fingerprint`] of its contents, and refuses any delta that does not
451//! belong.
452//!
453//! ```
454//! use delta_struct::{Applied, Delta, Fingerprint, Rejected, Versioned};
455//!
456//! #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
457//! #[delta_struct(delta_leader = "#[derive(Clone)]")]
458//! struct Config {
459//!     host: String,
460//!     port: u16,
461//! }
462//!
463//! let config = |port| Config { host: "localhost".to_string(), port };
464//!
465//! let mut sender = Versioned::new(config(80));
466//! let mut receiver = Versioned::new(config(80));
467//!
468//! let first = sender.commit(config(8080)).expect("the port changed");
469//! let second = sender.commit(config(9090)).expect("the port changed again");
470//!
471//! // Delivered twice: recognised and ignored.
472//! assert_eq!(receiver.apply(first.clone()), Ok(Applied::Updated));
473//! assert_eq!(receiver.apply(first), Ok(Applied::Stale));
474//! assert_eq!(receiver.apply(second), Ok(Applied::Updated));
475//! assert_eq!(receiver.get(), sender.get());
476//!
477//! // A delta from a stream this receiver never joined is refused rather than
478//! // half-applied.
479//! let mut stranger = Versioned::new(config(80));
480//! let orphan = Versioned::new(config(1)).commit(config(2)).unwrap();
481//! assert!(matches!(stranger.apply(orphan), Err(Rejected::Base { .. })));
482//! ```
483//!
484//! Every [`VersionedDelta`] carries four numbers, each catching a failure the
485//! others cannot:
486//!
487//! | Field | Catches |
488//! | --- | --- |
489//! | `from`, `to` | A message dropped, reordered, or replayed. |
490//! | `base` | A receiver whose state drifted for any reason, including one that never came through this stream. |
491//! | `result` | The delta itself being wrong — mismatched schema versions, or a bug. |
492//!
493//! A rejected delta leaves the receiver untouched and its version unmoved, so
494//! a later delta in the same stream fails too rather than papering over the
495//! hole. The answer to any [`Rejected`] is to resend the whole [`Versioned`],
496//! which serializes as a unit and carries the version the receiver resumes
497//! from.
498//!
499//! None of this touches the [`Delta`] trait, the derive, or any generated
500//! struct. If you are diffing locally rather than over a wire, you never name
501//! anything in this section and pay for none of it.
502//!
503//! ## `Fingerprint`
504//!
505//! [`Fingerprint`] is a separate derive because [`std::hash::Hash`] cannot do
506//! the job: it is not implemented for [`HashSet`](std::collections::HashSet)
507//! or [`HashMap`](std::collections::HashMap) — exactly the collections the
508//! `unordered` field types require — and its standard hasher is allowed to
509//! change between Rust releases, which would make a toolchain upgrade on one
510//! side of a connection look like corruption.
511//!
512//! So sets and maps fold commutatively, iteration order cannot reach the
513//! result, and the hash is pinned to FNV-1a constants written down in the
514//! source. The same value fingerprints identically on any platform and any
515//! Rust version. Unlike [`Delta`], it derives on enums too.
516//!
517//! Checking costs a full traversal of the state on each `commit` and each
518//! `apply` — cheaper than serializing it, but not free, which is the price of
519//! the `base` and `result` guarantees.
520//!
521//! # What gets generated
522//!
523//! For `struct Foo`, deriving [`Delta`] emits `struct FooDelta` with the same
524//! visibility as `Foo` and the same generic parameters, carrying over their
525//! bounds and `where` clause as written. All of its fields are
526//! `pub`, and by default it derives nothing at all — reach for `delta_leader`
527//! whenever you need `Debug`, `Clone`, or serde on it. (Likewise if your crate
528//! sets `#![deny(missing_docs)]`: the generated struct and its fields need doc
529//! comments supplied through `delta_leader`.)
530//!
531//! Every field type maps one source field onto exactly one delta field, so a
532//! delta struct always has the same fields in the same order as the struct it
533//! came from — only their types differ. A tuple struct's delta is a tuple
534//! struct in turn, so its fields keep their positions:
535//!
536//! ```
537//! use delta_struct::{Delta, ScalarDelta};
538//!
539//! #[derive(Delta)]
540//! struct Meters(i32);
541//!
542//! let delta = Delta::delta(Meters(3), Meters(4)).unwrap();
543//! assert_eq!(delta.0, ScalarDelta::Changed(4));
544//! ```
545//!
546//! # Limitations
547//!
548//! - **Unions are rejected.** Structs and enums are both supported; a union
549//!   has no way to say which of its fields is live, so there is nothing to
550//!   diff.
551//! - **An enum with no variants is rejected.** An uninhabited type has no two
552//!   values that could differ.
553//! - **Every type parameter gets a `PartialEq` bound** on the generated impl,
554//!   whether or not the field that uses it needs one.
555//! - **A unit struct's delta is always [`None`]**, as is that of a struct with
556//!   no fields — there is nothing that could differ.
557//! - **`ordered` items need `Hash + Eq`**, so float sequences are out. See
558//!   that section above.
559//! - **A [`Vec`] cannot be an `unordered` field.** Membership diffing goes
560//!   through [`TryIndex`], and a `Vec` has no sub-linear lookup to offer —
561//!   implementing it would only hide a quadratic scan behind an O(1)-looking
562//!   call. Use a [`HashSet`](std::collections::HashSet) or a
563//!   [`BTreeSet`](std::collections::BTreeSet), or `ordered` if position
564//!   matters.
565//! - **`unordered-delta` keys are the collection's own.** There is no way to
566//!   nominate a field of the value as the key, so a `Vec<Record>` has to
567//!   become a `HashMap<Id, Record>` to use it.
568//! - **[`Versioned`] assumes one writer per stream.** Two senders committing
569//!   against the same base both produce `from: 0`, and the second is rejected
570//!   rather than merged. Divergence is detected, not reconciled — reach for a
571//!   CRDT if you need concurrent writers.
572
573#![warn(missing_docs)]
574
575// The derive emits `::delta_struct::…` paths for the runtime items an
576// `ordered` field needs. That path has to resolve inside this crate too, or
577// the crate's own tests could not use its own derive.
578extern crate self as delta_struct;
579
580#[cfg(doctest)]
581#[doc = include_str!("../../README.md")]
582struct ReadmeDoctests;
583
584pub mod bag;
585pub mod entry;
586pub mod fingerprint;
587pub mod index;
588pub mod map;
589pub mod seq;
590pub mod unordered;
591pub mod variant;
592pub mod version;
593
594pub use bag::BagDelta;
595pub use delta_struct_macros::{Delta, Fingerprint};
596pub use entry::EntryDelta;
597pub use fingerprint::{fingerprint_of, Fingerprint};
598pub use index::{TryIndex, TryIndexMut};
599pub use map::{KeyedDelta, MapDelta, MapEntry};
600pub use seq::{SeqDelta, Splice};
601pub use unordered::Unordered;
602pub use variant::{EnumDelta, Mismatch};
603pub use version::{Applied, Rejected, Versioned, VersionedDelta};
604
605/// Computing the difference between two values, and applying it to a third.
606///
607/// You will normally derive this rather than implement it — see the
608/// [crate documentation](crate) for the derive's attributes and the shape of
609/// the type it generates. Implement it by hand when you want custom diffing
610/// for a type that other structs then reference with
611/// `#[delta_struct(field_type = "delta")]`.
612pub trait Delta {
613    /// The type describing a difference between two `Self` values.
614    ///
615    /// The derive sets this to the generated `{Self}Delta` struct — or, for an
616    /// enum, to [`EnumDelta<Self, {Self}Delta>`](EnumDelta), since a value can
617    /// change variant as well as change within one.
618    type Output;
619
620    /// Computes what it would take to turn `old` into `new`.
621    ///
622    /// Returns [`None`] when the two are equivalent, which lets callers skip
623    /// sending or storing an update that would do nothing. Both values are
624    /// consumed: the delta takes ownership of whatever it needs from `new`.
625    fn delta(old: Self, new: Self) -> Option<Self::Output>;
626
627    /// Applies a delta in place.
628    ///
629    /// Applying the delta from `delta(old, new)` to a value equal to `old`
630    /// yields a value equal to `new` — with the caveat that `unordered` fields
631    /// preserve membership rather than order.
632    ///
633    /// Fails only when the delta cannot fit the value, which only an enum can
634    /// manage: a delta built for one variant, applied to a value now in
635    /// another. See [`Mismatch`]. For a struct — and for an enum in the
636    /// variant its delta expects — this always returns `Ok`.
637    ///
638    /// A failure leaves the value partly updated, so treat it the way
639    /// [`Versioned`] does: the value is no longer trustworthy and wants
640    /// replacing wholesale, not patching again.
641    fn apply_delta(&mut self, delta: Self::Output) -> Result<(), Mismatch>;
642}
643
644/// This type exists as a workaround for the `serde` data model being unable to distinguish
645/// between `Some(None)` and `None` in serialized bytes. It is, in nearly every respect,
646/// [`Option<T>`], just with some more semantic clarity. You may freely convert between this
647/// and [`Option<T>`] using [`From::from`] or [`Into::into`].
648#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)]
649#[cfg_attr(
650    feature = "serde",
651    derive(serde::Serialize, serde::Deserialize),
652    serde(rename_all = "lowercase")
653)]
654pub enum ScalarDelta<T> {
655    /// The value was not altered, use the old value.
656    #[default]
657    Unchanged,
658    /// The value was altered, here is the new value.
659    Changed(T),
660}
661
662impl<T> ScalarDelta<T> {
663    /// Convenient shorthand for converting to `Option`.
664    pub fn opt(self) -> Option<T> {
665        self.into()
666    }
667
668    /// Returns true if this contains no change.
669    pub fn is_unchanged(&self) -> bool {
670        matches!(self, Self::Unchanged)
671    }
672
673    /// Returns true if this contains a change.
674    pub fn is_changed(&self) -> bool {
675        matches!(self, Self::Changed(_))
676    }
677}
678
679impl<T> From<Option<T>> for ScalarDelta<T> {
680    fn from(value: Option<T>) -> Self {
681        match value {
682            Some(v) => Self::Changed(v),
683            None => Self::Unchanged,
684        }
685    }
686}
687
688impl<T> From<ScalarDelta<T>> for Option<T> {
689    fn from(value: ScalarDelta<T>) -> Self {
690        match value {
691            ScalarDelta::Changed(v) => Some(v),
692            ScalarDelta::Unchanged => None,
693        }
694    }
695}
696
697#[cfg(test)]
698mod tests {
699    use super::*;
700    use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
701
702    #[derive(Delta)]
703    #[allow(dead_code)] // The derive is itself the test
704    struct UnitType;
705
706    #[derive(Delta, Clone, Debug, PartialEq, Eq)]
707    #[delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq, Eq)]")]
708    struct NewType(i32);
709
710    #[derive(Delta)]
711    #[allow(dead_code)] // The derive is itself the test
712    struct NewTypeWithGeneric<T>(T);
713
714    // A tuple struct's delta is a tuple struct, which puts its `where` clause
715    // after the fields rather than before them. Both spellings of a bound have
716    // to survive that.
717    #[derive(Delta)]
718    #[allow(dead_code)] // The derive is itself the test
719    struct InlineBoundNewType<T: Clone>(T);
720
721    #[derive(Delta)]
722    #[allow(dead_code)] // The derive is itself the test
723    struct WhereClauseNewType<T>(T)
724    where
725        T: Clone;
726
727    #[derive(Clone, Debug, Delta, PartialEq)]
728    #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
729    struct Reading(
730        #[delta_struct(field_type = "unordered")] BTreeSet<i32>,
731        #[delta_struct(field_type = "ordered")] Vec<String>,
732        #[delta_struct(field_type = "delta")] NewType,
733        bool,
734    );
735
736    #[test]
737    fn tuple_struct_delta_keeps_field_positions() {
738        let old = Reading(
739            vec![1, 2].into_iter().collect(),
740            vec!["a".to_string()],
741            NewType(7),
742            false,
743        );
744        let new = Reading(
745            vec![2, 3].into_iter().collect(),
746            vec!["a".to_string(), "b".to_string()],
747            NewType(8),
748            true,
749        );
750        let delta = Delta::delta(old.clone(), new.clone()).unwrap();
751        assert_eq!(delta.0.add, vec![3]);
752        assert_eq!(delta.0.remove, vec![1]);
753        assert_eq!(
754            delta.1.splices,
755            vec![Splice {
756                at: 1,
757                remove: 0,
758                insert: vec!["b".to_string()],
759            }]
760        );
761        assert_eq!(delta.2, Some(NewTypeDelta(ScalarDelta::Changed(8))));
762        assert_eq!(delta.3, ScalarDelta::Changed(true));
763
764        let mut applied = old;
765        applied.apply_delta(delta).unwrap();
766        assert_eq!(applied, new);
767    }
768
769    #[cfg(feature = "serde")]
770    #[test]
771    fn tuple_struct_delta_serializes_as_a_sequence() {
772        #[derive(Delta)]
773        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
774        struct Meters(i32, i32);
775
776        let delta = Delta::delta(Meters(1, 2), Meters(1, 3)).unwrap();
777        assert_eq!(
778            serde_json::to_string(&delta).unwrap(),
779            "[\"unchanged\",{\"changed\":3}]"
780        );
781    }
782
783    #[derive(Delta)]
784    struct InlineBoundGeneric<T: Clone> {
785        foo: T,
786        bar: bool,
787    }
788
789    #[derive(Delta)]
790    struct WhereClauseGeneric<T>
791    where
792        T: Clone,
793    {
794        foo: T,
795        bar: bool,
796    }
797
798    #[derive(Delta)]
799    struct InlineBoundDeltaField<T: Delta> {
800        #[delta_struct(field_type = "delta")]
801        foo: T,
802    }
803
804    #[derive(Delta)]
805    struct WhereClauseDeltaField<T>
806    where
807        T: Delta,
808    {
809        #[delta_struct(field_type = "delta")]
810        foo: T,
811    }
812
813    #[derive(Delta)]
814    struct SimpleType {
815        #[delta_struct(delta_leader = "/// This is foo.")]
816        foo: i32,
817        bar: bool,
818    }
819
820    #[derive(Delta)]
821    #[allow(dead_code)] // The derive is itself the test
822    struct SimpleTypeWithGeneric<T> {
823        foo: T,
824        bar: bool,
825    }
826
827    #[derive(Delta)]
828    struct SimpleCollectionWithGeneric<T: Ord> {
829        #[delta_struct(
830            field_type = "unordered",
831            delta_leader = "/// This the foo type on the delta struct."
832        )]
833        foo: BTreeSet<T>,
834        bar: bool,
835    }
836
837    #[derive(Delta)]
838    struct DeltaRecursion {
839        #[delta_struct(field_type = "delta")]
840        foo: NewType,
841        bar: bool,
842    }
843
844    #[derive(Delta)]
845    #[delta_struct(default = "unordered")]
846    struct AttributeTest {
847        #[delta_struct(field_type = "scalar")]
848        foo: i32,
849        #[delta_struct(field_type = "scalar")]
850        bar: i32,
851        baz: BTreeSet<i32>,
852    }
853
854    #[derive(Delta, Clone, Debug, PartialEq, Eq)]
855    struct AllFieldTypes {
856        #[delta_struct(field_type = "scalar")]
857        scalar: i32,
858        #[delta_struct(field_type = "delta")]
859        delta: NewType,
860        #[delta_struct(field_type = "unordered")]
861        unordered: HashSet<i32>,
862    }
863
864    #[derive(Clone, Debug, Delta, PartialEq)]
865    #[allow(dead_code)] // The derive is itself the test
866    struct DeviceConfig {
867        #[delta_struct(field_type = "unordered")]
868        pub services: HashSet<String>,
869        #[delta_struct(field_type = "unordered")]
870        pub settings: HashSet<String>,
871        pub thumbnail_request: i32,
872        pub speedtest_request: i32,
873        #[delta_struct(field_type = "delta")]
874        pub features: AllFieldTypes,
875        pub deprovision: bool,
876    }
877
878    #[test]
879    fn unordered_with_scalar() {
880        let old = SimpleCollectionWithGeneric {
881            foo: vec![1, 2, 3].into_iter().collect(),
882            bar: false,
883        };
884        let new = SimpleCollectionWithGeneric {
885            foo: vec![3, 4, 5].into_iter().collect(),
886            bar: true,
887        };
888        let delta = Delta::delta(old, new).unwrap();
889        assert_eq!(delta.foo.add, vec![4, 5]);
890        assert_eq!(delta.foo.remove, vec![1, 2]);
891        assert_eq!(delta.bar, ScalarDelta::Changed(true));
892    }
893
894    #[test]
895    fn unordered_apply_round_trips() {
896        #[derive(Clone, Debug, Delta, PartialEq)]
897        struct Tags {
898            #[delta_struct(field_type = "unordered")]
899            labels: HashSet<i32>,
900        }
901
902        let tags = |labels: &[i32]| Tags {
903            labels: labels.iter().copied().collect(),
904        };
905        let cases: &[(&[i32], &[i32])] = &[
906            (&[1, 2, 3], &[3, 4, 5]),
907            (&[1, 2], &[1, 2, 3]),
908            (&[1, 2, 3], &[1, 2]),
909            (&[], &[1, 2, 3]),
910            (&[1, 2, 3], &[]),
911            (&[1, 2], &[3, 4]),
912        ];
913        for (old, new) in cases {
914            let mut applied = tags(old);
915            let delta = Delta::delta(tags(old), tags(new)).unwrap();
916            applied.apply_delta(delta).unwrap();
917            assert_eq!(applied, tags(new), "{:?} -> {:?}", old, new);
918        }
919    }
920
921    #[test]
922    fn unordered_apply_ignores_absent_removals() {
923        // `apply` drops each removal by lookup rather than rebuilding, so a
924        // key that isn't there is a no-op — which makes applying the same
925        // delta twice harmless.
926        let old = AllFieldTypes {
927            scalar: 1,
928            delta: NewType(1),
929            unordered: vec![1, 2].into_iter().collect(),
930        };
931        let new = AllFieldTypes {
932            scalar: 1,
933            delta: NewType(1),
934            unordered: vec![2, 3].into_iter().collect(),
935        };
936        let mut applied = old.clone();
937        applied
938            .apply_delta(Delta::delta(old.clone(), new.clone()).unwrap())
939            .unwrap();
940        applied
941            .apply_delta(Delta::delta(old, new.clone()).unwrap())
942            .unwrap();
943        assert_eq!(applied, new);
944    }
945
946    #[cfg(feature = "serde")]
947    #[test]
948    fn unordered_delta_serializes_nested() {
949        #[derive(Delta)]
950        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
951        struct Device {
952            #[delta_struct(field_type = "unordered")]
953            services: BTreeSet<String>,
954        }
955
956        let device = |services: &[&str]| Device {
957            services: services.iter().map(|s| s.to_string()).collect(),
958        };
959        let delta = Delta::delta(device(&["ssh"]), device(&["mqtt"])).unwrap();
960        assert_eq!(
961            serde_json::to_string(&delta).unwrap(),
962            r#"{"services":{"add":["mqtt"],"remove":["ssh"]}}"#
963        );
964    }
965
966    #[test]
967    fn delta_false_positive_check() {
968        let old = NewType(5);
969        let new = NewType(5);
970        let delta = Delta::delta(old, new);
971        assert!(delta.is_none());
972    }
973
974    #[test]
975    fn scalar_delta_false_positive_check() {
976        let old = SimpleType { foo: 5, bar: false };
977        let new = SimpleType { foo: 5, bar: true };
978        let delta = Delta::delta(old, new).unwrap();
979        assert!(delta.foo.is_unchanged());
980        assert_eq!(delta.bar, ScalarDelta::Changed(true));
981    }
982
983    #[test]
984    fn delta_field() {
985        let old = DeltaRecursion {
986            foo: NewType(5),
987            bar: false,
988        };
989        let new = DeltaRecursion {
990            foo: NewType(6),
991            bar: true,
992        };
993        let delta = Delta::delta(old, new).unwrap();
994        assert_eq!(delta.foo, Some(NewTypeDelta(ScalarDelta::Changed(6))));
995        assert_eq!(delta.bar, ScalarDelta::Changed(true));
996    }
997
998    #[test]
999    fn default_type_respected() {
1000        let old = AttributeTest {
1001            foo: 5,
1002            bar: 4,
1003            baz: BTreeSet::new(),
1004        };
1005        let new = AttributeTest {
1006            foo: 5,
1007            bar: 4,
1008            baz: vec![9, 4, 5].into_iter().collect(),
1009        };
1010        let delta = Delta::delta(old, new).unwrap();
1011        assert!(delta.foo.is_unchanged());
1012        assert!(delta.bar.is_unchanged());
1013        assert_eq!(delta.baz.add, vec![4, 5, 9]);
1014        assert_eq!(delta.baz.remove, Vec::<i32>::new());
1015    }
1016
1017    #[derive(Clone, Debug, Delta, PartialEq)]
1018    struct Playlist {
1019        #[delta_struct(field_type = "ordered")]
1020        tracks: Vec<String>,
1021        shuffle: bool,
1022    }
1023
1024    #[derive(Delta)]
1025    #[delta_struct(default = "ordered")]
1026    struct OrderedByDefault {
1027        a: Vec<i32>,
1028        b: Vec<i32>,
1029    }
1030
1031    fn playlist(tracks: &[&str], shuffle: bool) -> Playlist {
1032        Playlist {
1033            tracks: tracks.iter().map(|t| t.to_string()).collect(),
1034            shuffle,
1035        }
1036    }
1037
1038    #[test]
1039    fn ordered_records_position() {
1040        let delta = Delta::delta(
1041            playlist(&["a", "b", "c"], false),
1042            playlist(&["a", "x", "c"], false),
1043        )
1044        .unwrap();
1045        assert_eq!(
1046            delta.tracks.splices,
1047            vec![Splice {
1048                at: 1,
1049                remove: 1,
1050                insert: vec!["x".to_string()],
1051            }]
1052        );
1053        assert_eq!(delta.shuffle, ScalarDelta::Unchanged);
1054    }
1055
1056    #[test]
1057    fn ordered_distinguishes_reorder_from_unordered() {
1058        // Reordering is invisible to `unordered` but not to `ordered`.
1059        let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["b", "a"], false));
1060        assert!(delta.is_some());
1061    }
1062
1063    #[test]
1064    fn ordered_false_positive_check() {
1065        let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "b"], false));
1066        assert!(delta.is_none());
1067    }
1068
1069    #[test]
1070    fn ordered_apply_round_trips() {
1071        let cases: &[(&[&str], &[&str])] = &[
1072            (&["a", "b", "c"], &["a", "x", "c"]),
1073            (&["a", "b"], &["a", "b", "c"]),
1074            (&["b", "c"], &["a", "b", "c"]),
1075            (&["a", "b", "c"], &[]),
1076            (&[], &["a", "b", "c"]),
1077            (&["a", "b", "c", "d", "e"], &["a", "x", "c", "y", "e"]),
1078            (&["a", "a", "a", "b"], &["a", "b", "a", "a"]),
1079            (&["a", "b", "c"], &["c", "b", "a"]),
1080        ];
1081        for (old, new) in cases {
1082            let mut applied = playlist(old, true);
1083            let delta = Delta::delta(playlist(old, false), playlist(new, true)).unwrap();
1084            applied.apply_delta(delta).unwrap();
1085            assert_eq!(applied, playlist(new, true), "{:?} -> {:?}", old, new);
1086        }
1087    }
1088
1089    #[cfg(feature = "serde")]
1090    #[test]
1091    fn ordered_delta_serializes() {
1092        let delta =
1093            Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "c"], false)).unwrap();
1094        let json = serde_json::to_string(&delta.tracks).unwrap();
1095        assert_eq!(json, r#"{"splices":[{"at":1,"remove":1,"insert":["c"]}]}"#);
1096        let round_tripped: SeqDelta<String> = serde_json::from_str(&json).unwrap();
1097        let mut target = playlist(&["a", "b"], false);
1098        seq::apply(&mut target.tracks, round_tripped);
1099        assert_eq!(target.tracks, vec!["a".to_string(), "c".to_string()]);
1100    }
1101
1102    #[test]
1103    fn ordered_as_container_default() {
1104        let delta = Delta::delta(
1105            OrderedByDefault {
1106                a: vec![1, 2],
1107                b: vec![3],
1108            },
1109            OrderedByDefault {
1110                a: vec![1, 2],
1111                b: vec![3, 4],
1112            },
1113        )
1114        .unwrap();
1115        assert!(delta.a.is_empty());
1116        assert_eq!(
1117            delta.b.splices,
1118            vec![Splice {
1119                at: 1,
1120                remove: 0,
1121                insert: vec![4],
1122            }]
1123        );
1124    }
1125
1126    #[derive(Clone, Debug, Delta, PartialEq, serde::Serialize)]
1127    #[delta_struct(delta_leader = "#[derive(Debug, serde::Serialize)]")]
1128    struct Service {
1129        port: u16,
1130        healthy: bool,
1131    }
1132
1133    #[derive(Clone, Debug, Delta, PartialEq)]
1134    struct Cluster {
1135        #[delta_struct(field_type = "unordered-delta")]
1136        services: HashMap<String, Service>,
1137        region: String,
1138    }
1139
1140    #[derive(Clone, Debug, Delta, PartialEq)]
1141    #[delta_struct(delta_leader = "#[derive(Clone, Debug)]")]
1142    struct ClusterNoDelta {
1143        #[delta_struct(field_type = "unordered")]
1144        services: HashMap<String, String>,
1145        region: String,
1146    }
1147
1148    #[derive(Delta)]
1149    #[delta_struct(default = "unordered-delta")]
1150    #[allow(dead_code)] // The derive is itself the test
1151    struct UnorderedDeltaByDefault {
1152        a: HashMap<u8, NewType>,
1153        b: BTreeMap<u8, NewType>,
1154    }
1155
1156    #[derive(Delta)]
1157    #[allow(dead_code)] // The derive is itself the test
1158    struct UnorderedDeltaWithGeneric<K: std::hash::Hash + Eq, V: Delta> {
1159        #[delta_struct(
1160            field_type = "unordered-delta",
1161            delta_leader = "/// One part of the change to `foo`."
1162        )]
1163        foo: HashMap<K, V>,
1164    }
1165
1166    /// A cluster's services in the compact `(name, port, healthy)` form the
1167    /// tests below are written in.
1168    type Services<'a> = &'a [(&'a str, u16, bool)];
1169
1170    fn cluster(services: Services, region: &str) -> Cluster {
1171        Cluster {
1172            services: services
1173                .iter()
1174                .map(|(name, port, healthy)| {
1175                    (
1176                        name.to_string(),
1177                        Service {
1178                            port: *port,
1179                            healthy: *healthy,
1180                        },
1181                    )
1182                })
1183                .collect(),
1184            region: region.to_string(),
1185        }
1186    }
1187
1188    #[test]
1189    fn unordered_delta_diffs_values_in_place() {
1190        let delta = Delta::delta(
1191            cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
1192            cluster(&[("web", 8080, true), ("db", 5432, true)], "us"),
1193        )
1194        .unwrap();
1195        // `db` is untouched and `web` only moved its port, so neither entry is
1196        // resent in full.
1197        assert!(delta.services.add.is_empty());
1198        assert!(delta.services.remove.is_empty());
1199        assert_eq!(delta.services.change.len(), 1);
1200        assert_eq!(delta.services.change[0].key, "web");
1201        assert_eq!(
1202            delta.services.change[0].delta.port,
1203            ScalarDelta::Changed(8080)
1204        );
1205        assert_eq!(
1206            delta.services.change[0].delta.healthy,
1207            ScalarDelta::Unchanged
1208        );
1209        assert_eq!(delta.region, ScalarDelta::Unchanged);
1210    }
1211
1212    #[test]
1213    fn unordered_delta_adds_and_removes_by_key() {
1214        let delta = Delta::delta(
1215            cluster(&[("web", 80, true)], "us"),
1216            cluster(&[("db", 5432, false)], "us"),
1217        )
1218        .unwrap();
1219        assert_eq!(
1220            delta.services.add,
1221            vec![(
1222                "db".to_string(),
1223                Service {
1224                    port: 5432,
1225                    healthy: false
1226                }
1227            )]
1228        );
1229        assert_eq!(delta.services.remove, vec!["web".to_string()]);
1230        assert!(delta.services.change.is_empty());
1231    }
1232
1233    #[test]
1234    fn unordered_delta_false_positive_check() {
1235        let delta = Delta::delta(
1236            cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
1237            cluster(&[("db", 5432, true), ("web", 80, true)], "us"),
1238        );
1239        assert!(delta.is_none());
1240    }
1241
1242    #[test]
1243    fn unordered_delta_apply_round_trips() {
1244        let cases: &[(Services, Services)] = &[
1245            // A value changed under a stable key.
1246            (&[("web", 80, true)], &[("web", 8080, true)]),
1247            // Pure addition, pure removal, and both at once.
1248            (&[("web", 80, true)], &[("web", 80, true), ("db", 1, false)]),
1249            (&[("web", 80, true), ("db", 1, false)], &[("web", 80, true)]),
1250            (&[("web", 80, true)], &[("db", 1, false)]),
1251            // Every kind of change in one go.
1252            (
1253                &[("web", 80, true), ("db", 1, false), ("gone", 9, true)],
1254                &[("web", 8080, true), ("db", 1, false), ("new", 7, false)],
1255            ),
1256            (&[], &[("web", 80, true)]),
1257            (&[("web", 80, true)], &[]),
1258        ];
1259        for (old, new) in cases {
1260            let mut applied = cluster(old, "us");
1261            let delta = Delta::delta(cluster(old, "us"), cluster(new, "eu")).unwrap();
1262            applied.apply_delta(delta).unwrap();
1263            assert_eq!(applied, cluster(new, "eu"), "{:?} -> {:?}", old, new);
1264        }
1265    }
1266
1267    #[test]
1268    fn unordered_delta_over_a_btree_map() {
1269        // The field need not be a `HashMap`: any collection with a
1270        // `TryIndexMut` impl works, and a `BTreeMap`'s ordering makes the
1271        // three lists deterministic.
1272        #[derive(Clone, Debug, Delta, PartialEq)]
1273        struct Pairs {
1274            #[delta_struct(field_type = "unordered-delta")]
1275            entries: BTreeMap<u8, NewType>,
1276        }
1277
1278        let pairs = |entries: &[(u8, i32)]| Pairs {
1279            entries: entries.iter().map(|(k, v)| (*k, NewType(*v))).collect(),
1280        };
1281
1282        let mut applied = pairs(&[(1, 10), (2, 20)]);
1283        let delta = Delta::delta(pairs(&[(1, 10), (2, 20)]), pairs(&[(2, 21), (3, 30)])).unwrap();
1284        assert_eq!(delta.entries.add, vec![(3, NewType(30))]);
1285        assert_eq!(delta.entries.remove, vec![1]);
1286        assert_eq!(delta.entries.change.len(), 1);
1287        applied.apply_delta(delta).unwrap();
1288        assert_eq!(applied, pairs(&[(2, 21), (3, 30)]));
1289    }
1290
1291    /// A `ClusterNoDelta`'s services in the compact `(name, image)` form the
1292    /// tests below are written in.
1293    type Images<'a> = &'a [(&'a str, &'a str)];
1294
1295    fn cluster_no_delta(services: Images, region: &str) -> ClusterNoDelta {
1296        ClusterNoDelta {
1297            services: services
1298                .iter()
1299                .map(|(name, image)| (name.to_string(), image.to_string()))
1300                .collect(),
1301            region: region.to_string(),
1302        }
1303    }
1304
1305    /// A `HashMap` iterates in whatever order it likes, so an `EntryDelta`
1306    /// over one has to be sorted before it can be compared.
1307    fn sorted<T: Ord>(mut entries: Vec<T>) -> Vec<T> {
1308        entries.sort();
1309        entries
1310    }
1311
1312    #[test]
1313    fn unordered_over_a_map_sends_one_copy_of_a_changed_value() {
1314        // The case the field type exists for: `String` has no `Delta` impl, so
1315        // `unordered-delta` is out and membership is all there is to diff. The
1316        // key survived, so only what it holds now travels — the receiver
1317        // already has the old value and is never sent it back.
1318        let delta = Delta::delta(
1319            cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1320            cluster_no_delta(&[("web", "nginx:2"), ("db", "pg:14")], "us"),
1321        )
1322        .unwrap();
1323        assert_eq!(
1324            sorted(delta.services.add),
1325            vec![("web".to_string(), "nginx:2".to_string())]
1326        );
1327        assert!(delta.services.remove.is_empty());
1328        // `db` sat still on both sides, and so did the region.
1329        assert_eq!(delta.region, ScalarDelta::Unchanged);
1330    }
1331
1332    #[test]
1333    fn unordered_over_a_map_removes_by_bare_key() {
1334        let delta = Delta::delta(
1335            cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1336            cluster_no_delta(&[("db", "pg:14")], "us"),
1337        )
1338        .unwrap();
1339        assert!(delta.services.add.is_empty());
1340        assert_eq!(delta.services.remove, vec!["web".to_string()]);
1341    }
1342
1343    #[test]
1344    fn unordered_over_a_map_false_positive_check() {
1345        let delta = Delta::delta(
1346            cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1347            cluster_no_delta(&[("db", "pg:14"), ("web", "nginx:1")], "us"),
1348        );
1349        assert!(delta.is_none());
1350    }
1351
1352    #[test]
1353    fn unordered_over_a_map_apply_round_trips() {
1354        let cases: &[(Images, Images)] = &[
1355            // A value changed under a stable key.
1356            (&[("web", "nginx:1")], &[("web", "nginx:2")]),
1357            // Pure addition, pure removal, and both at once.
1358            (
1359                &[("web", "nginx:1")],
1360                &[("web", "nginx:1"), ("db", "pg:14")],
1361            ),
1362            (
1363                &[("web", "nginx:1"), ("db", "pg:14")],
1364                &[("web", "nginx:1")],
1365            ),
1366            (&[("web", "nginx:1")], &[("db", "pg:14")]),
1367            // Every kind of change in one go.
1368            (
1369                &[("web", "nginx:1"), ("db", "pg:14"), ("gone", "x:1")],
1370                &[("web", "nginx:2"), ("db", "pg:14"), ("new", "y:1")],
1371            ),
1372            (&[], &[("web", "nginx:1")]),
1373            (&[("web", "nginx:1")], &[]),
1374        ];
1375        for (old, new) in cases {
1376            let mut applied = cluster_no_delta(old, "us");
1377            let delta =
1378                Delta::delta(cluster_no_delta(old, "us"), cluster_no_delta(new, "eu")).unwrap();
1379            applied.apply_delta(delta).unwrap();
1380            assert_eq!(
1381                applied,
1382                cluster_no_delta(new, "eu"),
1383                "{:?} -> {:?}",
1384                old,
1385                new
1386            );
1387        }
1388    }
1389
1390    #[test]
1391    fn unordered_over_a_map_apply_ignores_absent_removals() {
1392        // Same tolerance the set case has: applying twice is harmless, since
1393        // the second removal finds nothing and the second addition overwrites
1394        // with what is already there.
1395        let delta = Delta::delta(
1396            cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1397            cluster_no_delta(&[("web", "nginx:2")], "us"),
1398        )
1399        .unwrap();
1400        let mut applied = cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us");
1401        applied.apply_delta(delta.clone()).unwrap();
1402        applied.apply_delta(delta).unwrap();
1403        assert_eq!(applied, cluster_no_delta(&[("web", "nginx:2")], "us"));
1404    }
1405
1406    #[test]
1407    fn unordered_over_a_btree_map() {
1408        // The field need not be a `HashMap`: any map with an `Unordered` impl
1409        // works, and a `BTreeMap`'s ordering makes the two lists
1410        // deterministic.
1411        #[derive(Clone, Debug, Delta, PartialEq)]
1412        struct Labels {
1413            #[delta_struct(field_type = "unordered")]
1414            entries: BTreeMap<u8, char>,
1415        }
1416
1417        let labels = |entries: &[(u8, char)]| Labels {
1418            entries: entries.iter().copied().collect(),
1419        };
1420
1421        let mut applied = labels(&[(1, 'a'), (2, 'b')]);
1422        let delta =
1423            Delta::delta(labels(&[(1, 'a'), (2, 'b')]), labels(&[(2, 'c'), (3, 'd')])).unwrap();
1424        // `2` changed and `3` arrived; both are additions, because applying
1425        // either means the same thing to a map.
1426        assert_eq!(delta.entries.add, vec![(2, 'c'), (3, 'd')]);
1427        assert_eq!(delta.entries.remove, vec![1]);
1428        applied.apply_delta(delta).unwrap();
1429        assert_eq!(applied, labels(&[(2, 'c'), (3, 'd')]));
1430    }
1431
1432    #[test]
1433    fn unordered_over_a_map_with_generics() {
1434        // The delta field is spelled `<HashMap<K, V> as Unordered>::Delta`, so
1435        // the generated struct only holds together when the projection
1436        // resolves through the source type's own bounds.
1437        #[derive(Clone, Debug, Delta, PartialEq)]
1438        #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
1439        struct Tagged<K: std::hash::Hash + Eq, V: PartialEq> {
1440            #[delta_struct(field_type = "unordered")]
1441            tags: HashMap<K, V>,
1442        }
1443
1444        let tagged = |v: u8| Tagged {
1445            tags: vec![("a", v)].into_iter().collect::<HashMap<&str, u8>>(),
1446        };
1447
1448        let delta = Delta::delta(tagged(1), tagged(2)).unwrap();
1449        assert_eq!(
1450            delta.tags,
1451            EntryDelta {
1452                add: vec![("a", 2)],
1453                remove: vec![]
1454            }
1455        );
1456
1457        let mut applied = tagged(1);
1458        applied.apply_delta(delta).unwrap();
1459        assert_eq!(applied, tagged(2));
1460    }
1461
1462    #[cfg(feature = "serde")]
1463    #[test]
1464    fn unordered_over_a_map_serializes() {
1465        #[derive(Delta)]
1466        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
1467        struct Labels {
1468            #[delta_struct(field_type = "unordered")]
1469            entries: BTreeMap<String, String>,
1470        }
1471
1472        let labels = |image: &str| Labels {
1473            entries: vec![("web".to_string(), image.to_string())]
1474                .into_iter()
1475                .collect(),
1476        };
1477
1478        let delta = Delta::delta(labels("nginx:1"), labels("nginx:2")).unwrap();
1479        assert_eq!(
1480            serde_json::to_string(&delta).unwrap(),
1481            r#"{"entries":{"add":[["web","nginx:2"]],"remove":[]}}"#
1482        );
1483    }
1484
1485    #[cfg(feature = "serde")]
1486    #[test]
1487    fn unordered_delta_serializes() {
1488        #[derive(Delta)]
1489        #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
1490        struct Fleet {
1491            #[delta_struct(field_type = "unordered-delta")]
1492            services: BTreeMap<String, Service>,
1493        }
1494
1495        let fleet = |port| Fleet {
1496            services: vec![(
1497                "web".to_string(),
1498                Service {
1499                    port,
1500                    healthy: true,
1501                },
1502            )]
1503            .into_iter()
1504            .collect(),
1505        };
1506        let delta = Delta::delta(fleet(80), fleet(8080)).unwrap();
1507        assert_eq!(
1508            serde_json::to_string(&delta).unwrap(),
1509            r#"{"services":{"add":[],"remove":[],"change":[{"key":"web","delta":{"port":{"changed":8080},"healthy":"unchanged"}}]}}"#
1510        );
1511    }
1512
1513    #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
1514    #[delta_struct(delta_leader = "#[derive(Clone, Debug)]")]
1515    struct Tracked {
1516        name: String,
1517        #[delta_struct(field_type = "unordered")]
1518        tags: HashSet<String>,
1519        revision: u32,
1520    }
1521
1522    fn tracked(name: &str, tags: &[&str], revision: u32) -> Tracked {
1523        Tracked {
1524            name: name.to_string(),
1525            tags: tags.iter().map(|t| t.to_string()).collect(),
1526            revision,
1527        }
1528    }
1529
1530    #[test]
1531    fn fingerprint_ignores_set_iteration_order() {
1532        // The whole point: two `HashSet`s built in different orders are the
1533        // same state and must fingerprint the same.
1534        let forwards = tracked("a", &["x", "y", "z"], 1);
1535        let backwards = tracked("a", &["z", "y", "x"], 1);
1536        assert_eq!(fingerprint_of(&forwards), fingerprint_of(&backwards));
1537        assert_ne!(
1538            fingerprint_of(&forwards),
1539            fingerprint_of(&tracked("a", &["x", "y"], 1))
1540        );
1541        assert_ne!(
1542            fingerprint_of(&forwards),
1543            fingerprint_of(&tracked("b", &["x", "y", "z"], 1))
1544        );
1545    }
1546
1547    #[test]
1548    fn fingerprint_derives_on_enums_and_tuple_structs() {
1549        #[derive(Fingerprint)]
1550        enum Shape {
1551            Empty,
1552            Circle(u32),
1553            Rect { w: u32, h: u32 },
1554        }
1555
1556        #[derive(Fingerprint)]
1557        struct Pair(u8, bool);
1558
1559        assert_ne!(
1560            fingerprint_of(&Shape::Empty),
1561            fingerprint_of(&Shape::Circle(0))
1562        );
1563        // Same payload, different variant, so the discriminant has to count.
1564        assert_ne!(
1565            fingerprint_of(&Shape::Circle(1)),
1566            fingerprint_of(&Shape::Rect { w: 1, h: 0 })
1567        );
1568        assert_eq!(
1569            fingerprint_of(&Shape::Rect { w: 2, h: 3 }),
1570            fingerprint_of(&Shape::Rect { w: 2, h: 3 })
1571        );
1572        assert_ne!(
1573            fingerprint_of(&Pair(1, true)),
1574            fingerprint_of(&Pair(1, false))
1575        );
1576    }
1577
1578    #[test]
1579    fn fingerprint_is_stable_across_runs() {
1580        // Pinned literals: if these ever change, every deployed sender and
1581        // receiver disagree until both are rebuilt.
1582        assert_eq!(fingerprint_of(&0u8), 0xaf63bd4c8601b7df);
1583        assert_eq!(fingerprint_of(&true), 0xaf63bc4c8601b62c);
1584        assert_eq!(fingerprint_of(&"delta"), 0x3035df3ae9e50ee6);
1585    }
1586
1587    #[test]
1588    fn versioned_round_trips() {
1589        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1590        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1591
1592        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1593        assert_eq!((message.from, message.to), (0, 1));
1594        assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1595        assert_eq!(receiver.get(), sender.get());
1596        assert_eq!(receiver.version(), sender.version());
1597    }
1598
1599    #[test]
1600    fn versioned_no_change_burns_nothing() {
1601        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1602        assert!(sender.commit(tracked("a", &["x"], 1)).is_none());
1603        assert_eq!(sender.version(), 0);
1604    }
1605
1606    #[test]
1607    fn versioned_ignores_a_replayed_delta() {
1608        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1609        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1610
1611        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1612        assert_eq!(receiver.apply(message.clone()), Ok(Applied::Updated));
1613        // Duplicate delivery is a no-op rather than a corruption.
1614        assert_eq!(receiver.apply(message), Ok(Applied::Stale));
1615        assert_eq!(receiver.get(), sender.get());
1616    }
1617
1618    #[test]
1619    fn versioned_catches_a_dropped_delta() {
1620        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1621        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1622
1623        let _lost = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1624        let second = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
1625
1626        assert_eq!(
1627            receiver.apply(second),
1628            Err(Rejected::Gap {
1629                expected: 0,
1630                found: 1
1631            })
1632        );
1633        // A rejected delta leaves the receiver untouched.
1634        assert_eq!(receiver.version(), 0);
1635        assert_eq!(receiver.get(), &tracked("a", &["x"], 1));
1636    }
1637
1638    #[test]
1639    fn versioned_catches_drift_from_outside_the_stream() {
1640        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1641        // The receiver starts at the right version but the wrong contents,
1642        // which no sequence number could notice.
1643        let mut receiver = Versioned::new(tracked("a", &["tampered"], 1));
1644
1645        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1646        match receiver.apply(message) {
1647            Err(Rejected::Base { expected, found }) => assert_ne!(expected, found),
1648            other => panic!("expected a base mismatch, got {:?}", other),
1649        }
1650        assert_eq!(receiver.version(), 0);
1651    }
1652
1653    #[test]
1654    fn versioned_resync_recovers() {
1655        // The documented answer to any `Mismatch`: send the whole thing.
1656        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1657        let mut receiver = Versioned::new(tracked("a", &["wrong"], 1));
1658
1659        let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1660        assert!(receiver.apply(message).is_err());
1661
1662        receiver = sender.clone();
1663        let next = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
1664        assert_eq!(receiver.apply(next), Ok(Applied::Updated));
1665        assert_eq!(receiver.get(), sender.get());
1666    }
1667
1668    #[test]
1669    fn versioned_catches_a_wrong_result() {
1670        // A hand-built delta whose `result` does not describe what applying it
1671        // actually does — the case only the second fingerprint can catch.
1672        let mut sender = Versioned::new(tracked("a", &["x"], 1));
1673        let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1674
1675        let mut message = sender.commit(tracked("a", &["x"], 2)).unwrap();
1676        message.result ^= 1;
1677
1678        match receiver.apply(message) {
1679            Err(Rejected::Result { expected, found }) => assert_ne!(expected, found),
1680            other => panic!("expected a result mismatch, got {:?}", other),
1681        }
1682        // Version not advanced, so the corruption cannot be mistaken for
1683        // healthy state by the next delta either.
1684        assert_eq!(receiver.version(), 0);
1685    }
1686
1687    #[cfg(feature = "serde")]
1688    #[test]
1689    fn versioned_delta_serializes() {
1690        #[derive(Clone, Delta, Fingerprint)]
1691        #[delta_struct(delta_leader = "#[derive(serde::Serialize, serde::Deserialize)]")]
1692        struct Config {
1693            port: u16,
1694        }
1695
1696        let mut sender = Versioned::new(Config { port: 80 });
1697        let mut receiver = Versioned::new(Config { port: 80 });
1698
1699        let payload =
1700            serde_json::to_string(&sender.commit(Config { port: 8080 }).unwrap()).unwrap();
1701        let message: VersionedDelta<ConfigDelta> = serde_json::from_str(&payload).unwrap();
1702        assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1703        assert_eq!(receiver.get().port, 8080);
1704    }
1705
1706    // `ShapeDelta` holds a `BagDelta`, which is only `Serialize` when the
1707    // crate's `serde` feature is on — so the serde half of this has to be
1708    // conditional, unlike the plain-`Option` deltas elsewhere in these tests.
1709    #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
1710    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1711    #[cfg_attr(
1712        feature = "serde",
1713        delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq, serde::Serialize)]")
1714    )]
1715    #[cfg_attr(
1716        not(feature = "serde"),
1717        delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq)]")
1718    )]
1719    enum Shape {
1720        Empty,
1721        Circle(u32),
1722        Rect {
1723            w: u32,
1724            h: u32,
1725            #[delta_struct(field_type = "unordered")]
1726            tags: BTreeSet<String>,
1727        },
1728    }
1729
1730    fn rect(w: u32, h: u32, tags: &[&str]) -> Shape {
1731        Shape::Rect {
1732            w,
1733            h,
1734            tags: tags.iter().map(|t| t.to_string()).collect(),
1735        }
1736    }
1737
1738    #[test]
1739    fn enum_diffs_within_a_variant() {
1740        let delta = Delta::delta(rect(1, 2, &["a"]), rect(1, 3, &["a", "b"])).unwrap();
1741        match delta {
1742            EnumDelta::Delta(ShapeDelta::Rect { w, h, tags }) => {
1743                assert_eq!(w, ScalarDelta::Unchanged); // unchanged, so it does not travel
1744                assert_eq!(h, ScalarDelta::Changed(3));
1745                assert_eq!(tags.add, vec!["b".to_string()]);
1746                assert!(tags.remove.is_empty());
1747            }
1748            other => panic!("expected a same-variant delta, got {:?}", other),
1749        }
1750    }
1751
1752    #[test]
1753    fn enum_replaces_across_variants() {
1754        let delta = Delta::delta(Shape::Circle(1), rect(1, 2, &[])).unwrap();
1755        assert_eq!(delta, EnumDelta::Became(rect(1, 2, &[])));
1756
1757        // A unit variant on either side is still just a replacement.
1758        let delta = Delta::delta(Shape::Empty, Shape::Circle(9)).unwrap();
1759        assert_eq!(delta, EnumDelta::Became(Shape::Circle(9)));
1760    }
1761
1762    #[test]
1763    fn enum_false_positive_check() {
1764        assert!(Delta::delta(Shape::Empty, Shape::Empty).is_none());
1765        assert!(Delta::delta(Shape::Circle(1), Shape::Circle(1)).is_none());
1766        assert!(Delta::delta(rect(1, 2, &["a"]), rect(1, 2, &["a"])).is_none());
1767    }
1768
1769    #[test]
1770    fn enum_apply_round_trips() {
1771        let cases: &[(Shape, Shape)] = &[
1772            (Shape::Circle(1), Shape::Circle(2)),
1773            (rect(1, 2, &["a"]), rect(9, 2, &["b"])),
1774            (Shape::Empty, Shape::Circle(3)),
1775            (Shape::Circle(3), Shape::Empty),
1776            (rect(1, 2, &[]), Shape::Circle(4)),
1777            (Shape::Circle(4), rect(5, 6, &["x", "y"])),
1778        ];
1779        for (old, new) in cases {
1780            let mut applied = old.clone();
1781            let delta = Delta::delta(old.clone(), new.clone()).unwrap();
1782            applied.apply_delta(delta).unwrap();
1783            assert_eq!(&applied, new, "{:?} -> {:?}", old, new);
1784        }
1785    }
1786
1787    #[test]
1788    fn enum_apply_reports_the_wrong_variant() {
1789        // The failure structs cannot have: a delta built while the value was a
1790        // `Rect`, applied to a value that is now a `Circle`.
1791        let delta = Delta::delta(rect(1, 2, &[]), rect(1, 3, &[])).unwrap();
1792        let mut diverged = Shape::Circle(7);
1793        assert_eq!(
1794            diverged.apply_delta(delta),
1795            Err(Mismatch {
1796                type_name: "Shape",
1797                expected: "Rect",
1798                found: "Circle",
1799            })
1800        );
1801        // Nothing was touched on the way to noticing.
1802        assert_eq!(diverged, Shape::Circle(7));
1803    }
1804
1805    #[test]
1806    fn enum_mismatch_propagates_through_a_struct() {
1807        #[derive(Clone, Debug, Delta, PartialEq)]
1808        struct Canvas {
1809            #[delta_struct(field_type = "delta")]
1810            shape: Shape,
1811            name: String,
1812        }
1813
1814        let canvas = |shape: Shape, name: &str| Canvas {
1815            shape,
1816            name: name.to_string(),
1817        };
1818        let delta =
1819            Delta::delta(canvas(rect(1, 2, &[]), "a"), canvas(rect(1, 3, &[]), "b")).unwrap();
1820
1821        let mut diverged = canvas(Shape::Empty, "a");
1822        // The innermost mismatch is what surfaces, not a wrapper naming
1823        // `Canvas`.
1824        assert_eq!(
1825            diverged.apply_delta(delta),
1826            Err(Mismatch {
1827                type_name: "Shape",
1828                expected: "Rect",
1829                found: "Empty",
1830            })
1831        );
1832    }
1833
1834    #[test]
1835    fn enum_of_only_unit_variants() {
1836        // Nothing is diffable, so the companion enum is uninhabited and every
1837        // change is a replacement. It still has to compile and work.
1838        #[derive(Clone, Debug, Delta, PartialEq)]
1839        enum Flag {
1840            On,
1841            Off,
1842        }
1843
1844        assert!(Delta::delta(Flag::On, Flag::On).is_none());
1845        let mut applied = Flag::On;
1846        applied
1847            .apply_delta(Delta::delta(Flag::On, Flag::Off).unwrap())
1848            .unwrap();
1849        assert_eq!(applied, Flag::Off);
1850    }
1851
1852    #[test]
1853    fn enum_with_generics() {
1854        #[derive(Clone, Debug, Delta, PartialEq)]
1855        #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
1856        #[allow(dead_code)] // `Empty` is here to be the non-diffable variant.
1857        enum Slot<T>
1858        where
1859            T: Clone,
1860        {
1861            Filled(T),
1862            Empty,
1863        }
1864
1865        let delta = Delta::delta(Slot::Filled(1), Slot::Filled(2)).unwrap();
1866        assert_eq!(
1867            delta,
1868            EnumDelta::Delta(SlotDelta::Filled(ScalarDelta::Changed(2)))
1869        );
1870
1871        let mut applied = Slot::Filled(1);
1872        applied.apply_delta(delta).unwrap();
1873        assert_eq!(applied, Slot::Filled(2));
1874    }
1875
1876    #[cfg(feature = "serde")]
1877    #[test]
1878    fn enum_delta_serializes() {
1879        let delta = Delta::delta(Shape::Circle(1), Shape::Circle(2)).unwrap();
1880        assert_eq!(
1881            serde_json::to_string(&delta).unwrap(),
1882            r#"{"Delta":{"Circle":{"changed":2}}}"#
1883        );
1884        // `Became` carries the source enum whole, which is why serializing an
1885        // enum's delta needs the enum itself to be serializable.
1886        let delta = Delta::delta(Shape::Empty, Shape::Circle(2)).unwrap();
1887        assert_eq!(
1888            serde_json::to_string(&delta).unwrap(),
1889            r#"{"Became":{"Circle":2}}"#
1890        );
1891    }
1892
1893    #[test]
1894    fn enum_inside_versioned() {
1895        let mut sender = Versioned::new(rect(1, 2, &["a"]));
1896        let mut receiver = Versioned::new(rect(1, 2, &["a"]));
1897        let message = sender.commit(rect(1, 3, &["a"])).unwrap();
1898        assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1899        assert_eq!(receiver.get(), sender.get());
1900
1901        // A receiver at the right version but in the wrong variant is caught
1902        // by the base fingerprint before `apply_delta` is ever reached, so it
1903        // is a `Base`, not an `Apply`.
1904        let mut fresh = Versioned::new(rect(1, 2, &["a"]));
1905        let mut diverged = Versioned::new(Shape::Circle(7));
1906        let message = fresh.commit(rect(1, 4, &["a"])).unwrap();
1907        assert!(matches!(
1908            diverged.apply(message),
1909            Err(Rejected::Base { .. })
1910        ));
1911    }
1912
1913    #[test]
1914    fn bounded_generics() {
1915        let delta = Delta::delta(
1916            InlineBoundGeneric { foo: 1, bar: false },
1917            InlineBoundGeneric { foo: 2, bar: false },
1918        )
1919        .unwrap();
1920        assert_eq!(delta.foo, ScalarDelta::Changed(2));
1921        assert_eq!(delta.bar, ScalarDelta::Unchanged);
1922
1923        let delta = Delta::delta(
1924            WhereClauseGeneric { foo: 1, bar: false },
1925            WhereClauseGeneric { foo: 2, bar: false },
1926        )
1927        .unwrap();
1928        assert_eq!(delta.foo, ScalarDelta::Changed(2));
1929        assert_eq!(delta.bar, ScalarDelta::Unchanged);
1930    }
1931
1932    #[test]
1933    fn bounded_generics_with_delta_field() {
1934        let delta = Delta::delta(
1935            InlineBoundDeltaField { foo: NewType(1) },
1936            InlineBoundDeltaField { foo: NewType(2) },
1937        )
1938        .unwrap();
1939        assert_eq!(delta.foo.unwrap().0, ScalarDelta::Changed(2));
1940
1941        let mut applied = WhereClauseDeltaField { foo: NewType(1) };
1942        let delta = Delta::delta(
1943            WhereClauseDeltaField { foo: NewType(1) },
1944            WhereClauseDeltaField { foo: NewType(2) },
1945        )
1946        .unwrap();
1947        applied.apply_delta(delta).unwrap();
1948        assert_eq!(applied.foo, NewType(2));
1949    }
1950
1951    #[test]
1952    fn apply_delta_all_field_types() {
1953        let old = AllFieldTypes {
1954            scalar: 1,
1955            delta: NewType(3),
1956            unordered: vec![1, 2, 3].into_iter().collect(),
1957        };
1958        let new = AllFieldTypes {
1959            scalar: 2,
1960            delta: NewType(4),
1961            unordered: vec![3, 4, 5].into_iter().collect(),
1962        };
1963        let new_clone = new.clone();
1964        let mut old_delta_applied = old.clone();
1965        let delta = Delta::delta(old, new);
1966        old_delta_applied.apply_delta(delta.unwrap()).unwrap();
1967        assert_eq!(new_clone, old_delta_applied);
1968    }
1969}