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

facet-rs / facet / 19803020611

30 Nov 2025 06:23PM UTC coverage: 57.923% (-3.0%) from 60.927%
19803020611

push

github

fasterthanlime
Convert facet-kdl to use define_attr_grammar!

- Replace ~140 lines of hand-written macros with concise grammar DSL
- Enhance make_parse_attr.rs grammar compiler:
  - Add `ns "kdl";` syntax for namespace declaration
  - Add to_snake_case() helper (NodeName → node_name)
  - Generate __attr! macro with proper ExtensionAttr return
  - Use () data for unit variants, full dispatch for complex ones
  - Add #[repr(u8)] to generated enums for Facet derive
  - Use HashMap for O(1) struct lookup
- Update design diagrams with namespace fixes

50 of 53 new or added lines in 1 file covered. (94.34%)

3583 existing lines in 40 files now uncovered.

18788 of 32436 relevant lines covered (57.92%)

179.8 hits per line

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

50.6
/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✔
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
502
                let other_str = Self::format_value(other);
×
503
                if other_str == "()" || other_str == "None" {
×
504
                    CheckResult::Same
×
505
                } else {
506
                    self.record_changed(dyn_peek, other);
×
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✔
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✔
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✔
539
                        _ => match (dyn_val.as_u64(), other_dyn.as_u64()) {
×
540
                            (Some(l), Some(r)) => l == r,
×
541
                            _ => match (dyn_val.as_f64(), other_dyn.as_f64()) {
×
542
                                (Some(l), Some(r)) => l == r,
×
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✔
560
                } else if let Some(dyn_u64) = dyn_val.as_u64() {
×
561
                    other_str.parse::<u64>().ok() == Some(dyn_u64)
×
562
                } else if let Some(dyn_f64) = dyn_val.as_f64() {
×
563
                    other_str.parse::<f64>().ok() == Some(dyn_f64)
×
564
                } else {
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
595
                let dyn_bytes = dyn_val.as_bytes();
×
596

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

604
                if dyn_bytes == other_bytes {
×
605
                    CheckResult::Same
×
606
                } else {
607
                    self.record_changed(dyn_peek, other);
×
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
            DynValueKind::DateTime => {
620
                // Compare datetime values by their components
UNCOV
621
                let dyn_dt = dyn_val.as_datetime();
×
622

623
                // Check if other is also a DynamicValue datetime
UNCOV
624
                let other_dt = if let Ok(other_dyn) = other.into_dynamic_value() {
×
UNCOV
625
                    other_dyn.as_datetime()
×
626
                } else {
UNCOV
627
                    None
×
628
                };
629

UNCOV
630
                if dyn_dt == other_dt {
×
UNCOV
631
                    CheckResult::Same
×
632
                } else {
UNCOV
633
                    self.record_changed(dyn_peek, other);
×
UNCOV
634
                    CheckResult::Different
×
635
                }
636
            }
637
        }
638
    }
43✔
639

640
    fn check_dyn_array_against_other(
9✔
641
        &mut self,
9✔
642
        dyn_peek: Peek<'_, '_>,
9✔
643
        dyn_val: facet_reflect::PeekDynamicValue<'_, '_>,
9✔
644
        other: Peek<'_, '_>,
9✔
645
    ) -> CheckResult {
9✔
646
        let dyn_len = dyn_val.array_len().unwrap_or(0);
9✔
647

648
        // Check if other is also a DynamicValue array
649
        if let Ok(other_dyn) = other.into_dynamic_value() {
9✔
650
            if other_dyn.kind() == DynValueKind::Array {
2✔
651
                let other_len = other_dyn.array_len().unwrap_or(0);
2✔
652
                return self
2✔
653
                    .check_two_dyn_arrays(dyn_peek, dyn_val, dyn_len, other, other_dyn, other_len);
2✔
654
            } else {
UNCOV
655
                self.record_changed(dyn_peek, other);
×
UNCOV
656
                return CheckResult::Different;
×
657
            }
658
        }
7✔
659

660
        // Check if other is list-like
661
        if let Ok(other_list) = other.into_list_like() {
7✔
662
            let other_len = other_list.len();
7✔
663
            let mut any_different = false;
7✔
664
            let min_len = dyn_len.min(other_len);
7✔
665

666
            // Compare common elements
667
            for i in 0..min_len {
18✔
668
                self.path.push(PathSegment::Index(i));
18✔
669

670
                if let (Some(dyn_elem), Some(other_elem)) =
18✔
671
                    (dyn_val.array_get(i), other_list.get(i))
18✔
672
                {
673
                    match self.check(dyn_elem, other_elem) {
18✔
674
                        CheckResult::Same => {}
17✔
675
                        CheckResult::Different => any_different = true,
1✔
UNCOV
676
                        opaque @ CheckResult::Opaque { .. } => {
×
UNCOV
677
                            self.path.pop();
×
UNCOV
678
                            return opaque;
×
679
                        }
680
                    }
681
                }
×
682

683
                self.path.pop();
18✔
684
            }
685

686
            // Elements only in dyn array
687
            for i in min_len..dyn_len {
7✔
688
                self.path.push(PathSegment::Index(i));
1✔
689
                if let Some(dyn_elem) = dyn_val.array_get(i) {
1✔
690
                    self.record_only_left(dyn_elem);
1✔
691
                    any_different = true;
1✔
692
                }
1✔
693
                self.path.pop();
1✔
694
            }
695

696
            // Elements only in other list
697
            for i in min_len..other_len {
7✔
UNCOV
698
                self.path.push(PathSegment::Index(i));
×
UNCOV
699
                if let Some(other_elem) = other_list.get(i) {
×
UNCOV
700
                    self.record_only_right(other_elem);
×
UNCOV
701
                    any_different = true;
×
UNCOV
702
                }
×
UNCOV
703
                self.path.pop();
×
704
            }
705

706
            if any_different {
7✔
707
                CheckResult::Different
2✔
708
            } else {
709
                CheckResult::Same
5✔
710
            }
711
        } else {
712
            // Other is not array-like, they're different
UNCOV
713
            self.record_changed(dyn_peek, other);
×
UNCOV
714
            CheckResult::Different
×
715
        }
716
    }
9✔
717

718
    fn check_two_dyn_arrays(
2✔
719
        &mut self,
2✔
720
        _left_peek: Peek<'_, '_>,
2✔
721
        left_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
722
        left_len: usize,
2✔
723
        _right_peek: Peek<'_, '_>,
2✔
724
        right_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
725
        right_len: usize,
2✔
726
    ) -> CheckResult {
2✔
727
        let mut any_different = false;
2✔
728
        let min_len = left_len.min(right_len);
2✔
729

730
        // Compare common elements
731
        for i in 0..min_len {
6✔
732
            self.path.push(PathSegment::Index(i));
6✔
733

734
            if let (Some(left_elem), Some(right_elem)) =
6✔
735
                (left_dyn.array_get(i), right_dyn.array_get(i))
6✔
736
            {
737
                match self.check(left_elem, right_elem) {
6✔
738
                    CheckResult::Same => {}
5✔
739
                    CheckResult::Different => any_different = true,
1✔
UNCOV
740
                    opaque @ CheckResult::Opaque { .. } => {
×
UNCOV
741
                        self.path.pop();
×
UNCOV
742
                        return opaque;
×
743
                    }
744
                }
745
            }
×
746

747
            self.path.pop();
6✔
748
        }
749

750
        // Elements only in left
751
        for i in min_len..left_len {
2✔
UNCOV
752
            self.path.push(PathSegment::Index(i));
×
UNCOV
753
            if let Some(left_elem) = left_dyn.array_get(i) {
×
UNCOV
754
                self.record_only_left(left_elem);
×
UNCOV
755
                any_different = true;
×
UNCOV
756
            }
×
UNCOV
757
            self.path.pop();
×
758
        }
759

760
        // Elements only in right
761
        for i in min_len..right_len {
2✔
UNCOV
762
            self.path.push(PathSegment::Index(i));
×
UNCOV
763
            if let Some(right_elem) = right_dyn.array_get(i) {
×
UNCOV
764
                self.record_only_right(right_elem);
×
UNCOV
765
                any_different = true;
×
UNCOV
766
            }
×
UNCOV
767
            self.path.pop();
×
768
        }
769

770
        if any_different {
2✔
771
            CheckResult::Different
1✔
772
        } else {
773
            CheckResult::Same
1✔
774
        }
775
    }
2✔
776

777
    fn check_dyn_object_against_other(
2✔
778
        &mut self,
2✔
779
        dyn_peek: Peek<'_, '_>,
2✔
780
        dyn_val: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
781
        other: Peek<'_, '_>,
2✔
782
    ) -> CheckResult {
2✔
783
        let dyn_len = dyn_val.object_len().unwrap_or(0);
2✔
784

785
        // Check if other is also a DynamicValue object
786
        if let Ok(other_dyn) = other.into_dynamic_value() {
2✔
787
            if other_dyn.kind() == DynValueKind::Object {
2✔
788
                let other_len = other_dyn.object_len().unwrap_or(0);
2✔
789
                return self.check_two_dyn_objects(
2✔
790
                    dyn_peek, dyn_val, dyn_len, other, other_dyn, other_len,
2✔
791
                );
792
            } else {
793
                self.record_changed(dyn_peek, other);
×
794
                return CheckResult::Different;
×
795
            }
796
        }
×
797

798
        // Check if other is a map
799
        if let Ok(other_map) = other.into_map() {
×
800
            let mut any_different = false;
×
801
            let mut seen_keys = std::collections::HashSet::new();
×
802

803
            // Check all entries in dyn object
804
            for i in 0..dyn_len {
×
805
                if let Some((key, dyn_value)) = dyn_val.object_get_entry(i) {
×
UNCOV
806
                    seen_keys.insert(key.to_owned());
×
UNCOV
807
                    self.path.push(PathSegment::Key(key.to_owned()));
×
808

809
                    // Try to find key in map - need to compare by formatted key
810
                    let mut found = false;
×
811
                    for (map_key, map_value) in other_map.iter() {
×
UNCOV
812
                        if Self::format_value(map_key) == format!("{key:?}") {
×
813
                            found = true;
×
814
                            match self.check(dyn_value, map_value) {
×
UNCOV
815
                                CheckResult::Same => {}
×
UNCOV
816
                                CheckResult::Different => any_different = true,
×
UNCOV
817
                                opaque @ CheckResult::Opaque { .. } => {
×
818
                                    self.path.pop();
×
819
                                    return opaque;
×
820
                                }
821
                            }
822
                            break;
×
823
                        }
×
824
                    }
825

826
                    if !found {
×
827
                        self.record_only_left(dyn_value);
×
UNCOV
828
                        any_different = true;
×
UNCOV
829
                    }
×
830

831
                    self.path.pop();
×
UNCOV
832
                }
×
833
            }
834

835
            // Check keys only in map
UNCOV
836
            for (map_key, map_value) in other_map.iter() {
×
837
                let key_str = Self::format_value(map_key);
×
838
                // Remove quotes for comparison
UNCOV
839
                let key_unquoted = key_str.trim_matches('"');
×
UNCOV
840
                if !seen_keys.contains(key_unquoted) {
×
841
                    self.path.push(PathSegment::Key(key_unquoted.to_owned()));
×
842
                    self.record_only_right(map_value);
×
843
                    any_different = true;
×
844
                    self.path.pop();
×
UNCOV
845
                }
×
846
            }
847

848
            if any_different {
×
849
                CheckResult::Different
×
850
            } else {
851
                CheckResult::Same
×
852
            }
UNCOV
853
        } else if let Ok(other_struct) = other.into_struct() {
×
854
            // Compare DynamicValue object against struct fields
855
            let mut any_different = false;
×
856
            let mut seen_fields = std::collections::HashSet::new();
×
857

858
            // Check all entries in dyn object against struct fields
UNCOV
859
            for i in 0..dyn_len {
×
860
                if let Some((key, dyn_value)) = dyn_val.object_get_entry(i) {
×
861
                    seen_fields.insert(key.to_owned());
×
UNCOV
862
                    self.path.push(PathSegment::Key(key.to_owned()));
×
863

UNCOV
864
                    if let Ok(struct_value) = other_struct.field_by_name(key) {
×
865
                        match self.check(dyn_value, struct_value) {
×
866
                            CheckResult::Same => {}
×
867
                            CheckResult::Different => any_different = true,
×
868
                            opaque @ CheckResult::Opaque { .. } => {
×
869
                                self.path.pop();
×
870
                                return opaque;
×
871
                            }
872
                        }
UNCOV
873
                    } else {
×
874
                        self.record_only_left(dyn_value);
×
875
                        any_different = true;
×
UNCOV
876
                    }
×
877

UNCOV
878
                    self.path.pop();
×
UNCOV
879
                }
×
880
            }
881

882
            // Check struct fields not in dyn object
UNCOV
883
            for (field, struct_value) in other_struct.fields() {
×
UNCOV
884
                if !seen_fields.contains(field.name) {
×
UNCOV
885
                    self.path.push(PathSegment::Field(field.name));
×
UNCOV
886
                    self.record_only_right(struct_value);
×
UNCOV
887
                    any_different = true;
×
UNCOV
888
                    self.path.pop();
×
UNCOV
889
                }
×
890
            }
891

UNCOV
892
            if any_different {
×
UNCOV
893
                CheckResult::Different
×
894
            } else {
UNCOV
895
                CheckResult::Same
×
896
            }
897
        } else {
898
            // Other is not object-like, they're different
UNCOV
899
            self.record_changed(dyn_peek, other);
×
UNCOV
900
            CheckResult::Different
×
901
        }
902
    }
2✔
903

904
    fn check_two_dyn_objects(
2✔
905
        &mut self,
2✔
906
        _left_peek: Peek<'_, '_>,
2✔
907
        left_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
908
        left_len: usize,
2✔
909
        _right_peek: Peek<'_, '_>,
2✔
910
        right_dyn: facet_reflect::PeekDynamicValue<'_, '_>,
2✔
911
        right_len: usize,
2✔
912
    ) -> CheckResult {
2✔
913
        let mut any_different = false;
2✔
914
        let mut seen_keys = std::collections::HashSet::new();
2✔
915

916
        // Check all entries in left
917
        for i in 0..left_len {
4✔
918
            if let Some((key, left_value)) = left_dyn.object_get_entry(i) {
4✔
919
                seen_keys.insert(key.to_owned());
4✔
920
                self.path.push(PathSegment::Key(key.to_owned()));
4✔
921

922
                if let Some(right_value) = right_dyn.object_get(key) {
4✔
923
                    match self.check(left_value, right_value) {
4✔
924
                        CheckResult::Same => {}
3✔
925
                        CheckResult::Different => any_different = true,
1✔
926
                        opaque @ CheckResult::Opaque { .. } => {
×
927
                            self.path.pop();
×
928
                            return opaque;
×
929
                        }
930
                    }
931
                } else {
×
UNCOV
932
                    self.record_only_left(left_value);
×
UNCOV
933
                    any_different = true;
×
UNCOV
934
                }
×
935

936
                self.path.pop();
4✔
UNCOV
937
            }
×
938
        }
939

940
        // Check entries only in right
941
        for i in 0..right_len {
4✔
942
            if let Some((key, right_value)) = right_dyn.object_get_entry(i) {
4✔
943
                if !seen_keys.contains(key) {
4✔
UNCOV
944
                    self.path.push(PathSegment::Key(key.to_owned()));
×
UNCOV
945
                    self.record_only_right(right_value);
×
UNCOV
946
                    any_different = true;
×
UNCOV
947
                    self.path.pop();
×
948
                }
4✔
UNCOV
949
            }
×
950
        }
951

952
        if any_different {
2✔
953
            CheckResult::Different
1✔
954
        } else {
955
            CheckResult::Same
1✔
956
        }
957
    }
2✔
958
}
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