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