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

facet-rs / facet / 20059345204

09 Dec 2025 09:57AM UTC coverage: 58.683% (-0.4%) from 59.082%
20059345204

push

github

fasterthanlime
Refactor VTable system: Direct/Indirect styles, OxRef/OxMut, builder patterns

Major architectural refactor of the facet vtable system for better code sharing
and reduced binary bloat.

- Remove ThinPtr/WidePtr in favor of PtrConst<'a>/PtrMut<'a>/PtrUninit<'a>
- Add OxRef<'a>/OxMut<'a> - shaped pointers bundling ptr + shape
- Add Ox<'a> enum for ownership tracking (like Cow for shaped pointers)

- VTableDirect: for concrete types, uses raw *const ()/*mut (), returns T directly
- VTableIndirect: for generic containers, uses OxRef/OxMut, returns Option<T>
- VTableErased enum wraps both styles
- Shape::call_* helpers dispatch to either style transparently

- vtable_direct! macro for sized types with compile-time known traits
- VTableIndirect::builder() for generic containers with runtime dispatch
- All container vtables (Array, Option, Result, List, Map, Set) use builders
- No more positional arguments - named builder methods only

- Hash trait is generic over H: Hasher, can't store directly in vtable
- HashProxy type-erases the hasher for vtable storage
- Enables hash support without wrapper function bloat

- impls_core/impls_alloc/impls_std -> impls/core/alloc/std/crates
- New internal/ module for facet's own types (Shape, Def, etc.)
- Cleaner separation of concerns

- Copy, Send, Sync, Eq, Unpin stored as bitflags on Shape
- Set via ShapeBuilder methods: .copy(), .send(), .sync(), .eq()

- ThinPtr -> *const () or PtrConst<'a>
- WidePtr -> OxRef<'a> or OxMut<'a>
- vtable_ref! -> vtable_direct! or VTableIndirect::builder()
- ValueVTable -> VTableDirect or VTableIndirect
- Closures in vtables -> named fn items

WIP: Some impl modules still need migration to new API.

4092 of 8007 new or added lines in 112 files covered. (51.11%)

117 existing lines in 26 files now uncovered.

26173 of 44601 relevant lines covered (58.68%)

635.02 hits per line

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

56.67
/facet-assert/src/same.rs
1
//! Structural sameness checking for Facet types.
2

3
use core::fmt;
4
use facet_core::{Def, DynValueKind, Facet, Type, UserType};
5
use facet_pretty::PrettyPrinter;
6
use facet_reflect::{HasFields, Peek, ScalarType};
7

8
/// Options for customizing structural comparison behavior.
9
///
10
/// Use the builder pattern to configure options:
11
///
12
/// ```
13
/// use facet_assert::SameOptions;
14
///
15
/// let options = SameOptions::new()
16
///     .float_tolerance(1e-6);
17
/// ```
18
#[derive(Debug, Clone, Default)]
19
pub struct SameOptions {
20
    /// Tolerance for floating-point comparisons.
21
    /// If set, two floats are considered equal if their absolute difference
22
    /// is less than or equal to this value.
23
    float_tolerance: Option<f64>,
24
}
25

26
impl SameOptions {
27
    /// Create a new `SameOptions` with default settings (exact comparison).
28
    pub fn new() -> Self {
10✔
29
        Self::default()
10✔
30
    }
10✔
31

32
    /// Set the tolerance for floating-point comparisons.
33
    ///
34
    /// When set, two `f32` or `f64` values are considered equal if:
35
    /// `|left - right| <= tolerance`
36
    ///
37
    /// # Example
38
    ///
39
    /// ```
40
    /// use facet_assert::{assert_same_with, SameOptions};
41
    ///
42
    /// let a = 1.0000001_f64;
43
    /// let b = 1.0000002_f64;
44
    ///
45
    /// // This would fail with exact comparison:
46
    /// // assert_same!(a, b);
47
    ///
48
    /// // But passes with tolerance:
49
    /// assert_same_with!(a, b, SameOptions::new().float_tolerance(1e-6));
50
    /// ```
51
    pub fn float_tolerance(mut self, tolerance: f64) -> Self {
10✔
52
        self.float_tolerance = Some(tolerance);
10✔
53
        self
10✔
54
    }
10✔
55
}
56

57
/// Result of checking if two values are structurally the same.
58
pub enum Sameness {
59
    /// The values are structurally the same.
60
    Same,
61
    /// The values differ - contains a formatted diff.
62
    Different(String),
63
    /// Encountered an opaque type that cannot be compared.
64
    Opaque {
65
        /// The type name of the opaque type.
66
        type_name: &'static str,
67
    },
68
}
69

70
/// Check if two Facet values are structurally the same.
71
///
72
/// This does NOT require `PartialEq` - it walks the structure via reflection.
73
/// Two values are "same" if they have the same structure and values, even if
74
/// they have different type names.
75
///
76
/// Returns [`Sameness::Opaque`] if either value contains an opaque type.
77
pub fn check_same<'f, T: Facet<'f>, U: Facet<'f>>(left: &T, right: &U) -> Sameness {
33✔
78
    check_same_with(left, right, SameOptions::default())
33✔
79
}
33✔
80

81
/// Check if two Facet values are structurally the same, with custom options.
82
///
83
/// Like [`check_same`], but allows configuring comparison behavior via [`SameOptions`].
84
///
85
/// # Example
86
///
87
/// ```
88
/// use facet_assert::{check_same_with, SameOptions, Sameness};
89
///
90
/// let a = 1.0000001_f64;
91
/// let b = 1.0000002_f64;
92
///
93
/// // With tolerance, these are considered the same
94
/// let options = SameOptions::new().float_tolerance(1e-6);
95
/// assert!(matches!(check_same_with(&a, &b, options), Sameness::Same));
96
/// ```
97
pub fn check_same_with<'f, T: Facet<'f>, U: Facet<'f>>(
39✔
98
    left: &T,
39✔
99
    right: &U,
39✔
100
    options: SameOptions,
39✔
101
) -> Sameness {
39✔
102
    let left_peek = Peek::new(left);
39✔
103
    let right_peek = Peek::new(right);
39✔
104

105
    let mut differ = Differ::new(options);
39✔
106
    match differ.check(left_peek, right_peek) {
39✔
107
        CheckResult::Same => Sameness::Same,
26✔
108
        CheckResult::Different => Sameness::Different(differ.into_diff()),
13✔
109
        CheckResult::Opaque { type_name } => Sameness::Opaque { type_name },
×
110
    }
111
}
39✔
112

113
enum CheckResult {
114
    Same,
115
    Different,
116
    Opaque { type_name: &'static str },
117
}
118

119
struct Differ {
120
    /// Differences found, stored as lines
121
    diffs: Vec<DiffLine>,
122
    /// Current path for context
123
    path: Vec<PathSegment>,
124
    /// Comparison options
125
    options: SameOptions,
126
}
127

128
enum PathSegment {
129
    Field(&'static str),
130
    Index(usize),
131
    Variant(&'static str),
132
    Key(String),
133
}
134

135
impl fmt::Display for PathSegment {
136
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
10✔
137
        match self {
10✔
138
            PathSegment::Field(name) => write!(f, ".{name}"),
2✔
139
            PathSegment::Index(i) => write!(f, "[{i}]"),
7✔
140
            PathSegment::Variant(name) => write!(f, "::{name}"),
×
141
            PathSegment::Key(k) => write!(f, "[{k:?}]"),
1✔
142
        }
143
    }
10✔
144
}
145

146
enum DiffLine {
147
    Changed {
148
        path: String,
149
        left: String,
150
        right: String,
151
    },
152
    OnlyLeft {
153
        path: String,
154
        value: String,
155
    },
156
    OnlyRight {
157
        path: String,
158
        value: String,
159
    },
160
}
161

162
impl Differ {
163
    fn new(options: SameOptions) -> Self {
43✔
164
        Self {
43✔
165
            diffs: Vec::new(),
43✔
166
            path: Vec::new(),
43✔
167
            options,
43✔
168
        }
43✔
169
    }
43✔
170

171
    /// Compare two f64 values, using tolerance if configured.
172
    fn floats_equal(&self, left: f64, right: f64) -> bool {
14✔
173
        if let Some(tolerance) = self.options.float_tolerance {
14✔
174
            (left - right).abs() <= tolerance
14✔
175
        } else {
176
            left == right
×
177
        }
178
    }
14✔
179

180
    /// Try to extract f64 values from two Peek values if they are both floats.
181
    /// Returns None if either value is not a float type.
182
    fn extract_floats(&self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> Option<(f64, f64)> {
15✔
183
        let left_f64 = match left.scalar_type()? {
15✔
184
            ScalarType::F64 => *left.get::<f64>().ok()?,
13✔
185
            ScalarType::F32 => *left.get::<f32>().ok()? as f64,
1✔
186
            _ => return None,
1✔
187
        };
188
        let right_f64 = match right.scalar_type()? {
14✔
189
            ScalarType::F64 => *right.get::<f64>().ok()?,
13✔
190
            ScalarType::F32 => *right.get::<f32>().ok()? as f64,
1✔
191
            _ => return None,
×
192
        };
193
        Some((left_f64, right_f64))
14✔
194
    }
15✔
195

196
    fn current_path(&self) -> String {
15✔
197
        if self.path.is_empty() {
15✔
198
            "root".to_string()
5✔
199
        } else {
200
            let mut s = String::new();
10✔
201
            for seg in &self.path {
10✔
202
                s.push_str(&seg.to_string());
10✔
203
            }
10✔
204
            s
10✔
205
        }
206
    }
15✔
207

208
    fn format_value(peek: Peek<'_, '_>) -> String {
969✔
209
        let printer = PrettyPrinter::default()
969✔
210
            .with_colors(false)
969✔
211
            .with_minimal_option_names(true);
969✔
212
        printer.format_peek(peek).to_string()
969✔
213
    }
969✔
214

215
    fn record_changed(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) {
14✔
216
        self.diffs.push(DiffLine::Changed {
14✔
217
            path: self.current_path(),
14✔
218
            left: Self::format_value(left),
14✔
219
            right: Self::format_value(right),
14✔
220
        });
14✔
221
    }
14✔
222

223
    fn record_only_left(&mut self, left: Peek<'_, '_>) {
1✔
224
        self.diffs.push(DiffLine::OnlyLeft {
1✔
225
            path: self.current_path(),
1✔
226
            value: Self::format_value(left),
1✔
227
        });
1✔
228
    }
1✔
229

230
    fn record_only_right(&mut self, right: Peek<'_, '_>) {
×
231
        self.diffs.push(DiffLine::OnlyRight {
×
232
            path: self.current_path(),
×
233
            value: Self::format_value(right),
×
234
        });
×
235
    }
×
236

237
    fn into_diff(self) -> String {
13✔
238
        use std::fmt::Write;
239

240
        let mut out = String::new();
13✔
241

242
        for diff in self.diffs {
15✔
243
            match diff {
15✔
244
                DiffLine::Changed { path, left, right } => {
14✔
245
                    writeln!(out, "\x1b[1m{path}\x1b[0m:").unwrap();
14✔
246
                    writeln!(out, "  \x1b[31m- {left}\x1b[0m").unwrap();
14✔
247
                    writeln!(out, "  \x1b[32m+ {right}\x1b[0m").unwrap();
14✔
248
                }
14✔
249
                DiffLine::OnlyLeft { path, value } => {
1✔
250
                    writeln!(out, "\x1b[1m{path}\x1b[0m (only in left):").unwrap();
1✔
251
                    writeln!(out, "  \x1b[31m- {value}\x1b[0m").unwrap();
1✔
252
                }
1✔
253
                DiffLine::OnlyRight { path, value } => {
×
254
                    writeln!(out, "\x1b[1m{path}\x1b[0m (only in right):").unwrap();
×
255
                    writeln!(out, "  \x1b[32m+ {value}\x1b[0m").unwrap();
×
256
                }
×
257
            }
258
        }
259

260
        out
13✔
261
    }
13✔
262

263
    fn check(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
2,259✔
264
        // Handle Option BEFORE innermost_peek (since Option's try_borrow_inner fails)
265
        if matches!(left.shape().def, Def::Option(_)) && matches!(right.shape().def, Def::Option(_))
2,259✔
266
        {
267
            return self.check_options(left, right);
289✔
268
        }
1,970✔
269

270
        // Unwrap transparent wrappers (like NonZero, newtype wrappers)
271
        let left = left.innermost_peek();
1,970✔
272
        let right = right.innermost_peek();
1,970✔
273

274
        // Try scalar comparison first (for leaf values like String, i32, etc.)
275
        // Scalars are compared by their formatted representation, except for floats
276
        // with tolerance configured.
277
        if matches!(left.shape().def, Def::Scalar) && matches!(right.shape().def, Def::Scalar) {
1,970✔
278
            // Try float comparison with tolerance if configured
279
            if self.options.float_tolerance.is_some()
474✔
280
                && let Some((left_f64, right_f64)) = self.extract_floats(left, right)
15✔
281
            {
282
                if self.floats_equal(left_f64, right_f64) {
14✔
283
                    return CheckResult::Same;
13✔
284
                } else {
285
                    self.record_changed(left, right);
1✔
286
                    return CheckResult::Different;
1✔
287
                }
288
            }
460✔
289

290
            // Default: compare by formatted representation
291
            let left_str = Self::format_value(left);
460✔
292
            let right_str = Self::format_value(right);
460✔
293
            if left_str == right_str {
460✔
294
                return CheckResult::Same;
453✔
295
            } else {
296
                self.record_changed(left, right);
7✔
297
                return CheckResult::Different;
7✔
298
            }
299
        }
1,496✔
300

301
        // Try to compare structurally based on type/def
302
        // Note: Many types are UserType::Opaque but still have a useful Def (like Vec -> Def::List)
303
        // So we check Def first before giving up on Opaque types.
304

305
        // Handle lists/arrays/slices (Vec is Opaque but has Def::List)
306
        if left.into_list_like().is_ok() && right.into_list_like().is_ok() {
1,496✔
307
            return self.check_lists(left, right);
441✔
308
        }
1,055✔
309

310
        // Handle maps
311
        if matches!(left.shape().def, Def::Map(_)) && matches!(right.shape().def, Def::Map(_)) {
1,055✔
312
            return self.check_maps(left, right);
×
313
        }
1,055✔
314

315
        // Handle smart pointers
316
        if matches!(left.shape().def, Def::Pointer(_))
1,055✔
317
            && matches!(right.shape().def, Def::Pointer(_))
101✔
318
        {
319
            return self.check_pointers(left, right);
101✔
320
        }
954✔
321

322
        // Handle structs
323
        if let (Type::User(UserType::Struct(_)), Type::User(UserType::Struct(_))) =
324
            (left.shape().ty, right.shape().ty)
954✔
325
        {
326
            return self.check_structs(left, right);
354✔
327
        }
600✔
328

329
        // Handle enums
330
        if let (Type::User(UserType::Enum(_)), Type::User(UserType::Enum(_))) =
331
            (left.shape().ty, right.shape().ty)
600✔
332
        {
333
            return self.check_enums(left, right);
557✔
334
        }
43✔
335

336
        // Handle dynamic values (like facet_value::Value) - compare based on their runtime kind
337
        // This allows comparing Value against concrete types (e.g., Value array vs Vec)
338
        if let Def::DynamicValue(_) = left.shape().def {
43✔
339
            return self.check_with_dynamic_value(left, right);
42✔
340
        }
1✔
341
        if let Def::DynamicValue(_) = right.shape().def {
1✔
342
            return self.check_with_dynamic_value(right, left);
1✔
343
        }
×
344

345
        // At this point, if either is Opaque and we haven't handled it above, fail
346
        if matches!(left.shape().ty, Type::User(UserType::Opaque)) {
×
347
            return CheckResult::Opaque {
×
348
                type_name: left.shape().type_identifier,
×
349
            };
×
350
        }
×
351
        if matches!(right.shape().ty, Type::User(UserType::Opaque)) {
×
352
            return CheckResult::Opaque {
×
353
                type_name: right.shape().type_identifier,
×
354
            };
×
355
        }
×
356

357
        // Fallback: format and compare
358
        let left_str = Self::format_value(left);
×
359
        let right_str = Self::format_value(right);
×
360
        if left_str == right_str {
×
361
            CheckResult::Same
×
362
        } else {
363
            self.record_changed(left, right);
×
364
            CheckResult::Different
×
365
        }
366
    }
2,259✔
367

368
    fn check_structs(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
354✔
369
        let left_struct = left.into_struct().unwrap();
354✔
370
        let right_struct = right.into_struct().unwrap();
354✔
371

372
        let mut any_different = false;
354✔
373
        let mut seen_fields = std::collections::HashSet::new();
354✔
374

375
        // Check all fields in left
376
        for (field, left_value) in left_struct.fields() {
1,653✔
377
            seen_fields.insert(field.name);
1,653✔
378
            self.path.push(PathSegment::Field(field.name));
1,653✔
379

380
            if let Ok(right_value) = right_struct.field_by_name(field.name) {
1,653✔
381
                match self.check(left_value, right_value) {
1,653✔
382
                    CheckResult::Same => {}
1,651✔
383
                    CheckResult::Different => any_different = true,
2✔
384
                    opaque @ CheckResult::Opaque { .. } => {
×
385
                        self.path.pop();
×
386
                        return opaque;
×
387
                    }
388
                }
389
            } else {
×
390
                // Field only in left
×
391
                self.record_only_left(left_value);
×
392
                any_different = true;
×
393
            }
×
394

395
            self.path.pop();
1,653✔
396
        }
397

398
        // Check fields only in right
399
        for (field, right_value) in right_struct.fields() {
1,653✔
400
            if !seen_fields.contains(field.name) {
1,653✔
401
                self.path.push(PathSegment::Field(field.name));
×
402
                self.record_only_right(right_value);
×
403
                any_different = true;
×
404
                self.path.pop();
×
405
            }
1,653✔
406
        }
407

408
        if any_different {
354✔
409
            CheckResult::Different
2✔
410
        } else {
411
            CheckResult::Same
352✔
412
        }
413
    }
354✔
414

415
    fn check_enums(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
557✔
416
        let left_enum = left.into_enum().unwrap();
557✔
417
        let right_enum = right.into_enum().unwrap();
557✔
418

419
        let left_variant = left_enum.active_variant().unwrap();
557✔
420
        let right_variant = right_enum.active_variant().unwrap();
557✔
421

422
        // Different variants = different
423
        if left_variant.name != right_variant.name {
557✔
424
            self.record_changed(left, right);
×
425
            return CheckResult::Different;
×
426
        }
557✔
427

428
        // Same variant - check fields
429
        self.path.push(PathSegment::Variant(left_variant.name));
557✔
430

431
        let mut any_different = false;
557✔
432
        let mut seen_fields = std::collections::HashSet::new();
557✔
433

434
        for (field, left_value) in left_enum.fields() {
557✔
435
            seen_fields.insert(field.name);
271✔
436
            self.path.push(PathSegment::Field(field.name));
271✔
437

438
            if let Ok(Some(right_value)) = right_enum.field_by_name(field.name) {
271✔
439
                match self.check(left_value, right_value) {
271✔
440
                    CheckResult::Same => {}
271✔
441
                    CheckResult::Different => any_different = true,
×
442
                    opaque @ CheckResult::Opaque { .. } => {
×
443
                        self.path.pop();
×
444
                        self.path.pop();
×
445
                        return opaque;
×
446
                    }
447
                }
448
            } else {
×
449
                self.record_only_left(left_value);
×
450
                any_different = true;
×
451
            }
×
452

453
            self.path.pop();
271✔
454
        }
455

456
        for (field, right_value) in right_enum.fields() {
557✔
457
            if !seen_fields.contains(field.name) {
271✔
458
                self.path.push(PathSegment::Field(field.name));
×
459
                self.record_only_right(right_value);
×
460
                any_different = true;
×
461
                self.path.pop();
×
462
            }
271✔
463
        }
464

465
        self.path.pop();
557✔
466

467
        if any_different {
557✔
468
            CheckResult::Different
×
469
        } else {
470
            CheckResult::Same
557✔
471
        }
472
    }
557✔
473

474
    fn check_options(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
289✔
475
        let left_opt = left.into_option().unwrap();
289✔
476
        let right_opt = right.into_option().unwrap();
289✔
477

478
        match (left_opt.value(), right_opt.value()) {
289✔
479
            (None, None) => CheckResult::Same,
216✔
480
            (Some(l), Some(r)) => self.check(l, r),
73✔
481
            (Some(_), None) | (None, Some(_)) => {
482
                self.record_changed(left, right);
×
483
                CheckResult::Different
×
484
            }
485
        }
486
    }
289✔
487

488
    fn check_lists(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
441✔
489
        let left_list = left.into_list_like().unwrap();
441✔
490
        let right_list = right.into_list_like().unwrap();
441✔
491

492
        let left_items: Vec<_> = left_list.iter().collect();
441✔
493
        let right_items: Vec<_> = right_list.iter().collect();
441✔
494

495
        let mut any_different = false;
441✔
496
        let min_len = left_items.len().min(right_items.len());
441✔
497

498
        // Compare common elements
499
        for i in 0..min_len {
441✔
500
            self.path.push(PathSegment::Index(i));
90✔
501

502
            match self.check(left_items[i], right_items[i]) {
90✔
503
                CheckResult::Same => {}
86✔
504
                CheckResult::Different => any_different = true,
4✔
505
                opaque @ CheckResult::Opaque { .. } => {
×
506
                    self.path.pop();
×
507
                    return opaque;
×
508
                }
509
            }
510

511
            self.path.pop();
90✔
512
        }
513

514
        // Elements only in left (removed)
515
        for (i, item) in left_items.iter().enumerate().skip(min_len) {
441✔
516
            self.path.push(PathSegment::Index(i));
×
517
            self.record_only_left(*item);
×
518
            any_different = true;
×
519
            self.path.pop();
×
520
        }
×
521

522
        // Elements only in right (added)
523
        for (i, item) in right_items.iter().enumerate().skip(min_len) {
441✔
524
            self.path.push(PathSegment::Index(i));
×
525
            self.record_only_right(*item);
×
526
            any_different = true;
×
527
            self.path.pop();
×
528
        }
×
529

530
        if any_different {
441✔
531
            CheckResult::Different
2✔
532
        } else {
533
            CheckResult::Same
439✔
534
        }
535
    }
441✔
536

537
    fn check_maps(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
×
538
        let left_map = left.into_map().unwrap();
×
539
        let right_map = right.into_map().unwrap();
×
540

541
        let mut any_different = false;
×
542
        let mut seen_keys = std::collections::HashSet::new();
×
543

544
        for (left_key, left_value) in left_map.iter() {
×
545
            let key_str = Self::format_value(left_key);
×
546
            seen_keys.insert(key_str.clone());
×
547
            self.path.push(PathSegment::Key(key_str));
×
548

549
            // Try to find matching key in right map
550
            let mut found = false;
×
551
            for (right_key, right_value) in right_map.iter() {
×
552
                if Self::format_value(left_key) == Self::format_value(right_key) {
×
553
                    found = true;
×
554
                    match self.check(left_value, right_value) {
×
555
                        CheckResult::Same => {}
×
556
                        CheckResult::Different => any_different = true,
×
557
                        opaque @ CheckResult::Opaque { .. } => {
×
558
                            self.path.pop();
×
559
                            return opaque;
×
560
                        }
561
                    }
562
                    break;
×
563
                }
×
564
            }
565

566
            if !found {
×
567
                self.record_only_left(left_value);
×
568
                any_different = true;
×
569
            }
×
570

571
            self.path.pop();
×
572
        }
573

574
        // Check keys only in right
575
        for (right_key, right_value) in right_map.iter() {
×
576
            let key_str = Self::format_value(right_key);
×
577
            if !seen_keys.contains(&key_str) {
×
578
                self.path.push(PathSegment::Key(key_str));
×
579
                self.record_only_right(right_value);
×
580
                any_different = true;
×
581
                self.path.pop();
×
582
            }
×
583
        }
584

585
        if any_different {
×
586
            CheckResult::Different
×
587
        } else {
588
            CheckResult::Same
×
589
        }
590
    }
×
591

592
    fn check_pointers(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
101✔
593
        let left_ptr = left.into_pointer().unwrap();
101✔
594
        let right_ptr = right.into_pointer().unwrap();
101✔
595

596
        match (left_ptr.borrow_inner(), right_ptr.borrow_inner()) {
101✔
597
            (Some(left_inner), Some(right_inner)) => self.check(left_inner, right_inner),
101✔
598
            (None, None) => CheckResult::Same,
×
599
            _ => {
600
                self.record_changed(left, right);
×
601
                CheckResult::Different
×
602
            }
603
        }
604
    }
101✔
605

606
    /// Compare a DynamicValue (left) against any other Peek (right) based on the DynamicValue's runtime kind.
607
    /// This enables comparing e.g. `Value::Array` against `Vec<i32>`.
608
    fn check_with_dynamic_value(
43✔
609
        &mut self,
43✔
610
        dyn_peek: Peek<'_, '_>,
43✔
611
        other: Peek<'_, '_>,
43✔
612
    ) -> CheckResult {
43✔
613
        let dyn_val = dyn_peek.into_dynamic_value().unwrap();
43✔
614
        let kind = dyn_val.kind();
43✔
615

616
        match kind {
43✔
617
            DynValueKind::Null => {
618
                // Null compares equal to () or Option::None
619
                let other_str = Self::format_value(other);
×
620
                if other_str == "()" || other_str == "None" {
×
621
                    CheckResult::Same
×
622
                } else {
623
                    self.record_changed(dyn_peek, other);
×
624
                    CheckResult::Different
×
625
                }
626
            }
627
            DynValueKind::Bool => {
628
                // Compare against bool
629
                let dyn_bool = dyn_val.as_bool();
2✔
630

631
                // Check if other is also a DynamicValue bool
632
                let other_bool = if let Ok(other_dyn) = other.into_dynamic_value() {
2✔
633
                    other_dyn.as_bool()
×
634
                } else {
635
                    let other_str = Self::format_value(other);
2✔
636
                    match other_str.as_str() {
2✔
637
                        "true" => Some(true),
2✔
638
                        "false" => Some(false),
1✔
639
                        _ => None,
×
640
                    }
641
                };
642

643
                if dyn_bool == other_bool {
2✔
644
                    CheckResult::Same
1✔
645
                } else {
646
                    self.record_changed(dyn_peek, other);
1✔
647
                    CheckResult::Different
1✔
648
                }
649
            }
650
            DynValueKind::Number => {
651
                // Check if other is also a DynamicValue number
652
                if let Ok(other_dyn) = other.into_dynamic_value() {
26✔
653
                    // Compare DynamicValue numbers directly
654
                    let same = match (dyn_val.as_i64(), other_dyn.as_i64()) {
8✔
655
                        (Some(l), Some(r)) => l == r,
8✔
656
                        _ => match (dyn_val.as_u64(), other_dyn.as_u64()) {
×
657
                            (Some(l), Some(r)) => l == r,
×
658
                            _ => match (dyn_val.as_f64(), other_dyn.as_f64()) {
×
659
                                (Some(l), Some(r)) => self.floats_equal(l, r),
×
660
                                _ => false,
×
661
                            },
662
                        },
663
                    };
664
                    if same {
8✔
665
                        return CheckResult::Same;
7✔
666
                    } else {
667
                        self.record_changed(dyn_peek, other);
1✔
668
                        return CheckResult::Different;
1✔
669
                    }
670
                }
18✔
671

672
                // Compare against scalar number by parsing formatted value
673
                let other_str = Self::format_value(other);
18✔
674

675
                let same = if let Some(dyn_i64) = dyn_val.as_i64() {
18✔
676
                    other_str.parse::<i64>().ok() == Some(dyn_i64)
18✔
677
                } else if let Some(dyn_u64) = dyn_val.as_u64() {
×
678
                    other_str.parse::<u64>().ok() == Some(dyn_u64)
×
679
                } else if let Some(dyn_f64) = dyn_val.as_f64() {
×
680
                    other_str
×
681
                        .parse::<f64>()
×
682
                        .ok()
×
683
                        .is_some_and(|other_f64| self.floats_equal(dyn_f64, other_f64))
×
684
                } else {
685
                    false
×
686
                };
687

688
                if same {
18✔
689
                    CheckResult::Same
16✔
690
                } else {
691
                    self.record_changed(dyn_peek, other);
2✔
692
                    CheckResult::Different
2✔
693
                }
694
            }
695
            DynValueKind::String => {
696
                // Compare against string types
697
                let dyn_str = dyn_val.as_str();
4✔
698

699
                // Check if other is also a DynamicValue string
700
                let other_str = if let Ok(other_dyn) = other.into_dynamic_value() {
4✔
701
                    other_dyn.as_str()
2✔
702
                } else {
703
                    other.as_str()
2✔
704
                };
705

706
                if dyn_str == other_str {
4✔
707
                    CheckResult::Same
2✔
708
                } else {
709
                    self.record_changed(dyn_peek, other);
2✔
710
                    CheckResult::Different
2✔
711
                }
712
            }
713
            DynValueKind::Bytes => {
714
                // Compare against byte slice types
715
                let dyn_bytes = dyn_val.as_bytes();
×
716

717
                // Check if other is also a DynamicValue bytes
718
                let other_bytes = if let Ok(other_dyn) = other.into_dynamic_value() {
×
719
                    other_dyn.as_bytes()
×
720
                } else {
721
                    other.as_bytes()
×
722
                };
723

724
                if dyn_bytes == other_bytes {
×
725
                    CheckResult::Same
×
726
                } else {
727
                    self.record_changed(dyn_peek, other);
×
728
                    CheckResult::Different
×
729
                }
730
            }
731
            DynValueKind::Array => {
732
                // Compare against any list-like type (Vec, array, slice, or another DynamicValue array)
733
                self.check_dyn_array_against_other(dyn_peek, dyn_val, other)
9✔
734
            }
735
            DynValueKind::Object => {
736
                // Compare against maps or structs
737
                self.check_dyn_object_against_other(dyn_peek, dyn_val, other)
2✔
738
            }
739
            DynValueKind::DateTime => {
740
                // Compare datetime values by their components
741
                let dyn_dt = dyn_val.as_datetime();
×
742

743
                // Check if other is also a DynamicValue datetime
744
                let other_dt = if let Ok(other_dyn) = other.into_dynamic_value() {
×
745
                    other_dyn.as_datetime()
×
746
                } else {
747
                    None
×
748
                };
749

750
                if dyn_dt == other_dt {
×
751
                    CheckResult::Same
×
752
                } else {
753
                    self.record_changed(dyn_peek, other);
×
754
                    CheckResult::Different
×
755
                }
756
            }
757
            DynValueKind::QName | DynValueKind::Uuid => {
758
                // For now, QName and Uuid are compared by formatted representation
759
                let dyn_str = Self::format_value(dyn_peek);
×
760
                let other_str = Self::format_value(other);
×
761
                if dyn_str == other_str {
×
762
                    CheckResult::Same
×
763
                } else {
764
                    self.record_changed(dyn_peek, other);
×
765
                    CheckResult::Different
×
766
                }
767
            }
768
        }
769
    }
43✔
770

771
    fn check_dyn_array_against_other(
9✔
772
        &mut self,
9✔
773
        dyn_peek: Peek<'_, '_>,
9✔
774
        dyn_val: facet_reflect::PeekDynamicValue<'_, '_>,
9✔
775
        other: Peek<'_, '_>,
9✔
776
    ) -> CheckResult {
9✔
777
        let dyn_len = dyn_val.array_len().unwrap_or(0);
9✔
778

779
        // Check if other is also a DynamicValue array
780
        if let Ok(other_dyn) = other.into_dynamic_value() {
9✔
781
            if other_dyn.kind() == DynValueKind::Array {
2✔
782
                let other_len = other_dyn.array_len().unwrap_or(0);
2✔
783
                return self
2✔
784
                    .check_two_dyn_arrays(dyn_peek, dyn_val, dyn_len, other, other_dyn, other_len);
2✔
785
            } else {
786
                self.record_changed(dyn_peek, other);
×
787
                return CheckResult::Different;
×
788
            }
789
        }
7✔
790

791
        // Check if other is list-like
792
        if let Ok(other_list) = other.into_list_like() {
7✔
793
            let other_len = other_list.len();
7✔
794
            let mut any_different = false;
7✔
795
            let min_len = dyn_len.min(other_len);
7✔
796

797
            // Compare common elements
798
            for i in 0..min_len {
18✔
799
                self.path.push(PathSegment::Index(i));
18✔
800

801
                if let (Some(dyn_elem), Some(other_elem)) =
18✔
802
                    (dyn_val.array_get(i), other_list.get(i))
18✔
803
                {
804
                    match self.check(dyn_elem, other_elem) {
18✔
805
                        CheckResult::Same => {}
17✔
806
                        CheckResult::Different => any_different = true,
1✔
807
                        opaque @ CheckResult::Opaque { .. } => {
×
808
                            self.path.pop();
×
809
                            return opaque;
×
810
                        }
811
                    }
812
                }
×
813

814
                self.path.pop();
18✔
815
            }
816

817
            // Elements only in dyn array
818
            for i in min_len..dyn_len {
7✔
819
                self.path.push(PathSegment::Index(i));
1✔
820
                if let Some(dyn_elem) = dyn_val.array_get(i) {
1✔
821
                    self.record_only_left(dyn_elem);
1✔
822
                    any_different = true;
1✔
823
                }
1✔
824
                self.path.pop();
1✔
825
            }
826

827
            // Elements only in other list
828
            for i in min_len..other_len {
7✔
829
                self.path.push(PathSegment::Index(i));
×
830
                if let Some(other_elem) = other_list.get(i) {
×
831
                    self.record_only_right(other_elem);
×
832
                    any_different = true;
×
833
                }
×
834
                self.path.pop();
×
835
            }
836

837
            if any_different {
7✔
838
                CheckResult::Different
2✔
839
            } else {
840
                CheckResult::Same
5✔
841
            }
842
        } else {
843
            // Other is not array-like, they're different
844
            self.record_changed(dyn_peek, other);
×
845
            CheckResult::Different
×
846
        }
847
    }
9✔
848

849
    fn check_two_dyn_arrays(
2✔
850
        &mut self,
2✔
851
        _left_peek: Peek<'_, '_>,
2✔
852
        left_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
853
        left_len: usize,
2✔
854
        _right_peek: Peek<'_, '_>,
2✔
855
        right_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
856
        right_len: usize,
2✔
857
    ) -> CheckResult {
2✔
858
        let mut any_different = false;
2✔
859
        let min_len = left_len.min(right_len);
2✔
860

861
        // Compare common elements
862
        for i in 0..min_len {
6✔
863
            self.path.push(PathSegment::Index(i));
6✔
864

865
            if let (Some(left_elem), Some(right_elem)) =
6✔
866
                (left_dyn.array_get(i), right_dyn.array_get(i))
6✔
867
            {
868
                match self.check(left_elem, right_elem) {
6✔
869
                    CheckResult::Same => {}
5✔
870
                    CheckResult::Different => any_different = true,
1✔
871
                    opaque @ CheckResult::Opaque { .. } => {
×
872
                        self.path.pop();
×
873
                        return opaque;
×
874
                    }
875
                }
876
            }
×
877

878
            self.path.pop();
6✔
879
        }
880

881
        // Elements only in left
882
        for i in min_len..left_len {
2✔
883
            self.path.push(PathSegment::Index(i));
×
884
            if let Some(left_elem) = left_dyn.array_get(i) {
×
885
                self.record_only_left(left_elem);
×
886
                any_different = true;
×
887
            }
×
888
            self.path.pop();
×
889
        }
890

891
        // Elements only in right
892
        for i in min_len..right_len {
2✔
893
            self.path.push(PathSegment::Index(i));
×
894
            if let Some(right_elem) = right_dyn.array_get(i) {
×
895
                self.record_only_right(right_elem);
×
896
                any_different = true;
×
897
            }
×
898
            self.path.pop();
×
899
        }
900

901
        if any_different {
2✔
902
            CheckResult::Different
1✔
903
        } else {
904
            CheckResult::Same
1✔
905
        }
906
    }
2✔
907

908
    fn check_dyn_object_against_other(
2✔
909
        &mut self,
2✔
910
        dyn_peek: Peek<'_, '_>,
2✔
911
        dyn_val: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
912
        other: Peek<'_, '_>,
2✔
913
    ) -> CheckResult {
2✔
914
        let dyn_len = dyn_val.object_len().unwrap_or(0);
2✔
915

916
        // Check if other is also a DynamicValue object
917
        if let Ok(other_dyn) = other.into_dynamic_value() {
2✔
918
            if other_dyn.kind() == DynValueKind::Object {
2✔
919
                let other_len = other_dyn.object_len().unwrap_or(0);
2✔
920
                return self.check_two_dyn_objects(
2✔
921
                    dyn_peek, dyn_val, dyn_len, other, other_dyn, other_len,
2✔
922
                );
923
            } else {
924
                self.record_changed(dyn_peek, other);
×
925
                return CheckResult::Different;
×
926
            }
927
        }
×
928

929
        // Check if other is a map
930
        if let Ok(other_map) = other.into_map() {
×
931
            let mut any_different = false;
×
932
            let mut seen_keys = std::collections::HashSet::new();
×
933

934
            // Check all entries in dyn object
935
            for i in 0..dyn_len {
×
936
                if let Some((key, dyn_value)) = dyn_val.object_get_entry(i) {
×
937
                    seen_keys.insert(key.to_owned());
×
938
                    self.path.push(PathSegment::Key(key.to_owned()));
×
939

940
                    // Try to find key in map - need to compare by formatted key
941
                    let mut found = false;
×
942
                    for (map_key, map_value) in other_map.iter() {
×
943
                        if Self::format_value(map_key) == format!("{key:?}") {
×
944
                            found = true;
×
945
                            match self.check(dyn_value, map_value) {
×
946
                                CheckResult::Same => {}
×
947
                                CheckResult::Different => any_different = true,
×
948
                                opaque @ CheckResult::Opaque { .. } => {
×
949
                                    self.path.pop();
×
950
                                    return opaque;
×
951
                                }
952
                            }
953
                            break;
×
954
                        }
×
955
                    }
956

957
                    if !found {
×
958
                        self.record_only_left(dyn_value);
×
959
                        any_different = true;
×
960
                    }
×
961

962
                    self.path.pop();
×
963
                }
×
964
            }
965

966
            // Check keys only in map
967
            for (map_key, map_value) in other_map.iter() {
×
968
                let key_str = Self::format_value(map_key);
×
969
                // Remove quotes for comparison
970
                let key_unquoted = key_str.trim_matches('"');
×
971
                if !seen_keys.contains(key_unquoted) {
×
972
                    self.path.push(PathSegment::Key(key_unquoted.to_owned()));
×
973
                    self.record_only_right(map_value);
×
974
                    any_different = true;
×
975
                    self.path.pop();
×
976
                }
×
977
            }
978

979
            if any_different {
×
980
                CheckResult::Different
×
981
            } else {
982
                CheckResult::Same
×
983
            }
984
        } else if let Ok(other_struct) = other.into_struct() {
×
985
            // Compare DynamicValue object against struct fields
986
            let mut any_different = false;
×
987
            let mut seen_fields = std::collections::HashSet::new();
×
988

989
            // Check all entries in dyn object against struct fields
990
            for i in 0..dyn_len {
×
991
                if let Some((key, dyn_value)) = dyn_val.object_get_entry(i) {
×
992
                    seen_fields.insert(key.to_owned());
×
993
                    self.path.push(PathSegment::Key(key.to_owned()));
×
994

995
                    if let Ok(struct_value) = other_struct.field_by_name(key) {
×
996
                        match self.check(dyn_value, struct_value) {
×
997
                            CheckResult::Same => {}
×
998
                            CheckResult::Different => any_different = true,
×
999
                            opaque @ CheckResult::Opaque { .. } => {
×
1000
                                self.path.pop();
×
1001
                                return opaque;
×
1002
                            }
1003
                        }
1004
                    } else {
×
1005
                        self.record_only_left(dyn_value);
×
1006
                        any_different = true;
×
1007
                    }
×
1008

1009
                    self.path.pop();
×
1010
                }
×
1011
            }
1012

1013
            // Check struct fields not in dyn object
1014
            for (field, struct_value) in other_struct.fields() {
×
1015
                if !seen_fields.contains(field.name) {
×
1016
                    self.path.push(PathSegment::Field(field.name));
×
1017
                    self.record_only_right(struct_value);
×
1018
                    any_different = true;
×
1019
                    self.path.pop();
×
1020
                }
×
1021
            }
1022

1023
            if any_different {
×
1024
                CheckResult::Different
×
1025
            } else {
1026
                CheckResult::Same
×
1027
            }
1028
        } else {
1029
            // Other is not object-like, they're different
1030
            self.record_changed(dyn_peek, other);
×
1031
            CheckResult::Different
×
1032
        }
1033
    }
2✔
1034

1035
    fn check_two_dyn_objects(
2✔
1036
        &mut self,
2✔
1037
        _left_peek: Peek<'_, '_>,
2✔
1038
        left_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
1039
        left_len: usize,
2✔
1040
        _right_peek: Peek<'_, '_>,
2✔
1041
        right_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
1042
        right_len: usize,
2✔
1043
    ) -> CheckResult {
2✔
1044
        let mut any_different = false;
2✔
1045
        let mut seen_keys = std::collections::HashSet::new();
2✔
1046

1047
        // Check all entries in left
1048
        for i in 0..left_len {
4✔
1049
            if let Some((key, left_value)) = left_dyn.object_get_entry(i) {
4✔
1050
                seen_keys.insert(key.to_owned());
4✔
1051
                self.path.push(PathSegment::Key(key.to_owned()));
4✔
1052

1053
                if let Some(right_value) = right_dyn.object_get(key) {
4✔
1054
                    match self.check(left_value, right_value) {
4✔
1055
                        CheckResult::Same => {}
3✔
1056
                        CheckResult::Different => any_different = true,
1✔
1057
                        opaque @ CheckResult::Opaque { .. } => {
×
1058
                            self.path.pop();
×
1059
                            return opaque;
×
1060
                        }
1061
                    }
1062
                } else {
×
1063
                    self.record_only_left(left_value);
×
1064
                    any_different = true;
×
1065
                }
×
1066

1067
                self.path.pop();
4✔
1068
            }
×
1069
        }
1070

1071
        // Check entries only in right
1072
        for i in 0..right_len {
4✔
1073
            if let Some((key, right_value)) = right_dyn.object_get_entry(i)
4✔
1074
                && !seen_keys.contains(key)
4✔
NEW
1075
            {
×
NEW
1076
                self.path.push(PathSegment::Key(key.to_owned()));
×
NEW
1077
                self.record_only_right(right_value);
×
NEW
1078
                any_different = true;
×
NEW
1079
                self.path.pop();
×
1080
            }
4✔
1081
        }
1082

1083
        if any_different {
2✔
1084
            CheckResult::Different
1✔
1085
        } else {
1086
            CheckResult::Same
1✔
1087
        }
1088
    }
2✔
1089
}
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

© 2026 Coveralls, Inc