Skip to content

Commit 8bb8707

Browse files
committed
Add support for sets
1 parent 55fcb30 commit 8bb8707

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ serde_json
5252
- [x] BTreeMap (thanks @milkey-mouse)
5353
- [x] Fixed-size arrays (thanks @Boscop)
5454
- [x] Tuples (thanks @Boscop)
55+
- [x] HashSet (thanks @Diggsey)
56+
- [x] BTreeSet (thanks @Diggsey)
5557

5658
# Simple example
5759

Diff for: examples/map.rs

+10
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
3434
.map
3535
.insert("hi".to_string(), vec!["planet".to_string()]);
3636

37+
let mut hi_planet_hello_world = TestStruct::default();
38+
hi_planet_hello_world
39+
.map
40+
.insert("hi".to_string(), vec!["planet".to_string()]);
41+
hi_planet_hello_world
42+
.map
43+
.insert("hello".to_string(), vec!["world".to_string()]);
44+
3745
let add_hello = serde_json::to_string(&Diff::serializable(&empty, &hello_world))?;
3846
let hello_to_hi = serde_json::to_string(&Diff::serializable(&hello_world, &hi_world))?;
3947
let add_planet = serde_json::to_string(&Diff::serializable(&hi_world, &hi_world_and_planet))?;
4048
let del_world = serde_json::to_string(&Diff::serializable(&hi_world_and_planet, &hi_planet))?;
4149
let no_change = serde_json::to_string(&Diff::serializable(&hi_planet, &hi_planet))?;
50+
let add_world = serde_json::to_string(&Diff::serializable(&hi_planet, &hi_planet_hello_world))?;
4251

4352
let mut built = TestStruct::default();
4453
for (diff, after) in &[
@@ -47,6 +56,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
4756
(add_planet, hi_world_and_planet),
4857
(del_world, hi_planet.clone()),
4958
(no_change, hi_planet),
59+
(add_world, hi_planet_hello_world),
5060
] {
5161
println!("{}", diff);
5262

Diff for: examples/set.rs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use serde::{Deserialize, Serialize};
2+
use serde_diff::{Apply, Diff, SerdeDiff};
3+
use std::collections::HashSet;
4+
5+
#[derive(SerdeDiff, Serialize, Deserialize, Debug, Default, PartialEq, Clone)]
6+
struct TestStruct {
7+
test: bool,
8+
//#[serde_diff(opaque)]
9+
set: HashSet<String>,
10+
}
11+
12+
fn main() -> Result<(), Box<dyn std::error::Error>> {
13+
let mut empty = TestStruct::default();
14+
empty.test = true;
15+
16+
let mut a = TestStruct::default();
17+
a.set.insert("a".to_string());
18+
19+
let mut ab = TestStruct::default();
20+
ab.set.insert("a".to_string());
21+
ab.set.insert("b".to_string());
22+
23+
let mut b = TestStruct::default();
24+
b.set.insert("b".to_string());
25+
26+
let mut c = TestStruct::default();
27+
c.set.insert("c".to_string());
28+
29+
let add_a = serde_json::to_string(&Diff::serializable(&empty, &a))?;
30+
let add_b = serde_json::to_string(&Diff::serializable(&a, &ab))?;
31+
let del_a = serde_json::to_string(&Diff::serializable(&ab, &b))?;
32+
let rep_b_c = serde_json::to_string(&Diff::serializable(&b, &c))?;
33+
let no_change = serde_json::to_string(&Diff::serializable(&c, &c))?;
34+
35+
let mut built = TestStruct::default();
36+
for (diff, after) in &[
37+
(add_a, a),
38+
(add_b, ab),
39+
(del_a, b),
40+
(rep_b_c, c.clone()),
41+
(no_change, c),
42+
] {
43+
println!("{}", diff);
44+
45+
let mut deserializer = serde_json::Deserializer::from_str(&diff);
46+
Apply::apply(&mut deserializer, &mut built)?;
47+
48+
assert_eq!(after, &built);
49+
}
50+
Ok(())
51+
}

Diff for: src/implementation.rs

+73-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
use serde::{de, ser::SerializeSeq, Deserialize, Serialize};
88

99
use std::{
10-
collections::{BTreeMap, HashMap},
10+
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
1111
hash::Hash,
1212
};
1313

@@ -237,6 +237,78 @@ macro_rules! map_serde_diff {
237237
map_serde_diff!(HashMap<K, V>, Hash, Eq);
238238
map_serde_diff!(BTreeMap<K, V>, Ord);
239239

240+
/// Implement SerdeDiff on a "set-like" type such as HashSet.
241+
macro_rules! set_serde_diff {
242+
($t:ty, $($extra_traits:path),*) => {
243+
impl<K> SerdeDiff for $t
244+
where
245+
K: SerdeDiff + Serialize + for<'a> Deserialize<'a> $(+ $extra_traits)*, // + Hash + Eq,
246+
{
247+
fn diff<'a, S: SerializeSeq>(
248+
&self,
249+
ctx: &mut $crate::difference::DiffContext<'a, S>,
250+
other: &Self,
251+
) -> Result<bool, S::Error> {
252+
use $crate::difference::DiffCommandRef;
253+
254+
let mut changed = false;
255+
256+
// TODO: detect renames
257+
for key in self.iter() {
258+
if !other.contains(key) {
259+
ctx.save_command(&DiffCommandRef::RemoveKey(key), true, true)?;
260+
changed = true;
261+
}
262+
}
263+
264+
for key in other.iter() {
265+
if !self.contains(key) {
266+
ctx.save_command(&DiffCommandRef::AddKey(key), true, true)?;
267+
changed = true;
268+
}
269+
}
270+
271+
if changed {
272+
ctx.save_command::<()>(&DiffCommandRef::Exit, true, false)?;
273+
}
274+
Ok(changed)
275+
}
276+
277+
fn apply<'de, A>(
278+
&mut self,
279+
seq: &mut A,
280+
ctx: &mut ApplyContext,
281+
) -> Result<bool, <A as de::SeqAccess<'de>>::Error>
282+
where
283+
A: de::SeqAccess<'de>,
284+
{
285+
let mut changed = false;
286+
while let Some(cmd) = ctx.read_next_command::<A, K>(seq)? {
287+
use $crate::difference::DiffCommandValue::*;
288+
use $crate::difference::DiffPathElementValue::*;
289+
match cmd {
290+
// we should not be getting fields when reading collection commands
291+
Enter(Field(_)) | EnterKey(_) => {
292+
ctx.skip_value(seq)?;
293+
break;
294+
}
295+
AddKey(key) => {
296+
self.insert(key);
297+
changed = true;
298+
}
299+
RemoveKey(key) => changed |= self.remove(&key),
300+
_ => break,
301+
}
302+
}
303+
Ok(changed)
304+
}
305+
}
306+
};
307+
}
308+
309+
set_serde_diff!(HashSet<K>, Hash, Eq);
310+
set_serde_diff!(BTreeSet<K>, Ord);
311+
240312
/// Implements SerdeDiff on a type given that it impls Serialize + Deserialize + PartialEq.
241313
/// This makes the type a "terminal" type in the SerdeDiff hierarchy, meaning deeper inspection
242314
/// will not be possible. Use the SerdeDiff derive macro for recursive field inspection.

0 commit comments

Comments
 (0)