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}