1use std::collections::HashMap;
2
3use facet::{Def, Facet};
4use facet_reflect::Peek;
5
6use crate::{FacetInspect, FacetPath};
7
8#[derive(Debug)]
9pub struct ModificationPeek<'mem, 'facet> {
10 pub old_value: Peek<'mem, 'facet>,
11 pub new_value: Peek<'mem, 'facet>,
12}
13
14#[derive(Debug, Facet, PartialEq)]
15#[repr(C)]
16pub enum Modification {
17 U32 { before: u32, after: u32 },
18 String { before: String, after: String },
19}
20
21impl<'a> From<ModificationPeek<'a, 'a>> for Modification {
22 fn from(peek: ModificationPeek<'a, 'a>) -> Self {
23 let old_value = peek.old_value;
24 let new_value = peek.new_value;
25
26 match old_value.shape().type_identifier {
27 "u32" => {
28 let before = *old_value.get::<u32>().expect("old_value should be u32");
29 let after = *new_value.get::<u32>().expect("new_value should be u32");
30 Modification::U32 { before, after }
31 }
32 "String" => {
33 let before = old_value
34 .get::<String>()
35 .expect("old_value should be String")
36 .clone();
37 let after = new_value
38 .get::<String>()
39 .expect("new_value should be String")
40 .clone();
41 Modification::String { before, after }
42 }
43 _ => {
44 panic!(
47 "Unsupported type for modification: {}",
48 old_value.shape().type_identifier
49 );
50 }
51 }
52 }
53}
54
55#[derive(Debug, Facet)]
56pub struct Diff {
57 pub changes: HashMap<FacetPath, Modification>,
58}
59
60impl Diff {
61 pub fn compare<'a, T: Facet<'a>>(facet1: &'a T, facet2: &'a T) -> Self {
62 let mut changes = HashMap::new();
63
64 for ((path, peek1), (_, peek2)) in facet1.inspect().zip(facet2.inspect()) {
65 if peek1 == peek2 {
66 continue; }
68
69 if peek1.shape() == peek2.shape() && !matches!(peek1.shape().def, Def::Scalar) {
70 continue;
73 }
74
75 let modif = ModificationPeek {
76 old_value: peek1,
77 new_value: peek2,
78 };
79
80 changes.insert(path, Modification::from(modif));
81 }
82
83 Diff { changes }
84 }
85}
86
87#[derive(Debug)]
88pub struct ShapeDiff<'a> {
89 pub changes: HashMap<FacetPath, DiffType<'a>>,
90}
91
92#[derive(Debug)]
93pub enum DiffType<'a> {
94 Added(Peek<'a, 'a>),
95 Removed(Peek<'a, 'a>),
96 Modified(ModificationPeek<'a, 'a>),
97}
98
99impl<'a> ShapeDiff<'a> {
100 pub fn compare<A: Facet<'a>, B: Facet<'a>>(facet1: &'a A, facet2: &'a B) -> Self {
101 let facet1_elements = facet1.inspect().collect::<HashMap<_, _>>();
102 let facet2_elements = facet2.inspect().collect::<HashMap<_, _>>();
103
104 let mut changes = HashMap::new();
105
106 for path in facet1_elements.keys().chain(facet2_elements.keys()) {
107 let peek1 = facet1_elements.get(path);
108 let peek2 = facet2_elements.get(path);
109
110 match (peek1, peek2) {
111 (Some(p1), Some(p2)) if p1 == p2 => continue, (Some(p1), Some(p2))
113 if p1.shape() == p2.shape() && !matches!(p1.shape().def, Def::Scalar) =>
114 {
115 continue;
118 }
119 (Some(p1), None) => {
120 changes.insert(path.clone(), DiffType::Removed(*p1));
121 }
122 (None, Some(p2)) => {
123 changes.insert(path.clone(), DiffType::Added(*p2));
124 }
125 (Some(p1), Some(p2)) => {
126 changes.insert(
127 path.clone(),
128 DiffType::Modified(ModificationPeek {
129 old_value: *p1,
130 new_value: *p2,
131 }),
132 );
133 }
134 (None, None) => unreachable!(), }
136 }
137
138 ShapeDiff { changes }
139 }
140}
141
142pub trait FacetDiff<'facet>: Facet<'facet> + Sized {
143 fn diff(&'facet self, other: &'facet Self) -> Diff {
144 Diff::compare(self, other)
145 }
146
147 fn shape_diff<T: Facet<'facet>>(&'facet self, other: &'facet T) -> ShapeDiff<'facet> {
148 ShapeDiff::compare(self, other)
149 }
150}
151
152impl<'facet, T: Facet<'facet>> FacetDiff<'facet> for T {}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use facet::Facet;
158
159 #[derive(Facet, Clone)]
160 struct TestFacet {
161 field1: u32,
162 field2: String,
163 }
164
165 #[derive(Facet)]
166 struct NestedFacet {
167 nested_field: TestFacet,
168 }
169
170 #[derive(Facet)]
171 struct AnotherNestedFacet {
172 nested_field: TestFacet,
173 another_field: u32,
174 }
175
176 #[test]
177 fn test_facet_diff() {
178 let sub_facet1 = TestFacet {
179 field1: 42,
180 field2: "Hello".to_string(),
181 };
182
183 let facet1 = NestedFacet {
184 nested_field: sub_facet1.clone(),
185 };
186
187 let facet2 = NestedFacet {
188 nested_field: TestFacet {
189 field1: 43,
190 field2: "World".to_string(),
191 },
192 };
193
194 let diff = facet1.diff(&facet2);
195
196 assert_eq!(
197 diff.changes.get(&"$.nested_field.field1".into()).unwrap(),
198 &Modification::U32 {
199 before: 42,
200 after: 43
201 }
202 );
203 }
204
205 #[test]
206 fn test_shape_diff() {
207 let sub_facet1 = TestFacet {
208 field1: 42,
209 field2: "Hello".to_string(),
210 };
211
212 let facet1 = NestedFacet {
213 nested_field: sub_facet1.clone(),
214 };
215
216 let facet2 = AnotherNestedFacet {
217 nested_field: TestFacet {
218 field1: 43,
219 field2: "World".to_string(),
220 },
221 another_field: 100,
222 };
223
224 let shape_diff = facet1.shape_diff(&facet2);
225 dbg!(shape_diff);
226 }
227}