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