facet_solver/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!("../README.md")]
3
4extern crate alloc;
5
6use alloc::collections::BTreeMap;
7use alloc::collections::BTreeSet;
8use alloc::string::{String, ToString};
9use alloc::vec;
10use alloc::vec::Vec;
11use core::fmt;
12
13use facet_core::{Def, Field, FieldFlags, Shape, StructType, Type, UserType, Variant};
14
15/// Cached schema for a type that may contain flattened fields.
16///
17/// This is computed once per Shape and can be cached forever since
18/// type information is static.
19#[derive(Debug)]
20pub struct Schema {
21 /// The shape this schema is for (kept for future caching key)
22 #[allow(dead_code)]
23 shape: &'static Shape,
24
25 /// All possible configurations of this type.
26 /// For types with no enums in flatten paths, this has exactly 1 entry.
27 /// For types with enums, this has one entry per valid combination of variants.
28 configurations: Vec<Configuration>,
29
30 /// Inverted index: field_name → bitmask of configuration indices.
31 /// Bit i is set if `configurations[i]` contains this field.
32 /// Uses a `Vec<u64>` to support arbitrary numbers of configurations.
33 field_to_configs: BTreeMap<&'static str, ConfigSet>,
34}
35
36/// A set of configuration indices, stored as a bitmask for O(1) intersection.
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct ConfigSet {
39 /// Bitmask where bit i indicates `configurations[i]` is in the set.
40 /// For most types, a single u64 suffices (up to 64 configs).
41 bits: Vec<u64>,
42 /// Number of configurations in the set.
43 count: usize,
44}
45
46impl ConfigSet {
47 /// Create an empty config set.
48 fn empty(num_configs: usize) -> Self {
49 let num_words = num_configs.div_ceil(64);
50 Self {
51 bits: vec![0; num_words],
52 count: 0,
53 }
54 }
55
56 /// Create a full config set (all configs present).
57 fn full(num_configs: usize) -> Self {
58 let num_words = num_configs.div_ceil(64);
59 let mut bits = vec![!0u64; num_words];
60 // Clear bits beyond num_configs
61 if num_configs % 64 != 0 {
62 let last_word_bits = num_configs % 64;
63 bits[num_words - 1] = (1u64 << last_word_bits) - 1;
64 }
65 Self {
66 bits,
67 count: num_configs,
68 }
69 }
70
71 /// Insert a configuration index.
72 fn insert(&mut self, idx: usize) {
73 let word = idx / 64;
74 let bit = idx % 64;
75 if self.bits[word] & (1u64 << bit) == 0 {
76 self.bits[word] |= 1u64 << bit;
77 self.count += 1;
78 }
79 }
80
81 /// Intersect with another config set in place.
82 fn intersect_with(&mut self, other: &ConfigSet) {
83 self.count = 0;
84 for (a, b) in self.bits.iter_mut().zip(other.bits.iter()) {
85 *a &= *b;
86 self.count += a.count_ones() as usize;
87 }
88 }
89
90 /// Get the number of configurations in the set.
91 fn len(&self) -> usize {
92 self.count
93 }
94
95 /// Check if empty.
96 fn is_empty(&self) -> bool {
97 self.count == 0
98 }
99
100 /// Get the first (lowest) configuration index in the set.
101 fn first(&self) -> Option<usize> {
102 for (word_idx, &word) in self.bits.iter().enumerate() {
103 if word != 0 {
104 return Some(word_idx * 64 + word.trailing_zeros() as usize);
105 }
106 }
107 None
108 }
109
110 /// Iterate over configuration indices in the set.
111 fn iter(&self) -> impl Iterator<Item = usize> + '_ {
112 self.bits.iter().enumerate().flat_map(|(word_idx, &word)| {
113 (0..64).filter_map(move |bit| {
114 if word & (1u64 << bit) != 0 {
115 Some(word_idx * 64 + bit)
116 } else {
117 None
118 }
119 })
120 })
121 }
122}
123
124/// One possible "shape" the flattened type could take.
125///
126/// Represents a specific choice of variants for all enums in the flatten tree.
127#[derive(Debug, Clone)]
128pub struct Configuration {
129 /// For each enum in the flatten path, which variant is selected.
130 /// The key is the path to the enum field, value is the variant.
131 variant_selections: Vec<VariantSelection>,
132
133 /// All fields in this configuration, keyed by serialized name.
134 fields: BTreeMap<&'static str, FieldInfo>,
135
136 /// Set of required field names (for quick matching)
137 required_field_names: BTreeSet<&'static str>,
138
139 /// All known key paths at all depths (for depth-aware probing).
140 /// Each path is a sequence of serialized key names from root.
141 /// E.g., for `{payload: {content: "hi"}}`, contains `["payload"]` and `["payload", "content"]`.
142 known_paths: BTreeSet<KeyPath>,
143}
144
145/// A path of serialized key names for probing.
146/// Unlike FieldPath which tracks the internal type structure (including variant selections),
147/// KeyPath only tracks the keys as they appear in the serialized format.
148pub type KeyPath = Vec<&'static str>;
149
150/// Records that a specific enum field has a specific variant selected.
151#[derive(Debug, Clone)]
152pub struct VariantSelection {
153 /// Path to the enum field from root
154 pub path: FieldPath,
155 /// Name of the enum type (e.g., "MessagePayload")
156 pub enum_name: &'static str,
157 /// Name of the selected variant (e.g., "Text")
158 pub variant_name: &'static str,
159}
160
161/// Information about a single field in a configuration.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct FieldInfo {
164 /// The name as it appears in the serialized format
165 pub serialized_name: &'static str,
166
167 /// Full path from root to this field
168 pub path: FieldPath,
169
170 /// Whether this field is required (not Option, no default)
171 pub required: bool,
172
173 /// The shape of this field's value
174 pub value_shape: &'static Shape,
175}
176
177/// A path through the type tree to a field.
178#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
179pub struct FieldPath {
180 segments: Vec<PathSegment>,
181}
182
183impl fmt::Debug for FieldPath {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 write!(f, "FieldPath(")?;
186 for (i, seg) in self.segments.iter().enumerate() {
187 if i > 0 {
188 write!(f, ".")?;
189 }
190 match seg {
191 PathSegment::Field(name) => write!(f, "{name}")?,
192 PathSegment::Variant(field, variant) => write!(f, "{field}::{variant}")?,
193 }
194 }
195 write!(f, ")")
196 }
197}
198
199impl fmt::Display for FieldPath {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 let mut first = true;
202 for seg in &self.segments {
203 match seg {
204 PathSegment::Field(name) => {
205 if !first {
206 write!(f, ".")?;
207 }
208 write!(f, "{name}")?;
209 first = false;
210 }
211 PathSegment::Variant(_, _) => {
212 // Skip variant segments in display path - they're internal
213 }
214 }
215 }
216 Ok(())
217 }
218}
219
220/// A segment in a field path.
221#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
222pub enum PathSegment {
223 /// A regular struct field
224 Field(&'static str),
225 /// An enum variant selection (field_name, variant_name)
226 Variant(&'static str, &'static str),
227}
228
229impl FieldPath {
230 /// Create an empty path (root level).
231 pub fn empty() -> Self {
232 Self {
233 segments: Vec::new(),
234 }
235 }
236
237 /// Get the depth of this path.
238 pub fn depth(&self) -> usize {
239 self.segments.len()
240 }
241
242 /// Push a field segment onto the path.
243 pub fn push_field(&self, name: &'static str) -> Self {
244 let mut new = self.clone();
245 new.segments.push(PathSegment::Field(name));
246 new
247 }
248
249 /// Push a variant segment onto the path.
250 pub fn push_variant(&self, field_name: &'static str, variant_name: &'static str) -> Self {
251 let mut new = self.clone();
252 new.segments
253 .push(PathSegment::Variant(field_name, variant_name));
254 new
255 }
256
257 /// Get the parent path (all segments except the last).
258 pub fn parent(&self) -> Self {
259 let mut new = self.clone();
260 new.segments.pop();
261 new
262 }
263
264 /// Get the segments of this path.
265 pub fn segments(&self) -> &[PathSegment] {
266 &self.segments
267 }
268
269 /// Get the last segment, if any.
270 pub fn last(&self) -> Option<&PathSegment> {
271 self.segments.last()
272 }
273}
274
275/// Result of matching input fields against a configuration.
276#[derive(Debug)]
277pub enum MatchResult {
278 /// All required fields present, all fields known
279 Exact,
280 /// All required fields present, some optional fields missing
281 WithOptionalMissing(Vec<&'static str>),
282 /// Does not match
283 NoMatch {
284 missing_required: Vec<&'static str>,
285 unknown: Vec<String>,
286 },
287}
288
289// ============================================================================
290// Incremental Solver
291// ============================================================================
292
293/// Incremental constraint solver for streaming deserialization.
294///
295/// Unlike batch solving where all field names are known upfront, this solver
296/// processes fields one at a time as they arrive. It minimizes deferred fields by
297/// immediately setting fields that have the same path in all remaining
298/// candidate configurations.
299///
300/// # Example
301///
302/// ```rust
303/// use facet::Facet;
304/// use facet_solver::{Schema, IncrementalSolver, FieldDecision};
305///
306/// #[derive(Facet)]
307/// struct SimpleConfig { port: u16 }
308///
309/// #[derive(Facet)]
310/// struct AdvancedConfig { host: String, port: u16 }
311///
312/// #[derive(Facet)]
313/// #[repr(u8)]
314/// enum Config {
315/// Simple(SimpleConfig),
316/// Advanced(AdvancedConfig),
317/// }
318///
319/// #[derive(Facet)]
320/// struct Server {
321/// name: String,
322/// #[facet(flatten)]
323/// config: Config,
324/// }
325///
326/// let schema = Schema::build(Server::SHAPE).unwrap();
327/// let mut solver = IncrementalSolver::new(&schema);
328///
329/// // "name" exists in all configs at the same path - set directly
330/// assert!(matches!(solver.see_key("name"), FieldDecision::SetDirectly(_)));
331///
332/// // "host" only exists in Advanced - disambiguates!
333/// match solver.see_key("host") {
334/// FieldDecision::Disambiguated { config, current_field } => {
335/// assert_eq!(current_field.serialized_name, "host");
336/// assert!(config.field("port").is_some()); // Advanced has port too
337/// }
338/// _ => panic!("expected disambiguation"),
339/// }
340/// ```
341#[derive(Debug)]
342pub struct IncrementalSolver<'a> {
343 /// Reference to the schema for configuration lookup
344 schema: &'a Schema,
345 /// Bitmask of remaining candidate configuration indices
346 candidates: ConfigSet,
347 /// Keys that have been deferred (for tracking purposes)
348 deferred_keys: Vec<&'a str>,
349}
350
351/// Result of presenting a key to the incremental solver.
352#[derive(Debug)]
353pub enum FieldDecision<'a> {
354 /// Field has the same path in all remaining candidates.
355 /// Set it directly - path is unambiguous.
356 SetDirectly(&'a FieldInfo),
357
358 /// Field exists in candidates but at different paths.
359 /// Defer (mark position for replay after disambiguation).
360 Defer,
361
362 /// This field narrowed candidates to exactly one.
363 /// Replay any deferred fields, then set this field.
364 Disambiguated {
365 /// The winning configuration
366 config: &'a Configuration,
367 /// Info for the field that caused disambiguation
368 current_field: &'a FieldInfo,
369 },
370
371 /// Field not found in any remaining candidate.
372 Unknown,
373}
374
375impl<'a> IncrementalSolver<'a> {
376 /// Create a new incremental solver from a schema.
377 pub fn new(schema: &'a Schema) -> Self {
378 Self {
379 schema,
380 candidates: ConfigSet::full(schema.configurations.len()),
381 deferred_keys: Vec::new(),
382 }
383 }
384
385 /// Get the current candidate configurations.
386 pub fn candidates(&self) -> Vec<&'a Configuration> {
387 self.candidates
388 .iter()
389 .map(|idx| &self.schema.configurations[idx])
390 .collect()
391 }
392
393 /// Process a field key. Returns what to do with the value.
394 pub fn see_key(&mut self, key: &'a str) -> FieldDecision<'a> {
395 // Use inverted index to find configs with this key - O(1) lookup!
396 let configs_with_key = match self.schema.field_to_configs.get(key) {
397 Some(set) => set,
398 None => return FieldDecision::Unknown,
399 };
400
401 // Intersect with current candidates - O(num_words) = O(configs/64)
402 let prev_count = self.candidates.len();
403 self.candidates.intersect_with(configs_with_key);
404
405 if self.candidates.is_empty() {
406 return FieldDecision::Unknown;
407 }
408
409 // Did this narrow our candidates?
410 let narrowed = self.candidates.len() < prev_count;
411
412 // Check if we've disambiguated to exactly one (but only if we actually narrowed)
413 if narrowed && self.candidates.len() == 1 {
414 let idx = self.candidates.first().unwrap();
415 let config = &self.schema.configurations[idx];
416 let current_field = config.field(key).unwrap();
417 return FieldDecision::Disambiguated {
418 config,
419 current_field,
420 };
421 }
422
423 // Check if path is the same in all remaining candidates
424 let mut paths = BTreeSet::new();
425 for idx in self.candidates.iter() {
426 let config = &self.schema.configurations[idx];
427 if let Some(info) = config.field(key) {
428 paths.insert(&info.path);
429 }
430 }
431
432 if paths.len() == 1 {
433 // Same path in all candidates - can set directly
434 let idx = self.candidates.first().unwrap();
435 FieldDecision::SetDirectly(self.schema.configurations[idx].field(key).unwrap())
436 } else {
437 // Different paths - defer until disambiguation
438 self.deferred_keys.push(key);
439 FieldDecision::Defer
440 }
441 }
442
443 /// Get the keys that have been deferred.
444 pub fn deferred_keys(&self) -> &[&'a str] {
445 &self.deferred_keys
446 }
447
448 /// Clear the deferred keys (call after replaying).
449 pub fn clear_deferred(&mut self) {
450 self.deferred_keys.clear();
451 }
452
453 /// Finish solving. Returns error if still ambiguous or missing required fields.
454 ///
455 /// When multiple candidates remain, this filters out any that are missing
456 /// required fields. This handles the case where one variant's fields are
457 /// a subset of another's (e.g., `Http { url }` vs `Git { url, branch }`).
458 /// If input only contains `url`, both match the "has this field" check,
459 /// but `Git` is filtered out because it's missing required `branch`.
460 pub fn finish(self, seen_keys: &BTreeSet<&str>) -> Result<&'a Configuration, SolverError> {
461 if self.candidates.is_empty() {
462 return Err(SolverError::NoMatch {
463 input_fields: seen_keys.iter().map(|s| (*s).into()).collect(),
464 missing_required: Vec::new(),
465 missing_required_detailed: Vec::new(),
466 unknown_fields: Vec::new(),
467 closest_config: None,
468 });
469 }
470
471 // Filter candidates to only those that have all required fields satisfied
472 let satisfied: Vec<usize> = self
473 .candidates
474 .iter()
475 .filter(|idx| {
476 let config = &self.schema.configurations[*idx];
477 config
478 .required_field_names
479 .iter()
480 .all(|f| seen_keys.contains(f))
481 })
482 .collect();
483
484 match satisfied.len() {
485 0 => {
486 // No config has all required fields satisfied.
487 // Find the "closest" match for a helpful error message.
488 let first_idx = self.candidates.first().unwrap();
489 let first_config = &self.schema.configurations[first_idx];
490 let missing: Vec<_> = first_config
491 .required_field_names
492 .iter()
493 .filter(|f| !seen_keys.contains(*f))
494 .copied()
495 .collect();
496 let missing_detailed: Vec<_> = missing
497 .iter()
498 .filter_map(|name| first_config.field(name))
499 .map(MissingFieldInfo::from_field_info)
500 .collect();
501 Err(SolverError::NoMatch {
502 input_fields: seen_keys.iter().map(|s| (*s).into()).collect(),
503 missing_required: missing,
504 missing_required_detailed: missing_detailed,
505 unknown_fields: Vec::new(),
506 closest_config: Some(first_config.describe()),
507 })
508 }
509 1 => {
510 let idx = satisfied[0];
511 Ok(&self.schema.configurations[idx])
512 }
513 _ => {
514 // Multiple configs still match after filtering - true ambiguity
515 // Find fields that could disambiguate (unique to only some configs)
516 let disambiguating = find_disambiguating_fields(
517 &satisfied
518 .iter()
519 .map(|i| &self.schema.configurations[*i])
520 .collect::<Vec<_>>(),
521 );
522 Err(SolverError::Ambiguous {
523 candidates: satisfied
524 .iter()
525 .map(|idx| self.schema.configurations[*idx].describe())
526 .collect(),
527 disambiguating_fields: disambiguating,
528 })
529 }
530 }
531 }
532}
533
534/// Find fields that could disambiguate between configurations.
535/// Returns fields that exist in some but not all configurations.
536fn find_disambiguating_fields(configs: &[&Configuration]) -> Vec<String> {
537 if configs.len() < 2 {
538 return Vec::new();
539 }
540
541 // Collect all field names across all configs
542 let mut all_fields: BTreeSet<&str> = BTreeSet::new();
543 for config in configs {
544 for name in config.fields.keys() {
545 all_fields.insert(name);
546 }
547 }
548
549 // Find fields that are in some but not all configs
550 let mut disambiguating = Vec::new();
551 for field in all_fields {
552 let count = configs.iter().filter(|c| c.field(field).is_some()).count();
553 if count > 0 && count < configs.len() {
554 disambiguating.push(field.to_string());
555 }
556 }
557
558 disambiguating
559}
560
561/// Information about a missing required field for error reporting.
562#[derive(Debug, Clone)]
563pub struct MissingFieldInfo {
564 /// The serialized field name (as it appears in input)
565 pub name: &'static str,
566 /// Full path to the field (e.g., "backend.connection.port")
567 pub path: String,
568 /// The Rust type that defines this field
569 pub defined_in: String,
570}
571
572impl MissingFieldInfo {
573 /// Create from a FieldInfo
574 fn from_field_info(info: &FieldInfo) -> Self {
575 Self {
576 name: info.serialized_name,
577 path: info.path.to_string(),
578 defined_in: info.value_shape.type_identifier.to_string(),
579 }
580 }
581}
582
583/// Errors that can occur when building a schema.
584#[derive(Debug, Clone)]
585pub enum SchemaError {
586 /// A field name appears from multiple sources (parent struct and flattened struct)
587 DuplicateField {
588 /// The conflicting field name
589 field_name: &'static str,
590 /// Path to the first occurrence
591 first_path: FieldPath,
592 /// Path to the second occurrence
593 second_path: FieldPath,
594 },
595}
596
597impl fmt::Display for SchemaError {
598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599 match self {
600 SchemaError::DuplicateField {
601 field_name,
602 first_path,
603 second_path,
604 } => {
605 write!(
606 f,
607 "Duplicate field name '{field_name}' from different sources: {first_path} vs {second_path}. \
608 This usually means a parent struct and a flattened struct both \
609 define a field with the same name."
610 )
611 }
612 }
613 }
614}
615
616#[cfg(feature = "std")]
617impl std::error::Error for SchemaError {}
618
619/// Errors that can occur during flatten resolution.
620#[derive(Debug)]
621pub enum SolverError {
622 /// No configuration matches the input fields
623 NoMatch {
624 /// The input fields that were provided
625 input_fields: Vec<String>,
626 /// Missing required fields (from the closest matching config) - simple names for backwards compat
627 missing_required: Vec<&'static str>,
628 /// Missing required fields with full path information
629 missing_required_detailed: Vec<MissingFieldInfo>,
630 /// Unknown fields that don't belong to any config
631 unknown_fields: Vec<String>,
632 /// Description of the closest matching configuration
633 closest_config: Option<String>,
634 },
635 /// Multiple configurations match the input fields
636 Ambiguous {
637 /// Descriptions of the matching configurations
638 candidates: Vec<String>,
639 /// Fields that could disambiguate (unique to specific configs)
640 disambiguating_fields: Vec<String>,
641 },
642}
643
644impl fmt::Display for SolverError {
645 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646 match self {
647 SolverError::NoMatch {
648 input_fields,
649 missing_required: _,
650 missing_required_detailed,
651 unknown_fields,
652 closest_config,
653 } => {
654 write!(f, "No matching configuration for fields {input_fields:?}")?;
655 if let Some(config) = closest_config {
656 write!(f, " (closest match: {config})")?;
657 }
658 if !missing_required_detailed.is_empty() {
659 write!(f, "; missing required fields:")?;
660 for info in missing_required_detailed {
661 write!(f, " {} (at path: {})", info.name, info.path)?;
662 }
663 }
664 if !unknown_fields.is_empty() {
665 write!(f, "; unknown: {unknown_fields:?}")?;
666 }
667 Ok(())
668 }
669 SolverError::Ambiguous {
670 candidates,
671 disambiguating_fields,
672 } => {
673 write!(
674 f,
675 "Ambiguous: multiple configurations match: {candidates:?}"
676 )?;
677 if !disambiguating_fields.is_empty() {
678 write!(
679 f,
680 "; try adding one of these fields to disambiguate: {disambiguating_fields:?}"
681 )?;
682 }
683 Ok(())
684 }
685 }
686 }
687}
688
689#[cfg(feature = "std")]
690impl std::error::Error for SolverError {}
691
692/// Compute a specificity score for a shape. Lower score = more specific.
693///
694/// This is used to disambiguate when a value could satisfy multiple types.
695/// For example, the value `42` fits both `u8` and `u16`, but `u8` is more
696/// specific (lower score), so it should be preferred.
697fn specificity_score(shape: &'static Shape) -> u64 {
698 // Use type_identifier to determine specificity
699 // Smaller integer types are more specific
700 match shape.type_identifier {
701 "u8" | "i8" => 8,
702 "u16" | "i16" => 16,
703 "u32" | "i32" | "f32" => 32,
704 "u64" | "i64" | "f64" => 64,
705 "u128" | "i128" => 128,
706 "usize" | "isize" => 64, // Treat as 64-bit
707 // Other types get a high score (less specific)
708 _ => 1000,
709 }
710}
711
712// ============================================================================
713// Solver (State Machine)
714// ============================================================================
715
716/// Result of reporting a key to the solver.
717#[derive(Debug)]
718pub enum KeyResult<'a> {
719 /// All candidates have the same type for this key.
720 /// The deserializer can parse the value directly.
721 Unambiguous {
722 /// The shape all candidates expect for this field
723 shape: &'static Shape,
724 },
725
726 /// Candidates have different types for this key - need disambiguation.
727 /// Deserializer should parse the value, determine which fields it can
728 /// satisfy, and call `satisfy()` with the viable fields.
729 ///
730 /// **Important**: When multiple fields can be satisfied by the value,
731 /// pick the one with the lowest score (most specific). Scores are assigned
732 /// by specificity, e.g., `u8` has a lower score than `u16`.
733 Ambiguous {
734 /// The unique fields across remaining candidates (deduplicated by shape),
735 /// paired with a specificity score. Lower score = more specific type.
736 /// Deserializer should check which of these the value can satisfy,
737 /// then pick the one with the lowest score.
738 fields: Vec<(&'a FieldInfo, u64)>,
739 },
740
741 /// This key disambiguated to exactly one configuration.
742 Solved(&'a Configuration),
743
744 /// This key doesn't exist in any remaining candidate.
745 Unknown,
746}
747
748/// Result of reporting which fields the value can satisfy.
749#[derive(Debug)]
750pub enum SatisfyResult<'a> {
751 /// Continue - still multiple candidates, keep feeding keys.
752 Continue,
753
754 /// Solved to exactly one configuration.
755 Solved(&'a Configuration),
756
757 /// No configuration can accept the value (no fields were satisfied).
758 NoMatch,
759}
760
761/// State machine solver for lazy value-based disambiguation.
762///
763/// This solver only requests value inspection when candidates disagree on type.
764/// For keys where all candidates expect the same type, the deserializer can
765/// skip detailed value analysis.
766///
767/// # Example
768///
769/// ```rust
770/// use facet::Facet;
771/// use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};
772///
773/// #[derive(Facet)]
774/// #[repr(u8)]
775/// enum NumericValue {
776/// Small(u8),
777/// Large(u16),
778/// }
779///
780/// #[derive(Facet)]
781/// struct Container {
782/// #[facet(flatten)]
783/// value: NumericValue,
784/// }
785///
786/// let schema = Schema::build(Container::SHAPE).unwrap();
787/// let mut solver = Solver::new(&schema);
788///
789/// // The field "0" has different types (u8 vs u16) - solver needs disambiguation
790/// match solver.see_key("0") {
791/// KeyResult::Ambiguous { fields } => {
792/// // Deserializer sees value "1000", checks which fields can accept it
793/// // u8 can't hold 1000, u16 can - so only report the u16 field
794/// // Fields come with specificity scores - lower = more specific
795/// let satisfied: Vec<_> = fields.iter()
796/// .filter(|(f, _score)| {
797/// // deserializer's logic: can this value parse as this field's type?
798/// f.value_shape.type_identifier == "u16"
799/// })
800/// .map(|(f, _)| *f)
801/// .collect();
802///
803/// match solver.satisfy(&satisfied) {
804/// SatisfyResult::Solved(config) => {
805/// assert!(config.describe().contains("Large"));
806/// }
807/// _ => panic!("expected solved"),
808/// }
809/// }
810/// _ => panic!("expected Ambiguous"),
811/// }
812/// ```
813#[derive(Debug)]
814pub struct Solver<'a> {
815 /// Reference to the schema for configuration lookup
816 schema: &'a Schema,
817 /// Bitmask of remaining candidate configuration indices
818 candidates: ConfigSet,
819 /// Set of seen keys for required field checking
820 seen_keys: BTreeSet<&'a str>,
821}
822
823impl<'a> Solver<'a> {
824 /// Create a new solver from a schema.
825 pub fn new(schema: &'a Schema) -> Self {
826 Self {
827 schema,
828 candidates: ConfigSet::full(schema.configurations.len()),
829 seen_keys: BTreeSet::new(),
830 }
831 }
832
833 /// Report a key. Returns what to do next.
834 ///
835 /// - `Unambiguous`: All candidates agree on the type - parse directly
836 /// - `Ambiguous`: Types differ - check which fields the value can satisfy
837 /// - `Solved`: Disambiguated to one config
838 /// - `Unknown`: Key not found in any candidate
839 pub fn see_key(&mut self, key: &'a str) -> KeyResult<'a> {
840 self.seen_keys.insert(key);
841
842 // Key-based filtering
843 let configs_with_key = match self.schema.field_to_configs.get(key) {
844 Some(set) => set,
845 None => return KeyResult::Unknown,
846 };
847
848 self.candidates.intersect_with(configs_with_key);
849
850 if self.candidates.is_empty() {
851 return KeyResult::Unknown;
852 }
853
854 // Check if we've disambiguated to exactly one
855 if self.candidates.len() == 1 {
856 let idx = self.candidates.first().unwrap();
857 return KeyResult::Solved(&self.schema.configurations[idx]);
858 }
859
860 // Collect unique fields (by shape pointer) across remaining candidates
861 let mut unique_fields: Vec<&'a FieldInfo> = Vec::new();
862 for idx in self.candidates.iter() {
863 let config = &self.schema.configurations[idx];
864 if let Some(info) = config.field(key) {
865 // Deduplicate by shape pointer
866 if !unique_fields
867 .iter()
868 .any(|f| core::ptr::eq(f.value_shape, info.value_shape))
869 {
870 unique_fields.push(info);
871 }
872 }
873 }
874
875 if unique_fields.len() == 1 {
876 // All candidates have the same type - unambiguous
877 KeyResult::Unambiguous {
878 shape: unique_fields[0].value_shape,
879 }
880 } else {
881 // Different types - need disambiguation
882 // Attach specificity scores so caller can pick most specific when multiple match
883 let fields_with_scores: Vec<_> = unique_fields
884 .into_iter()
885 .map(|f| (f, specificity_score(f.value_shape)))
886 .collect();
887 KeyResult::Ambiguous {
888 fields: fields_with_scores,
889 }
890 }
891 }
892
893 /// Report which fields the value can satisfy after `Ambiguous` result.
894 ///
895 /// The deserializer should pass the subset of fields (from the `Ambiguous` result)
896 /// that the actual value can be parsed into.
897 pub fn satisfy(&mut self, satisfied_fields: &[&FieldInfo]) -> SatisfyResult<'a> {
898 let satisfied_shapes: Vec<_> = satisfied_fields.iter().map(|f| f.value_shape).collect();
899 self.satisfy_shapes(&satisfied_shapes)
900 }
901
902 /// Report which shapes the value can satisfy after `Ambiguous` result from `probe_key`.
903 ///
904 /// This is the shape-based version of `satisfy`, used when disambiguating
905 /// by nested field types. The deserializer should pass the shapes that
906 /// the actual value can be parsed into.
907 ///
908 /// # Example
909 ///
910 /// ```rust
911 /// use facet::Facet;
912 /// use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};
913 ///
914 /// #[derive(Facet)]
915 /// struct SmallPayload { value: u8 }
916 ///
917 /// #[derive(Facet)]
918 /// struct LargePayload { value: u16 }
919 ///
920 /// #[derive(Facet)]
921 /// #[repr(u8)]
922 /// enum PayloadKind {
923 /// Small { payload: SmallPayload },
924 /// Large { payload: LargePayload },
925 /// }
926 ///
927 /// #[derive(Facet)]
928 /// struct Container {
929 /// #[facet(flatten)]
930 /// inner: PayloadKind,
931 /// }
932 ///
933 /// let schema = Schema::build(Container::SHAPE).unwrap();
934 /// let mut solver = Solver::new(&schema);
935 ///
936 /// // Report nested key
937 /// solver.probe_key(&[], "payload");
938 ///
939 /// // At payload.value, value is 1000 - doesn't fit u8
940 /// // Get shapes at this path
941 /// let shapes = solver.get_shapes_at_path(&["payload", "value"]);
942 /// // Filter to shapes that can hold 1000
943 /// let works: Vec<_> = shapes.iter()
944 /// .filter(|s| s.type_identifier == "u16")
945 /// .copied()
946 /// .collect();
947 /// solver.satisfy_shapes(&works);
948 /// ```
949 pub fn satisfy_shapes(&mut self, satisfied_shapes: &[&'static Shape]) -> SatisfyResult<'a> {
950 if satisfied_shapes.is_empty() {
951 self.candidates = ConfigSet::empty(self.schema.configurations.len());
952 return SatisfyResult::NoMatch;
953 }
954
955 let mut new_candidates = ConfigSet::empty(self.schema.configurations.len());
956 for idx in self.candidates.iter() {
957 let config = &self.schema.configurations[idx];
958 // Check if any of this config's fields match the satisfied shapes
959 for field in config.fields.values() {
960 if satisfied_shapes
961 .iter()
962 .any(|s| core::ptr::eq(*s, field.value_shape))
963 {
964 new_candidates.insert(idx);
965 break;
966 }
967 }
968 }
969 self.candidates = new_candidates;
970
971 match self.candidates.len() {
972 0 => SatisfyResult::NoMatch,
973 1 => {
974 let idx = self.candidates.first().unwrap();
975 SatisfyResult::Solved(&self.schema.configurations[idx])
976 }
977 _ => SatisfyResult::Continue,
978 }
979 }
980
981 /// Get the shapes at a nested path across all remaining candidates.
982 ///
983 /// This is useful when you have an `Ambiguous` result from `probe_key`
984 /// and need to know what types are possible at that path.
985 pub fn get_shapes_at_path(&self, path: &[&str]) -> Vec<&'static Shape> {
986 let mut shapes: Vec<&'static Shape> = Vec::new();
987 for idx in self.candidates.iter() {
988 let config = &self.schema.configurations[idx];
989 if let Some(shape) = self.get_shape_at_path(config, path) {
990 if !shapes.iter().any(|s| core::ptr::eq(*s, shape)) {
991 shapes.push(shape);
992 }
993 }
994 }
995 shapes
996 }
997
998 /// Report which shapes at a nested path the value can satisfy.
999 ///
1000 /// This is the path-aware version of `satisfy_shapes`, used when disambiguating
1001 /// by nested field types after `probe_key`.
1002 ///
1003 /// - `path`: The full path to the field (e.g., `["payload", "value"]`)
1004 /// - `satisfied_shapes`: The shapes that the value can be parsed into
1005 pub fn satisfy_at_path(
1006 &mut self,
1007 path: &[&str],
1008 satisfied_shapes: &[&'static Shape],
1009 ) -> SatisfyResult<'a> {
1010 if satisfied_shapes.is_empty() {
1011 self.candidates = ConfigSet::empty(self.schema.configurations.len());
1012 return SatisfyResult::NoMatch;
1013 }
1014
1015 // Keep only candidates where the shape at this path is in the satisfied set
1016 let mut new_candidates = ConfigSet::empty(self.schema.configurations.len());
1017 for idx in self.candidates.iter() {
1018 let config = &self.schema.configurations[idx];
1019 if let Some(shape) = self.get_shape_at_path(config, path) {
1020 if satisfied_shapes.iter().any(|s| core::ptr::eq(*s, shape)) {
1021 new_candidates.insert(idx);
1022 }
1023 }
1024 }
1025 self.candidates = new_candidates;
1026
1027 match self.candidates.len() {
1028 0 => SatisfyResult::NoMatch,
1029 1 => {
1030 let idx = self.candidates.first().unwrap();
1031 SatisfyResult::Solved(&self.schema.configurations[idx])
1032 }
1033 _ => SatisfyResult::Continue,
1034 }
1035 }
1036
1037 /// Get the current candidate configurations.
1038 pub fn candidates(&self) -> Vec<&'a Configuration> {
1039 self.candidates
1040 .iter()
1041 .map(|idx| &self.schema.configurations[idx])
1042 .collect()
1043 }
1044
1045 /// Get the seen keys.
1046 pub fn seen_keys(&self) -> &BTreeSet<&'a str> {
1047 &self.seen_keys
1048 }
1049
1050 /// Hint that a specific enum variant should be selected.
1051 ///
1052 /// This filters the candidates to only those configurations where at least one
1053 /// variant selection has the given variant name. This is useful for explicit
1054 /// type disambiguation via annotations (e.g., KDL type annotations like `(Http)node`).
1055 ///
1056 /// Returns `true` if at least one candidate remains after filtering, `false` if
1057 /// no candidates match the variant name (in which case candidates are unchanged).
1058 ///
1059 /// # Example
1060 ///
1061 /// ```rust
1062 /// use facet::Facet;
1063 /// use facet_solver::{Schema, Solver};
1064 ///
1065 /// #[derive(Facet)]
1066 /// struct HttpSource { url: String }
1067 ///
1068 /// #[derive(Facet)]
1069 /// struct GitSource { url: String, branch: String }
1070 ///
1071 /// #[derive(Facet)]
1072 /// #[repr(u8)]
1073 /// enum SourceKind {
1074 /// Http(HttpSource),
1075 /// Git(GitSource),
1076 /// }
1077 ///
1078 /// #[derive(Facet)]
1079 /// struct Source {
1080 /// #[facet(flatten)]
1081 /// kind: SourceKind,
1082 /// }
1083 ///
1084 /// let schema = Schema::build(Source::SHAPE).unwrap();
1085 /// let mut solver = Solver::new(&schema);
1086 ///
1087 /// // Without hint, both variants are candidates
1088 /// assert_eq!(solver.candidates().len(), 2);
1089 ///
1090 /// // Hint at Http variant
1091 /// assert!(solver.hint_variant("Http"));
1092 /// assert_eq!(solver.candidates().len(), 1);
1093 /// ```
1094 pub fn hint_variant(&mut self, variant_name: &str) -> bool {
1095 // Build a set of configs that have this variant name
1096 let mut matching = ConfigSet::empty(self.schema.configurations.len());
1097
1098 for idx in self.candidates.iter() {
1099 let config = &self.schema.configurations[idx];
1100 // Check if any variant selection matches the given name
1101 if config
1102 .variant_selections()
1103 .iter()
1104 .any(|vs| vs.variant_name == variant_name)
1105 {
1106 matching.insert(idx);
1107 }
1108 }
1109
1110 if matching.is_empty() {
1111 // No matches - keep candidates unchanged
1112 false
1113 } else {
1114 self.candidates = matching;
1115 true
1116 }
1117 }
1118
1119 /// Mark a key as seen without filtering candidates.
1120 ///
1121 /// This is useful when the key is known to be present through means other than
1122 /// parsing (e.g., type annotations). Call this after `hint_variant` to mark
1123 /// the variant name as seen so that `finish()` doesn't report it as missing.
1124 pub fn mark_seen(&mut self, key: &'a str) {
1125 self.seen_keys.insert(key);
1126 }
1127
1128 /// Report a key at a nested path. Returns what to do next.
1129 ///
1130 /// This is the depth-aware version of `see_key`. Use this when probing
1131 /// nested structures where disambiguation might require looking inside objects.
1132 ///
1133 /// - `path`: The ancestor keys (e.g., `["payload"]` when inside a payload object)
1134 /// - `key`: The key found at this level (e.g., `"value"`)
1135 ///
1136 /// # Example
1137 ///
1138 /// ```rust
1139 /// use facet::Facet;
1140 /// use facet_solver::{Schema, Solver, KeyResult};
1141 ///
1142 /// #[derive(Facet)]
1143 /// struct SmallPayload { value: u8 }
1144 ///
1145 /// #[derive(Facet)]
1146 /// struct LargePayload { value: u16 }
1147 ///
1148 /// #[derive(Facet)]
1149 /// #[repr(u8)]
1150 /// enum PayloadKind {
1151 /// Small { payload: SmallPayload },
1152 /// Large { payload: LargePayload },
1153 /// }
1154 ///
1155 /// #[derive(Facet)]
1156 /// struct Container {
1157 /// #[facet(flatten)]
1158 /// inner: PayloadKind,
1159 /// }
1160 ///
1161 /// let schema = Schema::build(Container::SHAPE).unwrap();
1162 /// let mut solver = Solver::new(&schema);
1163 ///
1164 /// // "payload" exists in both - keep going
1165 /// solver.probe_key(&[], "payload");
1166 ///
1167 /// // "value" inside payload - both have it but different types!
1168 /// match solver.probe_key(&["payload"], "value") {
1169 /// KeyResult::Ambiguous { fields } => {
1170 /// // fields is Vec<(&FieldInfo, u64)> - field + specificity score
1171 /// // Deserializer checks: 1000 fits u16 but not u8
1172 /// // When multiple match, pick the one with lowest score (most specific)
1173 /// }
1174 /// _ => {}
1175 /// }
1176 /// ```
1177 pub fn probe_key(&mut self, path: &[&str], key: &str) -> KeyResult<'a> {
1178 // Build full path
1179 let mut full_path: Vec<&str> = path.to_vec();
1180 full_path.push(key);
1181
1182 // Filter candidates to only those that have this key path
1183 let mut new_candidates = ConfigSet::empty(self.schema.configurations.len());
1184 for idx in self.candidates.iter() {
1185 let config = &self.schema.configurations[idx];
1186 if config.has_key_path(&full_path) {
1187 new_candidates.insert(idx);
1188 }
1189 }
1190 self.candidates = new_candidates;
1191
1192 if self.candidates.is_empty() {
1193 return KeyResult::Unknown;
1194 }
1195
1196 // Check if we've disambiguated to exactly one
1197 if self.candidates.len() == 1 {
1198 let idx = self.candidates.first().unwrap();
1199 return KeyResult::Solved(&self.schema.configurations[idx]);
1200 }
1201
1202 // Get the shape at this path for each remaining candidate
1203 // We need to traverse the type tree to find the actual field type
1204 let mut unique_shapes: Vec<(&'static Shape, usize)> = Vec::new(); // (shape, config_idx)
1205
1206 for idx in self.candidates.iter() {
1207 let config = &self.schema.configurations[idx];
1208 if let Some(shape) = self.get_shape_at_path(config, &full_path) {
1209 // Deduplicate by shape pointer
1210 if !unique_shapes.iter().any(|(s, _)| core::ptr::eq(*s, shape)) {
1211 unique_shapes.push((shape, idx));
1212 }
1213 }
1214 }
1215
1216 match unique_shapes.len() {
1217 0 => KeyResult::Unknown,
1218 1 => {
1219 // All candidates have the same type at this path - unambiguous
1220 KeyResult::Unambiguous {
1221 shape: unique_shapes[0].0,
1222 }
1223 }
1224 _ => {
1225 // Different types at this path - need disambiguation
1226 // Build FieldInfo with scores for each unique shape
1227 let fields: Vec<(&'a FieldInfo, u64)> = unique_shapes
1228 .iter()
1229 .filter_map(|(shape, idx)| {
1230 let config = &self.schema.configurations[*idx];
1231 // For nested paths, we need the parent field
1232 // e.g., for ["payload", "value"], get the "payload" field
1233 let field = if path.is_empty() {
1234 config.field(key)
1235 } else {
1236 // Return the top-level field that contains this path
1237 config.field(path[0])
1238 }?;
1239 Some((field, specificity_score(shape)))
1240 })
1241 .collect();
1242
1243 KeyResult::Ambiguous { fields }
1244 }
1245 }
1246 }
1247
1248 /// Get the shape at a nested path within a configuration.
1249 fn get_shape_at_path(
1250 &self,
1251 config: &'a Configuration,
1252 path: &[&str],
1253 ) -> Option<&'static Shape> {
1254 if path.is_empty() {
1255 return None;
1256 }
1257
1258 // Start with the top-level field
1259 let top_field = config.field(path[0])?;
1260 let mut current_shape = top_field.value_shape;
1261
1262 // Navigate through nested structs
1263 for &key in &path[1..] {
1264 current_shape = self.get_field_shape(current_shape, key)?;
1265 }
1266
1267 Some(current_shape)
1268 }
1269
1270 /// Get the shape of a field within a struct shape.
1271 fn get_field_shape(&self, shape: &'static Shape, field_name: &str) -> Option<&'static Shape> {
1272 use facet_core::{StructType, Type, UserType};
1273
1274 match shape.ty {
1275 Type::User(UserType::Struct(StructType { fields, .. })) => {
1276 for field in fields {
1277 if field.name == field_name {
1278 return Some(field.shape());
1279 }
1280 }
1281 None
1282 }
1283 _ => None,
1284 }
1285 }
1286
1287 /// Finish solving. Call this after all keys have been processed.
1288 ///
1289 /// This method is necessary because key-based filtering alone cannot disambiguate
1290 /// when one variant's required fields are a subset of another's.
1291 ///
1292 /// # Why not just use `see_key()` results?
1293 ///
1294 /// `see_key()` returns `Solved` when a key *excludes* candidates down to one.
1295 /// But when the input is a valid subset of multiple variants, no key excludes
1296 /// anything — you need `finish()` to check which candidates have all their
1297 /// required fields satisfied.
1298 ///
1299 /// # Example
1300 ///
1301 /// ```rust,ignore
1302 /// enum Source {
1303 /// Http { url: String }, // required: url
1304 /// Git { url: String, branch: String }, // required: url, branch
1305 /// }
1306 /// ```
1307 ///
1308 /// | Input | `see_key` behavior | Resolution |
1309 /// |------------------------|-------------------------------------------|-----------------------|
1310 /// | `{ "url", "branch" }` | `branch` excludes `Http` → candidates = 1 | Early `Solved(Git)` |
1311 /// | `{ "url" }` | both have `url` → candidates = 2 | `finish()` → `Http` |
1312 ///
1313 /// In the second case, no key ever excludes a candidate. Only `finish()` can
1314 /// determine that `Git` is missing its required `branch` field, leaving `Http`
1315 /// as the sole viable configuration.
1316 pub fn finish(self) -> Result<&'a Configuration, SolverError> {
1317 if self.candidates.is_empty() {
1318 return Err(SolverError::NoMatch {
1319 input_fields: self.seen_keys.iter().map(|s| (*s).into()).collect(),
1320 missing_required: Vec::new(),
1321 missing_required_detailed: Vec::new(),
1322 unknown_fields: Vec::new(),
1323 closest_config: None,
1324 });
1325 }
1326
1327 // Filter candidates to only those that have all required fields satisfied
1328 let viable: Vec<usize> = self
1329 .candidates
1330 .iter()
1331 .filter(|idx| {
1332 let config = &self.schema.configurations[*idx];
1333 config
1334 .required_field_names
1335 .iter()
1336 .all(|f| self.seen_keys.contains(f))
1337 })
1338 .collect();
1339
1340 match viable.len() {
1341 0 => {
1342 let first_idx = self.candidates.first().unwrap();
1343 let first_config = &self.schema.configurations[first_idx];
1344 let missing: Vec<_> = first_config
1345 .required_field_names
1346 .iter()
1347 .filter(|f| !self.seen_keys.contains(*f))
1348 .copied()
1349 .collect();
1350 let missing_detailed: Vec<_> = missing
1351 .iter()
1352 .filter_map(|name| first_config.field(name))
1353 .map(MissingFieldInfo::from_field_info)
1354 .collect();
1355 Err(SolverError::NoMatch {
1356 input_fields: self.seen_keys.iter().map(|s| (*s).into()).collect(),
1357 missing_required: missing,
1358 missing_required_detailed: missing_detailed,
1359 unknown_fields: Vec::new(),
1360 closest_config: Some(first_config.describe()),
1361 })
1362 }
1363 _ => {
1364 // Return first viable (preserves variant declaration order)
1365 Ok(&self.schema.configurations[viable[0]])
1366 }
1367 }
1368 }
1369}
1370
1371// ============================================================================
1372// Probing Solver (Depth-Aware)
1373// ============================================================================
1374
1375/// Result of reporting a key to the probing solver.
1376#[derive(Debug)]
1377pub enum ProbeResult<'a> {
1378 /// Keep reporting keys - not yet disambiguated
1379 KeepGoing,
1380 /// Solved! Use this configuration
1381 Solved(&'a Configuration),
1382 /// No configuration matches the observed keys
1383 NoMatch,
1384}
1385
1386/// Depth-aware probing solver for streaming deserialization.
1387///
1388/// Unlike the batch solver or incremental solver, this solver accepts
1389/// key reports at arbitrary depths. It's designed for the "peek" strategy:
1390///
1391/// 1. Deserializer scans keys (without parsing values) and reports them
1392/// 2. Solver filters candidates based on which configs have that key path
1393/// 3. Once one candidate remains, solver returns `Solved`
1394/// 4. Deserializer rewinds and parses into the resolved type
1395///
1396/// # Example
1397///
1398/// ```rust
1399/// use facet::Facet;
1400/// use facet_solver::{Schema, ProbingSolver, ProbeResult};
1401///
1402/// #[derive(Facet)]
1403/// struct TextPayload { content: String }
1404///
1405/// #[derive(Facet)]
1406/// struct BinaryPayload { bytes: Vec<u8> }
1407///
1408/// #[derive(Facet)]
1409/// #[repr(u8)]
1410/// enum MessageKind {
1411/// Text { payload: TextPayload },
1412/// Binary { payload: BinaryPayload },
1413/// }
1414///
1415/// #[derive(Facet)]
1416/// struct Message {
1417/// id: String,
1418/// #[facet(flatten)]
1419/// kind: MessageKind,
1420/// }
1421///
1422/// let schema = Schema::build(Message::SHAPE).unwrap();
1423/// let mut solver = ProbingSolver::new(&schema);
1424///
1425/// // "id" exists in both configs - keep going
1426/// assert!(matches!(solver.probe_key(&[], "id"), ProbeResult::KeepGoing));
1427///
1428/// // "payload" exists in both configs - keep going
1429/// assert!(matches!(solver.probe_key(&[], "payload"), ProbeResult::KeepGoing));
1430///
1431/// // "content" inside payload only exists in Text - solved!
1432/// match solver.probe_key(&["payload"], "content") {
1433/// ProbeResult::Solved(config) => {
1434/// assert!(config.has_key_path(&["payload", "content"]));
1435/// }
1436/// _ => panic!("expected Solved"),
1437/// }
1438/// ```
1439#[derive(Debug)]
1440pub struct ProbingSolver<'a> {
1441 /// Remaining candidate configurations
1442 candidates: Vec<&'a Configuration>,
1443}
1444
1445impl<'a> ProbingSolver<'a> {
1446 /// Create a new probing solver from a schema.
1447 pub fn new(schema: &'a Schema) -> Self {
1448 Self {
1449 candidates: schema.configurations.iter().collect(),
1450 }
1451 }
1452
1453 /// Create a new probing solver from configurations directly.
1454 pub fn from_configurations(configs: &'a [Configuration]) -> Self {
1455 Self {
1456 candidates: configs.iter().collect(),
1457 }
1458 }
1459
1460 /// Report a key found at a path during probing.
1461 ///
1462 /// - `path`: The ancestor keys (e.g., `["payload"]` when inside the payload object)
1463 /// - `key`: The key found at this level (e.g., `"content"`)
1464 ///
1465 /// Returns what to do next.
1466 pub fn probe_key(&mut self, path: &[&str], key: &str) -> ProbeResult<'a> {
1467 // Build the full key path (runtime strings, compared against static schema)
1468 let mut full_path: Vec<&str> = path.to_vec();
1469 full_path.push(key);
1470
1471 // Filter to candidates that have this key path
1472 self.candidates.retain(|c| c.has_key_path(&full_path));
1473
1474 match self.candidates.len() {
1475 0 => ProbeResult::NoMatch,
1476 1 => ProbeResult::Solved(self.candidates[0]),
1477 _ => ProbeResult::KeepGoing,
1478 }
1479 }
1480
1481 /// Get the current candidate configurations.
1482 pub fn candidates(&self) -> &[&'a Configuration] {
1483 &self.candidates
1484 }
1485
1486 /// Finish probing - returns Solved if exactly one candidate remains.
1487 pub fn finish(&self) -> ProbeResult<'a> {
1488 match self.candidates.len() {
1489 0 => ProbeResult::NoMatch,
1490 1 => ProbeResult::Solved(self.candidates[0]),
1491 _ => ProbeResult::KeepGoing, // Still ambiguous
1492 }
1493 }
1494}
1495
1496// ============================================================================
1497// Schema Builder
1498// ============================================================================
1499
1500/// How enum variants are represented in the serialized format.
1501#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1502pub enum EnumRepr {
1503 /// Variant fields are flattened to the same level as other fields.
1504 /// Used by formats like KDL, TOML where all fields appear at one level.
1505 /// Example: `{"name": "...", "host": "...", "port": 8080}`
1506 #[default]
1507 Flattened,
1508
1509 /// Variant name is a key, variant content is nested under it.
1510 /// Used by JSON with externally-tagged enums.
1511 /// Example: `{"name": "...", "Tcp": {"host": "...", "port": 8080}}`
1512 ExternallyTagged,
1513}
1514
1515impl Schema {
1516 /// Build a schema for the given shape with flattened enum representation.
1517 ///
1518 /// Returns an error if the type definition contains conflicts, such as
1519 /// duplicate field names from parent and flattened structs.
1520 pub fn build(shape: &'static Shape) -> Result<Self, SchemaError> {
1521 Self::build_with_repr(shape, EnumRepr::Flattened)
1522 }
1523
1524 /// Build a schema for externally-tagged enum representation (e.g., JSON).
1525 ///
1526 /// In this representation, the variant name appears as a key and the
1527 /// variant's content is nested under it. The solver will only expect
1528 /// to see the variant name as a top-level key, not the variant's fields.
1529 pub fn build_externally_tagged(shape: &'static Shape) -> Result<Self, SchemaError> {
1530 Self::build_with_repr(shape, EnumRepr::ExternallyTagged)
1531 }
1532
1533 /// Build a schema with the specified enum representation.
1534 pub fn build_with_repr(shape: &'static Shape, repr: EnumRepr) -> Result<Self, SchemaError> {
1535 let builder = SchemaBuilder::new(shape, repr);
1536 builder.into_schema()
1537 }
1538
1539 /// Get the configurations for this schema.
1540 pub fn configurations(&self) -> &[Configuration] {
1541 &self.configurations
1542 }
1543}
1544
1545impl Configuration {
1546 /// Create a new empty configuration.
1547 fn new() -> Self {
1548 Self {
1549 variant_selections: Vec::new(),
1550 fields: BTreeMap::new(),
1551 required_field_names: BTreeSet::new(),
1552 known_paths: BTreeSet::new(),
1553 }
1554 }
1555
1556 /// Add a key path (for depth-aware probing).
1557 fn add_key_path(&mut self, path: KeyPath) {
1558 self.known_paths.insert(path);
1559 }
1560
1561 /// Add a field to this configuration.
1562 ///
1563 /// Returns an error if a field with the same serialized name already exists
1564 /// but comes from a different source (different path). This catches duplicate
1565 /// field name conflicts between parent structs and flattened fields.
1566 fn add_field(&mut self, info: FieldInfo) -> Result<(), SchemaError> {
1567 if let Some(existing) = self.fields.get(info.serialized_name) {
1568 if existing.path != info.path {
1569 return Err(SchemaError::DuplicateField {
1570 field_name: info.serialized_name,
1571 first_path: existing.path.clone(),
1572 second_path: info.path,
1573 });
1574 }
1575 }
1576 if info.required {
1577 self.required_field_names.insert(info.serialized_name);
1578 }
1579 self.fields.insert(info.serialized_name, info);
1580 Ok(())
1581 }
1582
1583 /// Add a variant selection to this configuration.
1584 fn add_variant_selection(
1585 &mut self,
1586 path: FieldPath,
1587 enum_name: &'static str,
1588 variant_name: &'static str,
1589 ) {
1590 self.variant_selections.push(VariantSelection {
1591 path,
1592 enum_name,
1593 variant_name,
1594 });
1595 }
1596
1597 /// Merge another configuration into this one.
1598 ///
1599 /// Returns an error if a field with the same serialized name already exists
1600 /// but comes from a different source (different path). This catches duplicate
1601 /// field name conflicts between parent structs and flattened fields.
1602 fn merge(&mut self, other: &Configuration) -> Result<(), SchemaError> {
1603 for (name, info) in &other.fields {
1604 if let Some(existing) = self.fields.get(*name) {
1605 if existing.path != info.path {
1606 return Err(SchemaError::DuplicateField {
1607 field_name: name,
1608 first_path: existing.path.clone(),
1609 second_path: info.path.clone(),
1610 });
1611 }
1612 }
1613 self.fields.insert(*name, info.clone());
1614 if info.required {
1615 self.required_field_names.insert(*name);
1616 }
1617 }
1618 for vs in &other.variant_selections {
1619 self.variant_selections.push(vs.clone());
1620 }
1621 for path in &other.known_paths {
1622 self.known_paths.insert(path.clone());
1623 }
1624 Ok(())
1625 }
1626
1627 /// Mark all fields as optional (required = false).
1628 /// Used when a flattened field is wrapped in `Option<T>`.
1629 fn mark_all_optional(&mut self) {
1630 self.required_field_names.clear();
1631 for info in self.fields.values_mut() {
1632 info.required = false;
1633 }
1634 }
1635
1636 /// Check if this configuration matches the input fields.
1637 pub fn matches(&self, input_fields: &BTreeSet<&str>) -> MatchResult {
1638 let mut missing_required = Vec::new();
1639 let mut missing_optional = Vec::new();
1640
1641 for (name, info) in &self.fields {
1642 if !input_fields.contains(name) {
1643 if info.required {
1644 missing_required.push(*name);
1645 } else {
1646 missing_optional.push(*name);
1647 }
1648 }
1649 }
1650
1651 // Check for unknown fields
1652 let unknown: Vec<String> = input_fields
1653 .iter()
1654 .filter(|f| !self.fields.contains_key(*f))
1655 .map(|s| (*s).into())
1656 .collect();
1657
1658 if !missing_required.is_empty() || !unknown.is_empty() {
1659 MatchResult::NoMatch {
1660 missing_required,
1661 unknown,
1662 }
1663 } else if missing_optional.is_empty() {
1664 MatchResult::Exact
1665 } else {
1666 MatchResult::WithOptionalMissing(missing_optional)
1667 }
1668 }
1669
1670 /// Get a human-readable description of this configuration.
1671 ///
1672 /// Returns something like `MessagePayload::Text` or `Auth::Token + Transport::Tcp`
1673 /// for configurations with multiple variant selections.
1674 pub fn describe(&self) -> String {
1675 if self.variant_selections.is_empty() {
1676 String::from("(no variants)")
1677 } else {
1678 let parts: Vec<_> = self
1679 .variant_selections
1680 .iter()
1681 .map(|vs| alloc::format!("{}::{}", vs.enum_name, vs.variant_name))
1682 .collect();
1683 parts.join(" + ")
1684 }
1685 }
1686
1687 /// Get the fields in deserialization order (deepest first).
1688 pub fn deserialization_order(&self) -> Vec<&FieldInfo> {
1689 let mut fields: Vec<_> = self.fields.values().collect();
1690 fields.sort_by(|a, b| {
1691 // Deeper paths first
1692 b.path
1693 .depth()
1694 .cmp(&a.path.depth())
1695 // Then lexicographic for determinism
1696 .then_with(|| a.path.cmp(&b.path))
1697 });
1698 fields
1699 }
1700
1701 /// Get a field by name.
1702 pub fn field(&self, name: &str) -> Option<&FieldInfo> {
1703 self.fields.get(name)
1704 }
1705
1706 /// Get all fields.
1707 pub fn fields(&self) -> &BTreeMap<&'static str, FieldInfo> {
1708 &self.fields
1709 }
1710
1711 /// Get optional fields that were NOT provided in the input.
1712 ///
1713 /// This is useful for deserializers that need to initialize missing
1714 /// optional fields to `None` or their default value.
1715 ///
1716 /// # Example
1717 ///
1718 /// ```rust
1719 /// # use facet::Facet;
1720 /// # use facet_solver::Schema;
1721 /// # use std::collections::BTreeSet;
1722 /// #[derive(Facet)]
1723 /// struct Config {
1724 /// required_field: String,
1725 /// optional_field: Option<u32>,
1726 /// }
1727 ///
1728 /// let schema = Schema::build(Config::SHAPE).unwrap();
1729 /// let config = &schema.configurations()[0];
1730 ///
1731 /// // If we only saw "required_field"
1732 /// let mut seen = BTreeSet::new();
1733 /// seen.insert("required_field");
1734 ///
1735 /// let missing: Vec<_> = config.missing_optional_fields(&seen).collect();
1736 /// assert_eq!(missing.len(), 1);
1737 /// assert_eq!(missing[0].serialized_name, "optional_field");
1738 /// ```
1739 pub fn missing_optional_fields<'a>(
1740 &'a self,
1741 seen_keys: &'a BTreeSet<&str>,
1742 ) -> impl Iterator<Item = &'a FieldInfo> {
1743 self.fields
1744 .values()
1745 .filter(move |info| !info.required && !seen_keys.contains(info.serialized_name))
1746 }
1747
1748 /// Get variant selections.
1749 pub fn variant_selections(&self) -> &[VariantSelection] {
1750 &self.variant_selections
1751 }
1752
1753 /// Get all known key paths (for depth-aware probing).
1754 pub fn known_paths(&self) -> &BTreeSet<KeyPath> {
1755 &self.known_paths
1756 }
1757
1758 /// Check if this configuration has a specific key path.
1759 /// Compares runtime strings against static schema paths.
1760 pub fn has_key_path(&self, path: &[&str]) -> bool {
1761 self.known_paths.iter().any(|known| {
1762 known.len() == path.len() && known.iter().zip(path.iter()).all(|(a, b)| *a == *b)
1763 })
1764 }
1765}
1766
1767struct SchemaBuilder {
1768 shape: &'static Shape,
1769 enum_repr: EnumRepr,
1770}
1771
1772impl SchemaBuilder {
1773 fn new(shape: &'static Shape, enum_repr: EnumRepr) -> Self {
1774 Self { shape, enum_repr }
1775 }
1776
1777 fn analyze(&self) -> Result<Vec<Configuration>, SchemaError> {
1778 self.analyze_shape(self.shape, FieldPath::empty(), Vec::new())
1779 }
1780
1781 /// Analyze a shape and return all possible configurations.
1782 /// Returns a Vec because enums create multiple configurations.
1783 ///
1784 /// - `current_path`: The internal field path (for FieldInfo)
1785 /// - `key_prefix`: The serialized key path prefix (for known_paths)
1786 fn analyze_shape(
1787 &self,
1788 shape: &'static Shape,
1789 current_path: FieldPath,
1790 key_prefix: KeyPath,
1791 ) -> Result<Vec<Configuration>, SchemaError> {
1792 match shape.ty {
1793 Type::User(UserType::Struct(struct_type)) => {
1794 self.analyze_struct(struct_type, current_path, key_prefix)
1795 }
1796 Type::User(UserType::Enum(enum_type)) => {
1797 // Enum at root level: create one configuration per variant
1798 self.analyze_enum(shape, enum_type, current_path, key_prefix)
1799 }
1800 _ => {
1801 // For non-struct types at root level, return single empty config
1802 Ok(vec![Configuration::new()])
1803 }
1804 }
1805 }
1806
1807 /// Analyze an enum and return one configuration per variant.
1808 ///
1809 /// - `current_path`: The internal field path (for FieldInfo)
1810 /// - `key_prefix`: The serialized key path prefix (for known_paths)
1811 fn analyze_enum(
1812 &self,
1813 shape: &'static Shape,
1814 enum_type: facet_core::EnumType,
1815 current_path: FieldPath,
1816 key_prefix: KeyPath,
1817 ) -> Result<Vec<Configuration>, SchemaError> {
1818 let enum_name = shape.type_identifier;
1819 let mut result = Vec::new();
1820
1821 for variant in enum_type.variants {
1822 let mut config = Configuration::new();
1823
1824 // Record this variant selection
1825 config.add_variant_selection(current_path.clone(), enum_name, variant.name);
1826
1827 let variant_path = current_path.push_variant("", variant.name);
1828
1829 // Get configurations from the variant's content
1830 let variant_configs =
1831 self.analyze_variant_content(variant, &variant_path, &key_prefix)?;
1832
1833 // Merge each variant config into the base
1834 for variant_config in variant_configs {
1835 let mut final_config = config.clone();
1836 final_config.merge(&variant_config)?;
1837 result.push(final_config);
1838 }
1839 }
1840
1841 Ok(result)
1842 }
1843
1844 /// Analyze a struct and return all possible configurations.
1845 ///
1846 /// - `current_path`: The internal field path (for FieldInfo)
1847 /// - `key_prefix`: The serialized key path prefix (for known_paths)
1848 fn analyze_struct(
1849 &self,
1850 struct_type: StructType,
1851 current_path: FieldPath,
1852 key_prefix: KeyPath,
1853 ) -> Result<Vec<Configuration>, SchemaError> {
1854 // Start with one empty configuration
1855 let mut configs = vec![Configuration::new()];
1856
1857 // Process each field, potentially multiplying configurations
1858 for field in struct_type.fields {
1859 configs =
1860 self.analyze_field_into_configs(field, ¤t_path, &key_prefix, configs)?;
1861 }
1862
1863 Ok(configs)
1864 }
1865
1866 /// Process a field and return updated configurations.
1867 /// If the field is a flattened enum, this may multiply the number of configs.
1868 ///
1869 /// - `parent_path`: The internal field path to the parent (for FieldInfo)
1870 /// - `key_prefix`: The serialized key path prefix (for known_paths)
1871 fn analyze_field_into_configs(
1872 &self,
1873 field: &'static Field,
1874 parent_path: &FieldPath,
1875 key_prefix: &KeyPath,
1876 mut configs: Vec<Configuration>,
1877 ) -> Result<Vec<Configuration>, SchemaError> {
1878 let is_flatten = field.flags.contains(FieldFlags::FLATTEN);
1879
1880 if is_flatten {
1881 // Flattened: inner keys bubble up to current level (same key_prefix)
1882 self.analyze_flattened_field_into_configs(field, parent_path, key_prefix, configs)
1883 } else {
1884 // Regular field: add to ALL current configs
1885 let field_path = parent_path.push_field(field.name);
1886 let required =
1887 !field.flags.contains(FieldFlags::DEFAULT) && !is_option_type(field.shape());
1888
1889 // Build the key path for this field
1890 let mut field_key_path = key_prefix.clone();
1891 field_key_path.push(field.name);
1892
1893 let field_info = FieldInfo {
1894 serialized_name: field.name,
1895 path: field_path,
1896 required,
1897 value_shape: field.shape(),
1898 };
1899
1900 for config in &mut configs {
1901 config.add_field(field_info.clone())?;
1902 // Add this field's key path
1903 config.add_key_path(field_key_path.clone());
1904 }
1905
1906 // If the field's value is a struct, recurse to collect nested key paths
1907 // (for probing, not for flattening - these are nested in serialized format)
1908 // This may fork configurations if the nested struct contains flattened enums!
1909 configs =
1910 self.collect_nested_key_paths_for_shape(field.shape(), &field_key_path, configs)?;
1911
1912 Ok(configs)
1913 }
1914 }
1915
1916 /// Collect nested key paths from a shape into configurations.
1917 /// This handles the case where a non-flattened field contains a struct with flattened enums.
1918 /// Returns updated configurations (may fork if flattened enums are encountered).
1919 fn collect_nested_key_paths_for_shape(
1920 &self,
1921 shape: &'static Shape,
1922 key_prefix: &KeyPath,
1923 configs: Vec<Configuration>,
1924 ) -> Result<Vec<Configuration>, SchemaError> {
1925 match shape.ty {
1926 Type::User(UserType::Struct(struct_type)) => {
1927 self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
1928 }
1929 _ => Ok(configs),
1930 }
1931 }
1932
1933 /// Collect nested key paths from a struct, potentially forking for flattened enums.
1934 fn collect_nested_key_paths_for_struct(
1935 &self,
1936 struct_type: StructType,
1937 key_prefix: &KeyPath,
1938 mut configs: Vec<Configuration>,
1939 ) -> Result<Vec<Configuration>, SchemaError> {
1940 for field in struct_type.fields {
1941 let is_flatten = field.flags.contains(FieldFlags::FLATTEN);
1942 let mut field_key_path = key_prefix.clone();
1943
1944 if is_flatten {
1945 // Flattened field: keys bubble up to current level, may fork configs
1946 configs =
1947 self.collect_nested_key_paths_for_flattened(field, key_prefix, configs)?;
1948 } else {
1949 // Regular field: add key path and recurse
1950 field_key_path.push(field.name);
1951
1952 for config in &mut configs {
1953 config.add_key_path(field_key_path.clone());
1954 }
1955
1956 // Recurse into nested structs
1957 configs = self.collect_nested_key_paths_for_shape(
1958 field.shape(),
1959 &field_key_path,
1960 configs,
1961 )?;
1962 }
1963 }
1964 Ok(configs)
1965 }
1966
1967 /// Handle flattened fields when collecting nested key paths.
1968 /// This may fork configurations for flattened enums.
1969 fn collect_nested_key_paths_for_flattened(
1970 &self,
1971 field: &'static Field,
1972 key_prefix: &KeyPath,
1973 configs: Vec<Configuration>,
1974 ) -> Result<Vec<Configuration>, SchemaError> {
1975 let shape = field.shape();
1976
1977 match shape.ty {
1978 Type::User(UserType::Struct(struct_type)) => {
1979 // Flattened struct: recurse with same key_prefix
1980 self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
1981 }
1982 Type::User(UserType::Enum(enum_type)) => {
1983 // Flattened enum: fork configurations
1984 // We need to match each config to its corresponding variant
1985 let mut result = Vec::new();
1986
1987 for config in configs {
1988 // Find which variant this config has selected for this field
1989 let selected_variant = config
1990 .variant_selections
1991 .iter()
1992 .find(|vs| {
1993 // Match by the field name in the path
1994 vs.path.segments.last() == Some(&PathSegment::Field(field.name))
1995 })
1996 .map(|vs| vs.variant_name);
1997
1998 if let Some(variant_name) = selected_variant {
1999 // Find the variant and collect its key paths
2000 if let Some(variant) =
2001 enum_type.variants.iter().find(|v| v.name == variant_name)
2002 {
2003 let mut updated_config = config;
2004 updated_config = self.collect_variant_key_paths(
2005 variant,
2006 key_prefix,
2007 updated_config,
2008 )?;
2009 result.push(updated_config);
2010 } else {
2011 result.push(config);
2012 }
2013 } else {
2014 result.push(config);
2015 }
2016 }
2017 Ok(result)
2018 }
2019 _ => Ok(configs),
2020 }
2021 }
2022
2023 /// Collect key paths from an enum variant's content.
2024 fn collect_variant_key_paths(
2025 &self,
2026 variant: &'static Variant,
2027 key_prefix: &KeyPath,
2028 mut config: Configuration,
2029 ) -> Result<Configuration, SchemaError> {
2030 // Check if this is a newtype variant (single unnamed field)
2031 if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2032 let inner_field = &variant.data.fields[0];
2033 let inner_shape = inner_field.shape();
2034
2035 // If the inner type is a struct, flatten its fields
2036 if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
2037 let configs = self.collect_nested_key_paths_for_struct(
2038 inner_struct,
2039 key_prefix,
2040 vec![config],
2041 )?;
2042 return Ok(configs
2043 .into_iter()
2044 .next()
2045 .unwrap_or_else(Configuration::new));
2046 }
2047 }
2048
2049 // Named fields - process each
2050 for variant_field in variant.data.fields {
2051 let is_flatten = variant_field.flags.contains(FieldFlags::FLATTEN);
2052
2053 if is_flatten {
2054 let configs = self.collect_nested_key_paths_for_flattened(
2055 variant_field,
2056 key_prefix,
2057 vec![config],
2058 )?;
2059 config = configs
2060 .into_iter()
2061 .next()
2062 .unwrap_or_else(Configuration::new);
2063 } else {
2064 let mut field_key_path = key_prefix.clone();
2065 field_key_path.push(variant_field.name);
2066 config.add_key_path(field_key_path.clone());
2067
2068 let configs = self.collect_nested_key_paths_for_shape(
2069 variant_field.shape(),
2070 &field_key_path,
2071 vec![config],
2072 )?;
2073 config = configs
2074 .into_iter()
2075 .next()
2076 .unwrap_or_else(Configuration::new);
2077 }
2078 }
2079 Ok(config)
2080 }
2081
2082 /// Collect ONLY key paths from a variant's content (no fields added).
2083 /// Used for externally-tagged enums where variant content is nested and
2084 /// will be parsed separately by the deserializer.
2085 fn collect_variant_key_paths_only(
2086 &self,
2087 variant: &'static Variant,
2088 key_prefix: &KeyPath,
2089 config: &mut Configuration,
2090 ) -> Result<(), SchemaError> {
2091 // Check if this is a newtype variant (single unnamed field)
2092 if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2093 let inner_field = &variant.data.fields[0];
2094 let inner_shape = inner_field.shape();
2095
2096 // If the inner type is a struct, add key paths for its fields
2097 if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
2098 Self::collect_struct_key_paths_only(inner_struct, key_prefix, config);
2099 return Ok(());
2100 }
2101 }
2102
2103 // Named fields - add key paths for each
2104 for variant_field in variant.data.fields {
2105 let mut field_key_path = key_prefix.clone();
2106 field_key_path.push(variant_field.name);
2107 config.add_key_path(field_key_path.clone());
2108
2109 // Recurse into nested structs
2110 if let Type::User(UserType::Struct(inner_struct)) = variant_field.shape().ty {
2111 Self::collect_struct_key_paths_only(inner_struct, &field_key_path, config);
2112 }
2113 }
2114 Ok(())
2115 }
2116
2117 /// Recursively collect key paths from a struct (no fields added).
2118 fn collect_struct_key_paths_only(
2119 struct_type: StructType,
2120 key_prefix: &KeyPath,
2121 config: &mut Configuration,
2122 ) {
2123 for field in struct_type.fields {
2124 let is_flatten = field.flags.contains(FieldFlags::FLATTEN);
2125
2126 if is_flatten {
2127 // Flattened field: keys bubble up to current level
2128 if let Type::User(UserType::Struct(inner_struct)) = field.shape().ty {
2129 Self::collect_struct_key_paths_only(inner_struct, key_prefix, config);
2130 }
2131 } else {
2132 // Regular field: add its key path
2133 let mut field_key_path = key_prefix.clone();
2134 field_key_path.push(field.name);
2135 config.add_key_path(field_key_path.clone());
2136
2137 // Recurse into nested structs
2138 if let Type::User(UserType::Struct(inner_struct)) = field.shape().ty {
2139 Self::collect_struct_key_paths_only(inner_struct, &field_key_path, config);
2140 }
2141 }
2142 }
2143 }
2144
2145 /// Process a flattened field, potentially forking configurations for enums.
2146 ///
2147 /// For flattened fields, the inner keys bubble up to the current level,
2148 /// so we pass the same key_prefix (not key_prefix + field.name).
2149 ///
2150 /// If the field is `Option<T>`, we unwrap to get T and mark all resulting
2151 /// fields as optional (since the entire flattened block can be omitted).
2152 fn analyze_flattened_field_into_configs(
2153 &self,
2154 field: &'static Field,
2155 parent_path: &FieldPath,
2156 key_prefix: &KeyPath,
2157 configs: Vec<Configuration>,
2158 ) -> Result<Vec<Configuration>, SchemaError> {
2159 let field_path = parent_path.push_field(field.name);
2160 let original_shape = field.shape();
2161
2162 // Check if this is Option<T> - if so, unwrap and mark all fields optional
2163 let (shape, is_optional_flatten) = match unwrap_option_type(original_shape) {
2164 Some(inner) => (inner, true),
2165 None => (original_shape, false),
2166 };
2167
2168 match shape.ty {
2169 Type::User(UserType::Struct(struct_type)) => {
2170 // Flatten a struct: get its configurations and merge into each of ours
2171 // Key prefix stays the same - inner keys bubble up
2172 let mut struct_configs =
2173 self.analyze_struct(struct_type, field_path, key_prefix.clone())?;
2174
2175 // If the flatten field was Option<T>, mark all inner fields as optional
2176 if is_optional_flatten {
2177 for config in &mut struct_configs {
2178 config.mark_all_optional();
2179 }
2180 }
2181
2182 // Each of our configs combines with each struct config
2183 // (usually struct_configs has 1 element unless it contains enums)
2184 let mut result = Vec::new();
2185 for base_config in configs {
2186 for struct_config in &struct_configs {
2187 let mut merged = base_config.clone();
2188 merged.merge(struct_config)?;
2189 result.push(merged);
2190 }
2191 }
2192 Ok(result)
2193 }
2194 Type::User(UserType::Enum(enum_type)) => {
2195 // Fork: each existing config × each variant
2196 let mut result = Vec::new();
2197 let enum_name = shape.type_identifier;
2198
2199 for base_config in configs {
2200 for variant in enum_type.variants {
2201 let mut forked = base_config.clone();
2202 forked.add_variant_selection(field_path.clone(), enum_name, variant.name);
2203
2204 let variant_path = field_path.push_variant(field.name, variant.name);
2205
2206 match self.enum_repr {
2207 EnumRepr::ExternallyTagged => {
2208 // For externally tagged enums, the variant name is a key
2209 // at the current level, and its content is nested underneath.
2210 let mut variant_key_prefix = key_prefix.clone();
2211 variant_key_prefix.push(variant.name);
2212
2213 // Add the variant name itself as a known key path
2214 forked.add_key_path(variant_key_prefix.clone());
2215
2216 // Add the variant name as a field (the key that selects this variant)
2217 let variant_field_info = FieldInfo {
2218 serialized_name: variant.name,
2219 path: variant_path.clone(),
2220 required: !is_optional_flatten,
2221 value_shape: shape, // The enum shape
2222 };
2223 forked.add_field(variant_field_info)?;
2224
2225 // For externally-tagged enums, we do NOT add the variant's
2226 // inner fields to required fields. They're nested and will
2227 // be parsed separately by the deserializer.
2228 // Only add them to known_paths for depth-aware probing.
2229 self.collect_variant_key_paths_only(
2230 variant,
2231 &variant_key_prefix,
2232 &mut forked,
2233 )?;
2234
2235 result.push(forked);
2236 }
2237 EnumRepr::Flattened => {
2238 // For flattened enums, the variant's fields appear at the
2239 // same level as other fields. The variant name is NOT a key;
2240 // only the variant's inner fields are keys.
2241
2242 // Get configurations from the variant's content
2243 // Key prefix stays the same - inner keys bubble up
2244 let mut variant_configs = self.analyze_variant_content(
2245 variant,
2246 &variant_path,
2247 key_prefix,
2248 )?;
2249
2250 // If the flatten field was Option<T>, mark all inner fields as optional
2251 if is_optional_flatten {
2252 for config in &mut variant_configs {
2253 config.mark_all_optional();
2254 }
2255 }
2256
2257 // Merge each variant config into the forked base
2258 for variant_config in variant_configs {
2259 let mut final_config = forked.clone();
2260 final_config.merge(&variant_config)?;
2261 result.push(final_config);
2262 }
2263 }
2264 }
2265 }
2266 }
2267 Ok(result)
2268 }
2269 _ => {
2270 // Can't flatten other types - treat as regular field
2271 // For Option<T> flatten, also consider optionality from the wrapper
2272 let required = !field.flags.contains(FieldFlags::DEFAULT)
2273 && !is_option_type(shape)
2274 && !is_optional_flatten;
2275
2276 // For non-flattenable types, add the field with its key path
2277 let mut field_key_path = key_prefix.clone();
2278 field_key_path.push(field.name);
2279
2280 let field_info = FieldInfo {
2281 serialized_name: field.name,
2282 path: field_path,
2283 required,
2284 value_shape: shape,
2285 };
2286
2287 let mut result = configs;
2288 for config in &mut result {
2289 config.add_field(field_info.clone())?;
2290 config.add_key_path(field_key_path.clone());
2291 }
2292 Ok(result)
2293 }
2294 }
2295 }
2296
2297 /// Analyze a variant's content and return configurations.
2298 ///
2299 /// - `variant_path`: The internal field path (for FieldInfo)
2300 /// - `key_prefix`: The serialized key path prefix (for known_paths)
2301 fn analyze_variant_content(
2302 &self,
2303 variant: &'static Variant,
2304 variant_path: &FieldPath,
2305 key_prefix: &KeyPath,
2306 ) -> Result<Vec<Configuration>, SchemaError> {
2307 // Check if this is a newtype variant (single unnamed field like `Foo(Bar)`)
2308 if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2309 let inner_field = &variant.data.fields[0];
2310 let inner_shape = inner_field.shape();
2311
2312 // If the inner type is a struct, flatten its fields
2313 // Key prefix stays the same - inner keys bubble up
2314 if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
2315 let inner_path = variant_path.push_field("0");
2316 return self.analyze_struct(inner_struct, inner_path, key_prefix.clone());
2317 }
2318 }
2319
2320 // Named fields or multiple fields - analyze as a pseudo-struct
2321 let mut configs = vec![Configuration::new()];
2322 for variant_field in variant.data.fields {
2323 configs =
2324 self.analyze_field_into_configs(variant_field, variant_path, key_prefix, configs)?;
2325 }
2326 Ok(configs)
2327 }
2328
2329 fn into_schema(self) -> Result<Schema, SchemaError> {
2330 let configurations = self.analyze()?;
2331 let num_configs = configurations.len();
2332
2333 // Build inverted index: field_name → bitmask of config indices
2334 let mut field_to_configs: BTreeMap<&'static str, ConfigSet> = BTreeMap::new();
2335 for (idx, config) in configurations.iter().enumerate() {
2336 for field_name in config.fields.keys() {
2337 field_to_configs
2338 .entry(*field_name)
2339 .or_insert_with(|| ConfigSet::empty(num_configs))
2340 .insert(idx);
2341 }
2342 }
2343
2344 Ok(Schema {
2345 shape: self.shape,
2346 configurations,
2347 field_to_configs,
2348 })
2349 }
2350}
2351
2352/// Check if a shape represents an Option type.
2353fn is_option_type(shape: &'static Shape) -> bool {
2354 matches!(shape.def, Def::Option(_))
2355}
2356
2357/// If shape is `Option<T>`, returns `Some(T's shape)`. Otherwise returns `None`.
2358fn unwrap_option_type(shape: &'static Shape) -> Option<&'static Shape> {
2359 match shape.def {
2360 Def::Option(option_def) => Some(option_def.t),
2361 _ => None,
2362 }
2363}