• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

facet-rs / facet / 16482855623

23 Jul 2025 10:08PM UTC coverage: 58.447% (-0.2%) from 58.68%
16482855623

Pull #855

github

web-flow
Merge dca4c2302 into 5e8e214d1
Pull Request #855: wip: Remove 'shape lifetime

400 of 572 new or added lines in 70 files covered. (69.93%)

3 existing lines in 3 files now uncovered.

11939 of 20427 relevant lines covered (58.45%)

120.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/facet-diff/src/diff.rs
1
use std::collections::{HashMap, HashSet};
2

3
use facet::{Def, Shape, StructKind, Type, UserType};
4
use facet_core::Facet;
5
use facet_reflect::{HasFields, Peek};
6

7
use 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.
12
pub 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>,
21
        to: Peek<'mem, 'facet>,
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,
28

29
        /// The shape of the `to` struct.
30
        to: &'static Shape,
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,
42

43
        /// The shape of the `to` sequence.
44
        to: &'static Shape,
45

46
        /// The updates on the sequence
47
        updates: Updates<'mem, 'facet>,
48
    },
49
}
50

51
pub 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>>,
63

64
        /// The fields that are in `to` but not in `from`.
65
        insertions: HashMap<&'static str, Peek<'mem, 'facet>>,
66

67
        /// The fields that are unchanged
68
        unchanged: HashSet<&'static str>,
69
    },
70
}
71

72
impl<'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

81
pub trait FacetDiff<'f>: Facet<'f> {
82
    fn diff<'a, U: Facet<'f>>(&'a self, other: &'a U) -> Diff<'a, 'f>;
83
}
84

85
impl<'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

91
impl<'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

