facet_diff/
diff.rs

1use std::collections::{HashMap, HashSet};
2
3use facet::{Def, Shape, StructKind, Type, UserType};
4use facet_core::Facet;
5use facet_reflect::{HasFields, Peek};
6
7use crate::sequences::{self, Updates};
8
9/// The difference between two values.
10///
11/// The `from` value does not necessarily have to have the same type as the `to` value.
12pub enum Diff<'mem, 'facet> {
13    /// The two values are equal
14    Equal,
15
16    /// Fallback case.
17    ///
18    /// We do not know much about the values, apart from that they are unequal to each other.
19    Replace {
20        from: Peek<'mem, 'facet, 'static>,
21        to: Peek<'mem, 'facet, 'static>,
22    },
23
24    /// The two values are both structures or both enums with similar variants.
25    User {
26        /// The shape of the `from` struct.
27        from: &'static Shape<'static>,
28
29        /// The shape of the `to` struct.
30        to: &'static Shape<'static>,
31
32        /// The name of the variant, this is [`None`] if the values are structs
33        variant: Option<&'static str>,
34
35        value: Value<'mem, 'facet>,
36    },
37
38    /// A diff between two sequences
39    Sequence {
40        /// The shape of the `from` sequence.
41        from: &'static Shape<'static>,
42
43        /// The shape of the `to` sequence.
44        to: &'static Shape<'static>,
45
46        /// The updates on the sequence
47        updates: Updates<'mem, 'facet>,
48    },
49}
50
51pub enum Value<'mem, 'facet> {
52    Tuple {
53        /// The updates on the sequence
54        updates: Updates<'mem, 'facet>,
55    },
56
57    Struct {
58        /// The fields that are updated between the structs
59        updates: HashMap<&'static str, Diff<'mem, 'facet>>,
60
61        /// The fields that are in `from` but not in `to`.
62        deletions: HashMap<&'static str, Peek<'mem, 'facet, 'static>>,
63
64        /// The fields that are in `to` but not in `from`.
65        insertions: HashMap<&'static str, Peek<'mem, 'facet, 'static>>,
66
67        /// The fields that are unchanged
68        unchanged: HashSet<&'static str>,
69    },
70}
71
72impl<'mem, 'facet> Value<'mem, 'facet> {
73    fn closeness(&self) -> usize {
74        match self {
75            Self::Tuple { updates } => updates.closeness(),
76            Self::Struct { unchanged, .. } => unchanged.len(),
77        }
78    }
79}
80
81pub trait FacetDiff<'f>: Facet<'f> {
82    fn diff<'a, U: Facet<'f>>(&'a self, other: &'a U) -> Diff<'a, 'f>;
83}
84
85impl<'f, T: Facet<'f>> FacetDiff<'f> for T {
86    fn diff<'a, U: Facet<'f>>(&'a self, other: &'a U) -> Diff<'a, 'f> {
87        Diff::new(self, other)
88    }
89}
90
91impl<'mem, 'facet> Diff<'mem, 'facet> {
92    pub fn is_equal(&self) -> bool {
93        matches!(self, Self::Equal)
94    }
95
96    pub fn new<T: Facet<'facet>, U: Facet<'facet>>(from: &'mem T, to: &'mem U) -> Self {
97        Self::new_peek(Peek::new(from), Peek::new(to))
98    }
99
100    pub(crate) fn new_peek(
101        from: Peek<'mem, 'facet, 'static>,
102        to: Peek<'mem, 'facet, 'static>,
103    ) -> Self {
104        if from.shape().id == to.shape().id && from.shape().is_partial_eq() && from == to {
105            return Diff::Equal;
106        }
107
108        match (
109            (from.shape().def, from.shape().ty),
110            (to.shape().def, to.shape().ty),
111        ) {
112            (
113                (_, Type::User(UserType::Struct(from_ty))),
114                (_, Type::User(UserType::Struct(to_ty))),
115            ) if from_ty.kind == to_ty.kind => {
116                let from_ty = from.into_struct().unwrap();
117                let to_ty = to.into_struct().unwrap();
118
119                let value =
120                    if [StructKind::Tuple, StructKind::TupleStruct].contains(&from_ty.ty().kind) {
121                        let from = from_ty.fields().map(|x| x.1).collect();
122                        let to = to_ty.fields().map(|x| x.1).collect();
123
124                        let updates = sequences::diff(from, to);
125
126                        Value::Tuple { updates }
127                    } else {
128                        let mut updates = HashMap::new();
129                        let mut deletions = HashMap::new();
130                        let mut insertions = HashMap::new();
131                        let mut unchanged = HashSet::new();
132
133                        for (field, from) in from_ty.fields() {
134                            if let Ok(to) = to_ty.field_by_name(field.name) {
135                                let diff = Diff::new_peek(from, to);
136                                if diff.is_equal() {
137                                    unchanged.insert(field.name);
138                                } else {
139                                    updates.insert(field.name, diff);
140                                }
141                            } else {
142                                deletions.insert(field.name, from);
143                            }
144                        }
145
146                        for (field, to) in to_ty.fields() {
147                            if from_ty.field_by_name(field.name).is_err() {
148                                insertions.insert(field.name, to);
149                            }
150                        }
151                        Value::Struct {
152                            updates,
153                            deletions,
154                            insertions,
155                            unchanged,
156                        }
157                    };
158
159                Diff::User {
160                    from: from.shape(),
161                    to: to.shape(),
162                    variant: None,
163                    value,
164                }
165            }
166            ((_, Type::User(UserType::Enum(_))), (_, Type::User(UserType::Enum(_)))) => {
167                let from_enum = from.into_enum().unwrap();
168                let to_enum = to.into_enum().unwrap();
169
170                let from_variant = from_enum.active_variant().unwrap();
171                let to_variant = to_enum.active_variant().unwrap();
172
173                if from_variant.name != to_variant.name
174                    || from_variant.data.kind != to_variant.data.kind
175                {
176                    return Diff::Replace { from, to };
177                }
178
179                let value = if [StructKind::Tuple, StructKind::TupleStruct]
180                    .contains(&from_variant.data.kind)
181                {
182                    let from = from_enum.fields().map(|x| x.1).collect();
183                    let to = to_enum.fields().map(|x| x.1).collect();
184
185                    let updates = sequences::diff(from, to);
186
187                    Value::Tuple { updates }
188                } else {
189                    let mut updates = HashMap::new();
190                    let mut deletions = HashMap::new();
191                    let mut insertions = HashMap::new();
192                    let mut unchanged = HashSet::new();
193
194                    for (field, from) in from_enum.fields() {
195                        if let Ok(Some(to)) = to_enum.field_by_name(field.name) {
196                            let diff = Diff::new_peek(from, to);
197                            if diff.is_equal() {
198                                unchanged.insert(field.name);
199                            } else {
200                                updates.insert(field.name, diff);
201                            }
202                        } else {
203                            deletions.insert(field.name, from);
204                        }
205                    }
206
207                    for (field, to) in to_enum.fields() {
208                        if !from_enum
209                            .field_by_name(field.name)
210                            .is_ok_and(|x| x.is_some())
211                        {
212                            insertions.insert(field.name, to);
213                        }
214                    }
215
216                    Value::Struct {
217                        updates,
218                        deletions,
219                        insertions,
220                        unchanged,
221                    }
222                };
223
224                Diff::User {
225                    from: from_enum.shape(),
226                    to: to_enum.shape(),
227                    variant: Some(from_variant.name),
228                    value,
229                }
230            }
231            ((Def::Option(_), _), (Def::Option(_), _)) => {
232                let from_option = from.into_option().unwrap();
233                let to_option = to.into_option().unwrap();
234
235                let (Some(from_value), Some(to_value)) = (from_option.value(), to_option.value())
236                else {
237                    return Diff::Replace { from, to };
238                };
239
240                let mut updates = Updates::default();
241
242                let diff = Self::new_peek(from_value, to_value);
243                if !diff.is_equal() {
244                    updates.push_add(to_value);
245                    updates.push_remove(from_value);
246                }
247
248                Diff::User {
249                    from: from.shape(),
250                    to: to.shape(),
251                    variant: Some("Some"),
252                    value: Value::Tuple { updates },
253                }
254            }
255            (
256                (Def::List(_), _) | (_, Type::Sequence(_)),
257                (Def::List(_), _) | (_, Type::Sequence(_)),
258            ) => {
259                let from_list = from.into_list_like().unwrap();
260                let to_list = to.into_list_like().unwrap();
261
262                let updates = sequences::diff(
263                    from_list.iter().collect::<Vec<_>>(),
264                    to_list.iter().collect::<Vec<_>>(),
265                );
266
267                Diff::Sequence {
268                    from: from.shape(),
269                    to: to.shape(),
270                    updates,
271                }
272            }
273            _ => Diff::Replace { from, to },
274        }
275    }
276
277    pub(crate) fn closeness(&self) -> usize {
278        match self {
279            Self::Equal => 1, // This does not actually matter for flattening sequence diffs, because all diffs there are non-equal
280            Self::Replace { .. } => 0,
281            Self::Sequence { updates, .. } => updates.closeness(),
282            Self::User {
283                from, to, value, ..
284            } => value.closeness() + (from == to) as usize,
285        }
286    }
287}