1use differs::{
2 changed, diff_changes,
3 Changed::{Added, AddedAt, Moved, Removed, RemovedAt},
4 Diff, Fields,
5 MapChanged::{AddedEntry, ChangedEntry, RemovedEntry},
6};
7use serde::{Deserialize, Serialize};
8use std::{
9 collections::{HashMap, HashSet},
10 hash::{Hash, Hasher},
11};
12
13#[derive(Diff, Fields, Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
14struct Address {
15 street: String,
16 city: String,
17}
18
19#[derive(Diff, Fields, Clone, Debug, Serialize, Deserialize)]
20pub struct Account {
21 id: u32,
22 username: String,
23 password: String,
24 roles: HashSet<String>,
25 preferences: HashMap<String, String>,
26 address: Address,
27}
28
29impl PartialEq for Account {
30 fn eq(&self, other: &Self) -> bool {
31 self.id == other.id
32 }
33}
34impl Eq for Account {}
35impl Hash for Account {
36 fn hash<H: Hasher>(&self, s: &mut H) {
37 self.id.hash(s)
38 }
39}
40
41#[derive(Diff, Fields, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
42pub struct Company {
43 name: String,
44 staff: Vec<Account>,
45}
46
47fn dump_company(changes: &[CompanyChange<'_>]) {
48 for ch in changes {
49 changed!(ch;
50 Company.name(v) => { println!("Company.name({v})"); };
51
52 Company.staff(Added(acc)) => { println!("Company.staff(Added({}))", acc.username); };
53 Company.staff(Removed(acc)) => { println!("Company.staff(Removed({}))", acc.username); };
54
55 Company.staff(AddedAt(i, acc, _)) => { println!("Company.staff(AddedAt({i}, {}))", acc.username); };
56 Company.staff(RemovedAt(i, acc, _)) => { println!("Company.staff(RemovedAt({i}, {}))", acc.username); };
57
58 Company.staff(Moved(acc, f, t)) => { println!("Company.staff(Moved({}, {f}->{t}))", acc.username); };
59 );
60 }
61}
62
63fn dump_account(changes: &[AccountChange<'_>]) {
64 for ch in changes {
65 changed!(ch;
66 Account.address.city(v) => {
68 println!("Account.address.city({v})");
69 };
70
71 Account.roles(Added(role)) => {
73 println!("Account.roles(Added({role}))");
74 };
75 Account.roles(Removed(role)) => {
76 println!("Account.roles(Removed({role}))");
77 };
78
79 Account.preferences(AddedEntry(k, v)) => {
81 println!("Account.preferences(AddedEntry({k}, {v}))");
82 };
83 Account.preferences(RemovedEntry(k, v)) => {
84 println!("Account.preferences(RemovedEntry({k}, {v}))");
85 };
86 Account.preferences(ChangedEntry(k)) => {
87 println!("Account.preferences(ChangedEntry({k}))");
88 };
89 );
90 }
91}
92
93fn main() -> Result<(), Box<dyn std::error::Error>> {
94 let old = Company {
96 name: "Acme Inc.".into(),
97 staff: vec![
98 Account {
99 id: 1,
100 username: "alice".into(),
101 password: "secret".into(),
102 roles: HashSet::from(["admin".into()]),
103 preferences: HashMap::from([("theme".into(), "dark".into())]),
104 address: Address {
105 street: "1 Main".into(),
106 city: "Paris".into(),
107 },
108 },
109 Account {
110 id: 2,
111 username: "bob".into(),
112 password: "hunter2".into(),
113 roles: HashSet::from(["user".into()]),
114 preferences: HashMap::new(),
115 address: Address {
116 street: "99 Broadway".into(),
117 city: "London".into(),
118 },
119 },
120 ],
121 };
122
123 let mut new = old.clone();
125 new.name = "Acme Corp.".into();
126 new.staff[0].address.city = "Berlin".into();
127 new.staff[0].roles.insert("devops".into());
128 new.staff[1].roles.remove("user");
129 new.staff[0]
130 .preferences
131 .insert("notifications".into(), "email".into());
132
133 let charlie = Account {
135 id: 3,
136 username: "charlie".into(),
137 password: "pwd".into(),
138 roles: HashSet::from(["user".into()]),
139 preferences: HashMap::new(),
140 address: Address {
141 street: "5 High".into(),
142 city: "Madrid".into(),
143 },
144 };
145 new.staff.insert(0, charlie); new.staff.swap(1, 2); println!("Company diff");
149 let diff = diff_changes(&old, &new);
150 dump_company(&diff);
151
152 println!("\n Alice diff");
153 let old_alice = old.staff.iter().find(|a| a.id == 1).unwrap();
154 let new_alice = new.staff.iter().find(|a| a.id == 1).unwrap();
155 let alice_diff = diff_changes(old_alice, new_alice);
156 dump_account(&alice_diff);
157
158 Ok(())
159}