facet_format/
visitor.rs

1extern crate alloc;
2
3use alloc::{collections::BTreeSet, vec::Vec};
4
5use facet_reflect::FieldInfo;
6use facet_solver::Resolution;
7
8/// Result of checking a serialized field against the active resolution.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum FieldMatch<'a> {
11    /// Field exists in the schema and this is the first time we've seen it.
12    KnownFirst(&'a FieldInfo),
13    /// Field exists but was already provided earlier (duplicate).
14    Duplicate(&'a FieldInfo),
15    /// Field name is not part of this resolution.
16    Unknown,
17}
18
19/// Tracks which fields have been seen while deserializing a struct.
20///
21/// This is the first building block of the shared visitor requested in issue
22/// #1127 – it centralizes duplicate detection, unknown-field tracking, and
23/// default synthesis hooks so individual format crates don't have to reimplement
24/// the bookkeeping.
25pub struct StructFieldTracker<'a> {
26    resolution: &'a Resolution,
27    seen: BTreeSet<&'static str>,
28}
29
30impl<'a> StructFieldTracker<'a> {
31    /// Create a tracker for the given resolution.
32    pub fn new(resolution: &'a Resolution) -> Self {
33        Self {
34            resolution,
35            seen: BTreeSet::new(),
36        }
37    }
38
39    /// Record an incoming serialized field and classify it.
40    pub fn record(&mut self, name: &str) -> FieldMatch<'a> {
41        match self.resolution.field(name) {
42            Some(info) => {
43                if self.seen.insert(info.serialized_name) {
44                    FieldMatch::KnownFirst(info)
45                } else {
46                    FieldMatch::Duplicate(info)
47                }
48            }
49            None => FieldMatch::Unknown,
50        }
51    }
52
53    /// Return serialized names for required fields that have not been seen yet.
54    pub fn missing_required(&self) -> Vec<&'static str> {
55        self.resolution
56            .required_field_names()
57            .iter()
58            .copied()
59            .filter(|name| !self.seen.contains(name))
60            .collect()
61    }
62
63    /// Iterate over optional fields that are still unset (useful for defaults).
64    pub fn missing_optional(&self) -> impl Iterator<Item = &'a FieldInfo> {
65        self.resolution
66            .fields()
67            .values()
68            .filter(move |info| !info.required && !self.seen.contains(&info.serialized_name))
69    }
70}