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

facet-rs / facet / 19775426569

28 Nov 2025 11:06PM UTC coverage: 60.423% (+0.8%) from 59.624%
19775426569

push

github

fasterthanlime
Add facet-assert/diff/pretty support for facet_value::Value

This adds full support for comparing, diffing, and pretty-printing
`facet_value::Value` types through the facet reflection system.

Key changes:

- facet-reflect: Add `PeekDynamicValue` for inspecting DynamicValue types
  - Methods for accessing scalars (bool, number, string, bytes)
  - Iterators for arrays and objects
  - `Peek::into_dynamic_value()` method

- facet-assert: Enable structural comparison between Value and concrete types
  - `value!([1, 2, 3])` can now be compared with `vec![1, 2, 3]`
  - Value strings compare equal to String
  - Value numbers compare equal to numeric types
  - Handles nested structures recursively

- facet-diff: Add DynamicValue diffing support
  - Arrays produce Diff::Sequence with element diffs
  - Objects produce Diff::User with field-level diffs

- facet-pretty: Add DynamicValue formatting
  - Handles all DynValueKind variants
  - Proper indentation for nested arrays/objects

- Tests: 27 tests covering all functionality

356 of 588 new or added lines in 5 files covered. (60.54%)

2 existing lines in 2 files now uncovered.

17185 of 28441 relevant lines covered (60.42%)

152.92 hits per line

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

51.31
/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};
7

8
/// Result of checking if two values are structurally the same.
9
pub enum Sameness {
10
    /// The values are structurally the same.
11
    Same,
12
    /// The values differ - contains a formatted diff.
13
    Different(String),
14
    /// Encountered an opaque type that cannot be compared.
15
    Opaque {
16
        /// The type name of the opaque type.
17
        type_name: &'static str,
18
    },
19
}
20

21
/// Check if two Facet values are structurally the same.
22
///
23
/// This does NOT require `PartialEq` - it walks the structure via reflection.
24
/// Two values are "same" if they have the same structure and values, even if
25
/// they have different type names.
26
///
27
/// Returns [`Sameness::Opaque`] if either value contains an opaque type.
28
pub fn check_same<'f, T: Facet<'f>, U: Facet<'f>>(left: &T, right: &U) -> Sameness {
25✔
29
    let left_peek = Peek::new(left);
25✔
30
    let right_peek = Peek::new(right);
25✔
31

32
    let mut differ = Differ::new();
25✔
33
    match differ.check(left_peek, right_peek) {
25✔
34
        CheckResult::Same => Sameness::Same,
16✔
35
        CheckResult::Different => Sameness::Different(differ.into_diff()),
9✔
36
        CheckResult::Opaque { type_name } => Sameness::Opaque { type_name },
×
37
    }
38
}
25✔
39

