Skip to main content

facet_solver/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2//!
3//! [![Coverage Status](https://coveralls.io/repos/github/facet-rs/facet-solver/badge.svg?branch=main)](https://coveralls.io/github/facet-rs/facet?branch=main)
4//! [![crates.io](https://img.shields.io/crates/v/facet-solver.svg)](https://crates.io/crates/facet-solver)
5//! [![documentation](https://docs.rs/facet-solver/badge.svg)](https://docs.rs/facet-solver)
6//! [![MIT/Apache-2.0 licensed](https://img.shields.io/crates/l/facet-solver.svg)](./LICENSE)
7//! [![Discord](https://img.shields.io/discord/1379550208551026748?logo=discord&label=discord)](https://discord.gg/JhD7CwCJ8F)
8//!
9//!
10//! Helps facet deserializers implement `#[facet(flatten)]` and `#[facet(untagged)]`
11//! correctly, efficiently, and with useful diagnostics.
12//!
13//! ## The Problem
14//!
15//! When deserializing a type with a flattened enum:
16//!
17//! ```rust
18//! # use facet::Facet;
19//! #[derive(Facet)]
20//! struct TextMessage { content: String }
21//!
22//! #[derive(Facet)]
23//! struct BinaryMessage { data: Vec<u8>, encoding: String }
24//!
25//! #[derive(Facet)]
26//! #[repr(u8)]
27//! enum MessagePayload {
28//!     Text(TextMessage),
29//!     Binary(BinaryMessage),
30//! }
31//!
32//! #[derive(Facet)]
33//! struct Message {
34//!     id: String,
35//!     #[facet(flatten)]
36//!     payload: MessagePayload,
37//! }
38//! ```
39//!
40//! ...we don't know which variant to use until we've seen the fields:
41//!
42//! ```json
43//! {"id": "msg-1", "content": "hello"}        // Text
44//! {"id": "msg-2", "data": [1,2,3], "encoding": "raw"}  // Binary
45//! ```
46//!
47//! The solver answers: "which variant has a `content` field?" or "which variant
48//! has both `data` and `encoding`?"
49//!
50//! ## How It Works
51//!
52//! The solver pre-computes all valid field combinations ("configurations") for a type,
53//! then uses an inverted index to quickly find which configuration(s) match the
54//! fields you've seen.
55//!
56//! ```rust
57//! use facet_solver::{KeyResult, Schema, Solver};
58//! # use facet::Facet;
59//! # #[derive(Facet)]
60//! # struct TextMessage { content: String }
61//! # #[derive(Facet)]
62//! # struct BinaryMessage { data: Vec<u8>, encoding: String }
63//! # #[derive(Facet)]
64//! # #[repr(u8)]
65//! # enum MessagePayload { Text(TextMessage), Binary(BinaryMessage) }
66//! # #[derive(Facet)]
67//! # struct Message { id: String, #[facet(flatten)] payload: MessagePayload }
68//!
69//! // Build schema once (can be cached)
70//! let schema = Schema::build(Message::SHAPE).unwrap();
71//!
72//! // Create a solver for this deserialization
73//! let mut solver = Solver::new(&schema);
74//!
75//! // As you see fields, report them:
76//! match solver.see_key("id") {
77//!     KeyResult::Unambiguous { .. } => { /* both configs have "id" */ }
78//!     _ => {}
79//! }
80//!
81//! match solver.see_key("content") {
82//!     KeyResult::Solved(config) => {
83//!         // Only Text has "content" - we now know the variant!
84//!         assert!(config.resolution().has_key_path(&["content"]));
85//!     }
86//!     _ => {}
87//! }
88//! ```
89//!
90//! ### Nested Disambiguation
91//!
92//! When top-level keys don't distinguish variants, the solver can look deeper:
93//!
94//! ```rust
95//! # use facet::Facet;
96//! #[derive(Facet)]
97//! struct TextPayload { content: String }
98//!
99//! #[derive(Facet)]
100//! struct BinaryPayload { bytes: Vec<u8> }
101//!
102//! #[derive(Facet)]
103//! #[repr(u8)]
104//! enum Payload {
105//!     Text { inner: TextPayload },
106//!     Binary { inner: BinaryPayload },
107//! }
108//!
109//! #[derive(Facet)]
110//! struct Wrapper {
111//!     #[facet(flatten)]
112//!     payload: Payload,
113//! }
114//! ```
115//!
116//! Both variants have an `inner` field. But `inner.content` only exists in `Text`,
117//! and `inner.bytes` only exists in `Binary`. The `ProbingSolver` handles this:
118//!
119//! ```rust
120//! use facet_solver::{ProbingSolver, ProbeResult, Schema};
121//! # use facet::Facet;
122//! # #[derive(Facet)]
123//! # struct TextPayload { content: String }
124//! # #[derive(Facet)]
125//! # struct BinaryPayload { bytes: Vec<u8> }
126//! # #[derive(Facet)]
127//! # #[repr(u8)]
128//! # enum Payload { Text { inner: TextPayload }, Binary { inner: BinaryPayload } }
129//! # #[derive(Facet)]
130//! # struct Wrapper { #[facet(flatten)] payload: Payload }
131//!
132//! let schema = Schema::build(Wrapper::SHAPE).unwrap();
133//! let mut solver = ProbingSolver::new(&schema);
134//!
135//! // Top-level "inner" doesn't disambiguate
136//! assert!(matches!(solver.probe_key(&[], "inner"), ProbeResult::KeepGoing));
137//!
138//! // But "inner.content" does!
139//! match solver.probe_key(&["inner"], "content") {
140//!     ProbeResult::Solved(config) => {
141//!         assert!(config.has_key_path(&["inner", "content"]));
142//!     }
143//!     _ => panic!("should have solved"),
144//! }
145//! ```
146//!
147//! ### Lazy Type Disambiguation
148//!
149//! Sometimes variants have **identical keys** but different value types. The solver handles
150//! this without buffering—it lets you probe "can this value fit type X?" lazily:
151//!
152//! ```rust
153//! # use facet::Facet;
154//! #[derive(Facet)]
155//! struct SmallPayload { value: u8 }
156//!
157//! #[derive(Facet)]
158//! struct LargePayload { value: u16 }
159//!
160//! #[derive(Facet)]
161//! #[repr(u8)]
162//! enum Payload {
163//!     Small { payload: SmallPayload },
164//!     Large { payload: LargePayload },
165//! }
166//!
167//! #[derive(Facet)]
168//! struct Container {
169//!     #[facet(flatten)]
170//!     inner: Payload,
171//! }
172//! ```
173//!
174//! Both variants have `payload.value`, but one is `u8` (max 255) and one is `u16` (max 65535).
175//! When the deserializer sees value `1000`, it can rule out `Small` without ever parsing into
176//! the wrong type:
177//!
178//! ```rust
179//! use facet_solver::{Solver, KeyResult, Schema};
180//! # use facet::Facet;
181//! # #[derive(Facet)]
182//! # struct SmallPayload { value: u8 }
183//! # #[derive(Facet)]
184//! # struct LargePayload { value: u16 }
185//! # #[derive(Facet)]
186//! # #[repr(u8)]
187//! # enum Payload { Small { payload: SmallPayload }, Large { payload: LargePayload } }
188//! # #[derive(Facet)]
189//! # struct Container { #[facet(flatten)] inner: Payload }
190//!
191//! let schema = Schema::build(Container::SHAPE).unwrap();
192//! let mut solver = Solver::new(&schema);
193//!
194//! // "payload" exists in both - ambiguous by key alone
195//! solver.probe_key(&[], "payload");
196//!
197//! // "value" also exists in both, but with different types!
198//! match solver.probe_key(&["payload"], "value") {
199//!     KeyResult::Ambiguous { fields } => {
200//!         // fields contains (FieldInfo, score) pairs for u8 and u16
201//!         // Lower score = more specific type
202//!         assert_eq!(fields.len(), 2);
203//!     }
204//!     _ => {}
205//! }
206//!
207//! // Deserializer sees value 1000 - ask which types fit
208//! let shapes = solver.get_shapes_at_path(&["payload", "value"]);
209//! let fits: Vec<_> = shapes.iter()
210//!     .filter(|s| match s.type_identifier {
211//!         "u8" => "1000".parse::<u8>().is_ok(),   // false!
212//!         "u16" => "1000".parse::<u16>().is_ok(), // true
213//!         _ => false,
214//!     })
215//!     .copied()
216//!     .collect();
217//!
218//! // Narrow to types the value actually fits
219//! solver.satisfy_at_path(&["payload", "value"], &fits);
220//! assert_eq!(solver.candidates().len(), 1);  // Solved: Large
221//! ```
222//!
223//! This enables true streaming deserialization: you never buffer values, never parse
224//! speculatively, and never lose precision. The solver tells you what types are possible,
225//! you check which ones the raw input satisfies, and disambiguation happens lazily.
226//!
227//! ## Performance
228//!
229//! - **O(1) field lookup**: Inverted index maps field names to bitmasks
230//! - **O(configs/64) narrowing**: Bitwise AND to filter candidates
231//! - **Zero allocation during solving**: Schema is built once, solving just manipulates bitmasks
232//! - **Early termination**: Stops as soon as one candidate remains
233//!
234//! Typical disambiguation: ~50ns for 4 configurations, <1µs for 64+ configurations.
235//!
236//! ## Why This Exists
237//!
238//! Serde's `#[serde(flatten)]` and `#[serde(untagged)]` have fundamental limitations
239//! because they buffer values into an intermediate `Content` enum, then re-deserialize.
240//! This loses type information and breaks many use cases.
241//!
242//! Facet takes a different approach: **determine the type first, then deserialize
243//! directly**. No buffering, no loss of fidelity.
244//!
245//! ### Serde Issues This Resolves
246//!
247//! | Issue | Problem | Facet's Solution |
248//! |-------|---------|------------------|
249//! | [serde#2186](https://github.com/serde-rs/serde/issues/2186) | Flatten buffers into `Content`, losing type distinctions (e.g., `1` vs `"1"`) | Scan keys only, deserialize values directly into the resolved type |
250//! | [serde#1600](https://github.com/serde-rs/serde/issues/1600) | `flatten` + `deny_unknown_fields` doesn't work | Schema knows all valid fields per configuration |
251//! | [serde#1626](https://github.com/serde-rs/serde/issues/1626) | `flatten` + `default` on enums | Solver tracks required vs optional per-field |
252//! | [serde#1560](https://github.com/serde-rs/serde/issues/1560) | Empty variant ambiguity with "first match wins" | Explicit configuration enumeration, no guessing |
253//! | [serde_json#721](https://github.com/serde-rs/json/issues/721) | `arbitrary_precision` + `flatten` loses precision | No buffering through `serde_json::Value` |
254//! | [serde_json#1155](https://github.com/serde-rs/json/issues/1155) | `u128` in flattened struct fails | Direct deserialization, no `Value` intermediary |
255//!
256#![doc = include_str!("../readme-footer.md")]
257
258extern crate alloc;
259
260use alloc::borrow::Cow;
261use alloc::collections::BTreeMap;
262use alloc::collections::BTreeSet;
263use alloc::string::{String, ToString};
264use alloc::vec;
265use alloc::vec::Vec;
266use core::fmt;
267
268use facet_core::{Def, Field, Shape, StructType, Type, UserType, Variant};
269
270// Re-export resolution types from facet-reflect
271pub use facet_reflect::{
272    DuplicateFieldError, FieldCategory, FieldInfo, FieldKey, FieldPath, KeyPath, MatchResult,
273    PathSegment, Resolution, VariantSelection,
274};
275
276/// Format determines how fields are categorized and indexed in the schema.
277///
278/// Different serialization formats have different concepts of "fields":
279/// - Flat formats (JSON, TOML, YAML) treat all fields as key-value pairs
280/// - DOM formats (XML, HTML) distinguish attributes, elements, and text content
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
282#[non_exhaustive]
283pub enum Format {
284    /// Flat key-value formats (JSON, TOML, YAML, etc.)
285    ///
286    /// All fields are treated as keys with no distinction. The solver
287    /// uses `see_key()` to report field names.
288    #[default]
289    Flat,
290
291    /// DOM/tree formats (XML, HTML)
292    ///
293    /// Fields are categorized as attributes, elements, or text content.
294    /// The solver uses `see_attribute()`, `see_element()`, etc. to report
295    /// fields with their category.
296    Dom,
297}
298
299/// Cached schema for a type that may contain flattened fields.
300///
301/// This is computed once per Shape and can be cached forever since
302/// type information is static.
303#[derive(Debug)]
304pub struct Schema {
305    /// The shape this schema is for (kept for future caching key)
306    #[allow(dead_code)]
307    shape: &'static Shape,
308
309    /// The format this schema was built for.
310    format: Format,
311
312    /// All possible resolutions of this type.
313    /// For types with no enums in flatten paths, this has exactly 1 entry.
314    /// For types with enums, this has one entry per valid combination of variants.
315    resolutions: Vec<Resolution>,
316
317    /// Inverted index for Flat format: field_name → bitmask of configuration indices.
318    /// Bit i is set if `resolutions[i]` contains this field.
319    /// Uses a `Vec<u64>` to support arbitrary numbers of resolutions.
320    field_to_resolutions: BTreeMap<&'static str, ResolutionSet>,
321
322    /// Inverted index for Dom format: (category, name) → bitmask of configuration indices.
323    /// Only populated when format is Dom.
324    dom_field_to_resolutions: BTreeMap<(FieldCategory, &'static str), ResolutionSet>,
325}
326
327/// Handle that identifies a specific resolution inside a schema.
328#[derive(Debug, Clone, Copy)]
329pub struct ResolutionHandle<'a> {
330    index: usize,
331    resolution: &'a Resolution,
332}
333
334impl<'a> PartialEq for ResolutionHandle<'a> {
335    fn eq(&self, other: &Self) -> bool {
336        self.index == other.index
337    }
338}
339
340impl<'a> Eq for ResolutionHandle<'a> {}
341
342impl<'a> ResolutionHandle<'a> {
343    /// Internal helper to build a handle for an index within a schema.
344    fn from_schema(schema: &'a Schema, index: usize) -> Self {
345        Self {
346            index,
347            resolution: &schema.resolutions[index],
348        }
349    }
350
351    /// Resolution index within the originating schema.
352    pub const fn index(self) -> usize {
353        self.index
354    }
355
356    /// Access the underlying resolution metadata.
357    pub const fn resolution(self) -> &'a Resolution {
358        self.resolution
359    }
360}
361
362/// A set of configuration indices, stored as a bitmask for O(1) intersection.
363#[derive(Debug, Clone, PartialEq, Eq)]
364pub struct ResolutionSet {
365    /// Bitmask where bit i indicates `resolutions[i]` is in the set.
366    /// For most types, a single u64 suffices (up to 64 configs).
367    bits: Vec<u64>,
368    /// Number of resolutions in the set.
369    count: usize,
370}
371
372impl ResolutionSet {
373    /// Create an empty config set.
374    fn empty(num_resolutions: usize) -> Self {
375        let num_words = num_resolutions.div_ceil(64);
376        Self {
377            bits: vec![0; num_words],
378            count: 0,
379        }
380    }
381
382    /// Create a full config set (all configs present).
383    fn full(num_resolutions: usize) -> Self {
384        let num_words = num_resolutions.div_ceil(64);
385        let mut bits = vec![!0u64; num_words];
386        // Clear bits beyond num_resolutions
387        if !num_resolutions.is_multiple_of(64) {
388            let last_word_bits = num_resolutions % 64;
389            bits[num_words - 1] = (1u64 << last_word_bits) - 1;
390        }
391        Self {
392            bits,
393            count: num_resolutions,
394        }
395    }
396
397    /// Insert a configuration index.
398    fn insert(&mut self, idx: usize) {
399        let word = idx / 64;
400        let bit = idx % 64;
401        if self.bits[word] & (1u64 << bit) == 0 {
402            self.bits[word] |= 1u64 << bit;
403            self.count += 1;
404        }
405    }
406
407    /// Intersect with another config set in place.
408    fn intersect_with(&mut self, other: &ResolutionSet) {
409        self.count = 0;
410        for (a, b) in self.bits.iter_mut().zip(other.bits.iter()) {
411            *a &= *b;
412            self.count += a.count_ones() as usize;
413        }
414    }
415
416    /// Check if intersection with another set would be non-empty.
417    /// Does not modify either set.
418    fn intersects(&self, other: &ResolutionSet) -> bool {
419        self.bits
420            .iter()
421            .zip(other.bits.iter())
422            .any(|(a, b)| (*a & *b) != 0)
423    }
424
425    /// Get the number of resolutions in the set.
426    const fn len(&self) -> usize {
427        self.count
428    }
429
430    /// Check if empty.
431    const fn is_empty(&self) -> bool {
432        self.count == 0
433    }
434
435    /// Get the first (lowest) configuration index in the set.
436    fn first(&self) -> Option<usize> {
437        for (word_idx, &word) in self.bits.iter().enumerate() {
438            if word != 0 {
439                return Some(word_idx * 64 + word.trailing_zeros() as usize);
440            }
441        }
442        None
443    }
444
445    /// Iterate over configuration indices in the set.
446    fn iter(&self) -> impl Iterator<Item = usize> + '_ {
447        self.bits.iter().enumerate().flat_map(|(word_idx, &word)| {
448            (0..64).filter_map(move |bit| {
449                if word & (1u64 << bit) != 0 {
450                    Some(word_idx * 64 + bit)
451                } else {
452                    None
453                }
454            })
455        })
456    }
457}
458
459/// Find fields that could disambiguate between resolutions.
460/// Returns fields that exist in some but not all resolutions.
461fn find_disambiguating_fields(configs: &[&Resolution]) -> Vec<String> {
462    if configs.len() < 2 {
463        return Vec::new();
464    }
465
466    // Collect all field names across all configs
467    let mut all_fields: BTreeSet<&str> = BTreeSet::new();
468    for config in configs {
469        for info in config.fields().values() {
470            all_fields.insert(info.serialized_name);
471        }
472    }
473
474    // Find fields that are in some but not all configs
475    let mut disambiguating = Vec::new();
476    for field in all_fields {
477        let count = configs
478            .iter()
479            .filter(|c| c.field_by_name(field).is_some())
480            .count();
481        if count > 0 && count < configs.len() {
482            disambiguating.push(field.to_string());
483        }
484    }
485
486    disambiguating
487}
488
489/// Information about a missing required field for error reporting.
490#[derive(Debug, Clone)]
491#[non_exhaustive]
492pub struct MissingFieldInfo {
493    /// The serialized field name (as it appears in input)
494    pub name: &'static str,
495    /// Full path to the field (e.g., "backend.connection.port")
496    pub path: String,
497    /// The Rust type that defines this field
498    pub defined_in: String,
499}
500
501impl MissingFieldInfo {
502    /// Create from a FieldInfo
503    fn from_field_info(info: &FieldInfo) -> Self {
504        Self {
505            name: info.serialized_name,
506            path: info.path.to_string(),
507            defined_in: info.value_shape.type_identifier.to_string(),
508        }
509    }
510}
511
512/// Information about why a specific candidate (resolution) failed to match.
513#[derive(Debug, Clone)]
514#[non_exhaustive]
515pub struct CandidateFailure {
516    /// Human-readable description of the variant (e.g., "DatabaseBackend::Postgres")
517    pub variant_name: String,
518    /// Required fields that were not provided in the input
519    pub missing_fields: Vec<MissingFieldInfo>,
520    /// Fields in the input that don't exist in this candidate
521    pub unknown_fields: Vec<String>,
522    /// Number of unknown fields that have "did you mean?" suggestions for this candidate
523    /// Higher = more likely the user intended this variant
524    pub suggestion_matches: usize,
525}
526
527/// Suggestion for a field that might have been misspelled.
528#[derive(Debug, Clone)]
529#[non_exhaustive]
530pub struct FieldSuggestion {
531    /// The unknown field from input
532    pub unknown: String,
533    /// The suggested correct field name
534    pub suggestion: &'static str,
535    /// Similarity score (0.0 to 1.0, higher is more similar)
536    pub similarity: f64,
537}
538
539/// Errors that can occur when building a schema.
540#[derive(Debug, Clone)]
541#[non_exhaustive]
542pub enum SchemaError {
543    /// A field name appears from multiple sources (parent struct and flattened struct)
544    DuplicateField(DuplicateFieldError),
545}
546
547impl From<DuplicateFieldError> for SchemaError {
548    fn from(err: DuplicateFieldError) -> Self {
549        SchemaError::DuplicateField(err)
550    }
551}
552
553impl fmt::Display for SchemaError {
554    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555        match self {
556            SchemaError::DuplicateField(err) => {
557                write!(
558                    f,
559                    "Duplicate field name '{}' from different sources: {} vs {}. \
560                     This usually means a parent struct and a flattened struct both \
561                     define a field with the same name.",
562                    err.field_name, err.first_path, err.second_path
563                )
564            }
565        }
566    }
567}
568
569#[cfg(feature = "std")]
570impl std::error::Error for SchemaError {}
571
572/// Errors that can occur during flatten resolution.
573#[derive(Debug, Clone)]
574#[non_exhaustive]
575pub enum SolverError {
576    /// No configuration matches the input fields
577    NoMatch {
578        /// The input fields that were provided
579        input_fields: Vec<String>,
580        /// Missing required fields (from the closest matching config) - simple names for backwards compat
581        missing_required: Vec<&'static str>,
582        /// Missing required fields with full path information
583        missing_required_detailed: Vec<MissingFieldInfo>,
584        /// Unknown fields that don't belong to any config
585        unknown_fields: Vec<String>,
586        /// Description of the closest matching configuration
587        closest_resolution: Option<String>,
588        /// Why each candidate failed to match (detailed per-candidate info)
589        candidate_failures: Vec<CandidateFailure>,
590        /// "Did you mean?" suggestions for unknown fields
591        suggestions: Vec<FieldSuggestion>,
592    },
593    /// Multiple resolutions match the input fields
594    Ambiguous {
595        /// Descriptions of the matching resolutions
596        candidates: Vec<String>,
597        /// Fields that could disambiguate (unique to specific configs)
598        disambiguating_fields: Vec<String>,
599    },
600}
601
602impl fmt::Display for SolverError {
603    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
604        match self {
605            SolverError::NoMatch {
606                input_fields,
607                missing_required: _,
608                missing_required_detailed,
609                unknown_fields,
610                closest_resolution,
611                candidate_failures,
612                suggestions,
613            } => {
614                write!(f, "No matching configuration for fields {input_fields:?}")?;
615
616                // Show per-candidate failure reasons if available
617                if !candidate_failures.is_empty() {
618                    write!(f, "\n\nNo variant matched:")?;
619                    for failure in candidate_failures {
620                        write!(f, "\n  - {}", failure.variant_name)?;
621                        if !failure.missing_fields.is_empty() {
622                            let names: Vec<_> =
623                                failure.missing_fields.iter().map(|m| m.name).collect();
624                            if names.len() == 1 {
625                                write!(f, ": missing field '{}'", names[0])?;
626                            } else {
627                                write!(f, ": missing fields {names:?}")?;
628                            }
629                        }
630                        if !failure.unknown_fields.is_empty() {
631                            if failure.missing_fields.is_empty() {
632                                write!(f, ":")?;
633                            } else {
634                                write!(f, ",")?;
635                            }
636                            write!(f, " unknown fields {:?}", failure.unknown_fields)?;
637                        }
638                    }
639                } else if let Some(config) = closest_resolution {
640                    // Fallback to closest match if no per-candidate info
641                    write!(f, " (closest match: {config})")?;
642                    if !missing_required_detailed.is_empty() {
643                        write!(f, "; missing required fields:")?;
644                        for info in missing_required_detailed {
645                            write!(f, " {} (at path: {})", info.name, info.path)?;
646                        }
647                    }
648                }
649
650                // Show unknown fields with suggestions
651                if !unknown_fields.is_empty() {
652                    write!(f, "\n\nUnknown fields: {unknown_fields:?}")?;
653                }
654                for suggestion in suggestions {
655                    write!(
656                        f,
657                        "\n  Did you mean '{}' instead of '{}'?",
658                        suggestion.suggestion, suggestion.unknown
659                    )?;
660                }
661
662                Ok(())
663            }
664            SolverError::Ambiguous {
665                candidates,
666                disambiguating_fields,
667            } => {
668                write!(f, "Ambiguous: multiple resolutions match: {candidates:?}")?;
669                if !disambiguating_fields.is_empty() {
670                    write!(
671                        f,
672                        "; try adding one of these fields to disambiguate: {disambiguating_fields:?}"
673                    )?;
674                }
675                Ok(())
676            }
677        }
678    }
679}
680
681#[cfg(feature = "std")]
682impl std::error::Error for SolverError {}
683
684/// Compute a specificity score for a shape. Lower score = more specific.
685///
686/// This is used to disambiguate when a value could satisfy multiple types.
687/// For example, the value `42` fits both `u8` and `u16`, but `u8` is more
688/// specific (lower score), so it should be preferred.
689/// Compute a specificity score for a shape.
690///
691/// Lower score = more specific type. Used for type-based disambiguation
692/// where we want to try more specific types first (e.g., u8 before u16).
693pub fn specificity_score(shape: &'static Shape) -> u64 {
694    // Use type_identifier to determine specificity
695    // Smaller integer types are more specific
696    match shape.type_identifier {
697        "u8" | "i8" => 8,
698        "u16" | "i16" => 16,
699        "u32" | "i32" | "f32" => 32,
700        "u64" | "i64" | "f64" => 64,
701        "u128" | "i128" => 128,
702        "usize" | "isize" => 64, // Treat as 64-bit
703        // Other types get a high score (less specific)
704        _ => 1000,
705    }
706}
707
708// ============================================================================
709// Solver (State Machine)
710// ============================================================================
711
712/// Result of reporting a key to the solver.
713#[derive(Debug)]
714#[non_exhaustive]
715pub enum KeyResult<'a> {
716    /// All candidates have the same type for this key.
717    /// The deserializer can parse the value directly.
718    Unambiguous {
719        /// The shape all candidates expect for this field
720        shape: &'static Shape,
721    },
722
723    /// Candidates have different types for this key - need disambiguation.
724    /// Deserializer should parse the value, determine which fields it can
725    /// satisfy, and call `satisfy()` with the viable fields.
726    ///
727    /// **Important**: When multiple fields can be satisfied by the value,
728    /// pick the one with the lowest score (most specific). Scores are assigned
729    /// by specificity, e.g., `u8` has a lower score than `u16`.
730    Ambiguous {
731        /// The unique fields across remaining candidates (deduplicated by shape),
732        /// paired with a specificity score. Lower score = more specific type.
733        /// Deserializer should check which of these the value can satisfy,
734        /// then pick the one with the lowest score.
735        fields: Vec<(&'a FieldInfo, u64)>,
736    },
737
738    /// This key disambiguated to exactly one configuration.
739    Solved(ResolutionHandle<'a>),
740
741    /// This key doesn't exist in any remaining candidate.
742    Unknown,
743}
744
745/// Result of reporting which fields the value can satisfy.
746#[derive(Debug)]
747#[non_exhaustive]
748pub enum SatisfyResult<'a> {
749    /// Continue - still multiple candidates, keep feeding keys.
750    Continue,
751
752    /// Solved to exactly one configuration.
753    Solved(ResolutionHandle<'a>),
754
755    /// No configuration can accept the value (no fields were satisfied).
756    NoMatch,
757}
758
759/// State machine solver for lazy value-based disambiguation.
760///
761/// This solver only requests value inspection when candidates disagree on type.
762/// For keys where all candidates expect the same type, the deserializer can
763/// skip detailed value analysis.
764///
765/// # Example
766///
767/// ```rust
768/// use facet::Facet;
769/// use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};
770///
771/// #[derive(Facet)]
772/// #[repr(u8)]
773/// enum NumericValue {
774///     Small(u8),
775///     Large(u16),
776/// }
777///
778/// #[derive(Facet)]
779/// struct Container {
780///     #[facet(flatten)]
781///     value: NumericValue,
782/// }
783///
784/// let schema = Schema::build(Container::SHAPE).unwrap();
785/// let mut solver = Solver::new(&schema);
786///
787/// // The field "0" has different types (u8 vs u16) - solver needs disambiguation
788/// match solver.see_key("0") {
789///     KeyResult::Ambiguous { fields } => {
790///         // Deserializer sees value "1000", checks which fields can accept it
791///         // u8 can't hold 1000, u16 can - so only report the u16 field
792///         // Fields come with specificity scores - lower = more specific
793///         let satisfied: Vec<_> = fields.iter()
794///             .filter(|(f, _score)| {
795///                 // deserializer's logic: can this value parse as this field's type?
796///                 f.value_shape.type_identifier == "u16"
797///             })
798///             .map(|(f, _)| *f)
799///             .collect();
800///
801///         match solver.satisfy(&satisfied) {
802///             SatisfyResult::Solved(config) => {
803///                 assert!(config.resolution().describe().contains("Large"));
804///             }
805///             _ => panic!("expected solved"),
806///         }
807///     }
808///     _ => panic!("expected Ambiguous"),
809/// }
810/// ```
811#[derive(Debug)]
812pub struct Solver<'a> {
813    /// Reference to the schema for configuration lookup
814    schema: &'a Schema,
815    /// Bitmask of remaining candidate configuration indices
816    candidates: ResolutionSet,
817    /// Set of seen keys for required field checking.
818    /// For Flat format, stores FieldKey::Flat. For Dom format, stores FieldKey::Dom.
819    seen_keys: BTreeSet<FieldKey<'a>>,
820}
821
822impl<'a> Solver<'a> {
823    /// Create a new solver from a schema.
824    pub fn new(schema: &'a Schema) -> Self {
825        Self {
826            schema,
827            candidates: ResolutionSet::full(schema.resolutions.len()),
828            seen_keys: BTreeSet::new(),
829        }
830    }
831
832    /// Report a key. Returns what to do next.
833    ///
834    /// - `Unambiguous`: All candidates agree on the type - parse directly
835    /// - `Ambiguous`: Types differ - check which fields the value can satisfy
836    /// - `Solved`: Disambiguated to one config
837    /// - `Unknown`: Key not found in any candidate
838    ///
839    /// Accepts both borrowed (`&str`) and owned (`String`) keys via `Cow`.
840    /// For DOM format, use `see_attribute()`, `see_element()`, etc. instead.
841    pub fn see_key(&mut self, key: impl Into<FieldKey<'a>>) -> KeyResult<'a> {
842        let key = key.into();
843        self.see_key_internal(key)
844    }
845
846    /// Report an attribute key (DOM format only).
847    pub fn see_attribute(&mut self, name: impl Into<Cow<'a, str>>) -> KeyResult<'a> {
848        self.see_key_internal(FieldKey::attribute(name))
849    }
850
851    /// Report an element key (DOM format only).
852    pub fn see_element(&mut self, name: impl Into<Cow<'a, str>>) -> KeyResult<'a> {
853        self.see_key_internal(FieldKey::element(name))
854    }
855
856    /// Report a text content key (DOM format only).
857    pub fn see_text(&mut self) -> KeyResult<'a> {
858        self.see_key_internal(FieldKey::text())
859    }
860
861    /// Internal implementation of key lookup.
862    fn see_key_internal(&mut self, key: FieldKey<'a>) -> KeyResult<'a> {
863        self.seen_keys.insert(key.clone());
864
865        // Key-based filtering - use appropriate index based on format
866        let resolutions_with_key = match (&key, self.schema.format) {
867            (FieldKey::Flat(name), Format::Flat) => {
868                self.schema.field_to_resolutions.get(name.as_ref())
869            }
870            (FieldKey::Flat(name), Format::Dom) => {
871                // Flat key on DOM schema - try as element (most common)
872                self.schema
873                    .dom_field_to_resolutions
874                    .get(&(FieldCategory::Element, name.as_ref()))
875            }
876            (FieldKey::Dom(cat, name), Format::Dom) => {
877                // For Text/Tag/Elements categories, the name is often empty
878                // because there's only one such field per struct. Search by category.
879                if matches!(
880                    cat,
881                    FieldCategory::Text | FieldCategory::Tag | FieldCategory::Elements
882                ) && name.is_empty()
883                {
884                    // Find any field with this category
885                    self.schema
886                        .dom_field_to_resolutions
887                        .iter()
888                        .find(|((c, _), _)| c == cat)
889                        .map(|(_, rs)| rs)
890                } else {
891                    self.schema
892                        .dom_field_to_resolutions
893                        .get(&(*cat, name.as_ref()))
894                }
895            }
896            (FieldKey::Dom(_, name), Format::Flat) => {
897                // DOM key on flat schema - ignore category
898                self.schema.field_to_resolutions.get(name.as_ref())
899            }
900            // Unknown key/format combinations have no associated resolutions.
901            _ => None,
902        };
903
904        let resolutions_with_key = match resolutions_with_key {
905            Some(set) => set,
906            None => return KeyResult::Unknown,
907        };
908
909        // Check if this key exists in any current candidate.
910        // If not, treat it as unknown without modifying candidates.
911        // This ensures that extra/unknown fields don't eliminate valid candidates,
912        // which is important for "ignore unknown fields" semantics.
913        if !self.candidates.intersects(resolutions_with_key) {
914            return KeyResult::Unknown;
915        }
916
917        self.candidates.intersect_with(resolutions_with_key);
918
919        // Check if we've disambiguated to exactly one
920        if self.candidates.len() == 1 {
921            let idx = self.candidates.first().unwrap();
922            return KeyResult::Solved(self.handle(idx));
923        }
924
925        // Collect unique fields (by shape pointer) across remaining candidates
926        let mut unique_fields: Vec<&'a FieldInfo> = Vec::new();
927        for idx in self.candidates.iter() {
928            let config = &self.schema.resolutions[idx];
929            if let Some(info) = config.field_by_key(&key) {
930                // Deduplicate by shape pointer
931                if !unique_fields
932                    .iter()
933                    .any(|f| core::ptr::eq(f.value_shape, info.value_shape))
934                {
935                    unique_fields.push(info);
936                }
937            }
938        }
939
940        if unique_fields.len() == 1 {
941            // All candidates have the same type - unambiguous
942            KeyResult::Unambiguous {
943                shape: unique_fields[0].value_shape,
944            }
945        } else if unique_fields.is_empty() {
946            KeyResult::Unknown
947        } else {
948            // Different types - need disambiguation
949            // Attach specificity scores so caller can pick most specific when multiple match
950            let fields_with_scores: Vec<_> = unique_fields
951                .into_iter()
952                .map(|f| (f, specificity_score(f.value_shape)))
953                .collect();
954            KeyResult::Ambiguous {
955                fields: fields_with_scores,
956            }
957        }
958    }
959
960    /// Report which fields the value can satisfy after `Ambiguous` result.
961    ///
962    /// The deserializer should pass the subset of fields (from the `Ambiguous` result)
963    /// that the actual value can be parsed into.
964    pub fn satisfy(&mut self, satisfied_fields: &[&FieldInfo]) -> SatisfyResult<'a> {
965        let satisfied_shapes: Vec<_> = satisfied_fields.iter().map(|f| f.value_shape).collect();
966        self.satisfy_shapes(&satisfied_shapes)
967    }
968
969    /// Report which shapes the value can satisfy after `Ambiguous` result from `probe_key`.
970    ///
971    /// This is the shape-based version of `satisfy`, used when disambiguating
972    /// by nested field types. The deserializer should pass the shapes that
973    /// the actual value can be parsed into.
974    ///
975    /// # Example
976    ///
977    /// ```rust
978    /// use facet::Facet;
979    /// use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};
980    ///
981    /// #[derive(Facet)]
982    /// struct SmallPayload { value: u8 }
983    ///
984    /// #[derive(Facet)]
985    /// struct LargePayload { value: u16 }
986    ///
987    /// #[derive(Facet)]
988    /// #[repr(u8)]
989    /// enum PayloadKind {
990    ///     Small { payload: SmallPayload },
991    ///     Large { payload: LargePayload },
992    /// }
993    ///
994    /// #[derive(Facet)]
995    /// struct Container {
996    ///     #[facet(flatten)]
997    ///     inner: PayloadKind,
998    /// }
999    ///
1000    /// let schema = Schema::build(Container::SHAPE).unwrap();
1001    /// let mut solver = Solver::new(&schema);
1002    ///
1003    /// // Report nested key
1004    /// solver.probe_key(&[], "payload");
1005    ///
1006    /// // At payload.value, value is 1000 - doesn't fit u8
1007    /// // Get shapes at this path
1008    /// let shapes = solver.get_shapes_at_path(&["payload", "value"]);
1009    /// // Filter to shapes that can hold 1000
1010    /// let works: Vec<_> = shapes.iter()
1011    ///     .filter(|s| s.type_identifier == "u16")
1012    ///     .copied()
1013    ///     .collect();
1014    /// solver.satisfy_shapes(&works);
1015    /// ```
1016    pub fn satisfy_shapes(&mut self, satisfied_shapes: &[&'static Shape]) -> SatisfyResult<'a> {
1017        if satisfied_shapes.is_empty() {
1018            self.candidates = ResolutionSet::empty(self.schema.resolutions.len());
1019            return SatisfyResult::NoMatch;
1020        }
1021
1022        let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
1023        for idx in self.candidates.iter() {
1024            let config = &self.schema.resolutions[idx];
1025            // Check if any of this config's fields match the satisfied shapes
1026            for field in config.fields().values() {
1027                if satisfied_shapes
1028                    .iter()
1029                    .any(|s| core::ptr::eq(*s, field.value_shape))
1030                {
1031                    new_candidates.insert(idx);
1032                    break;
1033                }
1034            }
1035        }
1036        self.candidates = new_candidates;
1037
1038        match self.candidates.len() {
1039            0 => SatisfyResult::NoMatch,
1040            1 => {
1041                let idx = self.candidates.first().unwrap();
1042                SatisfyResult::Solved(self.handle(idx))
1043            }
1044            _ => SatisfyResult::Continue,
1045        }
1046    }
1047
1048    /// Get the shapes at a nested path across all remaining candidates.
1049    ///
1050    /// This is useful when you have an `Ambiguous` result from `probe_key`
1051    /// and need to know what types are possible at that path.
1052    pub fn get_shapes_at_path(&self, path: &[&str]) -> Vec<&'static Shape> {
1053        let mut shapes: Vec<&'static Shape> = Vec::new();
1054        for idx in self.candidates.iter() {
1055            let config = &self.schema.resolutions[idx];
1056            if let Some(shape) = self.get_shape_at_path(config, path)
1057                && !shapes.iter().any(|s| core::ptr::eq(*s, shape))
1058            {
1059                shapes.push(shape);
1060            }
1061        }
1062        shapes
1063    }
1064
1065    /// Report which shapes at a nested path the value can satisfy.
1066    ///
1067    /// This is the path-aware version of `satisfy_shapes`, used when disambiguating
1068    /// by nested field types after `probe_key`.
1069    ///
1070    /// - `path`: The full path to the field (e.g., `["payload", "value"]`)
1071    /// - `satisfied_shapes`: The shapes that the value can be parsed into
1072    pub fn satisfy_at_path(
1073        &mut self,
1074        path: &[&str],
1075        satisfied_shapes: &[&'static Shape],
1076    ) -> SatisfyResult<'a> {
1077        if satisfied_shapes.is_empty() {
1078            self.candidates = ResolutionSet::empty(self.schema.resolutions.len());
1079            return SatisfyResult::NoMatch;
1080        }
1081
1082        // Keep only candidates where the shape at this path is in the satisfied set
1083        let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
1084        for idx in self.candidates.iter() {
1085            let config = &self.schema.resolutions[idx];
1086            if let Some(shape) = self.get_shape_at_path(config, path)
1087                && satisfied_shapes.iter().any(|s| core::ptr::eq(*s, shape))
1088            {
1089                new_candidates.insert(idx);
1090            }
1091        }
1092        self.candidates = new_candidates;
1093
1094        match self.candidates.len() {
1095            0 => SatisfyResult::NoMatch,
1096            1 => {
1097                let idx = self.candidates.first().unwrap();
1098                SatisfyResult::Solved(self.handle(idx))
1099            }
1100            _ => SatisfyResult::Continue,
1101        }
1102    }
1103
1104    /// Get the current candidate resolutions.
1105    pub fn candidates(&self) -> Vec<ResolutionHandle<'a>> {
1106        self.candidates.iter().map(|idx| self.handle(idx)).collect()
1107    }
1108
1109    /// Get the seen keys.
1110    /// Get the seen keys.
1111    pub const fn seen_keys(&self) -> &BTreeSet<FieldKey<'a>> {
1112        &self.seen_keys
1113    }
1114
1115    /// Check if a field name was seen (regardless of category for DOM format).
1116    pub fn was_field_seen(&self, field_name: &str) -> bool {
1117        self.seen_keys.iter().any(|k| k.name() == field_name)
1118    }
1119
1120    #[inline]
1121    fn handle(&self, idx: usize) -> ResolutionHandle<'a> {
1122        ResolutionHandle::from_schema(self.schema, idx)
1123    }
1124
1125    /// Hint that a specific enum variant should be selected.
1126    ///
1127    /// This filters the candidates to only those resolutions where at least one
1128    /// variant selection has the given variant name. This is useful for explicit
1129    /// type disambiguation via annotations (e.g., type annotations in various formats).
1130    ///
1131    /// Returns `true` if at least one candidate remains after filtering, `false` if
1132    /// no candidates match the variant name (in which case candidates are unchanged).
1133    ///
1134    /// # Example
1135    ///
1136    /// ```rust
1137    /// use facet::Facet;
1138    /// use facet_solver::{Schema, Solver};
1139    ///
1140    /// #[derive(Facet)]
1141    /// struct HttpSource { url: String }
1142    ///
1143    /// #[derive(Facet)]
1144    /// struct GitSource { url: String, branch: String }
1145    ///
1146    /// #[derive(Facet)]
1147    /// #[repr(u8)]
1148    /// enum SourceKind {
1149    ///     Http(HttpSource),
1150    ///     Git(GitSource),
1151    /// }
1152    ///
1153    /// #[derive(Facet)]
1154    /// struct Source {
1155    ///     #[facet(flatten)]
1156    ///     kind: SourceKind,
1157    /// }
1158    ///
1159    /// let schema = Schema::build(Source::SHAPE).unwrap();
1160    /// let mut solver = Solver::new(&schema);
1161    ///
1162    /// // Without hint, both variants are candidates
1163    /// assert_eq!(solver.candidates().len(), 2);
1164    ///
1165    /// // Hint at Http variant
1166    /// assert!(solver.hint_variant("Http"));
1167    /// assert_eq!(solver.candidates().len(), 1);
1168    /// ```
1169    pub fn hint_variant(&mut self, variant_name: &str) -> bool {
1170        // Build a set of configs that have this variant name
1171        let mut matching = ResolutionSet::empty(self.schema.resolutions.len());
1172
1173        for idx in self.candidates.iter() {
1174            let config = &self.schema.resolutions[idx];
1175            // Check if any variant selection matches the given name
1176            if config
1177                .variant_selections()
1178                .iter()
1179                .any(|vs| vs.variant_name == variant_name)
1180            {
1181                matching.insert(idx);
1182            }
1183        }
1184
1185        if matching.is_empty() {
1186            // No matches - keep candidates unchanged
1187            false
1188        } else {
1189            self.candidates = matching;
1190            true
1191        }
1192    }
1193
1194    /// Hint that a variant is selected, but only if the field is actually a tag field
1195    /// for an internally-tagged enum.
1196    ///
1197    /// This is safer than `hint_variant` because it validates that `tag_field_name`
1198    /// is actually the tag field for an internally-tagged enum in at least one
1199    /// candidate resolution before applying the hint.
1200    ///
1201    /// Returns `true` if the hint was applied (field was a valid tag field and
1202    /// at least one candidate matches), `false` otherwise.
1203    pub fn hint_variant_for_tag(&mut self, tag_field_name: &str, variant_name: &str) -> bool {
1204        // First check if any candidate has this field as an internally-tagged enum tag field
1205        let is_tag_field = self.candidates.iter().any(|idx| {
1206            let config = &self.schema.resolutions[idx];
1207            // Look for a field with the given name that is a tag field
1208            config.fields().values().any(|field| {
1209                field.serialized_name == tag_field_name
1210                    && field
1211                        .value_shape
1212                        .get_tag_attr()
1213                        .is_some_and(|tag| tag == tag_field_name)
1214                    && field.value_shape.get_content_attr().is_none()
1215            })
1216        });
1217
1218        if !is_tag_field {
1219            return false;
1220        }
1221
1222        // Now apply the variant hint
1223        self.hint_variant(variant_name)
1224    }
1225
1226    /// Mark a key as seen without filtering candidates.
1227    ///
1228    /// This is useful when the key is known to be present through means other than
1229    /// parsing (e.g., type annotations). Call this after `hint_variant` to mark
1230    /// the variant name as seen so that `finish()` doesn't report it as missing.
1231    pub fn mark_seen(&mut self, key: impl Into<FieldKey<'a>>) {
1232        self.seen_keys.insert(key.into());
1233    }
1234
1235    /// Report a key at a nested path. Returns what to do next.
1236    ///
1237    /// This is the depth-aware version of `see_key`. Use this when probing
1238    /// nested structures where disambiguation might require looking inside objects.
1239    ///
1240    /// - `path`: The ancestor keys (e.g., `["payload"]` when inside a payload object)
1241    /// - `key`: The key found at this level (e.g., `"value"`)
1242    ///
1243    /// # Example
1244    ///
1245    /// ```rust
1246    /// use facet::Facet;
1247    /// use facet_solver::{Schema, Solver, KeyResult};
1248    ///
1249    /// #[derive(Facet)]
1250    /// struct SmallPayload { value: u8 }
1251    ///
1252    /// #[derive(Facet)]
1253    /// struct LargePayload { value: u16 }
1254    ///
1255    /// #[derive(Facet)]
1256    /// #[repr(u8)]
1257    /// enum PayloadKind {
1258    ///     Small { payload: SmallPayload },
1259    ///     Large { payload: LargePayload },
1260    /// }
1261    ///
1262    /// #[derive(Facet)]
1263    /// struct Container {
1264    ///     #[facet(flatten)]
1265    ///     inner: PayloadKind,
1266    /// }
1267    ///
1268    /// let schema = Schema::build(Container::SHAPE).unwrap();
1269    /// let mut solver = Solver::new(&schema);
1270    ///
1271    /// // "payload" exists in both - keep going
1272    /// solver.probe_key(&[], "payload");
1273    ///
1274    /// // "value" inside payload - both have it but different types!
1275    /// match solver.probe_key(&["payload"], "value") {
1276    ///     KeyResult::Ambiguous { fields } => {
1277    ///         // fields is Vec<(&FieldInfo, u64)> - field + specificity score
1278    ///         // Deserializer checks: 1000 fits u16 but not u8
1279    ///         // When multiple match, pick the one with lowest score (most specific)
1280    ///     }
1281    ///     _ => {}
1282    /// }
1283    /// ```
1284    pub fn probe_key(&mut self, path: &[&str], key: &str) -> KeyResult<'a> {
1285        // Build full path
1286        let mut full_path: Vec<&str> = path.to_vec();
1287        full_path.push(key);
1288
1289        // Filter candidates to only those that have this key path
1290        let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
1291        for idx in self.candidates.iter() {
1292            let config = &self.schema.resolutions[idx];
1293            if config.has_key_path(&full_path) {
1294                new_candidates.insert(idx);
1295            }
1296        }
1297        self.candidates = new_candidates;
1298
1299        if self.candidates.is_empty() {
1300            return KeyResult::Unknown;
1301        }
1302
1303        // Check if we've disambiguated to exactly one
1304        if self.candidates.len() == 1 {
1305            let idx = self.candidates.first().unwrap();
1306            return KeyResult::Solved(self.handle(idx));
1307        }
1308
1309        // Get the shape at this path for each remaining candidate
1310        // We need to traverse the type tree to find the actual field type
1311        let mut unique_shapes: Vec<(&'static Shape, usize)> = Vec::new(); // (shape, resolution_idx)
1312
1313        for idx in self.candidates.iter() {
1314            let config = &self.schema.resolutions[idx];
1315            if let Some(shape) = self.get_shape_at_path(config, &full_path) {
1316                // Deduplicate by shape pointer
1317                if !unique_shapes.iter().any(|(s, _)| core::ptr::eq(*s, shape)) {
1318                    unique_shapes.push((shape, idx));
1319                }
1320            }
1321        }
1322
1323        match unique_shapes.len() {
1324            0 => KeyResult::Unknown,
1325            1 => {
1326                // All candidates have the same type at this path - unambiguous
1327                KeyResult::Unambiguous {
1328                    shape: unique_shapes[0].0,
1329                }
1330            }
1331            _ => {
1332                // Different types at this path - need disambiguation
1333                // Build FieldInfo with scores for each unique shape
1334                let fields: Vec<(&'a FieldInfo, u64)> = unique_shapes
1335                    .iter()
1336                    .filter_map(|(shape, idx)| {
1337                        let config = &self.schema.resolutions[*idx];
1338                        // For nested paths, we need the parent field
1339                        // e.g., for ["payload", "value"], get the "payload" field
1340                        let field = if path.is_empty() {
1341                            config.field_by_name(key)
1342                        } else {
1343                            // Return the top-level field that contains this path
1344                            config.field_by_name(path[0])
1345                        }?;
1346                        Some((field, specificity_score(shape)))
1347                    })
1348                    .collect();
1349
1350                KeyResult::Ambiguous { fields }
1351            }
1352        }
1353    }
1354
1355    /// Get the shape at a nested path within a configuration.
1356    fn get_shape_at_path(&self, config: &'a Resolution, path: &[&str]) -> Option<&'static Shape> {
1357        if path.is_empty() {
1358            return None;
1359        }
1360
1361        // Start with the top-level field
1362        let top_field = config.field_by_name(path[0])?;
1363        let mut current_shape = top_field.value_shape;
1364
1365        // Navigate through nested structs
1366        for &key in &path[1..] {
1367            current_shape = self.get_field_shape(current_shape, key)?;
1368        }
1369
1370        Some(current_shape)
1371    }
1372
1373    /// Get the shape of a field within a struct shape.
1374    fn get_field_shape(&self, shape: &'static Shape, field_name: &str) -> Option<&'static Shape> {
1375        use facet_core::{StructType, Type, UserType};
1376
1377        match shape.ty {
1378            Type::User(UserType::Struct(StructType { fields, .. })) => {
1379                for field in fields {
1380                    if field.effective_name() == field_name {
1381                        return Some(field.shape());
1382                    }
1383                }
1384                None
1385            }
1386            _ => None,
1387        }
1388    }
1389
1390    /// Finish solving. Call this after all keys have been processed.
1391    ///
1392    /// This method is necessary because key-based filtering alone cannot disambiguate
1393    /// when one variant's required fields are a subset of another's.
1394    ///
1395    /// # Why not just use `see_key()` results?
1396    ///
1397    /// `see_key()` returns `Solved` when a key *excludes* candidates down to one.
1398    /// But when the input is a valid subset of multiple variants, no key excludes
1399    /// anything — you need `finish()` to check which candidates have all their
1400    /// required fields satisfied.
1401    ///
1402    /// # Example
1403    ///
1404    /// ```rust,ignore
1405    /// enum Source {
1406    ///     Http { url: String },                  // required: url
1407    ///     Git { url: String, branch: String },   // required: url, branch
1408    /// }
1409    /// ```
1410    ///
1411    /// | Input                  | `see_key` behavior                        | Resolution            |
1412    /// |------------------------|-------------------------------------------|-----------------------|
1413    /// | `{ "url", "branch" }`  | `branch` excludes `Http` → candidates = 1 | Early `Solved(Git)`   |
1414    /// | `{ "url" }`            | both have `url` → candidates = 2          | `finish()` → `Http`   |
1415    ///
1416    /// In the second case, no key ever excludes a candidate. Only `finish()` can
1417    /// determine that `Git` is missing its required `branch` field, leaving `Http`
1418    /// as the sole viable configuration.
1419    #[allow(clippy::result_large_err)] // SolverError intentionally contains detailed diagnostic info
1420    pub fn finish(self) -> Result<ResolutionHandle<'a>, SolverError> {
1421        let Solver {
1422            schema,
1423            candidates,
1424            seen_keys,
1425        } = self;
1426
1427        // Compute all known fields across all resolutions (for unknown field detection)
1428        let all_known_fields: BTreeSet<&'static str> = schema
1429            .resolutions
1430            .iter()
1431            .flat_map(|r| r.fields().values().map(|f| f.serialized_name))
1432            .collect();
1433
1434        // Find unknown fields (fields in input that don't exist in ANY resolution)
1435        let unknown_fields: Vec<String> = seen_keys
1436            .iter()
1437            .filter(|k| !all_known_fields.contains(k.name()))
1438            .map(|k| k.name().to_string())
1439            .collect();
1440
1441        // Compute suggestions for unknown fields
1442        let suggestions = compute_suggestions(&unknown_fields, &all_known_fields);
1443
1444        if candidates.is_empty() {
1445            // Build per-candidate failure info for all resolutions
1446            let mut candidate_failures: Vec<CandidateFailure> = schema
1447                .resolutions
1448                .iter()
1449                .map(|config| build_candidate_failure(config, &seen_keys))
1450                .collect();
1451
1452            // Sort by closeness (best match first)
1453            sort_candidates_by_closeness(&mut candidate_failures);
1454
1455            return Err(SolverError::NoMatch {
1456                input_fields: seen_keys.iter().map(|k| k.name().to_string()).collect(),
1457                missing_required: Vec::new(),
1458                missing_required_detailed: Vec::new(),
1459                unknown_fields,
1460                closest_resolution: None,
1461                candidate_failures,
1462                suggestions,
1463            });
1464        }
1465
1466        // Filter candidates to only those that have all required fields satisfied
1467        let viable: Vec<usize> = candidates
1468            .iter()
1469            .filter(|idx| {
1470                let config = &schema.resolutions[*idx];
1471                config
1472                    .required_field_names()
1473                    .iter()
1474                    .all(|f| seen_keys.iter().any(|k| k.name() == *f))
1475            })
1476            .collect();
1477
1478        match viable.len() {
1479            0 => {
1480                // No viable candidates - build per-candidate failure info
1481                let mut candidate_failures: Vec<CandidateFailure> = candidates
1482                    .iter()
1483                    .map(|idx| {
1484                        let config = &schema.resolutions[idx];
1485                        build_candidate_failure(config, &seen_keys)
1486                    })
1487                    .collect();
1488
1489                // Sort by closeness (best match first)
1490                sort_candidates_by_closeness(&mut candidate_failures);
1491
1492                // For backwards compatibility, also populate the "closest" fields
1493                // Now use the first (closest) candidate after sorting
1494                let closest_name = candidate_failures.first().map(|f| f.variant_name.clone());
1495                let closest_config = closest_name
1496                    .as_ref()
1497                    .and_then(|name| schema.resolutions.iter().find(|r| r.describe() == *name));
1498
1499                let (missing, missing_detailed, closest_resolution) =
1500                    if let Some(config) = closest_config {
1501                        let missing: Vec<_> = config
1502                            .required_field_names()
1503                            .iter()
1504                            .filter(|f| !seen_keys.iter().any(|k| k.name() == **f))
1505                            .copied()
1506                            .collect();
1507                        let missing_detailed: Vec<_> = missing
1508                            .iter()
1509                            .filter_map(|name| config.field_by_name(name))
1510                            .map(MissingFieldInfo::from_field_info)
1511                            .collect();
1512                        (missing, missing_detailed, Some(config.describe()))
1513                    } else {
1514                        (Vec::new(), Vec::new(), None)
1515                    };
1516
1517                Err(SolverError::NoMatch {
1518                    input_fields: seen_keys.iter().map(|s| s.to_string()).collect(),
1519                    missing_required: missing,
1520                    missing_required_detailed: missing_detailed,
1521                    unknown_fields,
1522                    closest_resolution,
1523                    candidate_failures,
1524                    suggestions,
1525                })
1526            }
1527            1 => {
1528                // Exactly one viable candidate - success!
1529                Ok(ResolutionHandle::from_schema(schema, viable[0]))
1530            }
1531            _ => {
1532                // Multiple viable candidates - ambiguous!
1533                let configs: Vec<_> = viable.iter().map(|&idx| &schema.resolutions[idx]).collect();
1534                let candidates: Vec<String> = configs.iter().map(|c| c.describe()).collect();
1535                let disambiguating_fields = find_disambiguating_fields(&configs);
1536
1537                Err(SolverError::Ambiguous {
1538                    candidates,
1539                    disambiguating_fields,
1540                })
1541            }
1542        }
1543    }
1544}
1545
1546/// Build a CandidateFailure for a resolution given the seen keys.
1547fn build_candidate_failure<'a>(
1548    config: &Resolution,
1549    seen_keys: &BTreeSet<FieldKey<'a>>,
1550) -> CandidateFailure {
1551    let missing_fields: Vec<MissingFieldInfo> = config
1552        .required_field_names()
1553        .iter()
1554        .filter(|f| !seen_keys.iter().any(|k| k.name() == **f))
1555        .filter_map(|f| config.field_by_name(f))
1556        .map(MissingFieldInfo::from_field_info)
1557        .collect();
1558
1559    let unknown_fields: Vec<String> = seen_keys
1560        .iter()
1561        .filter(|k| config.field_by_key(k).is_none())
1562        .map(|k| k.name().to_string())
1563        .collect();
1564
1565    // Compute closeness score for ranking
1566    let suggestion_matches = compute_closeness_score(&unknown_fields, &missing_fields, config);
1567
1568    CandidateFailure {
1569        variant_name: config.describe(),
1570        missing_fields,
1571        unknown_fields,
1572        suggestion_matches,
1573    }
1574}
1575
1576/// Compute a closeness score for ranking candidates.
1577/// Higher score = more likely the user intended this variant.
1578///
1579/// The score considers:
1580/// - Typo matches: unknown fields that are similar to known fields (weighted by similarity)
1581/// - Field coverage: if we fixed typos, would we have all required fields?
1582/// - Missing fields: fewer missing = better
1583/// - Unknown fields: fewer truly unknown (no suggestion) = better
1584#[cfg(feature = "suggestions")]
1585fn compute_closeness_score(
1586    unknown_fields: &[String],
1587    missing_fields: &[MissingFieldInfo],
1588    config: &Resolution,
1589) -> usize {
1590    const SIMILARITY_THRESHOLD: f64 = 0.6;
1591
1592    // Score components (scaled to integers for easy comparison)
1593    let mut typo_score: usize = 0;
1594    let mut fields_that_would_match: usize = 0;
1595
1596    // For each unknown field, find best matching known field
1597    for unknown in unknown_fields {
1598        let mut best_similarity = 0.0f64;
1599        let mut best_match: Option<&str> = None;
1600
1601        for info in config.fields().values() {
1602            let similarity = strsim::jaro_winkler(unknown, info.serialized_name);
1603            if similarity >= SIMILARITY_THRESHOLD && similarity > best_similarity {
1604                best_similarity = similarity;
1605                best_match = Some(info.serialized_name);
1606            }
1607        }
1608
1609        if let Some(_matched_field) = best_match {
1610            // Weight by similarity: 0.6 -> 60 points, 1.0 -> 100 points
1611            typo_score += (best_similarity * 100.0) as usize;
1612            fields_that_would_match += 1;
1613        }
1614    }
1615
1616    // Calculate how many required fields would be satisfied if typos were fixed
1617    let required_count = config.required_field_names().len();
1618    let currently_missing = missing_fields.len();
1619    let would_be_missing = currently_missing.saturating_sub(fields_that_would_match);
1620
1621    // Coverage score: percentage of required fields that would be present
1622    let coverage_score = required_count
1623        .saturating_sub(would_be_missing)
1624        .saturating_mul(100)
1625        .checked_div(required_count)
1626        .unwrap_or(100);
1627
1628    // Penalty for truly unknown fields (no typo suggestion)
1629    let truly_unknown = unknown_fields.len().saturating_sub(fields_that_would_match);
1630    let unknown_penalty = truly_unknown * 10;
1631
1632    // Combine scores: typo matches are most important, then coverage, then penalties
1633    // Each typo match can give up to 100 points, so scale coverage to match
1634    typo_score + coverage_score.saturating_sub(unknown_penalty)
1635}
1636
1637/// Compute closeness score (no-op without suggestions feature).
1638#[cfg(not(feature = "suggestions"))]
1639fn compute_closeness_score(
1640    _unknown_fields: &[String],
1641    _missing_fields: &[MissingFieldInfo],
1642    _config: &Resolution,
1643) -> usize {
1644    0
1645}
1646
1647/// Sort candidate failures by closeness (best match first).
1648fn sort_candidates_by_closeness(failures: &mut [CandidateFailure]) {
1649    failures.sort_by(|a, b| {
1650        // Higher suggestion_matches (closeness score) first
1651        b.suggestion_matches.cmp(&a.suggestion_matches)
1652    });
1653}
1654
1655/// Compute "did you mean?" suggestions for unknown fields.
1656#[cfg(feature = "suggestions")]
1657fn compute_suggestions(
1658    unknown_fields: &[String],
1659    all_known_fields: &BTreeSet<&'static str>,
1660) -> Vec<FieldSuggestion> {
1661    const SIMILARITY_THRESHOLD: f64 = 0.6;
1662
1663    let mut suggestions = Vec::new();
1664
1665    for unknown in unknown_fields {
1666        let mut best_match: Option<(&'static str, f64)> = None;
1667
1668        for known in all_known_fields {
1669            let similarity = strsim::jaro_winkler(unknown, known);
1670            if similarity >= SIMILARITY_THRESHOLD
1671                && best_match.is_none_or(|(_, best_sim)| similarity > best_sim)
1672            {
1673                best_match = Some((known, similarity));
1674            }
1675        }
1676
1677        if let Some((suggestion, similarity)) = best_match {
1678            suggestions.push(FieldSuggestion {
1679                unknown: unknown.clone(),
1680                suggestion,
1681                similarity,
1682            });
1683        }
1684    }
1685
1686    suggestions
1687}
1688
1689/// Compute "did you mean?" suggestions for unknown fields (no-op without strsim).
1690#[cfg(not(feature = "suggestions"))]
1691fn compute_suggestions(
1692    _unknown_fields: &[String],
1693    _all_known_fields: &BTreeSet<&'static str>,
1694) -> Vec<FieldSuggestion> {
1695    Vec::new()
1696}
1697
1698// ============================================================================
1699// Probing Solver (Depth-Aware)
1700// ============================================================================
1701
1702/// Result of reporting a key to the probing solver.
1703#[derive(Debug)]
1704#[non_exhaustive]
1705pub enum ProbeResult<'a> {
1706    /// Keep reporting keys - not yet disambiguated
1707    KeepGoing,
1708    /// Solved! Use this configuration
1709    Solved(&'a Resolution),
1710    /// No configuration matches the observed keys
1711    NoMatch,
1712}
1713
1714/// Depth-aware probing solver for streaming deserialization.
1715///
1716/// Unlike the batch solver, this solver accepts
1717/// key reports at arbitrary depths. It's designed for the "peek" strategy:
1718///
1719/// 1. Deserializer scans keys (without parsing values) and reports them
1720/// 2. Solver filters candidates based on which configs have that key path
1721/// 3. Once one candidate remains, solver returns `Solved`
1722/// 4. Deserializer rewinds and parses into the resolved type
1723///
1724/// # Example
1725///
1726/// ```rust
1727/// use facet::Facet;
1728/// use facet_solver::{Schema, ProbingSolver, ProbeResult};
1729///
1730/// #[derive(Facet)]
1731/// struct TextPayload { content: String }
1732///
1733/// #[derive(Facet)]
1734/// struct BinaryPayload { bytes: Vec<u8> }
1735///
1736/// #[derive(Facet)]
1737/// #[repr(u8)]
1738/// enum MessageKind {
1739///     Text { payload: TextPayload },
1740///     Binary { payload: BinaryPayload },
1741/// }
1742///
1743/// #[derive(Facet)]
1744/// struct Message {
1745///     id: String,
1746///     #[facet(flatten)]
1747///     kind: MessageKind,
1748/// }
1749///
1750/// let schema = Schema::build(Message::SHAPE).unwrap();
1751/// let mut solver = ProbingSolver::new(&schema);
1752///
1753/// // "id" exists in both configs - keep going
1754/// assert!(matches!(solver.probe_key(&[], "id"), ProbeResult::KeepGoing));
1755///
1756/// // "payload" exists in both configs - keep going
1757/// assert!(matches!(solver.probe_key(&[], "payload"), ProbeResult::KeepGoing));
1758///
1759/// // "content" inside payload only exists in Text - solved!
1760/// match solver.probe_key(&["payload"], "content") {
1761///     ProbeResult::Solved(config) => {
1762///         assert!(config.has_key_path(&["payload", "content"]));
1763///     }
1764///     _ => panic!("expected Solved"),
1765/// }
1766/// ```
1767#[derive(Debug)]
1768pub struct ProbingSolver<'a> {
1769    /// Remaining candidate resolutions
1770    candidates: Vec<&'a Resolution>,
1771}
1772
1773impl<'a> ProbingSolver<'a> {
1774    /// Create a new probing solver from a schema.
1775    pub fn new(schema: &'a Schema) -> Self {
1776        Self {
1777            candidates: schema.resolutions.iter().collect(),
1778        }
1779    }
1780
1781    /// Create a new probing solver from resolutions directly.
1782    pub fn from_resolutions(configs: &'a [Resolution]) -> Self {
1783        Self {
1784            candidates: configs.iter().collect(),
1785        }
1786    }
1787
1788    /// Report a key found at a path during probing.
1789    ///
1790    /// - `path`: The ancestor keys (e.g., `["payload"]` when inside the payload object)
1791    /// - `key`: The key found at this level (e.g., `"content"`)
1792    ///
1793    /// Returns what to do next.
1794    pub fn probe_key(&mut self, path: &[&str], key: &str) -> ProbeResult<'a> {
1795        // Build the full key path (runtime strings, compared against static schema)
1796        let mut full_path: Vec<&str> = path.to_vec();
1797        full_path.push(key);
1798
1799        // Filter to candidates that have this key path
1800        self.candidates.retain(|c| c.has_key_path(&full_path));
1801
1802        match self.candidates.len() {
1803            0 => ProbeResult::NoMatch,
1804            1 => ProbeResult::Solved(self.candidates[0]),
1805            _ => ProbeResult::KeepGoing,
1806        }
1807    }
1808
1809    /// Get the current candidate resolutions.
1810    pub fn candidates(&self) -> &[&'a Resolution] {
1811        &self.candidates
1812    }
1813
1814    /// Finish probing - returns Solved if exactly one candidate remains.
1815    pub fn finish(&self) -> ProbeResult<'a> {
1816        match self.candidates.len() {
1817            0 => ProbeResult::NoMatch,
1818            1 => ProbeResult::Solved(self.candidates[0]),
1819            _ => ProbeResult::KeepGoing, // Still ambiguous
1820        }
1821    }
1822}
1823
1824// ============================================================================
1825// Variant Format Classification
1826// ============================================================================
1827
1828/// Classification of an enum variant's expected serialized format.
1829///
1830/// This is used by deserializers to determine how to parse untagged enum variants
1831/// based on the YAML/JSON/etc. value type they encounter.
1832#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1833#[non_exhaustive]
1834pub enum VariantFormat {
1835    /// Unit variant: no fields, serializes as the variant name or nothing for untagged
1836    Unit,
1837
1838    /// Newtype variant wrapping a scalar type (String, numbers, bool, etc.)
1839    /// Serializes as just the scalar value for untagged enums.
1840    NewtypeScalar {
1841        /// The shape of the inner scalar type
1842        inner_shape: &'static Shape,
1843    },
1844
1845    /// Newtype variant wrapping a struct
1846    /// Serializes as a mapping for untagged enums.
1847    NewtypeStruct {
1848        /// The shape of the inner struct type
1849        inner_shape: &'static Shape,
1850    },
1851
1852    /// Newtype variant wrapping a tuple struct/tuple
1853    /// Serializes as a sequence for untagged enums.
1854    NewtypeTuple {
1855        /// The shape of the inner tuple type
1856        inner_shape: &'static Shape,
1857        /// Number of elements in the inner tuple
1858        arity: usize,
1859    },
1860
1861    /// Newtype variant wrapping a sequence type (Vec, Array, Slice, Set)
1862    /// Serializes as a sequence for untagged enums.
1863    NewtypeSequence {
1864        /// The shape of the inner sequence type
1865        inner_shape: &'static Shape,
1866    },
1867
1868    /// Newtype variant wrapping another type (enum, map, etc.)
1869    NewtypeOther {
1870        /// The shape of the inner type
1871        inner_shape: &'static Shape,
1872    },
1873
1874    /// Tuple variant with multiple fields
1875    /// Serializes as a sequence for untagged enums.
1876    Tuple {
1877        /// Number of fields in the tuple
1878        arity: usize,
1879    },
1880
1881    /// Struct variant with named fields
1882    /// Serializes as a mapping for untagged enums.
1883    Struct,
1884}
1885
1886impl VariantFormat {
1887    /// Classify a variant's expected serialized format.
1888    pub fn from_variant(variant: &'static Variant) -> Self {
1889        use facet_core::StructKind;
1890
1891        let fields = variant.data.fields;
1892        let kind = variant.data.kind;
1893
1894        match kind {
1895            StructKind::Unit => VariantFormat::Unit,
1896            // TupleStruct and Tuple are both used for tuple-like variants
1897            // depending on how they're defined. Handle them the same way.
1898            StructKind::TupleStruct | StructKind::Tuple => {
1899                if fields.len() == 1 {
1900                    // Newtype variant - classify by inner type
1901                    let field_shape = fields[0].shape();
1902                    // Dereference through pointers to get the actual inner type
1903                    let inner_shape = deref_pointer(field_shape);
1904
1905                    // Check if this is a metadata container (like Spanned<T>) and unwrap it for classification
1906                    // This allows untagged enum variants containing Spanned<String> etc.
1907                    // to match scalar values transparently
1908                    let classification_shape = if let Some(inner) =
1909                        facet_reflect::get_metadata_container_value_shape(field_shape)
1910                    {
1911                        inner
1912                    } else {
1913                        field_shape
1914                    };
1915
1916                    if is_scalar_shape(classification_shape)
1917                        || is_unit_enum_shape(classification_shape)
1918                    {
1919                        // Scalars and unit-only enums both serialize as primitive values
1920                        // Store the classification shape (unwrapped from Spanned if needed)
1921                        // so that type matching works correctly for multi-variant untagged enums
1922                        VariantFormat::NewtypeScalar {
1923                            inner_shape: classification_shape,
1924                        }
1925                    } else if let Some(arity) = tuple_struct_arity(classification_shape) {
1926                        VariantFormat::NewtypeTuple { inner_shape, arity }
1927                    } else if is_named_struct_shape(classification_shape)
1928                        || is_map_shape(classification_shape)
1929                    {
1930                        VariantFormat::NewtypeStruct { inner_shape }
1931                    } else if is_sequence_shape(classification_shape) {
1932                        VariantFormat::NewtypeSequence { inner_shape }
1933                    } else {
1934                        VariantFormat::NewtypeOther { inner_shape }
1935                    }
1936                } else {
1937                    // Multi-field tuple variant
1938                    VariantFormat::Tuple {
1939                        arity: fields.len(),
1940                    }
1941                }
1942            }
1943            StructKind::Struct => VariantFormat::Struct,
1944        }
1945    }
1946
1947    /// Returns true if this variant expects a scalar value in untagged format.
1948    pub const fn expects_scalar(&self) -> bool {
1949        matches!(self, VariantFormat::NewtypeScalar { .. })
1950    }
1951
1952    /// Returns true if this variant expects a sequence in untagged format.
1953    pub const fn expects_sequence(&self) -> bool {
1954        matches!(
1955            self,
1956            VariantFormat::Tuple { .. }
1957                | VariantFormat::NewtypeTuple { .. }
1958                | VariantFormat::NewtypeSequence { .. }
1959        )
1960    }
1961
1962    /// Returns true if this variant expects a mapping in untagged format.
1963    pub const fn expects_mapping(&self) -> bool {
1964        matches!(
1965            self,
1966            VariantFormat::Struct | VariantFormat::NewtypeStruct { .. }
1967        )
1968    }
1969
1970    /// Returns true if this is a unit variant (no data).
1971    pub const fn is_unit(&self) -> bool {
1972        matches!(self, VariantFormat::Unit)
1973    }
1974}
1975
1976/// Dereference through pointer types (like `Box<T>`) to get the pointee shape.
1977/// Returns the original shape if it's not a pointer.
1978fn deref_pointer(shape: &'static Shape) -> &'static Shape {
1979    use facet_core::Def;
1980
1981    match shape.def {
1982        Def::Pointer(pointer_def) => {
1983            if let Some(pointee) = pointer_def.pointee() {
1984                // Recursively dereference in case of nested pointers
1985                deref_pointer(pointee)
1986            } else {
1987                // Opaque pointer - can't dereference
1988                shape
1989            }
1990        }
1991        _ => shape,
1992    }
1993}
1994
1995/// Check if a shape represents a scalar type.
1996/// Transparently handles pointer types like `Box<i32>`.
1997fn is_scalar_shape(shape: &'static Shape) -> bool {
1998    let shape = deref_pointer(shape);
1999    shape.scalar_type().is_some()
2000}
2001
2002/// Returns the arity of a tuple struct/tuple shape, if applicable.
2003/// Transparently handles pointer types like `Box<(i32, i32)>`.
2004fn tuple_struct_arity(shape: &'static Shape) -> Option<usize> {
2005    use facet_core::{StructKind, Type, UserType};
2006
2007    let shape = deref_pointer(shape);
2008    match shape.ty {
2009        Type::User(UserType::Struct(struct_type)) => match struct_type.kind {
2010            StructKind::Tuple | StructKind::TupleStruct => Some(struct_type.fields.len()),
2011            _ => None,
2012        },
2013        _ => None,
2014    }
2015}
2016
2017/// Returns true if the shape is a named struct (non-tuple).
2018/// Transparently handles pointer types like `Box<MyStruct>`.
2019fn is_named_struct_shape(shape: &'static Shape) -> bool {
2020    use facet_core::{StructKind, Type, UserType};
2021
2022    let shape = deref_pointer(shape);
2023    matches!(
2024        shape.ty,
2025        Type::User(UserType::Struct(struct_type)) if matches!(struct_type.kind, StructKind::Struct)
2026    )
2027}
2028
2029/// Returns true if the shape is a sequence type (List, Array, Slice, Set).
2030/// These types serialize as arrays/sequences in formats like TOML, JSON, YAML.
2031/// Transparently handles pointer types like `Box<Vec<i32>>`.
2032fn is_sequence_shape(shape: &'static Shape) -> bool {
2033    use facet_core::Def;
2034
2035    let shape = deref_pointer(shape);
2036    matches!(
2037        shape.def,
2038        Def::List(_) | Def::Array(_) | Def::Slice(_) | Def::Set(_)
2039    )
2040}
2041
2042/// Check if a shape represents a map type (HashMap, BTreeMap, IndexMap, etc.)
2043fn is_map_shape(shape: &'static Shape) -> bool {
2044    use facet_core::Def;
2045
2046    let shape = deref_pointer(shape);
2047    matches!(shape.def, Def::Map(_))
2048}
2049
2050/// Returns true if the shape is a unit-only enum.
2051/// Unit-only enums serialize as strings in most formats (TOML, JSON, YAML).
2052/// Transparently handles pointer types like `Box<UnitEnum>`.
2053fn is_unit_enum_shape(shape: &'static Shape) -> bool {
2054    use facet_core::{Type, UserType};
2055
2056    let shape = deref_pointer(shape);
2057    match shape.ty {
2058        Type::User(UserType::Enum(enum_type)) => {
2059            // Check if all variants are unit variants
2060            enum_type.variants.iter().all(|v| v.data.fields.is_empty())
2061        }
2062        _ => false,
2063    }
2064}
2065
2066/// Information about variants grouped by their expected format.
2067///
2068/// Used by deserializers to efficiently dispatch untagged enum parsing
2069/// based on the type of value encountered.
2070#[derive(Debug, Default)]
2071pub struct VariantsByFormat {
2072    /// Variants that expect a scalar value (newtype wrapping String, i32, etc.)
2073    ///
2074    /// **Deprecated:** Use the type-specific fields below for better type matching.
2075    /// This field contains all scalar variants regardless of type.
2076    pub scalar_variants: Vec<(&'static Variant, &'static Shape)>,
2077
2078    /// Variants that expect a boolean value (newtype wrapping bool)
2079    pub bool_variants: Vec<(&'static Variant, &'static Shape)>,
2080
2081    /// Variants that expect an integer value (newtype wrapping i8, u8, i32, u64, etc.)
2082    pub int_variants: Vec<(&'static Variant, &'static Shape)>,
2083
2084    /// Variants that expect a float value (newtype wrapping f32, f64)
2085    pub float_variants: Vec<(&'static Variant, &'static Shape)>,
2086
2087    /// Variants that expect a string value (newtype wrapping String, `&str`, `Cow<str>`)
2088    pub string_variants: Vec<(&'static Variant, &'static Shape)>,
2089
2090    /// Variants that expect a sequence (tuple variants)
2091    /// Grouped by arity for efficient matching.
2092    pub tuple_variants: Vec<(&'static Variant, usize)>,
2093
2094    /// Variants that expect a mapping (struct variants, newtype wrapping struct)
2095    pub struct_variants: Vec<&'static Variant>,
2096
2097    /// Unit variants (no data)
2098    pub unit_variants: Vec<&'static Variant>,
2099
2100    /// Other variants that don't fit the above categories
2101    pub other_variants: Vec<&'static Variant>,
2102}
2103
2104impl VariantsByFormat {
2105    /// Build variant classification for an enum shape.
2106    ///
2107    /// Returns None if the shape is not an enum.
2108    pub fn from_shape(shape: &'static Shape) -> Option<Self> {
2109        use facet_core::{Type, UserType};
2110
2111        let enum_type = match shape.ty {
2112            Type::User(UserType::Enum(e)) => e,
2113            _ => return None,
2114        };
2115
2116        let mut result = Self::default();
2117
2118        for variant in enum_type.variants {
2119            match VariantFormat::from_variant(variant) {
2120                VariantFormat::Unit => {
2121                    result.unit_variants.push(variant);
2122                }
2123                VariantFormat::NewtypeScalar { inner_shape } => {
2124                    // Add to general scalar_variants (for backward compatibility)
2125                    result.scalar_variants.push((variant, inner_shape));
2126
2127                    // Classify by specific scalar type for better type matching
2128                    // Dereference through pointers (Box, &, etc.) to get the actual scalar type
2129                    use facet_core::ScalarType;
2130                    let scalar_shape = deref_pointer(inner_shape);
2131                    match scalar_shape.scalar_type() {
2132                        Some(ScalarType::Bool) => {
2133                            result.bool_variants.push((variant, inner_shape));
2134                        }
2135                        Some(
2136                            ScalarType::U8
2137                            | ScalarType::U16
2138                            | ScalarType::U32
2139                            | ScalarType::U64
2140                            | ScalarType::U128
2141                            | ScalarType::USize
2142                            | ScalarType::I8
2143                            | ScalarType::I16
2144                            | ScalarType::I32
2145                            | ScalarType::I64
2146                            | ScalarType::I128
2147                            | ScalarType::ISize,
2148                        ) => {
2149                            result.int_variants.push((variant, inner_shape));
2150                        }
2151                        Some(ScalarType::F32 | ScalarType::F64) => {
2152                            result.float_variants.push((variant, inner_shape));
2153                        }
2154                        #[cfg(feature = "alloc")]
2155                        Some(ScalarType::String | ScalarType::CowStr) => {
2156                            result.string_variants.push((variant, inner_shape));
2157                        }
2158                        Some(ScalarType::Str | ScalarType::Char) => {
2159                            result.string_variants.push((variant, inner_shape));
2160                        }
2161                        _ => {
2162                            // Other scalar types (Unit, SocketAddr, IpAddr, etc.) - leave in general scalar_variants only
2163                        }
2164                    }
2165                }
2166                VariantFormat::NewtypeStruct { .. } => {
2167                    result.struct_variants.push(variant);
2168                }
2169                VariantFormat::NewtypeTuple { arity, .. } => {
2170                    result.tuple_variants.push((variant, arity));
2171                }
2172                VariantFormat::NewtypeSequence { .. } => {
2173                    // Sequences like Vec<T> are variable-length, so we use arity 0
2174                    // to indicate "accepts any array" (not an exact match requirement)
2175                    result.tuple_variants.push((variant, 0));
2176                }
2177                VariantFormat::NewtypeOther { .. } => {
2178                    result.other_variants.push(variant);
2179                }
2180                VariantFormat::Tuple { arity } => {
2181                    result.tuple_variants.push((variant, arity));
2182                }
2183                VariantFormat::Struct => {
2184                    result.struct_variants.push(variant);
2185                }
2186            }
2187        }
2188
2189        Some(result)
2190    }
2191
2192    /// Get tuple variants with a specific arity.
2193    pub fn tuple_variants_with_arity(&self, arity: usize) -> Vec<&'static Variant> {
2194        self.tuple_variants
2195            .iter()
2196            .filter(|(_, a)| *a == arity)
2197            .map(|(v, _)| *v)
2198            .collect()
2199    }
2200
2201    /// Check if there are any scalar-expecting variants.
2202    pub const fn has_scalar_variants(&self) -> bool {
2203        !self.scalar_variants.is_empty()
2204    }
2205
2206    /// Check if there are any tuple-expecting variants.
2207    pub const fn has_tuple_variants(&self) -> bool {
2208        !self.tuple_variants.is_empty()
2209    }
2210
2211    /// Check if there are any struct-expecting variants.
2212    pub const fn has_struct_variants(&self) -> bool {
2213        !self.struct_variants.is_empty()
2214    }
2215}
2216
2217// ============================================================================
2218// Schema Builder
2219// ============================================================================
2220
2221/// How enum variants are represented in the serialized format.
2222#[derive(Debug, Clone, PartialEq, Eq, Default)]
2223#[non_exhaustive]
2224pub enum EnumRepr {
2225    /// Variant fields are flattened to the same level as other fields.
2226    /// Also used for `#[facet(untagged)]` enums where there's no tag at all.
2227    /// Used by formats like TOML where all fields appear at one level.
2228    /// Example: `{"name": "...", "host": "...", "port": 8080}`
2229    #[default]
2230    Flattened,
2231
2232    /// Variant name is a key, variant content is nested under it.
2233    /// This is the default serde representation for enums.
2234    /// Example: `{"name": "...", "Tcp": {"host": "...", "port": 8080}}`
2235    ExternallyTagged,
2236
2237    /// Tag field is inside the content, alongside variant fields.
2238    /// Used with `#[facet(tag = "type")]`.
2239    /// Example: `{"type": "Tcp", "host": "...", "port": 8080}`
2240    InternallyTagged {
2241        /// The name of the tag field (e.g., "type")
2242        tag: &'static str,
2243    },
2244
2245    /// Tag and content are adjacent fields at the same level.
2246    /// Used with `#[facet(tag = "t", content = "c")]`.
2247    /// Example: `{"t": "Tcp", "c": {"host": "...", "port": 8080}}`
2248    AdjacentlyTagged {
2249        /// The name of the tag field (e.g., "t")
2250        tag: &'static str,
2251        /// The name of the content field (e.g., "c")
2252        content: &'static str,
2253    },
2254}
2255
2256impl EnumRepr {
2257    /// Detect the enum representation from a Shape's attributes.
2258    ///
2259    /// Returns:
2260    /// - `Flattened` if `#[facet(untagged)]`
2261    /// - `InternallyTagged` if `#[facet(tag = "...")]` without content
2262    /// - `AdjacentlyTagged` if both `#[facet(tag = "...", content = "...")]`
2263    /// - `ExternallyTagged` if no attributes (the default enum representation)
2264    pub const fn from_shape(shape: &'static Shape) -> Self {
2265        let tag = shape.get_tag_attr();
2266        let content = shape.get_content_attr();
2267        let untagged = shape.is_untagged();
2268
2269        match (tag, content, untagged) {
2270            // Untagged explicitly requested
2271            (_, _, true) => EnumRepr::Flattened,
2272            // Both tag and content specified → adjacently tagged
2273            (Some(t), Some(c), false) => EnumRepr::AdjacentlyTagged { tag: t, content: c },
2274            // Only tag specified → internally tagged
2275            (Some(t), None, false) => EnumRepr::InternallyTagged { tag: t },
2276            // No attributes → default to externally tagged (variant name as key)
2277            (None, None, false) => EnumRepr::ExternallyTagged,
2278            // Content without tag is invalid, treat as externally tagged
2279            (None, Some(_), false) => EnumRepr::ExternallyTagged,
2280        }
2281    }
2282}
2283
2284impl Schema {
2285    /// Build a schema for the given shape with flattened enum representation.
2286    ///
2287    /// Returns an error if the type definition contains conflicts, such as
2288    /// duplicate field names from parent and flattened structs.
2289    ///
2290    /// Note: This defaults to `Flattened` representation. For auto-detection
2291    /// based on `#[facet(tag = "...")]` attributes, use [`Schema::build_auto`].
2292    pub fn build(shape: &'static Shape) -> Result<Self, SchemaError> {
2293        Self::build_with_repr(shape, EnumRepr::Flattened)
2294    }
2295
2296    /// Build a schema with auto-detected enum representation based on each enum's attributes.
2297    ///
2298    /// This inspects each flattened enum's shape attributes to determine its representation:
2299    /// - `#[facet(untagged)]` → Flattened
2300    /// - `#[facet(tag = "type")]` → InternallyTagged
2301    /// - `#[facet(tag = "t", content = "c")]` → AdjacentlyTagged
2302    /// - No attributes → Flattened (for flatten solver behavior)
2303    ///
2304    /// For externally-tagged enums (variant name as key), use [`Schema::build_externally_tagged`].
2305    pub fn build_auto(shape: &'static Shape) -> Result<Self, SchemaError> {
2306        let builder = SchemaBuilder::new(shape, EnumRepr::Flattened).with_auto_detect();
2307        builder.into_schema()
2308    }
2309
2310    /// Build a schema for externally-tagged enum representation (e.g., JSON).
2311    ///
2312    /// In this representation, the variant name appears as a key and the
2313    /// variant's content is nested under it. The solver will only expect
2314    /// to see the variant name as a top-level key, not the variant's fields.
2315    pub fn build_externally_tagged(shape: &'static Shape) -> Result<Self, SchemaError> {
2316        Self::build_with_repr(shape, EnumRepr::ExternallyTagged)
2317    }
2318
2319    /// Build a schema with the specified enum representation.
2320    pub fn build_with_repr(shape: &'static Shape, repr: EnumRepr) -> Result<Self, SchemaError> {
2321        let builder = SchemaBuilder::new(shape, repr);
2322        builder.into_schema()
2323    }
2324
2325    /// Get the resolutions for this schema.
2326    pub fn resolutions(&self) -> &[Resolution] {
2327        &self.resolutions
2328    }
2329
2330    /// Get the format this schema was built for.
2331    pub const fn format(&self) -> Format {
2332        self.format
2333    }
2334
2335    /// Build a schema for DOM format (XML, HTML) with auto-detected enum representation.
2336    ///
2337    /// In DOM format, fields are categorized as attributes, elements, or text content.
2338    /// The solver uses `see_attribute()`, `see_element()`, etc. to report fields.
2339    pub fn build_dom(shape: &'static Shape) -> Result<Self, SchemaError> {
2340        let builder = SchemaBuilder::new(shape, EnumRepr::Flattened)
2341            .with_auto_detect()
2342            .with_format(Format::Dom);
2343        builder.into_schema()
2344    }
2345
2346    /// Build a schema with a specific format.
2347    pub fn build_with_format(shape: &'static Shape, format: Format) -> Result<Self, SchemaError> {
2348        let builder = SchemaBuilder::new(shape, EnumRepr::Flattened)
2349            .with_auto_detect()
2350            .with_format(format);
2351        builder.into_schema()
2352    }
2353}
2354
2355struct SchemaBuilder {
2356    shape: &'static Shape,
2357    enum_repr: EnumRepr,
2358    /// If true, detect enum representation from each enum's shape attributes.
2359    /// If false, use `enum_repr` for all enums.
2360    auto_detect_enum_repr: bool,
2361    /// The format to build the schema for.
2362    format: Format,
2363}
2364
2365impl SchemaBuilder {
2366    const fn new(shape: &'static Shape, enum_repr: EnumRepr) -> Self {
2367        Self {
2368            shape,
2369            enum_repr,
2370            auto_detect_enum_repr: false,
2371            format: Format::Flat,
2372        }
2373    }
2374
2375    const fn with_auto_detect(mut self) -> Self {
2376        self.auto_detect_enum_repr = true;
2377        self
2378    }
2379
2380    const fn with_format(mut self, format: Format) -> Self {
2381        self.format = format;
2382        self
2383    }
2384
2385    fn analyze(&self) -> Result<Vec<Resolution>, SchemaError> {
2386        self.analyze_shape(self.shape, FieldPath::empty(), Vec::new())
2387    }
2388
2389    /// Analyze a shape and return all possible resolutions.
2390    /// Returns a Vec because enums create multiple resolutions.
2391    ///
2392    /// - `current_path`: The internal field path (for FieldInfo)
2393    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2394    fn analyze_shape(
2395        &self,
2396        shape: &'static Shape,
2397        current_path: FieldPath,
2398        key_prefix: KeyPath,
2399    ) -> Result<Vec<Resolution>, SchemaError> {
2400        match shape.ty {
2401            Type::User(UserType::Struct(struct_type)) => {
2402                self.analyze_struct(struct_type, current_path, key_prefix)
2403            }
2404            Type::User(UserType::Enum(enum_type)) => {
2405                // Enum at root level: create one configuration per variant
2406                self.analyze_enum(shape, enum_type, current_path, key_prefix)
2407            }
2408            _ => {
2409                // For non-struct types at root level, return single empty config
2410                Ok(vec![Resolution::new()])
2411            }
2412        }
2413    }
2414
2415    /// Analyze an enum and return one configuration per variant.
2416    ///
2417    /// - `current_path`: The internal field path (for FieldInfo)
2418    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2419    fn analyze_enum(
2420        &self,
2421        shape: &'static Shape,
2422        enum_type: facet_core::EnumType,
2423        current_path: FieldPath,
2424        key_prefix: KeyPath,
2425    ) -> Result<Vec<Resolution>, SchemaError> {
2426        let enum_name = shape.type_identifier;
2427        let mut result = Vec::new();
2428
2429        for variant in enum_type.variants {
2430            let mut config = Resolution::new();
2431
2432            // Record this variant selection
2433            config.add_variant_selection(current_path.clone(), enum_name, variant.name);
2434
2435            let variant_path = current_path.push_variant("", variant.name);
2436
2437            // Get resolutions from the variant's content
2438            let variant_configs =
2439                self.analyze_variant_content(variant, &variant_path, &key_prefix)?;
2440
2441            // Merge each variant config into the base
2442            for variant_config in variant_configs {
2443                let mut final_config = config.clone();
2444                final_config.merge(&variant_config)?;
2445                result.push(final_config);
2446            }
2447        }
2448
2449        Ok(result)
2450    }
2451
2452    /// Analyze a struct and return all possible resolutions.
2453    ///
2454    /// - `current_path`: The internal field path (for FieldInfo)
2455    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2456    fn analyze_struct(
2457        &self,
2458        struct_type: StructType,
2459        current_path: FieldPath,
2460        key_prefix: KeyPath,
2461    ) -> Result<Vec<Resolution>, SchemaError> {
2462        // Start with one empty configuration
2463        let mut configs = vec![Resolution::new()];
2464
2465        // Process each field, potentially multiplying resolutions
2466        for field in struct_type.fields {
2467            configs =
2468                self.analyze_field_into_configs(field, &current_path, &key_prefix, configs)?;
2469        }
2470
2471        Ok(configs)
2472    }
2473
2474    /// Process a field and return updated resolutions.
2475    /// If the field is a flattened enum, this may multiply the number of configs.
2476    ///
2477    /// - `parent_path`: The internal field path to the parent (for FieldInfo)
2478    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2479    fn analyze_field_into_configs(
2480        &self,
2481        field: &'static Field,
2482        parent_path: &FieldPath,
2483        key_prefix: &KeyPath,
2484        mut configs: Vec<Resolution>,
2485    ) -> Result<Vec<Resolution>, SchemaError> {
2486        let is_flatten = field.is_flattened();
2487
2488        if is_flatten {
2489            // Flattened: inner keys bubble up to current level (same key_prefix)
2490            self.analyze_flattened_field_into_configs(field, parent_path, key_prefix, configs)
2491        } else {
2492            // Regular field: add to ALL current configs
2493            let field_path = parent_path.push_field(field.name);
2494            let required = !field.has_default() && !is_option_type(field.shape());
2495
2496            // Build the key path for this field (uses effective_name for wire format)
2497            let mut field_key_path = key_prefix.clone();
2498            field_key_path.push(field.effective_name());
2499
2500            let field_info = FieldInfo {
2501                serialized_name: field.effective_name(),
2502                path: field_path,
2503                required,
2504                value_shape: field.shape(),
2505                field,
2506                category: if self.format == Format::Dom {
2507                    FieldCategory::from_field_dom(field).unwrap_or(FieldCategory::Element)
2508                } else {
2509                    FieldCategory::Flat
2510                },
2511            };
2512
2513            for config in &mut configs {
2514                config.add_field(field_info.clone())?;
2515                // Add this field's key path
2516                config.add_key_path(field_key_path.clone());
2517            }
2518
2519            // If the field's value is a struct, recurse to collect nested key paths
2520            // (for probing, not for flattening - these are nested in serialized format)
2521            // This may fork resolutions if the nested struct contains flattened enums!
2522            configs =
2523                self.collect_nested_key_paths_for_shape(field.shape(), &field_key_path, configs)?;
2524
2525            Ok(configs)
2526        }
2527    }
2528
2529    /// Collect nested key paths from a shape into resolutions.
2530    /// This handles the case where a non-flattened field contains a struct with flattened enums.
2531    /// Returns updated resolutions (may fork if flattened enums are encountered).
2532    fn collect_nested_key_paths_for_shape(
2533        &self,
2534        shape: &'static Shape,
2535        key_prefix: &KeyPath,
2536        configs: Vec<Resolution>,
2537    ) -> Result<Vec<Resolution>, SchemaError> {
2538        match shape.ty {
2539            Type::User(UserType::Struct(struct_type)) => {
2540                self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
2541            }
2542            _ => Ok(configs),
2543        }
2544    }
2545
2546    /// Collect nested key paths from a struct, potentially forking for flattened enums.
2547    fn collect_nested_key_paths_for_struct(
2548        &self,
2549        struct_type: StructType,
2550        key_prefix: &KeyPath,
2551        mut configs: Vec<Resolution>,
2552    ) -> Result<Vec<Resolution>, SchemaError> {
2553        for field in struct_type.fields {
2554            let is_flatten = field.is_flattened();
2555            let mut field_key_path = key_prefix.clone();
2556
2557            if is_flatten {
2558                // Flattened field: keys bubble up to current level, may fork configs
2559                configs =
2560                    self.collect_nested_key_paths_for_flattened(field, key_prefix, configs)?;
2561            } else {
2562                // Regular field: add key path and recurse
2563                field_key_path.push(field.effective_name());
2564
2565                for config in &mut configs {
2566                    config.add_key_path(field_key_path.clone());
2567                }
2568
2569                // Recurse into nested structs
2570                configs = self.collect_nested_key_paths_for_shape(
2571                    field.shape(),
2572                    &field_key_path,
2573                    configs,
2574                )?;
2575            }
2576        }
2577        Ok(configs)
2578    }
2579
2580    /// Handle flattened fields when collecting nested key paths.
2581    /// This may fork resolutions for flattened enums.
2582    fn collect_nested_key_paths_for_flattened(
2583        &self,
2584        field: &'static Field,
2585        key_prefix: &KeyPath,
2586        configs: Vec<Resolution>,
2587    ) -> Result<Vec<Resolution>, SchemaError> {
2588        let shape = field.shape();
2589
2590        match shape.ty {
2591            Type::User(UserType::Struct(struct_type)) => {
2592                // Flattened struct: recurse with same key_prefix
2593                self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
2594            }
2595            Type::User(UserType::Enum(enum_type)) => {
2596                // Flattened enum: fork resolutions
2597                // We need to match each config to its corresponding variant
2598                let mut result = Vec::new();
2599
2600                for config in configs {
2601                    // Find which variant this config has selected for this field
2602                    let selected_variant = config
2603                        .variant_selections()
2604                        .iter()
2605                        .find(|vs| {
2606                            // Match by the field name in the path
2607                            vs.path.segments().last() == Some(&PathSegment::Field(field.name))
2608                        })
2609                        .map(|vs| vs.variant_name);
2610
2611                    if let Some(variant_name) = selected_variant {
2612                        // Find the variant and collect its key paths
2613                        if let Some(variant) =
2614                            enum_type.variants.iter().find(|v| v.name == variant_name)
2615                        {
2616                            let mut updated_config = config;
2617                            updated_config = self.collect_variant_key_paths(
2618                                variant,
2619                                key_prefix,
2620                                updated_config,
2621                            )?;
2622                            result.push(updated_config);
2623                        } else {
2624                            result.push(config);
2625                        }
2626                    } else {
2627                        result.push(config);
2628                    }
2629                }
2630                Ok(result)
2631            }
2632            _ => Ok(configs),
2633        }
2634    }
2635
2636    /// Collect key paths from an enum variant's content.
2637    fn collect_variant_key_paths(
2638        &self,
2639        variant: &'static Variant,
2640        key_prefix: &KeyPath,
2641        mut config: Resolution,
2642    ) -> Result<Resolution, SchemaError> {
2643        // Check if this is a newtype variant (single unnamed field)
2644        if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2645            let inner_field = &variant.data.fields[0];
2646            let inner_shape = inner_field.shape();
2647
2648            // If the inner type is a struct, flatten its fields
2649            if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
2650                let configs = self.collect_nested_key_paths_for_struct(
2651                    inner_struct,
2652                    key_prefix,
2653                    vec![config],
2654                )?;
2655                return Ok(configs.into_iter().next().unwrap_or_else(Resolution::new));
2656            }
2657        }
2658
2659        // Named fields - process each
2660        for variant_field in variant.data.fields {
2661            let is_flatten = variant_field.is_flattened();
2662
2663            if is_flatten {
2664                let configs = self.collect_nested_key_paths_for_flattened(
2665                    variant_field,
2666                    key_prefix,
2667                    vec![config],
2668                )?;
2669                config = configs.into_iter().next().unwrap_or_else(Resolution::new);
2670            } else {
2671                let mut field_key_path = key_prefix.clone();
2672                field_key_path.push(variant_field.effective_name());
2673                config.add_key_path(field_key_path.clone());
2674
2675                let configs = self.collect_nested_key_paths_for_shape(
2676                    variant_field.shape(),
2677                    &field_key_path,
2678                    vec![config],
2679                )?;
2680                config = configs.into_iter().next().unwrap_or_else(Resolution::new);
2681            }
2682        }
2683        Ok(config)
2684    }
2685
2686    /// Collect ONLY key paths from a variant's content (no fields added).
2687    /// Used for externally-tagged enums where variant content is nested and
2688    /// will be parsed separately by the deserializer.
2689    fn collect_variant_key_paths_only(
2690        &self,
2691        variant: &'static Variant,
2692        key_prefix: &KeyPath,
2693        config: &mut Resolution,
2694    ) -> Result<(), SchemaError> {
2695        Self::collect_variant_fields_key_paths_only(variant, key_prefix, config);
2696        Ok(())
2697    }
2698
2699    /// Recursively collect key paths from a struct (no fields added).
2700    fn collect_struct_key_paths_only(
2701        struct_type: StructType,
2702        key_prefix: &KeyPath,
2703        config: &mut Resolution,
2704    ) {
2705        for field in struct_type.fields {
2706            let is_flatten = field.is_flattened();
2707
2708            if is_flatten {
2709                // Flattened field: keys bubble up to current level
2710                Self::collect_shape_key_paths_only(field.shape(), key_prefix, config);
2711            } else {
2712                // Regular field: add its key path
2713                let mut field_key_path = key_prefix.clone();
2714                field_key_path.push(field.effective_name());
2715                config.add_key_path(field_key_path.clone());
2716
2717                // Recurse into nested types
2718                Self::collect_shape_key_paths_only(field.shape(), &field_key_path, config);
2719            }
2720        }
2721    }
2722
2723    /// Recursively collect key paths from a shape (struct or enum).
2724    fn collect_shape_key_paths_only(
2725        shape: &'static Shape,
2726        key_prefix: &KeyPath,
2727        config: &mut Resolution,
2728    ) {
2729        match shape.ty {
2730            Type::User(UserType::Struct(inner_struct)) => {
2731                Self::collect_struct_key_paths_only(inner_struct, key_prefix, config);
2732            }
2733            Type::User(UserType::Enum(enum_type)) => {
2734                // For enums, collect key paths from ALL variants
2735                // (we don't know which variant will be selected)
2736                for variant in enum_type.variants {
2737                    Self::collect_variant_fields_key_paths_only(variant, key_prefix, config);
2738                }
2739            }
2740            _ => {}
2741        }
2742    }
2743
2744    /// Collect key paths from a variant's fields (not the variant itself).
2745    fn collect_variant_fields_key_paths_only(
2746        variant: &'static Variant,
2747        key_prefix: &KeyPath,
2748        config: &mut Resolution,
2749    ) {
2750        // Check if this is a newtype variant (single unnamed field)
2751        if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2752            let inner_field = &variant.data.fields[0];
2753            Self::collect_shape_key_paths_only(inner_field.shape(), key_prefix, config);
2754            return;
2755        }
2756
2757        // Named fields - add key paths for each
2758        for variant_field in variant.data.fields {
2759            let mut field_key_path = key_prefix.clone();
2760            field_key_path.push(variant_field.effective_name());
2761            config.add_key_path(field_key_path.clone());
2762
2763            // Recurse into nested types
2764            Self::collect_shape_key_paths_only(variant_field.shape(), &field_key_path, config);
2765        }
2766    }
2767
2768    /// Process a flattened field, potentially forking resolutions for enums.
2769    ///
2770    /// For flattened fields, the inner keys bubble up to the current level,
2771    /// so we pass the same key_prefix (not key_prefix + field.name).
2772    ///
2773    /// If the field is `Option<T>`, we unwrap to get T and mark all resulting
2774    /// fields as optional (since the entire flattened block can be omitted).
2775    fn analyze_flattened_field_into_configs(
2776        &self,
2777        field: &'static Field,
2778        parent_path: &FieldPath,
2779        key_prefix: &KeyPath,
2780        configs: Vec<Resolution>,
2781    ) -> Result<Vec<Resolution>, SchemaError> {
2782        let field_path = parent_path.push_field(field.name);
2783        let original_shape = field.shape();
2784
2785        // Check if this is Option<T> - if so, unwrap and mark all fields optional
2786        let (shape, is_optional_flatten) = match unwrap_option_type(original_shape) {
2787            Some(inner) => (inner, true),
2788            None => (original_shape, false),
2789        };
2790
2791        match shape.ty {
2792            Type::User(UserType::Struct(struct_type)) => {
2793                // Flatten a struct: get its resolutions and merge into each of ours
2794                // Key prefix stays the same - inner keys bubble up
2795                let mut struct_configs =
2796                    self.analyze_struct(struct_type, field_path, key_prefix.clone())?;
2797
2798                // If the flatten field was Option<T>, mark all inner fields as optional
2799                if is_optional_flatten {
2800                    for config in &mut struct_configs {
2801                        config.mark_all_optional();
2802                    }
2803                }
2804
2805                // Each of our configs combines with each struct config
2806                // (usually struct_configs has 1 element unless it contains enums)
2807                let mut result = Vec::new();
2808                for base_config in configs {
2809                    for struct_config in &struct_configs {
2810                        let mut merged = base_config.clone();
2811                        merged.merge(struct_config)?;
2812                        result.push(merged);
2813                    }
2814                }
2815                Ok(result)
2816            }
2817            Type::User(UserType::Enum(enum_type)) => {
2818                // Fork: each existing config × each variant
2819                let mut result = Vec::new();
2820                let enum_name = shape.type_identifier;
2821
2822                // Determine enum representation:
2823                // - If auto_detect_enum_repr is enabled, detect from the enum's shape attributes
2824                // - Otherwise, use the global enum_repr setting
2825                let enum_repr = if self.auto_detect_enum_repr {
2826                    EnumRepr::from_shape(shape)
2827                } else {
2828                    self.enum_repr.clone()
2829                };
2830
2831                for base_config in configs {
2832                    for variant in enum_type.variants {
2833                        let mut forked = base_config.clone();
2834                        forked.add_variant_selection(field_path.clone(), enum_name, variant.name);
2835
2836                        let variant_path = field_path.push_variant(field.name, variant.name);
2837
2838                        match &enum_repr {
2839                            EnumRepr::ExternallyTagged => {
2840                                // For externally tagged enums, the variant name is a key
2841                                // at the current level, and its content is nested underneath.
2842                                let mut variant_key_prefix = key_prefix.clone();
2843                                variant_key_prefix.push(variant.name);
2844
2845                                // Add the variant name itself as a known key path
2846                                forked.add_key_path(variant_key_prefix.clone());
2847
2848                                // Add the variant name as a field (the key that selects this variant)
2849                                let variant_field_info = FieldInfo {
2850                                    serialized_name: variant.name,
2851                                    path: variant_path.clone(),
2852                                    required: !is_optional_flatten,
2853                                    value_shape: shape, // The enum shape
2854                                    field,              // The original flatten field
2855                                    category: FieldCategory::Element, // Variant selector is like an element
2856                                };
2857                                forked.add_field(variant_field_info)?;
2858
2859                                // For externally-tagged enums, we do NOT add the variant's
2860                                // inner fields to required fields. They're nested and will
2861                                // be parsed separately by the deserializer.
2862                                // Only add them to known_paths for depth-aware probing.
2863                                self.collect_variant_key_paths_only(
2864                                    variant,
2865                                    &variant_key_prefix,
2866                                    &mut forked,
2867                                )?;
2868
2869                                result.push(forked);
2870                            }
2871                            EnumRepr::Flattened => {
2872                                // For flattened/untagged enums, the variant's fields appear at the
2873                                // same level as other fields. The variant name is NOT a key;
2874                                // only the variant's inner fields are keys.
2875
2876                                // Get resolutions from the variant's content
2877                                // Key prefix stays the same - inner keys bubble up
2878                                let mut variant_configs = self.analyze_variant_content(
2879                                    variant,
2880                                    &variant_path,
2881                                    key_prefix,
2882                                )?;
2883
2884                                // If the flatten field was Option<T>, mark all inner fields as optional
2885                                if is_optional_flatten {
2886                                    for config in &mut variant_configs {
2887                                        config.mark_all_optional();
2888                                    }
2889                                }
2890
2891                                // Merge each variant config into the forked base
2892                                for variant_config in variant_configs {
2893                                    let mut final_config = forked.clone();
2894                                    final_config.merge(&variant_config)?;
2895                                    result.push(final_config);
2896                                }
2897                            }
2898                            EnumRepr::InternallyTagged { tag } => {
2899                                // For internally tagged enums, the tag field appears at the
2900                                // same level as the variant's fields.
2901                                // Example: {"type": "Tcp", "host": "...", "port": 8080}
2902
2903                                // Add the tag field as a known key path
2904                                let mut tag_key_path = key_prefix.clone();
2905                                tag_key_path.push(tag);
2906                                forked.add_key_path(tag_key_path);
2907
2908                                // Add the tag field info - the tag discriminates the variant
2909                                // We use a synthetic field for the tag
2910                                let tag_field_info = FieldInfo {
2911                                    serialized_name: tag,
2912                                    path: variant_path.clone(),
2913                                    required: !is_optional_flatten,
2914                                    value_shape: shape, // The enum shape
2915                                    field,              // The original flatten field
2916                                    category: FieldCategory::Element, // Tag is a key field
2917                                };
2918                                forked.add_field(tag_field_info)?;
2919
2920                                // Get resolutions from the variant's content
2921                                // Key prefix stays the same - inner keys are at the same level
2922                                let mut variant_configs = self.analyze_variant_content(
2923                                    variant,
2924                                    &variant_path,
2925                                    key_prefix,
2926                                )?;
2927
2928                                // If the flatten field was Option<T>, mark all inner fields as optional
2929                                if is_optional_flatten {
2930                                    for config in &mut variant_configs {
2931                                        config.mark_all_optional();
2932                                    }
2933                                }
2934
2935                                // Merge each variant config into the forked base
2936                                for variant_config in variant_configs {
2937                                    let mut final_config = forked.clone();
2938                                    final_config.merge(&variant_config)?;
2939                                    result.push(final_config);
2940                                }
2941                            }
2942                            EnumRepr::AdjacentlyTagged { tag, content } => {
2943                                // For adjacently tagged enums, both tag and content fields
2944                                // appear at the same level. Content contains the variant's fields.
2945                                // Example: {"t": "Tcp", "c": {"host": "...", "port": 8080}}
2946
2947                                // Add the tag field as a known key path
2948                                let mut tag_key_path = key_prefix.clone();
2949                                tag_key_path.push(tag);
2950                                forked.add_key_path(tag_key_path);
2951
2952                                // Add the tag field info
2953                                let tag_field_info = FieldInfo {
2954                                    serialized_name: tag,
2955                                    path: variant_path.clone(),
2956                                    required: !is_optional_flatten,
2957                                    value_shape: shape, // The enum shape
2958                                    field,              // The original flatten field
2959                                    category: FieldCategory::Element, // Tag is a key field
2960                                };
2961                                forked.add_field(tag_field_info)?;
2962
2963                                // Add the content field as a known key path
2964                                let mut content_key_prefix = key_prefix.clone();
2965                                content_key_prefix.push(content);
2966                                forked.add_key_path(content_key_prefix.clone());
2967
2968                                // The variant's fields are nested under the content key
2969                                // Collect key paths for probing
2970                                self.collect_variant_key_paths_only(
2971                                    variant,
2972                                    &content_key_prefix,
2973                                    &mut forked,
2974                                )?;
2975
2976                                result.push(forked);
2977                            }
2978                        }
2979                    }
2980                }
2981                Ok(result)
2982            }
2983            _ => {
2984                // Check if this is a Map type - if so, it becomes a catch-all for unknown fields
2985                if let Def::Map(_) = &shape.def {
2986                    // Any map type can serve as a catch-all. Whether the key type can actually
2987                    // be deserialized from field name strings is the deserializer's problem,
2988                    // not the solver's.
2989                    let field_info = FieldInfo {
2990                        serialized_name: field.effective_name(),
2991                        path: field_path,
2992                        required: false, // Catch-all maps are never required
2993                        value_shape: shape,
2994                        field,
2995                        // For DOM format, determine if this catches attributes or elements
2996                        // based on the field's attributes
2997                        category: if self.format == Format::Dom {
2998                            if field.is_attribute() {
2999                                FieldCategory::Attribute
3000                            } else {
3001                                FieldCategory::Element
3002                            }
3003                        } else {
3004                            FieldCategory::Flat
3005                        },
3006                    };
3007
3008                    let mut result = configs;
3009                    for config in &mut result {
3010                        config.set_catch_all_map(field_info.category, field_info.clone());
3011                    }
3012                    return Ok(result);
3013                }
3014
3015                // Check if this is a DynamicValue type (like facet_value::Value) - also a catch-all
3016                if matches!(&shape.def, Def::DynamicValue(_)) {
3017                    let field_info = FieldInfo {
3018                        serialized_name: field.effective_name(),
3019                        path: field_path,
3020                        required: false, // Catch-all dynamic values are never required
3021                        value_shape: shape,
3022                        field,
3023                        category: if self.format == Format::Dom {
3024                            if field.is_attribute() {
3025                                FieldCategory::Attribute
3026                            } else {
3027                                FieldCategory::Element
3028                            }
3029                        } else {
3030                            FieldCategory::Flat
3031                        },
3032                    };
3033
3034                    let mut result = configs;
3035                    for config in &mut result {
3036                        config.set_catch_all_map(field_info.category, field_info.clone());
3037                    }
3038                    return Ok(result);
3039                }
3040
3041                // Can't flatten other types - treat as regular field
3042                // For Option<T> flatten, also consider optionality from the wrapper
3043                let required =
3044                    !field.has_default() && !is_option_type(shape) && !is_optional_flatten;
3045
3046                // For non-flattenable types, add the field with its key path
3047                let mut field_key_path = key_prefix.clone();
3048                field_key_path.push(field.effective_name());
3049
3050                let field_info = FieldInfo {
3051                    serialized_name: field.effective_name(),
3052                    path: field_path,
3053                    required,
3054                    value_shape: shape,
3055                    field,
3056                    category: if self.format == Format::Dom {
3057                        FieldCategory::from_field_dom(field).unwrap_or(FieldCategory::Element)
3058                    } else {
3059                        FieldCategory::Flat
3060                    },
3061                };
3062
3063                let mut result = configs;
3064                for config in &mut result {
3065                    config.add_field(field_info.clone())?;
3066                    config.add_key_path(field_key_path.clone());
3067                }
3068                Ok(result)
3069            }
3070        }
3071    }
3072
3073    /// Analyze a variant's content and return resolutions.
3074    ///
3075    /// - `variant_path`: The internal field path (for FieldInfo)
3076    /// - `key_prefix`: The serialized key path prefix (for known_paths)
3077    fn analyze_variant_content(
3078        &self,
3079        variant: &'static Variant,
3080        variant_path: &FieldPath,
3081        key_prefix: &KeyPath,
3082    ) -> Result<Vec<Resolution>, SchemaError> {
3083        // Check if this is a newtype variant (single unnamed field like `Foo(Bar)`)
3084        if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
3085            let inner_field = &variant.data.fields[0];
3086            let inner_shape = inner_field.shape();
3087
3088            // If the inner type is a struct, treat the newtype wrapper as transparent.
3089            //
3090            // Previously we pushed a synthetic `"0"` segment onto the path. That made the
3091            // solver think there was an extra field between the variant and the inner
3092            // struct (e.g., `backend.backend::Local.0.cache`). Format-specific flattening does not
3093            // expose that tuple wrapper, so the deserializer would try to open a field
3094            // named `"0"` on the inner struct/enum, causing "no such field" errors when
3095            // navigating paths like `backend::Local.cache`.
3096            //
3097            // Keep the synthetic `"0"` segment so the solver/reflect layer walks through
3098            // the tuple wrapper that Rust generates for newtype variants.
3099
3100            // For untagged enum variant resolution, we need to look at the "effective"
3101            // shape that determines the serialization format. This unwraps:
3102            // 1. Transparent wrappers (shape.inner) - e.g., `Curve64(GCurve<f64, f64>)`
3103            // 2. Proxy types (shape.proxy) - e.g., `GCurve` uses `GCurveProxy` for ser/de
3104            //
3105            // This ensures that `{"x":..., "y":...}` correctly matches `Linear(Curve64)`
3106            // where Curve64 is transparent around GCurve which has a proxy with x,y fields.
3107            let effective_shape = unwrap_to_effective_shape(inner_shape);
3108
3109            if let Type::User(UserType::Struct(inner_struct)) = effective_shape.ty {
3110                let inner_path = variant_path.push_field("0");
3111                return self.analyze_struct(inner_struct, inner_path, key_prefix.clone());
3112            }
3113        }
3114
3115        // Named fields or multiple fields - analyze as a pseudo-struct
3116        let mut configs = vec![Resolution::new()];
3117        for variant_field in variant.data.fields {
3118            configs =
3119                self.analyze_field_into_configs(variant_field, variant_path, key_prefix, configs)?;
3120        }
3121        Ok(configs)
3122    }
3123
3124    fn into_schema(self) -> Result<Schema, SchemaError> {
3125        let resolutions = self.analyze()?;
3126        let num_resolutions = resolutions.len();
3127
3128        // Build inverted index: field_name → bitmask of config indices (for Flat format)
3129        let mut field_to_resolutions: BTreeMap<&'static str, ResolutionSet> = BTreeMap::new();
3130        for (idx, config) in resolutions.iter().enumerate() {
3131            for field_info in config.fields().values() {
3132                field_to_resolutions
3133                    .entry(field_info.serialized_name)
3134                    .or_insert_with(|| ResolutionSet::empty(num_resolutions))
3135                    .insert(idx);
3136            }
3137        }
3138
3139        // Build DOM inverted index: (category, name) → bitmask of config indices
3140        let mut dom_field_to_resolutions: BTreeMap<(FieldCategory, &'static str), ResolutionSet> =
3141            BTreeMap::new();
3142        if self.format == Format::Dom {
3143            for (idx, config) in resolutions.iter().enumerate() {
3144                for field_info in config.fields().values() {
3145                    dom_field_to_resolutions
3146                        .entry((field_info.category, field_info.serialized_name))
3147                        .or_insert_with(|| ResolutionSet::empty(num_resolutions))
3148                        .insert(idx);
3149                }
3150            }
3151        }
3152
3153        Ok(Schema {
3154            shape: self.shape,
3155            format: self.format,
3156            resolutions,
3157            field_to_resolutions,
3158            dom_field_to_resolutions,
3159        })
3160    }
3161}
3162
3163/// Check if a shape represents an Option type.
3164const fn is_option_type(shape: &'static Shape) -> bool {
3165    matches!(shape.def, Def::Option(_))
3166}
3167
3168/// If shape is `Option<T>`, returns `Some(T's shape)`. Otherwise returns `None`.
3169const fn unwrap_option_type(shape: &'static Shape) -> Option<&'static Shape> {
3170    match shape.def {
3171        Def::Option(option_def) => Some(option_def.t),
3172        _ => None,
3173    }
3174}
3175
3176/// Unwrap transparent wrappers and proxies to get the effective shape for field matching.
3177///
3178/// When determining which untagged enum variant matches a set of fields, we need to
3179/// look at the "effective" shape that determines the serialization format:
3180///
3181/// 1. Transparent wrappers (shape.inner): e.g., `Curve64` wraps `GCurve<f64, f64>`
3182///    - The wrapper has no serialization presence; it serializes as its inner type
3183///
3184/// 2. Proxy types (shape.proxy): e.g., `GCurve` uses `GCurveProxy` for ser/de
3185///    - The proxy's fields are what appear in the serialized format
3186///
3187/// This function recursively unwraps these layers to find the shape whose fields
3188/// should be used for variant matching. For example:
3189/// - `Curve64` (transparent) → `GCurve<f64, f64>` (has proxy) → `GCurveProxy<f64, f64>`
3190fn unwrap_to_effective_shape(shape: &'static Shape) -> &'static Shape {
3191    // First, unwrap transparent wrappers
3192    let shape = unwrap_transparent(shape);
3193
3194    // Then, if there's a proxy, use its shape instead
3195    if let Some(proxy_def) = shape.proxy {
3196        // Recursively unwrap in case the proxy is also transparent or has its own proxy
3197        unwrap_to_effective_shape(proxy_def.shape)
3198    } else {
3199        shape
3200    }
3201}
3202
3203/// Recursively unwrap transparent wrappers to get to the innermost type.
3204fn unwrap_transparent(shape: &'static Shape) -> &'static Shape {
3205    if let Some(inner) = shape.inner {
3206        unwrap_transparent(inner)
3207    } else {
3208        shape
3209    }
3210}