Skip to main content

facet_reflect/
resolution.rs

1//! Resolution types for representing resolved type configurations.
2//!
3//! A [`Resolution`] represents one possible "shape" a type can take after
4//! all enum variants in flatten paths have been selected. It contains all
5//! the fields that exist in that configuration, along with their paths.
6
7extern crate alloc;
8
9use alloc::borrow::Cow;
10use alloc::collections::BTreeMap;
11use alloc::collections::BTreeSet;
12use alloc::format;
13use alloc::string::String;
14use alloc::string::ToString;
15use alloc::vec::Vec;
16use core::fmt;
17
18use facet_core::{Field, Shape};
19
20/// Category of a field for format-aware field lookup.
21///
22/// For flat formats (JSON, TOML, YAML), all fields are `Flat`.
23/// For DOM formats (XML, HTML), fields are categorized by their role
24/// in the tree structure.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Default)]
26pub enum FieldCategory {
27    /// Regular field in flat formats (JSON, TOML, YAML, etc.)
28    /// All fields are treated as simple key-value pairs.
29    #[default]
30    Flat,
31    /// Field is an attribute (`#[facet(attribute)]`, `xml::attribute`, `html::attribute`)
32    Attribute,
33    /// Field is a child element (default for DOM structs, or explicit `xml::element`)
34    Element,
35    /// Field captures text content (`xml::text`, `html::text`)
36    Text,
37    /// Field captures the tag name (`xml::tag`, `html::tag`)
38    Tag,
39    /// Field captures all unmatched children (`xml::elements`)
40    Elements,
41}
42
43impl FieldCategory {
44    /// Determine the DOM category of a field based on its attributes.
45    ///
46    /// Returns `None` for flattened fields (they don't have a single category).
47    /// For non-DOM contexts, use `FieldCategory::Flat` directly.
48    pub fn from_field_dom(field: &Field) -> Option<Self> {
49        if field.is_flattened() {
50            // Flattened fields don't have a category - their children do
51            return None;
52        }
53        if field.is_attribute() {
54            Some(FieldCategory::Attribute)
55        } else if field.is_text() {
56            Some(FieldCategory::Text)
57        } else if field.is_tag() {
58            Some(FieldCategory::Tag)
59        } else if field.is_elements() {
60            Some(FieldCategory::Elements)
61        } else {
62            // Default: child element
63            Some(FieldCategory::Element)
64        }
65    }
66}
67
68/// A key for field lookup in schemas and solvers.
69///
70/// For flat formats (JSON, TOML), keys are just field names.
71/// For DOM formats (XML, HTML), keys include a category (attribute, element, text).
72#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
73pub enum FieldKey<'a> {
74    /// Flat format key - just a name (for JSON, TOML, YAML, etc.)
75    Flat(Cow<'a, str>),
76    /// DOM format key - category + name (for XML, HTML)
77    Dom(FieldCategory, Cow<'a, str>),
78}
79
80impl<'a> FieldKey<'a> {
81    /// Create a flat key from a string.
82    pub fn flat(name: impl Into<Cow<'a, str>>) -> Self {
83        FieldKey::Flat(name.into())
84    }
85
86    /// Create a DOM attribute key.
87    pub fn attribute(name: impl Into<Cow<'a, str>>) -> Self {
88        FieldKey::Dom(FieldCategory::Attribute, name.into())
89    }
90
91    /// Create a DOM element key.
92    pub fn element(name: impl Into<Cow<'a, str>>) -> Self {
93        FieldKey::Dom(FieldCategory::Element, name.into())
94    }
95
96    /// Create a DOM text key.
97    pub fn text() -> Self {
98        FieldKey::Dom(FieldCategory::Text, Cow::Borrowed(""))
99    }
100
101    /// Create a DOM tag key.
102    pub fn tag() -> Self {
103        FieldKey::Dom(FieldCategory::Tag, Cow::Borrowed(""))
104    }
105
106    /// Create a DOM elements key (catch-all for unmatched children).
107    pub fn elements() -> Self {
108        FieldKey::Dom(FieldCategory::Elements, Cow::Borrowed(""))
109    }
110
111    /// Get the name portion of the key.
112    pub fn name(&self) -> &str {
113        match self {
114            FieldKey::Flat(name) => name.as_ref(),
115            FieldKey::Dom(_, name) => name.as_ref(),
116        }
117    }
118
119    /// Get the category if this is a DOM key.
120    pub fn category(&self) -> Option<FieldCategory> {
121        match self {
122            FieldKey::Flat(_) => None,
123            FieldKey::Dom(cat, _) => Some(*cat),
124        }
125    }
126
127    /// Convert to an owned version with 'static lifetime.
128    pub fn into_owned(self) -> FieldKey<'static> {
129        match self {
130            FieldKey::Flat(name) => FieldKey::Flat(Cow::Owned(name.into_owned())),
131            FieldKey::Dom(cat, name) => FieldKey::Dom(cat, Cow::Owned(name.into_owned())),
132        }
133    }
134}
135
136// Allow &str to convert to flat key
137impl<'a> From<&'a str> for FieldKey<'a> {
138    fn from(s: &'a str) -> Self {
139        FieldKey::Flat(Cow::Borrowed(s))
140    }
141}
142
143impl From<String> for FieldKey<'static> {
144    fn from(s: String) -> Self {
145        FieldKey::Flat(Cow::Owned(s))
146    }
147}
148
149impl<'a> From<&'a String> for FieldKey<'a> {
150    fn from(s: &'a String) -> Self {
151        FieldKey::Flat(Cow::Borrowed(s.as_str()))
152    }
153}
154
155impl<'a> From<Cow<'a, str>> for FieldKey<'a> {
156    fn from(s: Cow<'a, str>) -> Self {
157        FieldKey::Flat(s)
158    }
159}
160
161impl fmt::Display for FieldKey<'_> {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        match self {
164            FieldKey::Flat(name) => write!(f, "{}", name),
165            FieldKey::Dom(cat, name) => write!(f, "{:?}:{}", cat, name),
166        }
167    }
168}
169
170/// A path of serialized key names for probing (flat formats).
171/// Unlike FieldPath which tracks the internal type structure (including variant selections),
172/// KeyPath only tracks the keys as they appear in the serialized format.
173pub type KeyPath = Vec<&'static str>;
174
175/// A path of serialized keys for probing (DOM-aware).
176/// Each key includes the category (attribute vs element) for DOM formats.
177pub type DomKeyPath = Vec<FieldKey<'static>>;
178
179/// A segment in a field path.
180#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
181pub enum PathSegment {
182    /// A regular struct field
183    Field(&'static str),
184    /// An enum variant selection (field_name, variant_name)
185    Variant(&'static str, &'static str),
186}
187
188/// A path through the type tree to a field.
189#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
190pub struct FieldPath {
191    segments: Vec<PathSegment>,
192}
193
194impl fmt::Debug for FieldPath {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        write!(f, "FieldPath(")?;
197        for (i, seg) in self.segments.iter().enumerate() {
198            if i > 0 {
199                write!(f, ".")?;
200            }
201            match seg {
202                PathSegment::Field(name) => write!(f, "{name}")?,
203                PathSegment::Variant(field, variant) => write!(f, "{field}::{variant}")?,
204            }
205        }
206        write!(f, ")")
207    }
208}
209
210impl fmt::Display for FieldPath {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        let mut first = true;
213        for seg in &self.segments {
214            match seg {
215                PathSegment::Field(name) => {
216                    if !first {
217                        write!(f, ".")?;
218                    }
219                    write!(f, "{name}")?;
220                    first = false;
221                }
222                PathSegment::Variant(_, _) => {
223                    // Skip variant segments in display path - they're internal
224                }
225            }
226        }
227        Ok(())
228    }
229}
230
231impl FieldPath {
232    /// Create an empty path (root level).
233    pub const fn empty() -> Self {
234        Self {
235            segments: Vec::new(),
236        }
237    }
238
239    /// Get the depth of this path.
240    pub const fn depth(&self) -> usize {
241        self.segments.len()
242    }
243
244    /// Push a field segment onto the path.
245    pub fn push_field(&self, name: &'static str) -> Self {
246        let mut new = self.clone();
247        new.segments.push(PathSegment::Field(name));
248        new
249    }
250
251    /// Push a variant segment onto the path.
252    pub fn push_variant(&self, field_name: &'static str, variant_name: &'static str) -> Self {
253        let mut new = self.clone();
254        new.segments
255            .push(PathSegment::Variant(field_name, variant_name));
256        new
257    }
258
259    /// Get the parent path (all segments except the last).
260    pub fn parent(&self) -> Self {
261        let mut new = self.clone();
262        new.segments.pop();
263        new
264    }
265
266    /// Get the segments of this path.
267    pub fn segments(&self) -> &[PathSegment] {
268        &self.segments
269    }
270
271    /// Get the last segment, if any.
272    pub fn last(&self) -> Option<&PathSegment> {
273        self.segments.last()
274    }
275}
276
277/// Records that a specific enum field has a specific variant selected.
278#[derive(Debug, Clone)]
279pub struct VariantSelection {
280    /// Path to the enum field from root
281    pub path: FieldPath,
282    /// Name of the enum type (e.g., "MessagePayload")
283    pub enum_name: &'static str,
284    /// Name of the selected variant (e.g., "Text")
285    pub variant_name: &'static str,
286}
287
288/// Information about a single field in a resolution.
289#[derive(Debug, Clone)]
290pub struct FieldInfo {
291    /// The name as it appears in the serialized format
292    pub serialized_name: &'static str,
293
294    /// Full path from root to this field
295    pub path: FieldPath,
296
297    /// Whether this field is required (not Option, no default)
298    pub required: bool,
299
300    /// The shape of this field's value
301    pub value_shape: &'static Shape,
302
303    /// The original field definition (for accessing flags, attributes, etc.)
304    pub field: &'static Field,
305
306    /// Category of this field (Flat for JSON/TOML/YAML, or Attribute/Element/etc. for DOM).
307    pub category: FieldCategory,
308}
309
310impl PartialEq for FieldInfo {
311    fn eq(&self, other: &Self) -> bool {
312        self.serialized_name == other.serialized_name
313            && self.path == other.path
314            && self.required == other.required
315            && core::ptr::eq(self.value_shape, other.value_shape)
316            && core::ptr::eq(self.field, other.field)
317            && self.category == other.category
318    }
319}
320
321impl FieldInfo {
322    /// Get the key for this field, used for map lookups.
323    pub fn key(&self) -> FieldKey<'static> {
324        match self.category {
325            FieldCategory::Flat => FieldKey::Flat(Cow::Borrowed(self.serialized_name)),
326            cat => FieldKey::Dom(cat, Cow::Borrowed(self.serialized_name)),
327        }
328    }
329}
330
331impl Eq for FieldInfo {}
332
333/// Result of matching input fields against a resolution.
334#[derive(Debug)]
335pub enum MatchResult {
336    /// All required fields present, all fields known
337    Exact,
338    /// All required fields present, some optional fields missing
339    WithOptionalMissing(Vec<&'static str>),
340    /// Does not match
341    NoMatch {
342        /// Required fields that are missing
343        missing_required: Vec<&'static str>,
344        /// Fields that are not known in this resolution
345        unknown: Vec<String>,
346    },
347}
348
349/// One possible "shape" the flattened type could take.
350///
351/// Represents a specific choice of variants for all enums in the flatten tree.
352/// This is the "resolution" of all ambiguity in the type — all enum variants
353/// have been selected, all fields are known.
354#[derive(Debug, Clone)]
355pub struct Resolution {
356    /// For each enum in the flatten path, which variant is selected.
357    /// The key is the path to the enum field, value is the variant.
358    variant_selections: Vec<VariantSelection>,
359
360    /// All fields in this configuration, keyed by (category, name).
361    /// For flat formats, category is None. For DOM formats, category distinguishes
362    /// attributes from elements with the same name.
363    fields: BTreeMap<FieldKey<'static>, FieldInfo>,
364
365    /// Set of required field names (for quick matching)
366    required_field_names: BTreeSet<&'static str>,
367
368    /// All known key paths at all depths (for depth-aware probing, flat format).
369    /// Each path is a sequence of serialized key names from root.
370    /// E.g., for `{payload: {content: "hi"}}`, contains `["payload"]` and `["payload", "content"]`.
371    known_paths: BTreeSet<KeyPath>,
372
373    /// All known key paths at all depths (for depth-aware probing, DOM format).
374    /// Each path includes category information for each key.
375    dom_known_paths: BTreeSet<DomKeyPath>,
376
377    /// Catch-all map fields for capturing unknown keys, keyed by category.
378    /// For flat formats, `FieldCategory::Flat` captures all unknown keys.
379    /// For DOM formats, can have separate catch-alls for `Attribute` vs `Element`.
380    catch_all_maps: BTreeMap<FieldCategory, FieldInfo>,
381}
382
383/// Error when building a resolution.
384#[derive(Debug, Clone)]
385pub struct DuplicateFieldError {
386    /// The duplicate field name
387    pub field_name: &'static str,
388    /// The first path where this field was found
389    pub first_path: FieldPath,
390    /// The second path where this field was found
391    pub second_path: FieldPath,
392}
393
394impl fmt::Display for DuplicateFieldError {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        write!(
397            f,
398            "duplicate field '{}': found at {} and {}",
399            self.field_name, self.first_path, self.second_path
400        )
401    }
402}
403
404impl Resolution {
405    /// Create a new empty resolution.
406    pub const fn new() -> Self {
407        Self {
408            variant_selections: Vec::new(),
409            fields: BTreeMap::new(),
410            required_field_names: BTreeSet::new(),
411            known_paths: BTreeSet::new(),
412            dom_known_paths: BTreeSet::new(),
413            catch_all_maps: BTreeMap::new(),
414        }
415    }
416
417    /// Set a catch-all map field for capturing unknown keys of a given category.
418    pub fn set_catch_all_map(&mut self, category: FieldCategory, info: FieldInfo) {
419        self.catch_all_maps.insert(category, info);
420    }
421
422    /// Get the catch-all map field for a given category, if any.
423    pub fn catch_all_map(&self, category: FieldCategory) -> Option<&FieldInfo> {
424        self.catch_all_maps.get(&category)
425    }
426
427    /// Get all catch-all maps.
428    pub fn catch_all_maps(&self) -> &BTreeMap<FieldCategory, FieldInfo> {
429        &self.catch_all_maps
430    }
431
432    /// Add a key path (for depth-aware probing, flat format).
433    pub fn add_key_path(&mut self, path: KeyPath) {
434        self.known_paths.insert(path);
435    }
436
437    /// Add a DOM key path (for depth-aware probing, DOM format).
438    pub fn add_dom_key_path(&mut self, path: DomKeyPath) {
439        self.dom_known_paths.insert(path);
440    }
441
442    /// Add a field to this resolution.
443    ///
444    /// Returns an error if a field with the same key already exists
445    /// but comes from a different source (different path). This catches duplicate
446    /// field name conflicts between parent structs and flattened fields.
447    pub fn add_field(&mut self, info: FieldInfo) -> Result<(), DuplicateFieldError> {
448        let key = info.key();
449        if let Some(existing) = self.fields.get(&key)
450            && existing.path != info.path
451        {
452            return Err(DuplicateFieldError {
453                field_name: info.serialized_name,
454                first_path: existing.path.clone(),
455                second_path: info.path,
456            });
457        }
458        if info.required {
459            self.required_field_names.insert(info.serialized_name);
460        }
461        self.fields.insert(key, info);
462        Ok(())
463    }
464
465    /// Add a variant selection to this resolution.
466    pub fn add_variant_selection(
467        &mut self,
468        path: FieldPath,
469        enum_name: &'static str,
470        variant_name: &'static str,
471    ) {
472        self.variant_selections.push(VariantSelection {
473            path,
474            enum_name,
475            variant_name,
476        });
477    }
478
479    /// Merge another resolution into this one.
480    ///
481    /// Returns an error if a field with the same serialized name already exists
482    /// but comes from a different source (different path). This catches duplicate
483    /// field name conflicts between parent structs and flattened fields.
484    pub fn merge(&mut self, other: &Resolution) -> Result<(), DuplicateFieldError> {
485        for (key, info) in &other.fields {
486            if let Some(existing) = self.fields.get(key)
487                && existing.path != info.path
488            {
489                return Err(DuplicateFieldError {
490                    field_name: info.serialized_name,
491                    first_path: existing.path.clone(),
492                    second_path: info.path.clone(),
493                });
494            }
495            self.fields.insert(key.clone(), info.clone());
496            if info.required {
497                self.required_field_names.insert(info.serialized_name);
498            }
499        }
500        for vs in &other.variant_selections {
501            self.variant_selections.push(vs.clone());
502        }
503        for path in &other.known_paths {
504            self.known_paths.insert(path.clone());
505        }
506        for path in &other.dom_known_paths {
507            self.dom_known_paths.insert(path.clone());
508        }
509        // Merge catch-all maps (other's take precedence for same category)
510        for (cat, info) in &other.catch_all_maps {
511            self.catch_all_maps.insert(*cat, info.clone());
512        }
513        Ok(())
514    }
515
516    /// Mark all fields as optional (required = false).
517    /// Used when a flattened field is wrapped in `Option<T>`.
518    pub fn mark_all_optional(&mut self) {
519        self.required_field_names.clear();
520        for info in self.fields.values_mut() {
521            info.required = false;
522        }
523    }
524
525    /// Check if this resolution matches the input fields.
526    pub fn matches(&self, input_fields: &BTreeSet<Cow<'_, str>>) -> MatchResult {
527        let mut missing_required = Vec::new();
528        let mut missing_optional = Vec::new();
529
530        for info in self.fields.values() {
531            if !input_fields
532                .iter()
533                .any(|k| k.as_ref() == info.serialized_name)
534            {
535                if info.required {
536                    missing_required.push(info.serialized_name);
537                } else {
538                    missing_optional.push(info.serialized_name);
539                }
540            }
541        }
542
543        // Check for unknown fields
544        let unknown: Vec<String> = input_fields
545            .iter()
546            .filter(|f| {
547                !self
548                    .fields
549                    .values()
550                    .any(|info| info.serialized_name == f.as_ref())
551            })
552            .map(|s| s.to_string())
553            .collect();
554
555        if !missing_required.is_empty() || !unknown.is_empty() {
556            MatchResult::NoMatch {
557                missing_required,
558                unknown,
559            }
560        } else if missing_optional.is_empty() {
561            MatchResult::Exact
562        } else {
563            MatchResult::WithOptionalMissing(missing_optional)
564        }
565    }
566
567    /// Get a human-readable description of this resolution.
568    ///
569    /// Returns something like `MessagePayload::Text` or `Auth::Token + Transport::Tcp`
570    /// for resolutions with multiple variant selections.
571    pub fn describe(&self) -> String {
572        if self.variant_selections.is_empty() {
573            String::from("(no variants)")
574        } else {
575            let parts: Vec<_> = self
576                .variant_selections
577                .iter()
578                .map(|vs| format!("{}::{}", vs.enum_name, vs.variant_name))
579                .collect();
580            parts.join(" + ")
581        }
582    }
583
584    /// Get the fields in deserialization order (deepest first).
585    pub fn deserialization_order(&self) -> Vec<&FieldInfo> {
586        let mut fields: Vec<_> = self.fields.values().collect();
587        fields.sort_by(|a, b| {
588            // Deeper paths first
589            b.path
590                .depth()
591                .cmp(&a.path.depth())
592                // Then lexicographic for determinism
593                .then_with(|| a.path.cmp(&b.path))
594        });
595        fields
596    }
597
598    /// Get a field by key.
599    ///
600    /// For runtime keys, use `field_by_key()` which accepts any lifetime.
601    pub fn field(&self, key: &FieldKey<'static>) -> Option<&FieldInfo> {
602        self.fields.get(key)
603    }
604
605    /// Get a field by key with any lifetime.
606    ///
607    /// This is less efficient than `field()` because it searches linearly,
608    /// but works with runtime-constructed keys.
609    pub fn field_by_key(&self, key: &FieldKey<'_>) -> Option<&FieldInfo> {
610        self.fields.iter().find_map(|(k, v)| {
611            // Compare structurally regardless of lifetime
612            let matches = match (k, key) {
613                (FieldKey::Flat(a), FieldKey::Flat(b)) => a.as_ref() == b.as_ref(),
614                (FieldKey::Dom(cat_a, a), FieldKey::Dom(cat_b, b)) => {
615                    cat_a == cat_b && a.as_ref() == b.as_ref()
616                }
617                _ => false,
618            };
619            if matches { Some(v) } else { None }
620        })
621    }
622
623    /// Get a field by name (flat format lookup).
624    /// For DOM format, use `field()` with a `FieldKey` instead.
625    pub fn field_by_name(&self, name: &str) -> Option<&FieldInfo> {
626        // Search by serialized name - works for both flat and DOM keys
627        self.fields.values().find(|f| f.serialized_name == name)
628    }
629
630    /// Get all fields.
631    pub const fn fields(&self) -> &BTreeMap<FieldKey<'static>, FieldInfo> {
632        &self.fields
633    }
634
635    /// Get the set of required field names.
636    pub const fn required_field_names(&self) -> &BTreeSet<&'static str> {
637        &self.required_field_names
638    }
639
640    /// Get optional fields that were NOT provided in the input.
641    ///
642    /// This is useful for deserializers that need to initialize missing
643    /// optional fields to `None` or their default value.
644    pub fn missing_optional_fields<'a>(
645        &'a self,
646        seen_keys: &'a BTreeSet<Cow<'_, str>>,
647    ) -> impl Iterator<Item = &'a FieldInfo> {
648        self.fields.values().filter(move |info| {
649            !info.required && !seen_keys.iter().any(|k| k.as_ref() == info.serialized_name)
650        })
651    }
652
653    /// Get variant selections.
654    pub fn variant_selections(&self) -> &[VariantSelection] {
655        &self.variant_selections
656    }
657
658    /// Get all child fields (fields with the CHILD flag).
659    ///
660    /// This is useful for formats like XML where child elements need to be
661    /// processed separately from attributes.
662    pub fn child_fields(&self) -> impl Iterator<Item = &FieldInfo> {
663        self.fields.values().filter(|f| f.field.is_child())
664    }
665
666    /// Get all property fields (fields without the child attribute).
667    ///
668    /// This is useful for formats like XML where attributes are processed
669    /// separately from child elements.
670    pub fn property_fields(&self) -> impl Iterator<Item = &FieldInfo> {
671        self.fields.values().filter(|f| !f.field.is_child())
672    }
673
674    /// Get all known key paths (for depth-aware probing).
675    pub const fn known_paths(&self) -> &BTreeSet<KeyPath> {
676        &self.known_paths
677    }
678
679    /// Check if this resolution has a specific key path (flat format).
680    pub fn has_key_path(&self, path: &[&str]) -> bool {
681        self.known_paths.iter().any(|known| {
682            known.len() == path.len() && known.iter().zip(path.iter()).all(|(a, b)| *a == *b)
683        })
684    }
685
686    /// Check if this resolution has a specific DOM key path.
687    pub fn has_dom_key_path(&self, path: &[FieldKey<'_>]) -> bool {
688        self.dom_known_paths.iter().any(|known| {
689            known.len() == path.len()
690                && known.iter().zip(path.iter()).all(|(a, b)| {
691                    // Compare structurally regardless of lifetime
692                    match (a, b) {
693                        (FieldKey::Flat(sa), FieldKey::Flat(sb)) => sa.as_ref() == sb.as_ref(),
694                        (FieldKey::Dom(ca, sa), FieldKey::Dom(cb, sb)) => {
695                            ca == cb && sa.as_ref() == sb.as_ref()
696                        }
697                        _ => false,
698                    }
699                })
700        })
701    }
702
703    /// Get all known DOM key paths (for depth-aware probing).
704    pub const fn dom_known_paths(&self) -> &BTreeSet<DomKeyPath> {
705        &self.dom_known_paths
706    }
707}
708
709impl Default for Resolution {
710    fn default() -> Self {
711        Self::new()
712    }
713}