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).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 `Option<T>`: `Some(new_value)` when the two differ, [`None`]
57//! 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;
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, Some(8080));
189//! assert_eq!(delta.services.change[0].delta.healthy, None);
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;
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, None);
274//! assert_eq!(inner_delta.b, Some(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};
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, Some(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;
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, Some(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":null,"port":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(skip_serializing_if = \"Option::is_none\")]")]
430//! host: String,
431//! #[delta_struct(delta_leader = "#[serde(skip_serializing_if = \"Option::is_none\")]")]
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":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;
538//!
539//! #[derive(Delta)]
540//! struct Meters(i32);
541//!
542//! let delta = Delta::delta(Meters(3), Meters(4)).unwrap();
543//! assert_eq!(delta.0, Some(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
580pub mod bag;
581pub mod entry;
582pub mod fingerprint;
583pub mod index;
584pub mod map;
585pub mod seq;
586pub mod unordered;
587pub mod variant;
588pub mod version;
589
590pub use bag::BagDelta;
591pub use delta_struct_macros::{Delta, Fingerprint};
592pub use entry::EntryDelta;
593pub use fingerprint::{fingerprint_of, Fingerprint};
594pub use index::{TryIndex, TryIndexMut};
595pub use map::{KeyedDelta, MapDelta, MapEntry};
596pub use seq::{SeqDelta, Splice};
597pub use unordered::Unordered;
598pub use variant::{EnumDelta, Mismatch};
599pub use version::{Applied, Rejected, Versioned, VersionedDelta};
600
601/// Computing the difference between two values, and applying it to a third.
602///
603/// You will normally derive this rather than implement it — see the
604/// [crate documentation](crate) for the derive's attributes and the shape of
605/// the type it generates. Implement it by hand when you want custom diffing
606/// for a type that other structs then reference with
607/// `#[delta_struct(field_type = "delta")]`.
608pub trait Delta {
609 /// The type describing a difference between two `Self` values.
610 ///
611 /// The derive sets this to the generated `{Self}Delta` struct — or, for an
612 /// enum, to [`EnumDelta<Self, {Self}Delta>`](EnumDelta), since a value can
613 /// change variant as well as change within one.
614 type Output;
615
616 /// Computes what it would take to turn `old` into `new`.
617 ///
618 /// Returns [`None`] when the two are equivalent, which lets callers skip
619 /// sending or storing an update that would do nothing. Both values are
620 /// consumed: the delta takes ownership of whatever it needs from `new`.
621 fn delta(old: Self, new: Self) -> Option<Self::Output>;
622
623 /// Applies a delta in place.
624 ///
625 /// Applying the delta from `delta(old, new)` to a value equal to `old`
626 /// yields a value equal to `new` — with the caveat that `unordered` fields
627 /// preserve membership rather than order.
628 ///
629 /// Fails only when the delta cannot fit the value, which only an enum can
630 /// manage: a delta built for one variant, applied to a value now in
631 /// another. See [`Mismatch`]. For a struct — and for an enum in the
632 /// variant its delta expects — this always returns `Ok`.
633 ///
634 /// A failure leaves the value partly updated, so treat it the way
635 /// [`Versioned`] does: the value is no longer trustworthy and wants
636 /// replacing wholesale, not patching again.
637 fn apply_delta(&mut self, delta: Self::Output) -> Result<(), Mismatch>;
638}
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
643
644 #[derive(Delta)]
645 #[allow(dead_code)] // The derive is itself the test
646 struct UnitType;
647
648 #[derive(Delta, Clone, Debug, PartialEq, Eq)]
649 #[delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq, Eq)]")]
650 struct NewType(i32);
651
652 #[derive(Delta)]
653 #[allow(dead_code)] // The derive is itself the test
654 struct NewTypeWithGeneric<T>(T);
655
656 // A tuple struct's delta is a tuple struct, which puts its `where` clause
657 // after the fields rather than before them. Both spellings of a bound have
658 // to survive that.
659 #[derive(Delta)]
660 #[allow(dead_code)] // The derive is itself the test
661 struct InlineBoundNewType<T: Clone>(T);
662
663 #[derive(Delta)]
664 #[allow(dead_code)] // The derive is itself the test
665 struct WhereClauseNewType<T>(T)
666 where
667 T: Clone;
668
669 #[derive(Clone, Debug, Delta, PartialEq)]
670 #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
671 struct Reading(
672 #[delta_struct(field_type = "unordered")] BTreeSet<i32>,
673 #[delta_struct(field_type = "ordered")] Vec<String>,
674 #[delta_struct(field_type = "delta")] NewType,
675 bool,
676 );
677
678 #[test]
679 fn tuple_struct_delta_keeps_field_positions() {
680 let old = Reading(
681 vec![1, 2].into_iter().collect(),
682 vec!["a".to_string()],
683 NewType(7),
684 false,
685 );
686 let new = Reading(
687 vec![2, 3].into_iter().collect(),
688 vec!["a".to_string(), "b".to_string()],
689 NewType(8),
690 true,
691 );
692 let delta = Delta::delta(old.clone(), new.clone()).unwrap();
693 assert_eq!(delta.0.add, vec![3]);
694 assert_eq!(delta.0.remove, vec![1]);
695 assert_eq!(
696 delta.1.splices,
697 vec![Splice {
698 at: 1,
699 remove: 0,
700 insert: vec!["b".to_string()],
701 }]
702 );
703 assert_eq!(delta.2, Some(NewTypeDelta(Some(8))));
704 assert_eq!(delta.3, Some(true));
705
706 let mut applied = old;
707 applied.apply_delta(delta).unwrap();
708 assert_eq!(applied, new);
709 }
710
711 #[cfg(feature = "serde")]
712 #[test]
713 fn tuple_struct_delta_serializes_as_a_sequence() {
714 #[derive(Delta)]
715 #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
716 struct Meters(i32, i32);
717
718 let delta = Delta::delta(Meters(1, 2), Meters(1, 3)).unwrap();
719 assert_eq!(serde_json::to_string(&delta).unwrap(), "[null,3]");
720 }
721
722 #[derive(Delta)]
723 struct InlineBoundGeneric<T: Clone> {
724 foo: T,
725 bar: bool,
726 }
727
728 #[derive(Delta)]
729 struct WhereClauseGeneric<T>
730 where
731 T: Clone,
732 {
733 foo: T,
734 bar: bool,
735 }
736
737 #[derive(Delta)]
738 struct InlineBoundDeltaField<T: Delta> {
739 #[delta_struct(field_type = "delta")]
740 foo: T,
741 }
742
743 #[derive(Delta)]
744 struct WhereClauseDeltaField<T>
745 where
746 T: Delta,
747 {
748 #[delta_struct(field_type = "delta")]
749 foo: T,
750 }
751
752 #[derive(Delta)]
753 struct SimpleType {
754 #[delta_struct(delta_leader = "/// This is foo.")]
755 foo: i32,
756 bar: bool,
757 }
758
759 #[derive(Delta)]
760 #[allow(dead_code)] // The derive is itself the test
761 struct SimpleTypeWithGeneric<T> {
762 foo: T,
763 bar: bool,
764 }
765
766 #[derive(Delta)]
767 struct SimpleCollectionWithGeneric<T: Ord> {
768 #[delta_struct(
769 field_type = "unordered",
770 delta_leader = "/// This the foo type on the delta struct."
771 )]
772 foo: BTreeSet<T>,
773 bar: bool,
774 }
775
776 #[derive(Delta)]
777 struct DeltaRecursion {
778 #[delta_struct(field_type = "delta")]
779 foo: NewType,
780 bar: bool,
781 }
782
783 #[derive(Delta)]
784 #[delta_struct(default = "unordered")]
785 struct AttributeTest {
786 #[delta_struct(field_type = "scalar")]
787 foo: i32,
788 #[delta_struct(field_type = "scalar")]
789 bar: i32,
790 baz: BTreeSet<i32>,
791 }
792
793 #[derive(Delta, Clone, Debug, PartialEq, Eq)]
794 struct AllFieldTypes {
795 #[delta_struct(field_type = "scalar")]
796 scalar: i32,
797 #[delta_struct(field_type = "delta")]
798 delta: NewType,
799 #[delta_struct(field_type = "unordered")]
800 unordered: HashSet<i32>,
801 }
802
803 #[derive(Clone, Debug, Delta, PartialEq)]
804 #[allow(dead_code)] // The derive is itself the test
805 struct DeviceConfig {
806 #[delta_struct(field_type = "unordered")]
807 pub services: HashSet<String>,
808 #[delta_struct(field_type = "unordered")]
809 pub settings: HashSet<String>,
810 pub thumbnail_request: i32,
811 pub speedtest_request: i32,
812 #[delta_struct(field_type = "delta")]
813 pub features: AllFieldTypes,
814 pub deprovision: bool,
815 }
816
817 #[test]
818 fn unordered_with_scalar() {
819 let old = SimpleCollectionWithGeneric {
820 foo: vec![1, 2, 3].into_iter().collect(),
821 bar: false,
822 };
823 let new = SimpleCollectionWithGeneric {
824 foo: vec![3, 4, 5].into_iter().collect(),
825 bar: true,
826 };
827 let delta = Delta::delta(old, new).unwrap();
828 assert_eq!(delta.foo.add, vec![4, 5]);
829 assert_eq!(delta.foo.remove, vec![1, 2]);
830 assert_eq!(delta.bar, Some(true));
831 }
832
833 #[test]
834 fn unordered_apply_round_trips() {
835 #[derive(Clone, Debug, Delta, PartialEq)]
836 struct Tags {
837 #[delta_struct(field_type = "unordered")]
838 labels: HashSet<i32>,
839 }
840
841 let tags = |labels: &[i32]| Tags {
842 labels: labels.iter().copied().collect(),
843 };
844 let cases: &[(&[i32], &[i32])] = &[
845 (&[1, 2, 3], &[3, 4, 5]),
846 (&[1, 2], &[1, 2, 3]),
847 (&[1, 2, 3], &[1, 2]),
848 (&[], &[1, 2, 3]),
849 (&[1, 2, 3], &[]),
850 (&[1, 2], &[3, 4]),
851 ];
852 for (old, new) in cases {
853 let mut applied = tags(old);
854 let delta = Delta::delta(tags(old), tags(new)).unwrap();
855 applied.apply_delta(delta).unwrap();
856 assert_eq!(applied, tags(new), "{:?} -> {:?}", old, new);
857 }
858 }
859
860 #[test]
861 fn unordered_apply_ignores_absent_removals() {
862 // `apply` drops each removal by lookup rather than rebuilding, so a
863 // key that isn't there is a no-op — which makes applying the same
864 // delta twice harmless.
865 let old = AllFieldTypes {
866 scalar: 1,
867 delta: NewType(1),
868 unordered: vec![1, 2].into_iter().collect(),
869 };
870 let new = AllFieldTypes {
871 scalar: 1,
872 delta: NewType(1),
873 unordered: vec![2, 3].into_iter().collect(),
874 };
875 let mut applied = old.clone();
876 applied
877 .apply_delta(Delta::delta(old.clone(), new.clone()).unwrap())
878 .unwrap();
879 applied
880 .apply_delta(Delta::delta(old, new.clone()).unwrap())
881 .unwrap();
882 assert_eq!(applied, new);
883 }
884
885 #[cfg(feature = "serde")]
886 #[test]
887 fn unordered_delta_serializes_nested() {
888 #[derive(Delta)]
889 #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
890 struct Device {
891 #[delta_struct(field_type = "unordered")]
892 services: BTreeSet<String>,
893 }
894
895 let device = |services: &[&str]| Device {
896 services: services.iter().map(|s| s.to_string()).collect(),
897 };
898 let delta = Delta::delta(device(&["ssh"]), device(&["mqtt"])).unwrap();
899 assert_eq!(
900 serde_json::to_string(&delta).unwrap(),
901 r#"{"services":{"add":["mqtt"],"remove":["ssh"]}}"#
902 );
903 }
904
905 #[test]
906 fn delta_false_positive_check() {
907 let old = NewType(5);
908 let new = NewType(5);
909 let delta = Delta::delta(old, new);
910 assert!(delta.is_none());
911 }
912
913 #[test]
914 fn scalar_delta_false_positive_check() {
915 let old = SimpleType { foo: 5, bar: false };
916 let new = SimpleType { foo: 5, bar: true };
917 let delta = Delta::delta(old, new).unwrap();
918 assert!(delta.foo.is_none());
919 assert_eq!(delta.bar, Some(true));
920 }
921
922 #[test]
923 fn delta_field() {
924 let old = DeltaRecursion {
925 foo: NewType(5),
926 bar: false,
927 };
928 let new = DeltaRecursion {
929 foo: NewType(6),
930 bar: true,
931 };
932 let delta = Delta::delta(old, new).unwrap();
933 assert_eq!(delta.foo, Some(NewTypeDelta(Some(6))));
934 assert_eq!(delta.bar, Some(true));
935 }
936
937 #[test]
938 fn default_type_respected() {
939 let old = AttributeTest {
940 foo: 5,
941 bar: 4,
942 baz: BTreeSet::new(),
943 };
944 let new = AttributeTest {
945 foo: 5,
946 bar: 4,
947 baz: vec![9, 4, 5].into_iter().collect(),
948 };
949 let delta = Delta::delta(old, new).unwrap();
950 assert!(delta.foo.is_none());
951 assert!(delta.bar.is_none());
952 assert_eq!(delta.baz.add, vec![4, 5, 9]);
953 assert_eq!(delta.baz.remove, Vec::<i32>::new());
954 }
955
956 #[derive(Clone, Debug, Delta, PartialEq)]
957 struct Playlist {
958 #[delta_struct(field_type = "ordered")]
959 tracks: Vec<String>,
960 shuffle: bool,
961 }
962
963 #[derive(Delta)]
964 #[delta_struct(default = "ordered")]
965 struct OrderedByDefault {
966 a: Vec<i32>,
967 b: Vec<i32>,
968 }
969
970 fn playlist(tracks: &[&str], shuffle: bool) -> Playlist {
971 Playlist {
972 tracks: tracks.iter().map(|t| t.to_string()).collect(),
973 shuffle,
974 }
975 }
976
977 #[test]
978 fn ordered_records_position() {
979 let delta = Delta::delta(
980 playlist(&["a", "b", "c"], false),
981 playlist(&["a", "x", "c"], false),
982 )
983 .unwrap();
984 assert_eq!(
985 delta.tracks.splices,
986 vec![Splice {
987 at: 1,
988 remove: 1,
989 insert: vec!["x".to_string()],
990 }]
991 );
992 assert_eq!(delta.shuffle, None);
993 }
994
995 #[test]
996 fn ordered_distinguishes_reorder_from_unordered() {
997 // Reordering is invisible to `unordered` but not to `ordered`.
998 let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["b", "a"], false));
999 assert!(delta.is_some());
1000 }
1001
1002 #[test]
1003 fn ordered_false_positive_check() {
1004 let delta = Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "b"], false));
1005 assert!(delta.is_none());
1006 }
1007
1008 #[test]
1009 fn ordered_apply_round_trips() {
1010 let cases: &[(&[&str], &[&str])] = &[
1011 (&["a", "b", "c"], &["a", "x", "c"]),
1012 (&["a", "b"], &["a", "b", "c"]),
1013 (&["b", "c"], &["a", "b", "c"]),
1014 (&["a", "b", "c"], &[]),
1015 (&[], &["a", "b", "c"]),
1016 (&["a", "b", "c", "d", "e"], &["a", "x", "c", "y", "e"]),
1017 (&["a", "a", "a", "b"], &["a", "b", "a", "a"]),
1018 (&["a", "b", "c"], &["c", "b", "a"]),
1019 ];
1020 for (old, new) in cases {
1021 let mut applied = playlist(old, true);
1022 let delta = Delta::delta(playlist(old, false), playlist(new, true)).unwrap();
1023 applied.apply_delta(delta).unwrap();
1024 assert_eq!(applied, playlist(new, true), "{:?} -> {:?}", old, new);
1025 }
1026 }
1027
1028 #[cfg(feature = "serde")]
1029 #[test]
1030 fn ordered_delta_serializes() {
1031 let delta =
1032 Delta::delta(playlist(&["a", "b"], false), playlist(&["a", "c"], false)).unwrap();
1033 let json = serde_json::to_string(&delta.tracks).unwrap();
1034 assert_eq!(json, r#"{"splices":[{"at":1,"remove":1,"insert":["c"]}]}"#);
1035 let round_tripped: SeqDelta<String> = serde_json::from_str(&json).unwrap();
1036 let mut target = playlist(&["a", "b"], false);
1037 seq::apply(&mut target.tracks, round_tripped);
1038 assert_eq!(target.tracks, vec!["a".to_string(), "c".to_string()]);
1039 }
1040
1041 #[test]
1042 fn ordered_as_container_default() {
1043 let delta = Delta::delta(
1044 OrderedByDefault {
1045 a: vec![1, 2],
1046 b: vec![3],
1047 },
1048 OrderedByDefault {
1049 a: vec![1, 2],
1050 b: vec![3, 4],
1051 },
1052 )
1053 .unwrap();
1054 assert!(delta.a.is_empty());
1055 assert_eq!(
1056 delta.b.splices,
1057 vec![Splice {
1058 at: 1,
1059 remove: 0,
1060 insert: vec![4],
1061 }]
1062 );
1063 }
1064
1065 #[derive(Clone, Debug, Delta, PartialEq, serde::Serialize)]
1066 #[delta_struct(delta_leader = "#[derive(Debug, serde::Serialize)]")]
1067 struct Service {
1068 port: u16,
1069 healthy: bool,
1070 }
1071
1072 #[derive(Clone, Debug, Delta, PartialEq)]
1073 struct Cluster {
1074 #[delta_struct(field_type = "unordered-delta")]
1075 services: HashMap<String, Service>,
1076 region: String,
1077 }
1078
1079 #[derive(Clone, Debug, Delta, PartialEq)]
1080 #[delta_struct(delta_leader = "#[derive(Clone, Debug)]")]
1081 struct ClusterNoDelta {
1082 #[delta_struct(field_type = "unordered")]
1083 services: HashMap<String, String>,
1084 region: String,
1085 }
1086
1087 #[derive(Delta)]
1088 #[delta_struct(default = "unordered-delta")]
1089 #[allow(dead_code)] // The derive is itself the test
1090 struct UnorderedDeltaByDefault {
1091 a: HashMap<u8, NewType>,
1092 b: BTreeMap<u8, NewType>,
1093 }
1094
1095 #[derive(Delta)]
1096 #[allow(dead_code)] // The derive is itself the test
1097 struct UnorderedDeltaWithGeneric<K: std::hash::Hash + Eq, V: Delta> {
1098 #[delta_struct(
1099 field_type = "unordered-delta",
1100 delta_leader = "/// One part of the change to `foo`."
1101 )]
1102 foo: HashMap<K, V>,
1103 }
1104
1105 /// A cluster's services in the compact `(name, port, healthy)` form the
1106 /// tests below are written in.
1107 type Services<'a> = &'a [(&'a str, u16, bool)];
1108
1109 fn cluster(services: Services, region: &str) -> Cluster {
1110 Cluster {
1111 services: services
1112 .iter()
1113 .map(|(name, port, healthy)| {
1114 (
1115 name.to_string(),
1116 Service {
1117 port: *port,
1118 healthy: *healthy,
1119 },
1120 )
1121 })
1122 .collect(),
1123 region: region.to_string(),
1124 }
1125 }
1126
1127 #[test]
1128 fn unordered_delta_diffs_values_in_place() {
1129 let delta = Delta::delta(
1130 cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
1131 cluster(&[("web", 8080, true), ("db", 5432, true)], "us"),
1132 )
1133 .unwrap();
1134 // `db` is untouched and `web` only moved its port, so neither entry is
1135 // resent in full.
1136 assert!(delta.services.add.is_empty());
1137 assert!(delta.services.remove.is_empty());
1138 assert_eq!(delta.services.change.len(), 1);
1139 assert_eq!(delta.services.change[0].key, "web");
1140 assert_eq!(delta.services.change[0].delta.port, Some(8080));
1141 assert_eq!(delta.services.change[0].delta.healthy, None);
1142 assert_eq!(delta.region, None);
1143 }
1144
1145 #[test]
1146 fn unordered_delta_adds_and_removes_by_key() {
1147 let delta = Delta::delta(
1148 cluster(&[("web", 80, true)], "us"),
1149 cluster(&[("db", 5432, false)], "us"),
1150 )
1151 .unwrap();
1152 assert_eq!(
1153 delta.services.add,
1154 vec![(
1155 "db".to_string(),
1156 Service {
1157 port: 5432,
1158 healthy: false
1159 }
1160 )]
1161 );
1162 assert_eq!(delta.services.remove, vec!["web".to_string()]);
1163 assert!(delta.services.change.is_empty());
1164 }
1165
1166 #[test]
1167 fn unordered_delta_false_positive_check() {
1168 let delta = Delta::delta(
1169 cluster(&[("web", 80, true), ("db", 5432, true)], "us"),
1170 cluster(&[("db", 5432, true), ("web", 80, true)], "us"),
1171 );
1172 assert!(delta.is_none());
1173 }
1174
1175 #[test]
1176 fn unordered_delta_apply_round_trips() {
1177 let cases: &[(Services, Services)] = &[
1178 // A value changed under a stable key.
1179 (&[("web", 80, true)], &[("web", 8080, true)]),
1180 // Pure addition, pure removal, and both at once.
1181 (&[("web", 80, true)], &[("web", 80, true), ("db", 1, false)]),
1182 (&[("web", 80, true), ("db", 1, false)], &[("web", 80, true)]),
1183 (&[("web", 80, true)], &[("db", 1, false)]),
1184 // Every kind of change in one go.
1185 (
1186 &[("web", 80, true), ("db", 1, false), ("gone", 9, true)],
1187 &[("web", 8080, true), ("db", 1, false), ("new", 7, false)],
1188 ),
1189 (&[], &[("web", 80, true)]),
1190 (&[("web", 80, true)], &[]),
1191 ];
1192 for (old, new) in cases {
1193 let mut applied = cluster(old, "us");
1194 let delta = Delta::delta(cluster(old, "us"), cluster(new, "eu")).unwrap();
1195 applied.apply_delta(delta).unwrap();
1196 assert_eq!(applied, cluster(new, "eu"), "{:?} -> {:?}", old, new);
1197 }
1198 }
1199
1200 #[test]
1201 fn unordered_delta_over_a_btree_map() {
1202 // The field need not be a `HashMap`: any collection with a
1203 // `TryIndexMut` impl works, and a `BTreeMap`'s ordering makes the
1204 // three lists deterministic.
1205 #[derive(Clone, Debug, Delta, PartialEq)]
1206 struct Pairs {
1207 #[delta_struct(field_type = "unordered-delta")]
1208 entries: BTreeMap<u8, NewType>,
1209 }
1210
1211 let pairs = |entries: &[(u8, i32)]| Pairs {
1212 entries: entries.iter().map(|(k, v)| (*k, NewType(*v))).collect(),
1213 };
1214
1215 let mut applied = pairs(&[(1, 10), (2, 20)]);
1216 let delta = Delta::delta(pairs(&[(1, 10), (2, 20)]), pairs(&[(2, 21), (3, 30)])).unwrap();
1217 assert_eq!(delta.entries.add, vec![(3, NewType(30))]);
1218 assert_eq!(delta.entries.remove, vec![1]);
1219 assert_eq!(delta.entries.change.len(), 1);
1220 applied.apply_delta(delta).unwrap();
1221 assert_eq!(applied, pairs(&[(2, 21), (3, 30)]));
1222 }
1223
1224 /// A `ClusterNoDelta`'s services in the compact `(name, image)` form the
1225 /// tests below are written in.
1226 type Images<'a> = &'a [(&'a str, &'a str)];
1227
1228 fn cluster_no_delta(services: Images, region: &str) -> ClusterNoDelta {
1229 ClusterNoDelta {
1230 services: services
1231 .iter()
1232 .map(|(name, image)| (name.to_string(), image.to_string()))
1233 .collect(),
1234 region: region.to_string(),
1235 }
1236 }
1237
1238 /// A `HashMap` iterates in whatever order it likes, so an `EntryDelta`
1239 /// over one has to be sorted before it can be compared.
1240 fn sorted<T: Ord>(mut entries: Vec<T>) -> Vec<T> {
1241 entries.sort();
1242 entries
1243 }
1244
1245 #[test]
1246 fn unordered_over_a_map_sends_one_copy_of_a_changed_value() {
1247 // The case the field type exists for: `String` has no `Delta` impl, so
1248 // `unordered-delta` is out and membership is all there is to diff. The
1249 // key survived, so only what it holds now travels — the receiver
1250 // already has the old value and is never sent it back.
1251 let delta = Delta::delta(
1252 cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1253 cluster_no_delta(&[("web", "nginx:2"), ("db", "pg:14")], "us"),
1254 )
1255 .unwrap();
1256 assert_eq!(
1257 sorted(delta.services.add),
1258 vec![("web".to_string(), "nginx:2".to_string())]
1259 );
1260 assert!(delta.services.remove.is_empty());
1261 // `db` sat still on both sides, and so did the region.
1262 assert_eq!(delta.region, None);
1263 }
1264
1265 #[test]
1266 fn unordered_over_a_map_removes_by_bare_key() {
1267 let delta = Delta::delta(
1268 cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1269 cluster_no_delta(&[("db", "pg:14")], "us"),
1270 )
1271 .unwrap();
1272 assert!(delta.services.add.is_empty());
1273 assert_eq!(delta.services.remove, vec!["web".to_string()]);
1274 }
1275
1276 #[test]
1277 fn unordered_over_a_map_false_positive_check() {
1278 let delta = Delta::delta(
1279 cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1280 cluster_no_delta(&[("db", "pg:14"), ("web", "nginx:1")], "us"),
1281 );
1282 assert!(delta.is_none());
1283 }
1284
1285 #[test]
1286 fn unordered_over_a_map_apply_round_trips() {
1287 let cases: &[(Images, Images)] = &[
1288 // A value changed under a stable key.
1289 (&[("web", "nginx:1")], &[("web", "nginx:2")]),
1290 // Pure addition, pure removal, and both at once.
1291 (
1292 &[("web", "nginx:1")],
1293 &[("web", "nginx:1"), ("db", "pg:14")],
1294 ),
1295 (
1296 &[("web", "nginx:1"), ("db", "pg:14")],
1297 &[("web", "nginx:1")],
1298 ),
1299 (&[("web", "nginx:1")], &[("db", "pg:14")]),
1300 // Every kind of change in one go.
1301 (
1302 &[("web", "nginx:1"), ("db", "pg:14"), ("gone", "x:1")],
1303 &[("web", "nginx:2"), ("db", "pg:14"), ("new", "y:1")],
1304 ),
1305 (&[], &[("web", "nginx:1")]),
1306 (&[("web", "nginx:1")], &[]),
1307 ];
1308 for (old, new) in cases {
1309 let mut applied = cluster_no_delta(old, "us");
1310 let delta =
1311 Delta::delta(cluster_no_delta(old, "us"), cluster_no_delta(new, "eu")).unwrap();
1312 applied.apply_delta(delta).unwrap();
1313 assert_eq!(
1314 applied,
1315 cluster_no_delta(new, "eu"),
1316 "{:?} -> {:?}",
1317 old,
1318 new
1319 );
1320 }
1321 }
1322
1323 #[test]
1324 fn unordered_over_a_map_apply_ignores_absent_removals() {
1325 // Same tolerance the set case has: applying twice is harmless, since
1326 // the second removal finds nothing and the second addition overwrites
1327 // with what is already there.
1328 let delta = Delta::delta(
1329 cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us"),
1330 cluster_no_delta(&[("web", "nginx:2")], "us"),
1331 )
1332 .unwrap();
1333 let mut applied = cluster_no_delta(&[("web", "nginx:1"), ("db", "pg:14")], "us");
1334 applied.apply_delta(delta.clone()).unwrap();
1335 applied.apply_delta(delta).unwrap();
1336 assert_eq!(applied, cluster_no_delta(&[("web", "nginx:2")], "us"));
1337 }
1338
1339 #[test]
1340 fn unordered_over_a_btree_map() {
1341 // The field need not be a `HashMap`: any map with an `Unordered` impl
1342 // works, and a `BTreeMap`'s ordering makes the two lists
1343 // deterministic.
1344 #[derive(Clone, Debug, Delta, PartialEq)]
1345 struct Labels {
1346 #[delta_struct(field_type = "unordered")]
1347 entries: BTreeMap<u8, char>,
1348 }
1349
1350 let labels = |entries: &[(u8, char)]| Labels {
1351 entries: entries.iter().copied().collect(),
1352 };
1353
1354 let mut applied = labels(&[(1, 'a'), (2, 'b')]);
1355 let delta =
1356 Delta::delta(labels(&[(1, 'a'), (2, 'b')]), labels(&[(2, 'c'), (3, 'd')])).unwrap();
1357 // `2` changed and `3` arrived; both are additions, because applying
1358 // either means the same thing to a map.
1359 assert_eq!(delta.entries.add, vec![(2, 'c'), (3, 'd')]);
1360 assert_eq!(delta.entries.remove, vec![1]);
1361 applied.apply_delta(delta).unwrap();
1362 assert_eq!(applied, labels(&[(2, 'c'), (3, 'd')]));
1363 }
1364
1365 #[test]
1366 fn unordered_over_a_map_with_generics() {
1367 // The delta field is spelled `<HashMap<K, V> as Unordered>::Delta`, so
1368 // the generated struct only holds together when the projection
1369 // resolves through the source type's own bounds.
1370 #[derive(Clone, Debug, Delta, PartialEq)]
1371 #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
1372 struct Tagged<K: std::hash::Hash + Eq, V: PartialEq> {
1373 #[delta_struct(field_type = "unordered")]
1374 tags: HashMap<K, V>,
1375 }
1376
1377 let tagged = |v: u8| Tagged {
1378 tags: vec![("a", v)].into_iter().collect::<HashMap<&str, u8>>(),
1379 };
1380
1381 let delta = Delta::delta(tagged(1), tagged(2)).unwrap();
1382 assert_eq!(
1383 delta.tags,
1384 EntryDelta {
1385 add: vec![("a", 2)],
1386 remove: vec![]
1387 }
1388 );
1389
1390 let mut applied = tagged(1);
1391 applied.apply_delta(delta).unwrap();
1392 assert_eq!(applied, tagged(2));
1393 }
1394
1395 #[cfg(feature = "serde")]
1396 #[test]
1397 fn unordered_over_a_map_serializes() {
1398 #[derive(Delta)]
1399 #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
1400 struct Labels {
1401 #[delta_struct(field_type = "unordered")]
1402 entries: BTreeMap<String, String>,
1403 }
1404
1405 let labels = |image: &str| Labels {
1406 entries: vec![("web".to_string(), image.to_string())]
1407 .into_iter()
1408 .collect(),
1409 };
1410
1411 let delta = Delta::delta(labels("nginx:1"), labels("nginx:2")).unwrap();
1412 assert_eq!(
1413 serde_json::to_string(&delta).unwrap(),
1414 r#"{"entries":{"add":[["web","nginx:2"]],"remove":[]}}"#
1415 );
1416 }
1417
1418 #[cfg(feature = "serde")]
1419 #[test]
1420 fn unordered_delta_serializes() {
1421 #[derive(Delta)]
1422 #[delta_struct(delta_leader = "#[derive(serde::Serialize)]")]
1423 struct Fleet {
1424 #[delta_struct(field_type = "unordered-delta")]
1425 services: BTreeMap<String, Service>,
1426 }
1427
1428 let fleet = |port| Fleet {
1429 services: vec![(
1430 "web".to_string(),
1431 Service {
1432 port,
1433 healthy: true,
1434 },
1435 )]
1436 .into_iter()
1437 .collect(),
1438 };
1439 let delta = Delta::delta(fleet(80), fleet(8080)).unwrap();
1440 assert_eq!(
1441 serde_json::to_string(&delta).unwrap(),
1442 r#"{"services":{"add":[],"remove":[],"change":[{"key":"web","delta":{"port":8080,"healthy":null}}]}}"#
1443 );
1444 }
1445
1446 #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
1447 #[delta_struct(delta_leader = "#[derive(Clone, Debug)]")]
1448 struct Tracked {
1449 name: String,
1450 #[delta_struct(field_type = "unordered")]
1451 tags: HashSet<String>,
1452 revision: u32,
1453 }
1454
1455 fn tracked(name: &str, tags: &[&str], revision: u32) -> Tracked {
1456 Tracked {
1457 name: name.to_string(),
1458 tags: tags.iter().map(|t| t.to_string()).collect(),
1459 revision,
1460 }
1461 }
1462
1463 #[test]
1464 fn fingerprint_ignores_set_iteration_order() {
1465 // The whole point: two `HashSet`s built in different orders are the
1466 // same state and must fingerprint the same.
1467 let forwards = tracked("a", &["x", "y", "z"], 1);
1468 let backwards = tracked("a", &["z", "y", "x"], 1);
1469 assert_eq!(fingerprint_of(&forwards), fingerprint_of(&backwards));
1470 assert_ne!(
1471 fingerprint_of(&forwards),
1472 fingerprint_of(&tracked("a", &["x", "y"], 1))
1473 );
1474 assert_ne!(
1475 fingerprint_of(&forwards),
1476 fingerprint_of(&tracked("b", &["x", "y", "z"], 1))
1477 );
1478 }
1479
1480 #[test]
1481 fn fingerprint_derives_on_enums_and_tuple_structs() {
1482 #[derive(Fingerprint)]
1483 enum Shape {
1484 Empty,
1485 Circle(u32),
1486 Rect { w: u32, h: u32 },
1487 }
1488
1489 #[derive(Fingerprint)]
1490 struct Pair(u8, bool);
1491
1492 assert_ne!(
1493 fingerprint_of(&Shape::Empty),
1494 fingerprint_of(&Shape::Circle(0))
1495 );
1496 // Same payload, different variant, so the discriminant has to count.
1497 assert_ne!(
1498 fingerprint_of(&Shape::Circle(1)),
1499 fingerprint_of(&Shape::Rect { w: 1, h: 0 })
1500 );
1501 assert_eq!(
1502 fingerprint_of(&Shape::Rect { w: 2, h: 3 }),
1503 fingerprint_of(&Shape::Rect { w: 2, h: 3 })
1504 );
1505 assert_ne!(
1506 fingerprint_of(&Pair(1, true)),
1507 fingerprint_of(&Pair(1, false))
1508 );
1509 }
1510
1511 #[test]
1512 fn fingerprint_is_stable_across_runs() {
1513 // Pinned literals: if these ever change, every deployed sender and
1514 // receiver disagree until both are rebuilt.
1515 assert_eq!(fingerprint_of(&0u8), 0xaf63bd4c8601b7df);
1516 assert_eq!(fingerprint_of(&true), 0xaf63bc4c8601b62c);
1517 assert_eq!(fingerprint_of(&"delta"), 0x3035df3ae9e50ee6);
1518 }
1519
1520 #[test]
1521 fn versioned_round_trips() {
1522 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1523 let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1524
1525 let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1526 assert_eq!((message.from, message.to), (0, 1));
1527 assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1528 assert_eq!(receiver.get(), sender.get());
1529 assert_eq!(receiver.version(), sender.version());
1530 }
1531
1532 #[test]
1533 fn versioned_no_change_burns_nothing() {
1534 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1535 assert!(sender.commit(tracked("a", &["x"], 1)).is_none());
1536 assert_eq!(sender.version(), 0);
1537 }
1538
1539 #[test]
1540 fn versioned_ignores_a_replayed_delta() {
1541 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1542 let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1543
1544 let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1545 assert_eq!(receiver.apply(message.clone()), Ok(Applied::Updated));
1546 // Duplicate delivery is a no-op rather than a corruption.
1547 assert_eq!(receiver.apply(message), Ok(Applied::Stale));
1548 assert_eq!(receiver.get(), sender.get());
1549 }
1550
1551 #[test]
1552 fn versioned_catches_a_dropped_delta() {
1553 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1554 let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1555
1556 let _lost = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1557 let second = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
1558
1559 assert_eq!(
1560 receiver.apply(second),
1561 Err(Rejected::Gap {
1562 expected: 0,
1563 found: 1
1564 })
1565 );
1566 // A rejected delta leaves the receiver untouched.
1567 assert_eq!(receiver.version(), 0);
1568 assert_eq!(receiver.get(), &tracked("a", &["x"], 1));
1569 }
1570
1571 #[test]
1572 fn versioned_catches_drift_from_outside_the_stream() {
1573 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1574 // The receiver starts at the right version but the wrong contents,
1575 // which no sequence number could notice.
1576 let mut receiver = Versioned::new(tracked("a", &["tampered"], 1));
1577
1578 let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1579 match receiver.apply(message) {
1580 Err(Rejected::Base { expected, found }) => assert_ne!(expected, found),
1581 other => panic!("expected a base mismatch, got {:?}", other),
1582 }
1583 assert_eq!(receiver.version(), 0);
1584 }
1585
1586 #[test]
1587 fn versioned_resync_recovers() {
1588 // The documented answer to any `Mismatch`: send the whole thing.
1589 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1590 let mut receiver = Versioned::new(tracked("a", &["wrong"], 1));
1591
1592 let message = sender.commit(tracked("a", &["x", "y"], 2)).unwrap();
1593 assert!(receiver.apply(message).is_err());
1594
1595 receiver = sender.clone();
1596 let next = sender.commit(tracked("a", &["x", "y"], 3)).unwrap();
1597 assert_eq!(receiver.apply(next), Ok(Applied::Updated));
1598 assert_eq!(receiver.get(), sender.get());
1599 }
1600
1601 #[test]
1602 fn versioned_catches_a_wrong_result() {
1603 // A hand-built delta whose `result` does not describe what applying it
1604 // actually does — the case only the second fingerprint can catch.
1605 let mut sender = Versioned::new(tracked("a", &["x"], 1));
1606 let mut receiver = Versioned::new(tracked("a", &["x"], 1));
1607
1608 let mut message = sender.commit(tracked("a", &["x"], 2)).unwrap();
1609 message.result ^= 1;
1610
1611 match receiver.apply(message) {
1612 Err(Rejected::Result { expected, found }) => assert_ne!(expected, found),
1613 other => panic!("expected a result mismatch, got {:?}", other),
1614 }
1615 // Version not advanced, so the corruption cannot be mistaken for
1616 // healthy state by the next delta either.
1617 assert_eq!(receiver.version(), 0);
1618 }
1619
1620 #[cfg(feature = "serde")]
1621 #[test]
1622 fn versioned_delta_serializes() {
1623 #[derive(Clone, Delta, Fingerprint)]
1624 #[delta_struct(delta_leader = "#[derive(serde::Serialize, serde::Deserialize)]")]
1625 struct Config {
1626 port: u16,
1627 }
1628
1629 let mut sender = Versioned::new(Config { port: 80 });
1630 let mut receiver = Versioned::new(Config { port: 80 });
1631
1632 let payload =
1633 serde_json::to_string(&sender.commit(Config { port: 8080 }).unwrap()).unwrap();
1634 let message: VersionedDelta<ConfigDelta> = serde_json::from_str(&payload).unwrap();
1635 assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1636 assert_eq!(receiver.get().port, 8080);
1637 }
1638
1639 // `ShapeDelta` holds a `BagDelta`, which is only `Serialize` when the
1640 // crate's `serde` feature is on — so the serde half of this has to be
1641 // conditional, unlike the plain-`Option` deltas elsewhere in these tests.
1642 #[derive(Clone, Debug, Delta, Fingerprint, PartialEq)]
1643 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1644 #[cfg_attr(
1645 feature = "serde",
1646 delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq, serde::Serialize)]")
1647 )]
1648 #[cfg_attr(
1649 not(feature = "serde"),
1650 delta_struct(delta_leader = "#[derive(Clone, Debug, PartialEq)]")
1651 )]
1652 enum Shape {
1653 Empty,
1654 Circle(u32),
1655 Rect {
1656 w: u32,
1657 h: u32,
1658 #[delta_struct(field_type = "unordered")]
1659 tags: BTreeSet<String>,
1660 },
1661 }
1662
1663 fn rect(w: u32, h: u32, tags: &[&str]) -> Shape {
1664 Shape::Rect {
1665 w,
1666 h,
1667 tags: tags.iter().map(|t| t.to_string()).collect(),
1668 }
1669 }
1670
1671 #[test]
1672 fn enum_diffs_within_a_variant() {
1673 let delta = Delta::delta(rect(1, 2, &["a"]), rect(1, 3, &["a", "b"])).unwrap();
1674 match delta {
1675 EnumDelta::Delta(ShapeDelta::Rect { w, h, tags }) => {
1676 assert_eq!(w, None); // unchanged, so it does not travel
1677 assert_eq!(h, Some(3));
1678 assert_eq!(tags.add, vec!["b".to_string()]);
1679 assert!(tags.remove.is_empty());
1680 }
1681 other => panic!("expected a same-variant delta, got {:?}", other),
1682 }
1683 }
1684
1685 #[test]
1686 fn enum_replaces_across_variants() {
1687 let delta = Delta::delta(Shape::Circle(1), rect(1, 2, &[])).unwrap();
1688 assert_eq!(delta, EnumDelta::Became(rect(1, 2, &[])));
1689
1690 // A unit variant on either side is still just a replacement.
1691 let delta = Delta::delta(Shape::Empty, Shape::Circle(9)).unwrap();
1692 assert_eq!(delta, EnumDelta::Became(Shape::Circle(9)));
1693 }
1694
1695 #[test]
1696 fn enum_false_positive_check() {
1697 assert!(Delta::delta(Shape::Empty, Shape::Empty).is_none());
1698 assert!(Delta::delta(Shape::Circle(1), Shape::Circle(1)).is_none());
1699 assert!(Delta::delta(rect(1, 2, &["a"]), rect(1, 2, &["a"])).is_none());
1700 }
1701
1702 #[test]
1703 fn enum_apply_round_trips() {
1704 let cases: &[(Shape, Shape)] = &[
1705 (Shape::Circle(1), Shape::Circle(2)),
1706 (rect(1, 2, &["a"]), rect(9, 2, &["b"])),
1707 (Shape::Empty, Shape::Circle(3)),
1708 (Shape::Circle(3), Shape::Empty),
1709 (rect(1, 2, &[]), Shape::Circle(4)),
1710 (Shape::Circle(4), rect(5, 6, &["x", "y"])),
1711 ];
1712 for (old, new) in cases {
1713 let mut applied = old.clone();
1714 let delta = Delta::delta(old.clone(), new.clone()).unwrap();
1715 applied.apply_delta(delta).unwrap();
1716 assert_eq!(&applied, new, "{:?} -> {:?}", old, new);
1717 }
1718 }
1719
1720 #[test]
1721 fn enum_apply_reports_the_wrong_variant() {
1722 // The failure structs cannot have: a delta built while the value was a
1723 // `Rect`, applied to a value that is now a `Circle`.
1724 let delta = Delta::delta(rect(1, 2, &[]), rect(1, 3, &[])).unwrap();
1725 let mut diverged = Shape::Circle(7);
1726 assert_eq!(
1727 diverged.apply_delta(delta),
1728 Err(Mismatch {
1729 type_name: "Shape",
1730 expected: "Rect",
1731 found: "Circle",
1732 })
1733 );
1734 // Nothing was touched on the way to noticing.
1735 assert_eq!(diverged, Shape::Circle(7));
1736 }
1737
1738 #[test]
1739 fn enum_mismatch_propagates_through_a_struct() {
1740 #[derive(Clone, Debug, Delta, PartialEq)]
1741 struct Canvas {
1742 #[delta_struct(field_type = "delta")]
1743 shape: Shape,
1744 name: String,
1745 }
1746
1747 let canvas = |shape: Shape, name: &str| Canvas {
1748 shape,
1749 name: name.to_string(),
1750 };
1751 let delta =
1752 Delta::delta(canvas(rect(1, 2, &[]), "a"), canvas(rect(1, 3, &[]), "b")).unwrap();
1753
1754 let mut diverged = canvas(Shape::Empty, "a");
1755 // The innermost mismatch is what surfaces, not a wrapper naming
1756 // `Canvas`.
1757 assert_eq!(
1758 diverged.apply_delta(delta),
1759 Err(Mismatch {
1760 type_name: "Shape",
1761 expected: "Rect",
1762 found: "Empty",
1763 })
1764 );
1765 }
1766
1767 #[test]
1768 fn enum_of_only_unit_variants() {
1769 // Nothing is diffable, so the companion enum is uninhabited and every
1770 // change is a replacement. It still has to compile and work.
1771 #[derive(Clone, Debug, Delta, PartialEq)]
1772 enum Flag {
1773 On,
1774 Off,
1775 }
1776
1777 assert!(Delta::delta(Flag::On, Flag::On).is_none());
1778 let mut applied = Flag::On;
1779 applied
1780 .apply_delta(Delta::delta(Flag::On, Flag::Off).unwrap())
1781 .unwrap();
1782 assert_eq!(applied, Flag::Off);
1783 }
1784
1785 #[test]
1786 fn enum_with_generics() {
1787 #[derive(Clone, Debug, Delta, PartialEq)]
1788 #[delta_struct(delta_leader = "#[derive(Debug, PartialEq)]")]
1789 #[allow(dead_code)] // `Empty` is here to be the non-diffable variant.
1790 enum Slot<T>
1791 where
1792 T: Clone,
1793 {
1794 Filled(T),
1795 Empty,
1796 }
1797
1798 let delta = Delta::delta(Slot::Filled(1), Slot::Filled(2)).unwrap();
1799 assert_eq!(delta, EnumDelta::Delta(SlotDelta::Filled(Some(2))));
1800
1801 let mut applied = Slot::Filled(1);
1802 applied.apply_delta(delta).unwrap();
1803 assert_eq!(applied, Slot::Filled(2));
1804 }
1805
1806 #[cfg(feature = "serde")]
1807 #[test]
1808 fn enum_delta_serializes() {
1809 let delta = Delta::delta(Shape::Circle(1), Shape::Circle(2)).unwrap();
1810 assert_eq!(
1811 serde_json::to_string(&delta).unwrap(),
1812 r#"{"Delta":{"Circle":2}}"#
1813 );
1814 // `Became` carries the source enum whole, which is why serializing an
1815 // enum's delta needs the enum itself to be serializable.
1816 let delta = Delta::delta(Shape::Empty, Shape::Circle(2)).unwrap();
1817 assert_eq!(
1818 serde_json::to_string(&delta).unwrap(),
1819 r#"{"Became":{"Circle":2}}"#
1820 );
1821 }
1822
1823 #[test]
1824 fn enum_inside_versioned() {
1825 let mut sender = Versioned::new(rect(1, 2, &["a"]));
1826 let mut receiver = Versioned::new(rect(1, 2, &["a"]));
1827 let message = sender.commit(rect(1, 3, &["a"])).unwrap();
1828 assert_eq!(receiver.apply(message), Ok(Applied::Updated));
1829 assert_eq!(receiver.get(), sender.get());
1830
1831 // A receiver at the right version but in the wrong variant is caught
1832 // by the base fingerprint before `apply_delta` is ever reached, so it
1833 // is a `Base`, not an `Apply`.
1834 let mut fresh = Versioned::new(rect(1, 2, &["a"]));
1835 let mut diverged = Versioned::new(Shape::Circle(7));
1836 let message = fresh.commit(rect(1, 4, &["a"])).unwrap();
1837 assert!(matches!(
1838 diverged.apply(message),
1839 Err(Rejected::Base { .. })
1840 ));
1841 }
1842
1843 #[test]
1844 fn bounded_generics() {
1845 let delta = Delta::delta(
1846 InlineBoundGeneric { foo: 1, bar: false },
1847 InlineBoundGeneric { foo: 2, bar: false },
1848 )
1849 .unwrap();
1850 assert_eq!(delta.foo, Some(2));
1851 assert_eq!(delta.bar, None);
1852
1853 let delta = Delta::delta(
1854 WhereClauseGeneric { foo: 1, bar: false },
1855 WhereClauseGeneric { foo: 2, bar: false },
1856 )
1857 .unwrap();
1858 assert_eq!(delta.foo, Some(2));
1859 assert_eq!(delta.bar, None);
1860 }
1861
1862 #[test]
1863 fn bounded_generics_with_delta_field() {
1864 let delta = Delta::delta(
1865 InlineBoundDeltaField { foo: NewType(1) },
1866 InlineBoundDeltaField { foo: NewType(2) },
1867 )
1868 .unwrap();
1869 assert_eq!(delta.foo.unwrap().0, Some(2));
1870
1871 let mut applied = WhereClauseDeltaField { foo: NewType(1) };
1872 let delta = Delta::delta(
1873 WhereClauseDeltaField { foo: NewType(1) },
1874 WhereClauseDeltaField { foo: NewType(2) },
1875 )
1876 .unwrap();
1877 applied.apply_delta(delta).unwrap();
1878 assert_eq!(applied.foo, NewType(2));
1879 }
1880
1881 #[test]
1882 fn apply_delta_all_field_types() {
1883 let old = AllFieldTypes {
1884 scalar: 1,
1885 delta: NewType(3),
1886 unordered: vec![1, 2, 3].into_iter().collect(),
1887 };
1888 let new = AllFieldTypes {
1889 scalar: 2,
1890 delta: NewType(4),
1891 unordered: vec![3, 4, 5].into_iter().collect(),
1892 };
1893 let new_clone = new.clone();
1894 let mut old_delta_applied = old.clone();
1895 let delta = Delta::delta(old, new);
1896 old_delta_applied.apply_delta(delta.unwrap()).unwrap();
1897 assert_eq!(new_clone, old_delta_applied);
1898 }
1899}