40
enum CheckResult {
41
    Same,
42
    Different,
43
    Opaque { type_name: &'static str },
44
}
45

46
struct Differ {
47
    /// Differences found, stored as lines
48
    diffs: Vec<DiffLine>,
49
    /// Current path for context
50
    path: Vec<PathSegment>,
51
}
52

53
enum PathSegment {
54
    Field(&'static str),
55
    Index(usize),
56
    Variant(&'static str),
57
    Key(String),
58
}
59

60
impl fmt::Display for PathSegment {
61
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6✔
62
        match self {
6✔
63
            PathSegment::Field(name) => write!(f, ".{name}"),
1✔
64
            PathSegment::Index(i) => write!(f, "[{i}]"),
4✔
65
            PathSegment::Variant(name) => write!(f, "::{name}"),
×
66
            PathSegment::Key(k) => write!(f, "[{k:?}]"),
1✔
67
        }
68
    }
6✔
69
}
70

71
enum DiffLine {
72
    Changed {
73
        path: String,
74
        left: String,
75
        right: String,
76
    },
77
    OnlyLeft {
78
        path: String,
79
        value: String,
80
    },
81
    OnlyRight {
82
        path: String,
83
        value: String,
84
    },
85
}
86

87
impl Differ {
88
    fn new() -> Self {
26✔
89
        Self {
26✔
90
            diffs: Vec::new(),
26✔
91
            path: Vec::new(),
26✔
92
        }
26✔
93
    }
26✔
94

95
    fn current_path(&self) -> String {
9✔
96
        if self.path.is_empty() {
9✔
97
            "root".to_string()
3✔
98
        } else {
99
            let mut s = String::new();
6✔
100
            for seg in &self.path {
12✔
101
                s.push_str(&seg.to_string());
6✔
102
            }
6✔
103
            s
6✔
104
        }
105
    }
9✔
106

107
    fn format_value(peek: Peek<'_, '_>) -> String {
73✔
108
        let printer = PrettyPrinter::default().with_colors(false);
73✔
109
        printer.format_peek(peek).to_string()
73✔
110
    }
73✔
111

112
    fn record_changed(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) {
8✔
113
        self.diffs.push(DiffLine::Changed {
8✔
114
            path: self.current_path(),
8✔
115
            left: Self::format_value(left),
8✔
116
            right: Self::format_value(right),
8✔
117
        });
8✔
118
    }
8✔
119

120
    fn record_only_left(&mut self, left: Peek<'_, '_>) {
1✔
121
        self.diffs.push(DiffLine::OnlyLeft {
1✔
122
            path: self.current_path(),
1✔
123
            value: Self::format_value(left),
1✔
124
        });
1✔
125
    }
1✔
126

127
    fn record_only_right(&mut self, right: Peek<'_, '_>) {
×
128
        self.diffs.push(DiffLine::OnlyRight {
×
129
            path: self.current_path(),
×
130
            value: Self::format_value(right),
×
131
        });
×
132
    }
×
133

134
    fn into_diff(self) -> String {
9✔
135
        use std::fmt::Write;
136

137
        let mut out = String::new();
9✔
138

139
        for diff in self.diffs {
18✔
140
            match diff {
9✔
141
                DiffLine::Changed { path, left, right } => {
8✔
142
                    writeln!(out, "\x1b[1m{path}\x1b[0m:").unwrap();
8✔
143
                    writeln!(out, "  \x1b[31m- {left}\x1b[0m").unwrap();
8✔
144
                    writeln!(out, "  \x1b[32m+ {right}\x1b[0m").unwrap();
8✔
145
                }
8✔
146
                DiffLine::OnlyLeft { path, value } => {
1✔
147
                    writeln!(out, "\x1b[1m{path}\x1b[0m (only in left):").unwrap();
1✔
148
                    writeln!(out, "  \x1b[31m- {value}\x1b[0m").unwrap();
1✔
149
                }
1✔
150
                DiffLine::OnlyRight { path, value } => {
×
151
                    writeln!(out, "\x1b[1m{path}\x1b[0m (only in right):").unwrap();
×
152
                    writeln!(out, "  \x1b[32m+ {value}\x1b[0m").unwrap();
×
153
                }
×
154
            }
155
        }
156

157
        out
9✔
158
    }
9✔
159

160
    fn check(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
70✔
161
        // Handle Option BEFORE innermost_peek (since Option's try_borrow_inner fails)
162
        if matches!(left.shape().def, Def::Option(_)) && matches!(right.shape().def, Def::Option(_))
70✔
163
        {
164
            return self.check_options(left, right);
2✔
165
        }
68✔
166

167
        // Unwrap transparent wrappers (like NonZero, newtype wrappers)
168
        let left = left.innermost_peek();
68✔
169
        let right = right.innermost_peek();
68✔
170

171
        // Try scalar comparison first (for leaf values like String, i32, etc.)
172
        // Scalars are compared by their formatted representation.
173
        if matches!(left.shape().def, Def::Scalar) && matches!(right.shape().def, Def::Scalar) {
68✔
174
            let left_str = Self::format_value(left);
18✔
175
            let right_str = Self::format_value(right);
18✔
176
            if left_str == right_str {
18✔
177
                return CheckResult::Same;
16✔
178
            } else {
179
                self.record_changed(left, right);
2✔
180
                return CheckResult::Different;
2✔
181
            }
182
        }
50✔
183

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

188
        // Handle lists/arrays/slices (Vec is Opaque but has Def::List)
189
        if left.into_list_like().is_ok() && right.into_list_like().is_ok() {
50✔
190
            return self.check_lists(left, right);
2✔
191
        }
48✔
192

193
        // Handle maps
194
        if matches!(left.shape().def, Def::Map(_)) && matches!(right.shape().def, Def::Map(_)) {
48✔
195
            return self.check_maps(left, right);
×
196
        }
48✔
197

198
        // Handle smart pointers
199
        if matches!(left.shape().def, Def::Pointer(_))
48✔
200
            && matches!(right.shape().def, Def::Pointer(_))
1✔
201
        {
202
            return self.check_pointers(left, right);
1✔
203
        }
47✔
204

205
        // Handle structs
206
        if let (Type::User(UserType::Struct(_)), Type::User(UserType::Struct(_))) =
207
            (left.shape().ty, right.shape().ty)
47✔
208
        {
209
            return self.check_structs(left, right);
4✔
210
        }
43✔
211

212
        // Handle enums
213
        if let (Type::User(UserType::Enum(_)), Type::User(UserType::Enum(_))) =
214
            (left.shape().ty, right.shape().ty)
43✔
215
        {
216
            return self.check_enums(left, right);
×
217
        }
43✔
218

219
        // Handle dynamic values (like facet_value::Value) - compare based on their runtime kind
220
        // This allows comparing Value against concrete types (e.g., Value array vs Vec)
221
        if let Def::DynamicValue(_) = left.shape().def {
43✔
222
            return self.check_with_dynamic_value(left, right);
42✔
223
        }
1✔
224
        if let Def::DynamicValue(_) = right.shape().def {
1✔
225
            return self.check_with_dynamic_value(right, left);
1✔
NEW
226
        }
×
227

228
        // At this point, if either is Opaque and we haven't handled it above, fail
229
        if matches!(left.shape().ty, Type::User(UserType::Opaque)) {
×
230
            return CheckResult::Opaque {
×
231
                type_name: left.shape().type_identifier,
×
232
            };
×
233
        }
×
234
        if matches!(right.shape().ty, Type::User(UserType::Opaque)) {
×
235
            return CheckResult::Opaque {
×
236
                type_name: right.shape().type_identifier,
×
237
            };
×
238
        }
×
239

240
        // Fallback: format and compare
241
        let left_str = Self::format_value(left);
×
242
        let right_str = Self::format_value(right);
×
243
        if left_str == right_str {
×
244
            CheckResult::Same
×
245
        } else {
246
            self.record_changed(left, right);
×
247
            CheckResult::Different
×
248
        }
249
    }
70✔
250

251
    fn check_structs(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
4✔
252
        let left_struct = left.into_struct().unwrap();
4✔
253
        let right_struct = right.into_struct().unwrap();
4✔
254

255
        let mut any_different = false;
4✔
256
        let mut seen_fields = std::collections::HashSet::new();
4✔
257

258
        // Check all fields in left
259
        for (field, left_value) in left_struct.fields() {
8✔
260
            seen_fields.insert(field.name);
8✔
261
            self.path.push(PathSegment::Field(field.name));
8✔
262

263
            if let Ok(right_value) = right_struct.field_by_name(field.name) {
8✔
264
                match self.check(left_value, right_value) {
8✔
265
                    CheckResult::Same => {}
7✔
266
                    CheckResult::Different => any_different = true,
1✔
267
                    opaque @ CheckResult::Opaque { .. } => {
×
268
                        self.path.pop();
×
269
                        return opaque;
×
270
                    }
271
                }
272
            } else {
×
273
                // Field only in left
×
274
                self.record_only_left(left_value);
×
275
                any_different = true;
×
276
            }
×
277

278
            self.path.pop();
8✔
279
        }
280

281
        // Check fields only in right
282
        for (field, right_value) in right_struct.fields() {
8✔
283
            if !seen_fields.contains(field.name) {
8✔
284
                self.path.push(PathSegment::Field(field.name));
×
285
                self.record_only_right(right_value);
×
286
                any_different = true;
×
287
                self.path.pop();
×
288
            }
8✔
289
        }
290

291
        if any_different {
4✔
292
            CheckResult::Different
1✔
293
        } else {
294
            CheckResult::Same
3✔
295
        }
296
    }
4✔
297

298
    fn check_enums(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
×
299
        let left_enum = left.into_enum().unwrap();
×
300
        let right_enum = right.into_enum().unwrap();
×
301

302
        let left_variant = left_enum.active_variant().unwrap();
×
303
        let right_variant = right_enum.active_variant().unwrap();
×
304

305
        // Different variants = different
306
        if left_variant.name != right_variant.name {
×
307
            self.record_changed(left, right);
×
308
            return CheckResult::Different;
×
309
        }
×
310

311
        // Same variant - check fields
312
        self.path.push(PathSegment::Variant(left_variant.name));
×
313

314
        let mut any_different = false;
×
315
        let mut seen_fields = std::collections::HashSet::new();
×
316

317
        for (field, left_value) in left_enum.fields() {
×
318
            seen_fields.insert(field.name);
×
319
            self.path.push(PathSegment::Field(field.name));
×
320

321
            if let Ok(Some(right_value)) = right_enum.field_by_name(field.name) {
×
322
                match self.check(left_value, right_value) {
×
323
                    CheckResult::Same => {}
×
324
                    CheckResult::Different => any_different = true,
×
325
                    opaque @ CheckResult::Opaque { .. } => {
×
326
                        self.path.pop();
×
327
                        self.path.pop();
×
328
                        return opaque;
×
329
                    }
330
                }
331
            } else {
×
332
                self.record_only_left(left_value);
×
333
                any_different = true;
×
334
            }
×
335

336
            self.path.pop();
×
337
        }
338

339
        for (field, right_value) in right_enum.fields() {
×
340
            if !seen_fields.contains(field.name) {
×
341
                self.path.push(PathSegment::Field(field.name));
×
342
                self.record_only_right(right_value);
×
343
                any_different = true;
×
344
                self.path.pop();
×
345
            }
×
346
        }
347

348
        self.path.pop();
×
349

350
        if any_different {
×
351
            CheckResult::Different
×
352
        } else {
353
            CheckResult::Same
×
354
        }
355
    }
×
356

357
    fn check_options(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
2✔
358
        let left_opt = left.into_option().unwrap();
2✔
359
        let right_opt = right.into_option().unwrap();
2✔
360

361
        match (left_opt.value(), right_opt.value()) {
2✔
362
            (None, None) => CheckResult::Same,
1✔
363
            (Some(l), Some(r)) => self.check(l, r),
1✔
364
            (Some(_), None) | (None, Some(_)) => {
365
                self.record_changed(left, right);
×
366
                CheckResult::Different
×
367
            }
368
        }
369
    }
2✔
370

371
    fn check_lists(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
2✔
372
        let left_list = left.into_list_like().unwrap();
2✔
373
        let right_list = right.into_list_like().unwrap();
2✔
374

375
        let left_items: Vec<_> = left_list.iter().collect();
2✔
376
        let right_items: Vec<_> = right_list.iter().collect();
2✔
377

378
        let mut any_different = false;
2✔
379
        let min_len = left_items.len().min(right_items.len());
2✔
380

381
        // Compare common elements
382
        for i in 0..min_len {
6✔
383
            self.path.push(PathSegment::Index(i));
6✔
384

385
            match self.check(left_items[i], right_items[i]) {
6✔
386
                CheckResult::Same => {}
5✔
387
                CheckResult::Different => any_different = true,
1✔
388
                opaque @ CheckResult::Opaque { .. } => {
×
389
                    self.path.pop();
×
390
                    return opaque;
×
391
                }
392
            }
393

394
            self.path.pop();
6✔
395
        }
396

397
        // Elements only in left (removed)
398
        for (i, item) in left_items.iter().enumerate().skip(min_len) {
2✔
399
            self.path.push(PathSegment::Index(i));
×
400
            self.record_only_left(*item);
×
401
            any_different = true;
×
402
            self.path.pop();
×
403
        }
×
404

405
        // Elements only in right (added)
406
        for (i, item) in right_items.iter().enumerate().skip(min_len) {
2✔
407
            self.path.push(PathSegment::Index(i));
×
408
            self.record_only_right(*item);
×
409
            any_different = true;
×
410
            self.path.pop();
×
411
        }
×
412

413
        if any_different {
2✔
414
            CheckResult::Different
1✔
415
        } else {
416
            CheckResult::Same
1✔
417
        }
418
    }
2✔
419

420
    fn check_maps(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
×
421
        let left_map = left.into_map().unwrap();
×
422
        let right_map = right.into_map().unwrap();
×
423

424
        let mut any_different = false;
×
425
        let mut seen_keys = std::collections::HashSet::new();
×
426

427
        for (left_key, left_value) in left_map.iter() {
×
428
            let key_str = Self::format_value(left_key);
×
429
            seen_keys.insert(key_str.clone());
×
430
            self.path.push(PathSegment::Key(key_str));
×
431

432
            // Try to find matching key in right map
433
            let mut found = false;
×
434
            for (right_key, right_value) in right_map.iter() {
×
435
                if Self::format_value(left_key) == Self::format_value(right_key) {
×
436
                    found = true;
×
437
                    match self.check(left_value, right_value) {
×
438
                        CheckResult::Same => {}
×
439
                        CheckResult::Different => any_different = true,
×
440
                        opaque @ CheckResult::Opaque { .. } => {
×
441
                            self.path.pop();
×
442
                            return opaque;
×
443
                        }
444
                    }
445
                    break;
×
446
                }
×
447
            }
448

449
            if !found {
×
450
                self.record_only_left(left_value);
×
451
                any_different = true;
×
452
            }
×
453

454
            self.path.pop();
×
455
        }
456

457
        // Check keys only in right
458
        for (right_key, right_value) in right_map.iter() {
×
459
            let key_str = Self::format_value(right_key);
×
460
            if !seen_keys.contains(&key_str) {
×
461
                self.path.push(PathSegment::Key(key_str));
×
462
                self.record_only_right(right_value);
×
463
                any_different = true;
×
464
                self.path.pop();
×
465
            }
×
466
        }
467

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

475
    fn check_pointers(&mut self, left: Peek<'_, '_>, right: Peek<'_, '_>) -> CheckResult {
1✔
476
        let left_ptr = left.into_pointer().unwrap();
1✔
477
        let right_ptr = right.into_pointer().unwrap();
1✔
478

479
        match (left_ptr.borrow_inner(), right_ptr.borrow_inner()) {
1✔
480
            (Some(left_inner), Some(right_inner)) => self.check(left_inner, right_inner),
1✔
481
            (None, None) => CheckResult::Same,
×
482
            _ => {
483
                self.record_changed(left, right);
×
484
                CheckResult::Different
×
485
            }
486
        }
487
    }
1✔
488

489
    /// Compare a DynamicValue (left) against any other Peek (right) based on the DynamicValue's runtime kind.
490
    /// This enables comparing e.g. `Value::Array` against `Vec<i32>`.
491
    fn check_with_dynamic_value(
43✔
492
        &mut self,
43✔
493
        dyn_peek: Peek<'_, '_>,
43✔
494
        other: Peek<'_, '_>,
43✔
495
    ) -> CheckResult {
43✔
496
        let dyn_val = dyn_peek.into_dynamic_value().unwrap();
43✔
497
        let kind = dyn_val.kind();
43✔
498

499
        match kind {
43✔
500
            DynValueKind::Null => {
501
                // Null compares equal to () or Option::None
NEW
502
                let other_str = Self::format_value(other);
×
NEW
503
                if other_str == "()" || other_str == "None" {
×
NEW
504
                    CheckResult::Same
×
505
                } else {
NEW
506
                    self.record_changed(dyn_peek, other);
×
NEW
507
                    CheckResult::Different
×
508
                }
509
            }
510
            DynValueKind::Bool => {
511
                // Compare against bool
512
                let dyn_bool = dyn_val.as_bool();
2✔
513

514
                // Check if other is also a DynamicValue bool
515
                let other_bool = if let Ok(other_dyn) = other.into_dynamic_value() {
2✔
NEW
516
                    other_dyn.as_bool()
×
517
                } else {
518
                    let other_str = Self::format_value(other);
2✔
519
                    match other_str.as_str() {
2✔
520
                        "true" => Some(true),
2✔
521
                        "false" => Some(false),
1✔
NEW
522
                        _ => None,
×
523
                    }
524
                };
525

526
                if dyn_bool == other_bool {
2✔
527
                    CheckResult::Same
1✔
528
                } else {
529
                    self.record_changed(dyn_peek, other);
1✔
530
                    CheckResult::Different
1✔
531
                }
532
            }
533
            DynValueKind::Number => {
534
                // Check if other is also a DynamicValue number
535
                if let Ok(other_dyn) = other.into_dynamic_value() {
26✔
536
                    // Compare DynamicValue numbers directly
537
                    let same = match (dyn_val.as_i64(), other_dyn.as_i64()) {
8✔
538
                        (Some(l), Some(r)) => l == r,
8✔
NEW
539
                        _ => match (dyn_val.as_u64(), other_dyn.as_u64()) {
×
NEW
540
                            (Some(l), Some(r)) => l == r,
×
NEW
541
                            _ => match (dyn_val.as_f64(), other_dyn.as_f64()) {
×
NEW
542
                                (Some(l), Some(r)) => l == r,
×
NEW
543
                                _ => false,
×
544
                            },
545
                        },
546
                    };
547
                    if same {
8✔
548
                        return CheckResult::Same;
7✔
549
                    } else {
550
                        self.record_changed(dyn_peek, other);
1✔
551
                        return CheckResult::Different;
1✔
552
                    }
553
                }
18✔
554

555
                // Compare against scalar number by parsing formatted value
556
                let other_str = Self::format_value(other);
18✔
557

558
                let same = if let Some(dyn_i64) = dyn_val.as_i64() {
18✔
559
                    other_str.parse::<i64>().ok() == Some(dyn_i64)
18✔
NEW
560
                } else if let Some(dyn_u64) = dyn_val.as_u64() {
×
NEW
561
                    other_str.parse::<u64>().ok() == Some(dyn_u64)
×
NEW
562
                } else if let Some(dyn_f64) = dyn_val.as_f64() {
×
NEW
563
                    other_str.parse::<f64>().ok() == Some(dyn_f64)
×
564
                } else {
NEW
565
                    false
×
566
                };
567

568
                if same {
18✔
569
                    CheckResult::Same
16✔
570
                } else {
571
                    self.record_changed(dyn_peek, other);
2✔
572
                    CheckResult::Different
2✔
573
                }
574
            }
575
            DynValueKind::String => {
576
                // Compare against string types
577
                let dyn_str = dyn_val.as_str();
4✔
578

579
                // Check if other is also a DynamicValue string
580
                let other_str = if let Ok(other_dyn) = other.into_dynamic_value() {
4✔
581
                    other_dyn.as_str()
2✔
582
                } else {
583
                    other.as_str()
2✔
584
                };
585

586
                if dyn_str == other_str {
4✔
587
                    CheckResult::Same
2✔
588
                } else {
589
                    self.record_changed(dyn_peek, other);
2✔
590
                    CheckResult::Different
2✔
591
                }
592
            }
593
            DynValueKind::Bytes => {
594
                // Compare against byte slice types
NEW
595
                let dyn_bytes = dyn_val.as_bytes();
×
596

597
                // Check if other is also a DynamicValue bytes
NEW
598
                let other_bytes = if let Ok(other_dyn) = other.into_dynamic_value() {
×
NEW
599
                    other_dyn.as_bytes()
×
600
                } else {
NEW
601
                    other.as_bytes()
×
602
                };
603

NEW
604
                if dyn_bytes == other_bytes {
×
NEW
605
                    CheckResult::Same
×
606
                } else {
NEW
607
                    self.record_changed(dyn_peek, other);
×
NEW
608
                    CheckResult::Different
×
609
                }
610
            }
611
            DynValueKind::Array => {
612
                // Compare against any list-like type (Vec, array, slice, or another DynamicValue array)
613
                self.check_dyn_array_against_other(dyn_peek, dyn_val, other)
9✔
614
            }
615
            DynValueKind::Object => {
616
                // Compare against maps or structs
617
                self.check_dyn_object_against_other(dyn_peek, dyn_val, other)
2✔
618
            }
619
        }
620
    }
43✔
621

622
    fn check_dyn_array_against_other(
9✔
623
        &mut self,
9✔
624
        dyn_peek: Peek<'_, '_>,
9✔
625
        dyn_val: facet_reflect::PeekDynamicValue<'_, '_>,
9✔
626
        other: Peek<'_, '_>,
9✔
627
    ) -> CheckResult {
9✔
628
        let dyn_len = dyn_val.array_len().unwrap_or(0);
9✔
629

630
        // Check if other is also a DynamicValue array
631
        if let Ok(other_dyn) = other.into_dynamic_value() {
9✔
632
            if other_dyn.kind() == DynValueKind::Array {
2✔
633
                let other_len = other_dyn.array_len().unwrap_or(0);
2✔
634
                return self
2✔
635
                    .check_two_dyn_arrays(dyn_peek, dyn_val, dyn_len, other, other_dyn, other_len);
2✔
636
            } else {
NEW
637
                self.record_changed(dyn_peek, other);
×
NEW
638
                return CheckResult::Different;
×
639
            }
640
        }
7✔
641

642
        // Check if other is list-like
643
        if let Ok(other_list) = other.into_list_like() {
7✔
644
            let other_len = other_list.len();
7✔
645
            let mut any_different = false;
7✔
646
            let min_len = dyn_len.min(other_len);
7✔
647

648
            // Compare common elements
649
            for i in 0..min_len {
18✔
650
                self.path.push(PathSegment::Index(i));
18✔
651

652
                if let (Some(dyn_elem), Some(other_elem)) =
18✔
653
                    (dyn_val.array_get(i), other_list.get(i))
18✔
654
                {
655
                    match self.check(dyn_elem, other_elem) {
18✔
656
                        CheckResult::Same => {}
17✔
657
                        CheckResult::Different => any_different = true,
1✔
NEW
658
                        opaque @ CheckResult::Opaque { .. } => {
×
NEW
659
                            self.path.pop();
×
NEW
660
                            return opaque;
×
661
                        }
662
                    }
NEW
663
                }
×
664

665
                self.path.pop();
18✔
666
            }
667

668
            // Elements only in dyn array
669
            for i in min_len..dyn_len {
7✔
670
                self.path.push(PathSegment::Index(i));
1✔
671
                if let Some(dyn_elem) = dyn_val.array_get(i) {
1✔
672
                    self.record_only_left(dyn_elem);
1✔
673
                    any_different = true;
1✔
674
                }
1✔
675
                self.path.pop();
1✔
676
            }
677

678
            // Elements only in other list
679
            for i in min_len..other_len {
7✔
NEW
680
                self.path.push(PathSegment::Index(i));
×
NEW
681
                if let Some(other_elem) = other_list.get(i) {
×
NEW
682
                    self.record_only_right(other_elem);
×
NEW
683
                    any_different = true;
×
NEW
684
                }
×
NEW
685
                self.path.pop();
×
686
            }
687

688
            if any_different {
7✔
689
                CheckResult::Different
2✔
690
            } else {
691
                CheckResult::Same
5✔
692
            }
693
        } else {
694
            // Other is not array-like, they're different
NEW
695
            self.record_changed(dyn_peek, other);
×
NEW
696
            CheckResult::Different
×
697
        }
698
    }
9✔
699

700
    fn check_two_dyn_arrays(
2✔
701
        &mut self,
2✔
702
        _left_peek: Peek<'_, '_>,
2✔
703
        left_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
704
        left_len: usize,
2✔
705
        _right_peek: Peek<'_, '_>,
2✔
706
        right_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
707
        right_len: usize,
2✔
708
    ) -> CheckResult {
2✔
709
        let mut any_different = false;
2✔
710
        let min_len = left_len.min(right_len);
2✔
711

712
        // Compare common elements
713
        for i in 0..min_len {
6✔
714
            self.path.push(PathSegment::Index(i));
6✔
715

716
            if let (Some(left_elem), Some(right_elem)) =
6✔
717
                (left_dyn.array_get(i), right_dyn.array_get(i))
6✔
718
            {
719
                match self.check(left_elem, right_elem) {
6✔
720
                    CheckResult::Same => {}
5✔
721
                    CheckResult::Different => any_different = true,
1✔
NEW
722
                    opaque @ CheckResult::Opaque { .. } => {
×
NEW
723
                        self.path.pop();
×
NEW
724
                        return opaque;
×
725
                    }
726
                }
NEW
727
            }
×
728

729
            self.path.pop();
6✔
730
        }
731

732
        // Elements only in left
733
        for i in min_len..left_len {
2✔
NEW
734
            self.path.push(PathSegment::Index(i));
×
NEW
735
            if let Some(left_elem) = left_dyn.array_get(i) {
×
NEW
736
                self.record_only_left(left_elem);
×
NEW
737
                any_different = true;
×
NEW
738
            }
×
NEW
739
            self.path.pop();
×
740
        }
741

742
        // Elements only in right
743
        for i in min_len..right_len {
2✔
NEW
744
            self.path.push(PathSegment::Index(i));
×
NEW
745
            if let Some(right_elem) = right_dyn.array_get(i) {
×
NEW
746
                self.record_only_right(right_elem);
×
NEW
747
                any_different = true;
×
NEW
748
            }
×
NEW
749
            self.path.pop();
×
750
        }
751

752
        if any_different {
2✔
753
            CheckResult::Different
1✔
754
        } else {
755
            CheckResult::Same
1✔
756
        }
757
    }
2✔
758

759
    fn check_dyn_object_against_other(
2✔
760
        &mut self,
2✔
761
        dyn_peek: Peek<'_, '_>,
2✔
762
        dyn_val: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
763
        other: Peek<'_, '_>,
2✔
764
    ) -> CheckResult {
2✔
765
        let dyn_len = dyn_val.object_len().unwrap_or(0);
2✔
766

767
        // Check if other is also a DynamicValue object
768
        if let Ok(other_dyn) = other.into_dynamic_value() {
2✔
769
            if other_dyn.kind() == DynValueKind::Object {
2✔
770
                let other_len = other_dyn.object_len().unwrap_or(0);
2✔
771
                return self.check_two_dyn_objects(
2✔
772
                    dyn_peek, dyn_val, dyn_len, other, other_dyn, other_len,
2✔
773
                );
774
            } else {
NEW
775
                self.record_changed(dyn_peek, other);
×
NEW
776
                return CheckResult::Different;
×
777
            }
NEW
778
        }
×
779

780
        // Check if other is a map
NEW
781
        if let Ok(other_map) = other.into_map() {
×
NEW
782
            let mut any_different = false;
×
NEW
783
            let mut seen_keys = std::collections::HashSet::new();
×
784

785
            // Check all entries in dyn object
NEW
786
            for i in 0..dyn_len {
×
NEW
787
                if let Some((key, dyn_value)) = dyn_val.object_get_entry(i) {
×
NEW
788
                    seen_keys.insert(key.to_owned());
×
NEW
789
                    self.path.push(PathSegment::Key(key.to_owned()));
×
790

791
                    // Try to find key in map - need to compare by formatted key
NEW
792
                    let mut found = false;
×
NEW
793
                    for (map_key, map_value) in other_map.iter() {
×
NEW
794
                        if Self::format_value(map_key) == format!("{key:?}") {
×
NEW
795
                            found = true;
×
NEW
796
                            match self.check(dyn_value, map_value) {
×
NEW
797
                                CheckResult::Same => {}
×
NEW
798
                                CheckResult::Different => any_different = true,
×
NEW
799
                                opaque @ CheckResult::Opaque { .. } => {
×
NEW
800
                                    self.path.pop();
×
NEW
801
                                    return opaque;
×
802
                                }
803
                            }
NEW
804
                            break;
×
NEW
805
                        }
×
806
                    }
807

NEW
808
                    if !found {
×
NEW
809
                        self.record_only_left(dyn_value);
×
NEW
810
                        any_different = true;
×
NEW
811
                    }
×
812

NEW
813
                    self.path.pop();
×
NEW
814
                }
×
815
            }
816

817
            // Check keys only in map
NEW
818
            for (map_key, map_value) in other_map.iter() {
×
NEW
819
                let key_str = Self::format_value(map_key);
×
820
                // Remove quotes for comparison
NEW
821
                let key_unquoted = key_str.trim_matches('"');
×
NEW
822
                if !seen_keys.contains(key_unquoted) {
×
NEW
823
                    self.path.push(PathSegment::Key(key_unquoted.to_owned()));
×
NEW
824
                    self.record_only_right(map_value);
×
NEW
825
                    any_different = true;
×
NEW
826
                    self.path.pop();
×
NEW
827
                }
×
828
            }
829

NEW
830
            if any_different {
×
NEW
831
                CheckResult::Different
×
832
            } else {
NEW
833
                CheckResult::Same
×
834
            }
NEW
835
        } else if let Ok(other_struct) = other.into_struct() {
×
836
            // Compare DynamicValue object against struct fields
NEW
837
            let mut any_different = false;
×
NEW
838
            let mut seen_fields = std::collections::HashSet::new();
×
839

840
            // Check all entries in dyn object against struct fields
NEW
841
            for i in 0..dyn_len {
×
NEW
842
                if let Some((key, dyn_value)) = dyn_val.object_get_entry(i) {
×
NEW
843
                    seen_fields.insert(key.to_owned());
×
NEW
844
                    self.path.push(PathSegment::Key(key.to_owned()));
×
845

NEW
846
                    if let Ok(struct_value) = other_struct.field_by_name(key) {
×
NEW
847
                        match self.check(dyn_value, struct_value) {
×
NEW
848
                            CheckResult::Same => {}
×
NEW
849
                            CheckResult::Different => any_different = true,
×
NEW
850
                            opaque @ CheckResult::Opaque { .. } => {
×
NEW
851
                                self.path.pop();
×
NEW
852
                                return opaque;
×
853
                            }
854
                        }
NEW
855
                    } else {
×
NEW
856
                        self.record_only_left(dyn_value);
×
NEW
857
                        any_different = true;
×
NEW
858
                    }
×
859

NEW
860
                    self.path.pop();
×
NEW
861
                }
×
862
            }
863

864
            // Check struct fields not in dyn object
NEW
865
            for (field, struct_value) in other_struct.fields() {
×
NEW
866
                if !seen_fields.contains(field.name) {
×
NEW
867
                    self.path.push(PathSegment::Field(field.name));
×
NEW
868
                    self.record_only_right(struct_value);
×
NEW
869
                    any_different = true;
×
NEW
870
                    self.path.pop();
×
NEW
871
                }
×
872
            }
873

NEW
874
            if any_different {
×
NEW
875
                CheckResult::Different
×
876
            } else {
NEW
877
                CheckResult::Same
×
878
            }
879
        } else {
880
            // Other is not object-like, they're different
NEW
881
            self.record_changed(dyn_peek, other);
×
NEW
882
            CheckResult::Different
×
883
        }
884
    }
2✔
885

886
    fn check_two_dyn_objects(
2✔
887
        &mut self,
2✔
888
        _left_peek: Peek<'_, '_>,
2✔
889
        left_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
890
        left_len: usize,
2✔
891
        _right_peek: Peek<'_, '_>,
2✔
892
        right_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
893
        right_len: usize,
2✔
894
    ) -> CheckResult {
2✔
895
        let mut any_different = false;
2✔
896
        let mut seen_keys = std::collections::HashSet::new();
2✔
897

898
        // Check all entries in left
899
        for i in 0..left_len {
4✔
900
            if let Some((key, left_value)) = left_dyn.object_get_entry(i) {
4✔
901
                seen_keys.insert(key.to_owned());
4✔
902
                self.path.push(PathSegment::Key(key.to_owned()));
4✔
903

904
                if let Some(right_value) = right_dyn.object_get(key) {
4✔
905
                    match self.check(left_value, right_value) {
4✔
906
                        CheckResult::Same => {}
3✔
907
                        CheckResult::Different => any_different = true,
1✔
NEW
908
                        opaque @ CheckResult::Opaque { .. } => {
×
NEW
909
                            self.path.pop();
×
NEW
910
                            return opaque;
×
911
                        }
912
                    }
NEW
913
                } else {
×
NEW
914
                    self.record_only_left(left_value);
×
NEW
915
                    any_different = true;
×
NEW
916
                }
×
917

918
                self.path.pop();
4✔
NEW
919
            }
×
920
        }
921

922
        // Check entries only in right
923
        for i in 0..right_len {
4✔
924
            if let Some((key, right_value)) = right_dyn.object_get_entry(i) {
4✔
925
                if !seen_keys.contains(key) {
4✔
NEW
926
                    self.path.push(PathSegment::Key(key.to_owned()));
×
NEW
927
                    self.record_only_right(right_value);
×
NEW
928
                    any_different = true;
×
NEW
929
                    self.path.pop();
×
930
                }
4✔
NEW
931
            }
×
932
        }
933

934
        if any_different {
2✔
935
            CheckResult::Different
1✔
936
        } else {
937
            CheckResult::Same
1✔
938
        }
939
    }
2✔
940
}
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