complex/
complex.rs

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            /* scalar inside nested struct */
67            Account.address.city(v)                     => {
68                println!("Account.address.city({v})");
69            };
70
71            /* HashSet<String> */
72            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            /* HashMap<String,String> */
80            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    /* baseline */
95    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    /* edited version  */
124    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    /* insert + move */
134    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); // AddedAt
146    new.staff.swap(1, 2); // Moved
147
148    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}