Skip to main content

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