NEW
100
    pub(crate) fn new_peek(from: Peek<'mem, 'facet>, to: Peek<'mem, 'facet>) -> Self {
×
101
        if from.shape().id == to.shape().id && from.shape().is_partial_eq() && from == to {
×
102
            return Diff::Equal;
×
103
        }
×
104

105
        match (
106
            (from.shape().def, from.shape().ty),
×
107
            (to.shape().def, to.shape().ty),
×
108
        ) {
109
            (
110
                (_, Type::User(UserType::Struct(from_ty))),
×
111
                (_, Type::User(UserType::Struct(to_ty))),
×
112
            ) if from_ty.kind == to_ty.kind => {
×
113
                let from_ty = from.into_struct().unwrap();
×
114
                let to_ty = to.into_struct().unwrap();
×
115

116
                let value =
×
117
                    if [StructKind::Tuple, StructKind::TupleStruct].contains(&from_ty.ty().kind) {
×
118
                        let from = from_ty.fields().map(|x| x.1).collect();
×
119
                        let to = to_ty.fields().map(|x| x.1).collect();
×
120

121
                        let updates = sequences::diff(from, to);
×
122

123
                        Value::Tuple { updates }
×
124
                    } else {
125
                        let mut updates = HashMap::new();
×
126
                        let mut deletions = HashMap::new();
×
127
                        let mut insertions = HashMap::new();
×
128
                        let mut unchanged = HashSet::new();
×
129

130
                        for (field, from) in from_ty.fields() {
×
131
                            if let Ok(to) = to_ty.field_by_name(field.name) {
×
132
                                let diff = Diff::new_peek(from, to);
×
133
                                if diff.is_equal() {
×
134
                                    unchanged.insert(field.name);
×
135
                                } else {
×
136
                                    updates.insert(field.name, diff);
×
137
                                }
×
138
                            } else {
×
139
                                deletions.insert(field.name, from);
×
140
                            }
×
141
                        }
142

143
                        for (field, to) in to_ty.fields() {
×
144
                            if from_ty.field_by_name(field.name).is_err() {
×
145
                                insertions.insert(field.name, to);
×
146
                            }
×
147
                        }
148
                        Value::Struct {
×
149
                            updates,
×
150
                            deletions,
×
151
                            insertions,
×
152
                            unchanged,
×
153
                        }
×
154
                    };
155

156
                Diff::User {
×
157
                    from: from.shape(),
×
158
                    to: to.shape(),
×
159
                    variant: None,
×
160
                    value,
×
161
                }
×
162
            }
163
            ((_, Type::User(UserType::Enum(_))), (_, Type::User(UserType::Enum(_)))) => {
164
                let from_enum = from.into_enum().unwrap();
×
165
                let to_enum = to.into_enum().unwrap();
×
166

167
                let from_variant = from_enum.active_variant().unwrap();
×
168
                let to_variant = to_enum.active_variant().unwrap();
×
169

170
                if from_variant.name != to_variant.name
×
171
                    || from_variant.data.kind != to_variant.data.kind
×
172
                {
173
                    return Diff::Replace { from, to };
×
174
                }
×
175

176
                let value = if [StructKind::Tuple, StructKind::TupleStruct]
×
177
                    .contains(&from_variant.data.kind)
×
178
                {
179
                    let from = from_enum.fields().map(|x| x.1).collect();
×
180
                    let to = to_enum.fields().map(|x| x.1).collect();
×
181

182
                    let updates = sequences::diff(from, to);
×
183

184
                    Value::Tuple { updates }
×
185
                } else {
186
                    let mut updates = HashMap::new();
×
187
                    let mut deletions = HashMap::new();
×
188
                    let mut insertions = HashMap::new();
×
189
                    let mut unchanged = HashSet::new();
×
190

191
                    for (field, from) in from_enum.fields() {
×
192
                        if let Ok(Some(to)) = to_enum.field_by_name(field.name) {
×
193
                            let diff = Diff::new_peek(from, to);
×
194
                            if diff.is_equal() {
×
195
                                unchanged.insert(field.name);
×
196
                            } else {
×
197
                                updates.insert(field.name, diff);
×
198
                            }
×
199
                        } else {
×
200
                            deletions.insert(field.name, from);
×
201
                        }
×
202
                    }
203

204
                    for (field, to) in to_enum.fields() {
×
205
                        if !from_enum
×
206
                            .field_by_name(field.name)
×
207
                            .is_ok_and(|x| x.is_some())
×
208
                        {
×
209
                            insertions.insert(field.name, to);
×
210
                        }
×
211
                    }
212

213
                    Value::Struct {
×
214
                        updates,
×
215
                        deletions,
×
216
                        insertions,
×
217
                        unchanged,
×
218
                    }
×
219
                };
220

221
                Diff::User {
×
222
                    from: from_enum.shape(),
×
223
                    to: to_enum.shape(),
×
224
                    variant: Some(from_variant.name),
×
225
                    value,
×
226
                }
×
227
            }
228
            ((Def::Option(_), _), (Def::Option(_), _)) => {
229
                let from_option = from.into_option().unwrap();
×
230
                let to_option = to.into_option().unwrap();
×
231

232
                let (Some(from_value), Some(to_value)) = (from_option.value(), to_option.value())
×
233
                else {
234
                    return Diff::Replace { from, to };
×
235
                };
236

237
                let mut updates = Updates::default();
×
238

239
                let diff = Self::new_peek(from_value, to_value);
×
240
                if !diff.is_equal() {
×
241
                    updates.push_add(to_value);
×
242
                    updates.push_remove(from_value);
×
243
                }
×
244

245
                Diff::User {
×
246
                    from: from.shape(),
×
247
                    to: to.shape(),
×
248
                    variant: Some("Some"),
×
249
                    value: Value::Tuple { updates },
×
250
                }
×
251
            }
252
            (
253
                (Def::List(_), _) | (_, Type::Sequence(_)),
254
                (Def::List(_), _) | (_, Type::Sequence(_)),
255
            ) => {
256
                let from_list = from.into_list_like().unwrap();
×
257
                let to_list = to.into_list_like().unwrap();
×
258

259
                let updates = sequences::diff(
×
260
                    from_list.iter().collect::<Vec<_>>(),
×
261
                    to_list.iter().collect::<Vec<_>>(),
×
262
                );
263

264
                Diff::Sequence {
×
265
                    from: from.shape(),
×
266
                    to: to.shape(),
×
267
                    updates,
×
268
                }
×
269
            }
270
            _ => Diff::Replace { from, to },
×
271
        }
272
    }
×
273

274
    pub(crate) fn closeness(&self) -> usize {
×
275
        match self {
×
276
            Self::Equal => 1, // This does not actually matter for flattening sequence diffs, because all diffs there are non-equal
×
277
            Self::Replace { .. } => 0,
×
278
            Self::Sequence { updates, .. } => updates.closeness(),
×
279
            Self::User {
×
280
                from, to, value, ..
×
281
            } => value.closeness() + (from == to) as usize,
×
282
        }
283
    }
×
284
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc