#![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!("../readme-footer.md")]
extern crate alloc;
use alloc::borrow::Cow;
use alloc::collections::BTreeMap;
use alloc::collections::BTreeSet;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use facet_core::{Def, Field, Shape, StructType, Type, UserType, Variant};
pub use facet_reflect::{
DuplicateFieldError, FieldCategory, FieldInfo, FieldKey, FieldPath, KeyPath, MatchResult,
PathSegment, Resolution, VariantSelection,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Format {
#[default]
Flat,
Dom,
}
#[derive(Debug)]
pub struct Schema {
#[allow(dead_code)]
shape: &'static Shape,
format: Format,
resolutions: Vec<Resolution>,
field_to_resolutions: BTreeMap<&'static str, ResolutionSet>,
dom_field_to_resolutions: BTreeMap<(FieldCategory, &'static str), ResolutionSet>,
}
#[derive(Debug, Clone, Copy)]
pub struct ResolutionHandle<'a> {
index: usize,
resolution: &'a Resolution,
}
impl<'a> PartialEq for ResolutionHandle<'a> {
fn eq(&self, other: &Self) -> bool {
self.index == other.index
}
}
impl<'a> Eq for ResolutionHandle<'a> {}
impl<'a> ResolutionHandle<'a> {
fn from_schema(schema: &'a Schema, index: usize) -> Self {
Self {
index,
resolution: &schema.resolutions[index],
}
}
pub const fn index(self) -> usize {
self.index
}
pub const fn resolution(self) -> &'a Resolution {
self.resolution
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolutionSet {
bits: Vec<u64>,
count: usize,
}
impl ResolutionSet {
fn empty(num_resolutions: usize) -> Self {
let num_words = num_resolutions.div_ceil(64);
Self {
bits: vec![0; num_words],
count: 0,
}
}
fn full(num_resolutions: usize) -> Self {
let num_words = num_resolutions.div_ceil(64);
let mut bits = vec![!0u64; num_words];
if !num_resolutions.is_multiple_of(64) {
let last_word_bits = num_resolutions % 64;
bits[num_words - 1] = (1u64 << last_word_bits) - 1;
}
Self {
bits,
count: num_resolutions,
}
}
fn insert(&mut self, idx: usize) {
let word = idx / 64;
let bit = idx % 64;
if self.bits[word] & (1u64 << bit) == 0 {
self.bits[word] |= 1u64 << bit;
self.count += 1;
}
}
fn intersect_with(&mut self, other: &ResolutionSet) {
self.count = 0;
for (a, b) in self.bits.iter_mut().zip(other.bits.iter()) {
*a &= *b;
self.count += a.count_ones() as usize;
}
}
fn intersects(&self, other: &ResolutionSet) -> bool {
self.bits
.iter()
.zip(other.bits.iter())
.any(|(a, b)| (*a & *b) != 0)
}
const fn len(&self) -> usize {
self.count
}
const fn is_empty(&self) -> bool {
self.count == 0
}
fn first(&self) -> Option<usize> {
for (word_idx, &word) in self.bits.iter().enumerate() {
if word != 0 {
return Some(word_idx * 64 + word.trailing_zeros() as usize);
}
}
None
}
fn iter(&self) -> impl Iterator<Item = usize> + '_ {
self.bits.iter().enumerate().flat_map(|(word_idx, &word)| {
(0..64).filter_map(move |bit| {
if word & (1u64 << bit) != 0 {
Some(word_idx * 64 + bit)
} else {
None
}
})
})
}
}
fn find_disambiguating_fields(configs: &[&Resolution]) -> Vec<String> {
if configs.len() < 2 {
return Vec::new();
}
let mut all_fields: BTreeSet<&str> = BTreeSet::new();
for config in configs {
for info in config.fields().values() {
all_fields.insert(info.serialized_name);
}
}
let mut disambiguating = Vec::new();
for field in all_fields {
let count = configs
.iter()
.filter(|c| c.field_by_name(field).is_some())
.count();
if count > 0 && count < configs.len() {
disambiguating.push(field.to_string());
}
}
disambiguating
}
#[derive(Debug, Clone)]
pub struct MissingFieldInfo {
pub name: &'static str,
pub path: String,
pub defined_in: String,
}
impl MissingFieldInfo {
fn from_field_info(info: &FieldInfo) -> Self {
Self {
name: info.serialized_name,
path: info.path.to_string(),
defined_in: info.value_shape.type_identifier.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct CandidateFailure {
pub variant_name: String,
pub missing_fields: Vec<MissingFieldInfo>,
pub unknown_fields: Vec<String>,
pub suggestion_matches: usize,
}
#[derive(Debug, Clone)]
pub struct FieldSuggestion {
pub unknown: String,
pub suggestion: &'static str,
pub similarity: f64,
}
#[derive(Debug, Clone)]
pub enum SchemaError {
DuplicateField(DuplicateFieldError),
}
impl From<DuplicateFieldError> for SchemaError {
fn from(err: DuplicateFieldError) -> Self {
SchemaError::DuplicateField(err)
}
}
impl fmt::Display for SchemaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SchemaError::DuplicateField(err) => {
write!(
f,
"Duplicate field name '{}' from different sources: {} vs {}. \
This usually means a parent struct and a flattened struct both \
define a field with the same name.",
err.field_name, err.first_path, err.second_path
)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SchemaError {}
#[derive(Debug, Clone)]
pub enum SolverError {
NoMatch {
input_fields: Vec<String>,
missing_required: Vec<&'static str>,
missing_required_detailed: Vec<MissingFieldInfo>,
unknown_fields: Vec<String>,
closest_resolution: Option<String>,
candidate_failures: Vec<CandidateFailure>,
suggestions: Vec<FieldSuggestion>,
},
Ambiguous {
candidates: Vec<String>,
disambiguating_fields: Vec<String>,
},
}
impl fmt::Display for SolverError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SolverError::NoMatch {
input_fields,
missing_required: _,
missing_required_detailed,
unknown_fields,
closest_resolution,
candidate_failures,
suggestions,
} => {
write!(f, "No matching configuration for fields {input_fields:?}")?;
if !candidate_failures.is_empty() {
write!(f, "\n\nNo variant matched:")?;
for failure in candidate_failures {
write!(f, "\n - {}", failure.variant_name)?;
if !failure.missing_fields.is_empty() {
let names: Vec<_> =
failure.missing_fields.iter().map(|m| m.name).collect();
if names.len() == 1 {
write!(f, ": missing field '{}'", names[0])?;
} else {
write!(f, ": missing fields {names:?}")?;
}
}
if !failure.unknown_fields.is_empty() {
if failure.missing_fields.is_empty() {
write!(f, ":")?;
} else {
write!(f, ",")?;
}
write!(f, " unknown fields {:?}", failure.unknown_fields)?;
}
}
} else if let Some(config) = closest_resolution {
write!(f, " (closest match: {config})")?;
if !missing_required_detailed.is_empty() {
write!(f, "; missing required fields:")?;
for info in missing_required_detailed {
write!(f, " {} (at path: {})", info.name, info.path)?;
}
}
}
if !unknown_fields.is_empty() {
write!(f, "\n\nUnknown fields: {unknown_fields:?}")?;
}
for suggestion in suggestions {
write!(
f,
"\n Did you mean '{}' instead of '{}'?",
suggestion.suggestion, suggestion.unknown
)?;
}
Ok(())
}
SolverError::Ambiguous {
candidates,
disambiguating_fields,
} => {
write!(f, "Ambiguous: multiple resolutions match: {candidates:?}")?;
if !disambiguating_fields.is_empty() {
write!(
f,
"; try adding one of these fields to disambiguate: {disambiguating_fields:?}"
)?;
}
Ok(())
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for SolverError {}
pub fn specificity_score(shape: &'static Shape) -> u64 {
match shape.type_identifier {
"u8" | "i8" => 8,
"u16" | "i16" => 16,
"u32" | "i32" | "f32" => 32,
"u64" | "i64" | "f64" => 64,
"u128" | "i128" => 128,
"usize" | "isize" => 64, _ => 1000,
}
}
#[derive(Debug)]
pub enum KeyResult<'a> {
Unambiguous {
shape: &'static Shape,
},
Ambiguous {
fields: Vec<(&'a FieldInfo, u64)>,
},
Solved(ResolutionHandle<'a>),
Unknown,
}
#[derive(Debug)]
pub enum SatisfyResult<'a> {
Continue,
Solved(ResolutionHandle<'a>),
NoMatch,
}
#[derive(Debug)]
pub struct Solver<'a> {
schema: &'a Schema,
candidates: ResolutionSet,
seen_keys: BTreeSet<FieldKey<'a>>,
}
impl<'a> Solver<'a> {
pub fn new(schema: &'a Schema) -> Self {
Self {
schema,
candidates: ResolutionSet::full(schema.resolutions.len()),
seen_keys: BTreeSet::new(),
}
}
pub fn see_key(&mut self, key: impl Into<FieldKey<'a>>) -> KeyResult<'a> {
let key = key.into();
self.see_key_internal(key)
}
pub fn see_attribute(&mut self, name: impl Into<Cow<'a, str>>) -> KeyResult<'a> {
self.see_key_internal(FieldKey::attribute(name))
}
pub fn see_element(&mut self, name: impl Into<Cow<'a, str>>) -> KeyResult<'a> {
self.see_key_internal(FieldKey::element(name))
}
pub fn see_text(&mut self) -> KeyResult<'a> {
self.see_key_internal(FieldKey::text())
}
fn see_key_internal(&mut self, key: FieldKey<'a>) -> KeyResult<'a> {
self.seen_keys.insert(key.clone());
let resolutions_with_key = match (&key, self.schema.format) {
(FieldKey::Flat(name), Format::Flat) => {
self.schema.field_to_resolutions.get(name.as_ref())
}
(FieldKey::Flat(name), Format::Dom) => {
self.schema
.dom_field_to_resolutions
.get(&(FieldCategory::Element, name.as_ref()))
}
(FieldKey::Dom(cat, name), Format::Dom) => {
if matches!(
cat,
FieldCategory::Text | FieldCategory::Tag | FieldCategory::Elements
) && name.is_empty()
{
self.schema
.dom_field_to_resolutions
.iter()
.find(|((c, _), _)| c == cat)
.map(|(_, rs)| rs)
} else {
self.schema
.dom_field_to_resolutions
.get(&(*cat, name.as_ref()))
}
}
(FieldKey::Dom(_, name), Format::Flat) => {
self.schema.field_to_resolutions.get(name.as_ref())
}
};
let resolutions_with_key = match resolutions_with_key {
Some(set) => set,
None => return KeyResult::Unknown,
};
if !self.candidates.intersects(resolutions_with_key) {
return KeyResult::Unknown;
}
self.candidates.intersect_with(resolutions_with_key);
if self.candidates.len() == 1 {
let idx = self.candidates.first().unwrap();
return KeyResult::Solved(self.handle(idx));
}
let mut unique_fields: Vec<&'a FieldInfo> = Vec::new();
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
if let Some(info) = config.field_by_key(&key) {
if !unique_fields
.iter()
.any(|f| core::ptr::eq(f.value_shape, info.value_shape))
{
unique_fields.push(info);
}
}
}
if unique_fields.len() == 1 {
KeyResult::Unambiguous {
shape: unique_fields[0].value_shape,
}
} else if unique_fields.is_empty() {
KeyResult::Unknown
} else {
let fields_with_scores: Vec<_> = unique_fields
.into_iter()
.map(|f| (f, specificity_score(f.value_shape)))
.collect();
KeyResult::Ambiguous {
fields: fields_with_scores,
}
}
}
pub fn satisfy(&mut self, satisfied_fields: &[&FieldInfo]) -> SatisfyResult<'a> {
let satisfied_shapes: Vec<_> = satisfied_fields.iter().map(|f| f.value_shape).collect();
self.satisfy_shapes(&satisfied_shapes)
}
pub fn satisfy_shapes(&mut self, satisfied_shapes: &[&'static Shape]) -> SatisfyResult<'a> {
if satisfied_shapes.is_empty() {
self.candidates = ResolutionSet::empty(self.schema.resolutions.len());
return SatisfyResult::NoMatch;
}
let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
for field in config.fields().values() {
if satisfied_shapes
.iter()
.any(|s| core::ptr::eq(*s, field.value_shape))
{
new_candidates.insert(idx);
break;
}
}
}
self.candidates = new_candidates;
match self.candidates.len() {
0 => SatisfyResult::NoMatch,
1 => {
let idx = self.candidates.first().unwrap();
SatisfyResult::Solved(self.handle(idx))
}
_ => SatisfyResult::Continue,
}
}
pub fn get_shapes_at_path(&self, path: &[&str]) -> Vec<&'static Shape> {
let mut shapes: Vec<&'static Shape> = Vec::new();
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
if let Some(shape) = self.get_shape_at_path(config, path)
&& !shapes.iter().any(|s| core::ptr::eq(*s, shape))
{
shapes.push(shape);
}
}
shapes
}
pub fn satisfy_at_path(
&mut self,
path: &[&str],
satisfied_shapes: &[&'static Shape],
) -> SatisfyResult<'a> {
if satisfied_shapes.is_empty() {
self.candidates = ResolutionSet::empty(self.schema.resolutions.len());
return SatisfyResult::NoMatch;
}
let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
if let Some(shape) = self.get_shape_at_path(config, path)
&& satisfied_shapes.iter().any(|s| core::ptr::eq(*s, shape))
{
new_candidates.insert(idx);
}
}
self.candidates = new_candidates;
match self.candidates.len() {
0 => SatisfyResult::NoMatch,
1 => {
let idx = self.candidates.first().unwrap();
SatisfyResult::Solved(self.handle(idx))
}
_ => SatisfyResult::Continue,
}
}
pub fn candidates(&self) -> Vec<ResolutionHandle<'a>> {
self.candidates.iter().map(|idx| self.handle(idx)).collect()
}
pub const fn seen_keys(&self) -> &BTreeSet<FieldKey<'a>> {
&self.seen_keys
}
pub fn was_field_seen(&self, field_name: &str) -> bool {
self.seen_keys.iter().any(|k| k.name() == field_name)
}
#[inline]
fn handle(&self, idx: usize) -> ResolutionHandle<'a> {
ResolutionHandle::from_schema(self.schema, idx)
}
pub fn hint_variant(&mut self, variant_name: &str) -> bool {
let mut matching = ResolutionSet::empty(self.schema.resolutions.len());
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
if config
.variant_selections()
.iter()
.any(|vs| vs.variant_name == variant_name)
{
matching.insert(idx);
}
}
if matching.is_empty() {
false
} else {
self.candidates = matching;
true
}
}
pub fn hint_variant_for_tag(&mut self, tag_field_name: &str, variant_name: &str) -> bool {
let is_tag_field = self.candidates.iter().any(|idx| {
let config = &self.schema.resolutions[idx];
config.fields().values().any(|field| {
field.serialized_name == tag_field_name
&& field
.value_shape
.get_tag_attr()
.is_some_and(|tag| tag == tag_field_name)
&& field.value_shape.get_content_attr().is_none()
})
});
if !is_tag_field {
return false;
}
self.hint_variant(variant_name)
}
pub fn mark_seen(&mut self, key: impl Into<FieldKey<'a>>) {
self.seen_keys.insert(key.into());
}
pub fn probe_key(&mut self, path: &[&str], key: &str) -> KeyResult<'a> {
let mut full_path: Vec<&str> = path.to_vec();
full_path.push(key);
let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
if config.has_key_path(&full_path) {
new_candidates.insert(idx);
}
}
self.candidates = new_candidates;
if self.candidates.is_empty() {
return KeyResult::Unknown;
}
if self.candidates.len() == 1 {
let idx = self.candidates.first().unwrap();
return KeyResult::Solved(self.handle(idx));
}
let mut unique_shapes: Vec<(&'static Shape, usize)> = Vec::new();
for idx in self.candidates.iter() {
let config = &self.schema.resolutions[idx];
if let Some(shape) = self.get_shape_at_path(config, &full_path) {
if !unique_shapes.iter().any(|(s, _)| core::ptr::eq(*s, shape)) {
unique_shapes.push((shape, idx));
}
}
}
match unique_shapes.len() {
0 => KeyResult::Unknown,
1 => {
KeyResult::Unambiguous {
shape: unique_shapes[0].0,
}
}
_ => {
let fields: Vec<(&'a FieldInfo, u64)> = unique_shapes
.iter()
.filter_map(|(shape, idx)| {
let config = &self.schema.resolutions[*idx];
let field = if path.is_empty() {
config.field_by_name(key)
} else {
config.field_by_name(path[0])
}?;
Some((field, specificity_score(shape)))
})
.collect();
KeyResult::Ambiguous { fields }
}
}
}
fn get_shape_at_path(&self, config: &'a Resolution, path: &[&str]) -> Option<&'static Shape> {
if path.is_empty() {
return None;
}
let top_field = config.field_by_name(path[0])?;
let mut current_shape = top_field.value_shape;
for &key in &path[1..] {
current_shape = self.get_field_shape(current_shape, key)?;
}
Some(current_shape)
}
fn get_field_shape(&self, shape: &'static Shape, field_name: &str) -> Option<&'static Shape> {
use facet_core::{StructType, Type, UserType};
match shape.ty {
Type::User(UserType::Struct(StructType { fields, .. })) => {
for field in fields {
if field.effective_name() == field_name {
return Some(field.shape());
}
}
None
}
_ => None,
}
}
#[allow(clippy::result_large_err)] pub fn finish(self) -> Result<ResolutionHandle<'a>, SolverError> {
let Solver {
schema,
candidates,
seen_keys,
} = self;
let all_known_fields: BTreeSet<&'static str> = schema
.resolutions
.iter()
.flat_map(|r| r.fields().values().map(|f| f.serialized_name))
.collect();
let unknown_fields: Vec<String> = seen_keys
.iter()
.filter(|k| !all_known_fields.contains(k.name()))
.map(|k| k.name().to_string())
.collect();
let suggestions = compute_suggestions(&unknown_fields, &all_known_fields);
if candidates.is_empty() {
let mut candidate_failures: Vec<CandidateFailure> = schema
.resolutions
.iter()
.map(|config| build_candidate_failure(config, &seen_keys))
.collect();
sort_candidates_by_closeness(&mut candidate_failures);
return Err(SolverError::NoMatch {
input_fields: seen_keys.iter().map(|k| k.name().to_string()).collect(),
missing_required: Vec::new(),
missing_required_detailed: Vec::new(),
unknown_fields,
closest_resolution: None,
candidate_failures,
suggestions,
});
}
let viable: Vec<usize> = candidates
.iter()
.filter(|idx| {
let config = &schema.resolutions[*idx];
config
.required_field_names()
.iter()
.all(|f| seen_keys.iter().any(|k| k.name() == *f))
})
.collect();
match viable.len() {
0 => {
let mut candidate_failures: Vec<CandidateFailure> = candidates
.iter()
.map(|idx| {
let config = &schema.resolutions[idx];
build_candidate_failure(config, &seen_keys)
})
.collect();
sort_candidates_by_closeness(&mut candidate_failures);
let closest_name = candidate_failures.first().map(|f| f.variant_name.clone());
let closest_config = closest_name
.as_ref()
.and_then(|name| schema.resolutions.iter().find(|r| r.describe() == *name));
let (missing, missing_detailed, closest_resolution) =
if let Some(config) = closest_config {
let missing: Vec<_> = config
.required_field_names()
.iter()
.filter(|f| !seen_keys.iter().any(|k| k.name() == **f))
.copied()
.collect();
let missing_detailed: Vec<_> = missing
.iter()
.filter_map(|name| config.field_by_name(name))
.map(MissingFieldInfo::from_field_info)
.collect();
(missing, missing_detailed, Some(config.describe()))
} else {
(Vec::new(), Vec::new(), None)
};
Err(SolverError::NoMatch {
input_fields: seen_keys.iter().map(|s| s.to_string()).collect(),
missing_required: missing,
missing_required_detailed: missing_detailed,
unknown_fields,
closest_resolution,
candidate_failures,
suggestions,
})
}
1 => {
Ok(ResolutionHandle::from_schema(schema, viable[0]))
}
_ => {
let configs: Vec<_> = viable.iter().map(|&idx| &schema.resolutions[idx]).collect();
let candidates: Vec<String> = configs.iter().map(|c| c.describe()).collect();
let disambiguating_fields = find_disambiguating_fields(&configs);
Err(SolverError::Ambiguous {
candidates,
disambiguating_fields,
})
}
}
}
}
fn build_candidate_failure<'a>(
config: &Resolution,
seen_keys: &BTreeSet<FieldKey<'a>>,
) -> CandidateFailure {
let missing_fields: Vec<MissingFieldInfo> = config
.required_field_names()
.iter()
.filter(|f| !seen_keys.iter().any(|k| k.name() == **f))
.filter_map(|f| config.field_by_name(f))
.map(MissingFieldInfo::from_field_info)
.collect();
let unknown_fields: Vec<String> = seen_keys
.iter()
.filter(|k| config.field_by_key(k).is_none())
.map(|k| k.name().to_string())
.collect();
let suggestion_matches = compute_closeness_score(&unknown_fields, &missing_fields, config);
CandidateFailure {
variant_name: config.describe(),
missing_fields,
unknown_fields,
suggestion_matches,
}
}
#[cfg(feature = "suggestions")]
fn compute_closeness_score(
unknown_fields: &[String],
missing_fields: &[MissingFieldInfo],
config: &Resolution,
) -> usize {
const SIMILARITY_THRESHOLD: f64 = 0.6;
let mut typo_score: usize = 0;
let mut fields_that_would_match: usize = 0;
for unknown in unknown_fields {
let mut best_similarity = 0.0f64;
let mut best_match: Option<&str> = None;
for info in config.fields().values() {
let similarity = strsim::jaro_winkler(unknown, info.serialized_name);
if similarity >= SIMILARITY_THRESHOLD && similarity > best_similarity {
best_similarity = similarity;
best_match = Some(info.serialized_name);
}
}
if let Some(_matched_field) = best_match {
typo_score += (best_similarity * 100.0) as usize;
fields_that_would_match += 1;
}
}
let required_count = config.required_field_names().len();
let currently_missing = missing_fields.len();
let would_be_missing = currently_missing.saturating_sub(fields_that_would_match);
let coverage_score = if required_count > 0 {
((required_count - would_be_missing) * 100) / required_count
} else {
100 };
let truly_unknown = unknown_fields.len().saturating_sub(fields_that_would_match);
let unknown_penalty = truly_unknown * 10;
typo_score + coverage_score.saturating_sub(unknown_penalty)
}
#[cfg(not(feature = "suggestions"))]
fn compute_closeness_score(
_unknown_fields: &[String],
_missing_fields: &[MissingFieldInfo],
_config: &Resolution,
) -> usize {
0
}
fn sort_candidates_by_closeness(failures: &mut [CandidateFailure]) {
failures.sort_by(|a, b| {
b.suggestion_matches.cmp(&a.suggestion_matches)
});
}
#[cfg(feature = "suggestions")]
fn compute_suggestions(
unknown_fields: &[String],
all_known_fields: &BTreeSet<&'static str>,
) -> Vec<FieldSuggestion> {
const SIMILARITY_THRESHOLD: f64 = 0.6;
let mut suggestions = Vec::new();
for unknown in unknown_fields {
let mut best_match: Option<(&'static str, f64)> = None;
for known in all_known_fields {
let similarity = strsim::jaro_winkler(unknown, known);
if similarity >= SIMILARITY_THRESHOLD
&& best_match.is_none_or(|(_, best_sim)| similarity > best_sim)
{
best_match = Some((known, similarity));
}
}
if let Some((suggestion, similarity)) = best_match {
suggestions.push(FieldSuggestion {
unknown: unknown.clone(),
suggestion,
similarity,
});
}
}
suggestions
}
#[cfg(not(feature = "suggestions"))]
fn compute_suggestions(
_unknown_fields: &[String],
_all_known_fields: &BTreeSet<&'static str>,
) -> Vec<FieldSuggestion> {
Vec::new()
}
#[derive(Debug)]
pub enum ProbeResult<'a> {
KeepGoing,
Solved(&'a Resolution),
NoMatch,
}
#[derive(Debug)]
pub struct ProbingSolver<'a> {
candidates: Vec<&'a Resolution>,
}
impl<'a> ProbingSolver<'a> {
pub fn new(schema: &'a Schema) -> Self {
Self {
candidates: schema.resolutions.iter().collect(),
}
}
pub fn from_resolutions(configs: &'a [Resolution]) -> Self {
Self {
candidates: configs.iter().collect(),
}
}
pub fn probe_key(&mut self, path: &[&str], key: &str) -> ProbeResult<'a> {
let mut full_path: Vec<&str> = path.to_vec();
full_path.push(key);
self.candidates.retain(|c| c.has_key_path(&full_path));
match self.candidates.len() {
0 => ProbeResult::NoMatch,
1 => ProbeResult::Solved(self.candidates[0]),
_ => ProbeResult::KeepGoing,
}
}
pub fn candidates(&self) -> &[&'a Resolution] {
&self.candidates
}
pub fn finish(&self) -> ProbeResult<'a> {
match self.candidates.len() {
0 => ProbeResult::NoMatch,
1 => ProbeResult::Solved(self.candidates[0]),
_ => ProbeResult::KeepGoing, }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VariantFormat {
Unit,
NewtypeScalar {
inner_shape: &'static Shape,
},
NewtypeStruct {
inner_shape: &'static Shape,
},
NewtypeTuple {
inner_shape: &'static Shape,
arity: usize,
},
NewtypeSequence {
inner_shape: &'static Shape,
},
NewtypeOther {
inner_shape: &'static Shape,
},
Tuple {
arity: usize,
},
Struct,
}
impl VariantFormat {
pub fn from_variant(variant: &'static Variant) -> Self {
use facet_core::StructKind;
let fields = variant.data.fields;
let kind = variant.data.kind;
match kind {
StructKind::Unit => VariantFormat::Unit,
StructKind::TupleStruct | StructKind::Tuple => {
if fields.len() == 1 {
let field_shape = fields[0].shape();
let inner_shape = deref_pointer(field_shape);
let classification_shape = if let Some(inner) =
facet_reflect::get_metadata_container_value_shape(field_shape)
{
inner
} else {
field_shape
};
if is_scalar_shape(classification_shape)
|| is_unit_enum_shape(classification_shape)
{
VariantFormat::NewtypeScalar {
inner_shape: classification_shape,
}
} else if let Some(arity) = tuple_struct_arity(classification_shape) {
VariantFormat::NewtypeTuple { inner_shape, arity }
} else if is_named_struct_shape(classification_shape)
|| is_map_shape(classification_shape)
{
VariantFormat::NewtypeStruct { inner_shape }
} else if is_sequence_shape(classification_shape) {
VariantFormat::NewtypeSequence { inner_shape }
} else {
VariantFormat::NewtypeOther { inner_shape }
}
} else {
VariantFormat::Tuple {
arity: fields.len(),
}
}
}
StructKind::Struct => VariantFormat::Struct,
}
}
pub const fn expects_scalar(&self) -> bool {
matches!(self, VariantFormat::NewtypeScalar { .. })
}
pub const fn expects_sequence(&self) -> bool {
matches!(
self,
VariantFormat::Tuple { .. }
| VariantFormat::NewtypeTuple { .. }
| VariantFormat::NewtypeSequence { .. }
)
}
pub const fn expects_mapping(&self) -> bool {
matches!(
self,
VariantFormat::Struct | VariantFormat::NewtypeStruct { .. }
)
}
pub const fn is_unit(&self) -> bool {
matches!(self, VariantFormat::Unit)
}
}
fn deref_pointer(shape: &'static Shape) -> &'static Shape {
use facet_core::Def;
match shape.def {
Def::Pointer(pointer_def) => {
if let Some(pointee) = pointer_def.pointee() {
deref_pointer(pointee)
} else {
shape
}
}
_ => shape,
}
}
fn is_scalar_shape(shape: &'static Shape) -> bool {
let shape = deref_pointer(shape);
shape.scalar_type().is_some()
}
fn tuple_struct_arity(shape: &'static Shape) -> Option<usize> {
use facet_core::{StructKind, Type, UserType};
let shape = deref_pointer(shape);
match shape.ty {
Type::User(UserType::Struct(struct_type)) => match struct_type.kind {
StructKind::Tuple | StructKind::TupleStruct => Some(struct_type.fields.len()),
_ => None,
},
_ => None,
}
}
fn is_named_struct_shape(shape: &'static Shape) -> bool {
use facet_core::{StructKind, Type, UserType};
let shape = deref_pointer(shape);
matches!(
shape.ty,
Type::User(UserType::Struct(struct_type)) if matches!(struct_type.kind, StructKind::Struct)
)
}
fn is_sequence_shape(shape: &'static Shape) -> bool {
use facet_core::Def;
let shape = deref_pointer(shape);
matches!(
shape.def,
Def::List(_) | Def::Array(_) | Def::Slice(_) | Def::Set(_)
)
}
fn is_map_shape(shape: &'static Shape) -> bool {
use facet_core::Def;
let shape = deref_pointer(shape);
matches!(shape.def, Def::Map(_))
}
fn is_unit_enum_shape(shape: &'static Shape) -> bool {
use facet_core::{Type, UserType};
let shape = deref_pointer(shape);
match shape.ty {
Type::User(UserType::Enum(enum_type)) => {
enum_type.variants.iter().all(|v| v.data.fields.is_empty())
}
_ => false,
}
}
#[derive(Debug, Default)]
pub struct VariantsByFormat {
pub scalar_variants: Vec<(&'static Variant, &'static Shape)>,
pub bool_variants: Vec<(&'static Variant, &'static Shape)>,
pub int_variants: Vec<(&'static Variant, &'static Shape)>,
pub float_variants: Vec<(&'static Variant, &'static Shape)>,
pub string_variants: Vec<(&'static Variant, &'static Shape)>,
pub tuple_variants: Vec<(&'static Variant, usize)>,
pub struct_variants: Vec<&'static Variant>,
pub unit_variants: Vec<&'static Variant>,
pub other_variants: Vec<&'static Variant>,
}
impl VariantsByFormat {
pub fn from_shape(shape: &'static Shape) -> Option<Self> {
use facet_core::{Type, UserType};
let enum_type = match shape.ty {
Type::User(UserType::Enum(e)) => e,
_ => return None,
};
let mut result = Self::default();
for variant in enum_type.variants {
match VariantFormat::from_variant(variant) {
VariantFormat::Unit => {
result.unit_variants.push(variant);
}
VariantFormat::NewtypeScalar { inner_shape } => {
result.scalar_variants.push((variant, inner_shape));
use facet_core::ScalarType;
let scalar_shape = deref_pointer(inner_shape);
match scalar_shape.scalar_type() {
Some(ScalarType::Bool) => {
result.bool_variants.push((variant, inner_shape));
}
Some(
ScalarType::U8
| ScalarType::U16
| ScalarType::U32
| ScalarType::U64
| ScalarType::U128
| ScalarType::USize
| ScalarType::I8
| ScalarType::I16
| ScalarType::I32
| ScalarType::I64
| ScalarType::I128
| ScalarType::ISize,
) => {
result.int_variants.push((variant, inner_shape));
}
Some(ScalarType::F32 | ScalarType::F64) => {
result.float_variants.push((variant, inner_shape));
}
#[cfg(feature = "alloc")]
Some(ScalarType::String | ScalarType::CowStr) => {
result.string_variants.push((variant, inner_shape));
}
Some(ScalarType::Str | ScalarType::Char) => {
result.string_variants.push((variant, inner_shape));
}
_ => {
}
}
}
VariantFormat::NewtypeStruct { .. } => {
result.struct_variants.push(variant);
}
VariantFormat::NewtypeTuple { arity, .. } => {
result.tuple_variants.push((variant, arity));
}
VariantFormat::NewtypeSequence { .. } => {
result.tuple_variants.push((variant, 0));
}
VariantFormat::NewtypeOther { .. } => {
result.other_variants.push(variant);
}
VariantFormat::Tuple { arity } => {
result.tuple_variants.push((variant, arity));
}
VariantFormat::Struct => {
result.struct_variants.push(variant);
}
}
}
Some(result)
}
pub fn tuple_variants_with_arity(&self, arity: usize) -> Vec<&'static Variant> {
self.tuple_variants
.iter()
.filter(|(_, a)| *a == arity)
.map(|(v, _)| *v)
.collect()
}
pub const fn has_scalar_variants(&self) -> bool {
!self.scalar_variants.is_empty()
}
pub const fn has_tuple_variants(&self) -> bool {
!self.tuple_variants.is_empty()
}
pub const fn has_struct_variants(&self) -> bool {
!self.struct_variants.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum EnumRepr {
#[default]
Flattened,
ExternallyTagged,
InternallyTagged {
tag: &'static str,
},
AdjacentlyTagged {
tag: &'static str,
content: &'static str,
},
}
impl EnumRepr {
pub const fn from_shape(shape: &'static Shape) -> Self {
let tag = shape.get_tag_attr();
let content = shape.get_content_attr();
let untagged = shape.is_untagged();
match (tag, content, untagged) {
(_, _, true) => EnumRepr::Flattened,
(Some(t), Some(c), false) => EnumRepr::AdjacentlyTagged { tag: t, content: c },
(Some(t), None, false) => EnumRepr::InternallyTagged { tag: t },
(None, None, false) => EnumRepr::ExternallyTagged,
(None, Some(_), false) => EnumRepr::ExternallyTagged,
}
}
}
impl Schema {
pub fn build(shape: &'static Shape) -> Result<Self, SchemaError> {
Self::build_with_repr(shape, EnumRepr::Flattened)
}
pub fn build_auto(shape: &'static Shape) -> Result<Self, SchemaError> {
let builder = SchemaBuilder::new(shape, EnumRepr::Flattened).with_auto_detect();
builder.into_schema()
}
pub fn build_externally_tagged(shape: &'static Shape) -> Result<Self, SchemaError> {
Self::build_with_repr(shape, EnumRepr::ExternallyTagged)
}
pub fn build_with_repr(shape: &'static Shape, repr: EnumRepr) -> Result<Self, SchemaError> {
let builder = SchemaBuilder::new(shape, repr);
builder.into_schema()
}
pub fn resolutions(&self) -> &[Resolution] {
&self.resolutions
}
pub const fn format(&self) -> Format {
self.format
}
pub fn build_dom(shape: &'static Shape) -> Result<Self, SchemaError> {
let builder = SchemaBuilder::new(shape, EnumRepr::Flattened)
.with_auto_detect()
.with_format(Format::Dom);
builder.into_schema()
}
pub fn build_with_format(shape: &'static Shape, format: Format) -> Result<Self, SchemaError> {
let builder = SchemaBuilder::new(shape, EnumRepr::Flattened)
.with_auto_detect()
.with_format(format);
builder.into_schema()
}
}
struct SchemaBuilder {
shape: &'static Shape,
enum_repr: EnumRepr,
auto_detect_enum_repr: bool,
format: Format,
}
impl SchemaBuilder {
const fn new(shape: &'static Shape, enum_repr: EnumRepr) -> Self {
Self {
shape,
enum_repr,
auto_detect_enum_repr: false,
format: Format::Flat,
}
}
const fn with_auto_detect(mut self) -> Self {
self.auto_detect_enum_repr = true;
self
}
const fn with_format(mut self, format: Format) -> Self {
self.format = format;
self
}
fn analyze(&self) -> Result<Vec<Resolution>, SchemaError> {
self.analyze_shape(self.shape, FieldPath::empty(), Vec::new())
}
fn analyze_shape(
&self,
shape: &'static Shape,
current_path: FieldPath,
key_prefix: KeyPath,
) -> Result<Vec<Resolution>, SchemaError> {
match shape.ty {
Type::User(UserType::Struct(struct_type)) => {
self.analyze_struct(struct_type, current_path, key_prefix)
}
Type::User(UserType::Enum(enum_type)) => {
self.analyze_enum(shape, enum_type, current_path, key_prefix)
}
_ => {
Ok(vec![Resolution::new()])
}
}
}
fn analyze_enum(
&self,
shape: &'static Shape,
enum_type: facet_core::EnumType,
current_path: FieldPath,
key_prefix: KeyPath,
) -> Result<Vec<Resolution>, SchemaError> {
let enum_name = shape.type_identifier;
let mut result = Vec::new();
for variant in enum_type.variants {
let mut config = Resolution::new();
config.add_variant_selection(current_path.clone(), enum_name, variant.name);
let variant_path = current_path.push_variant("", variant.name);
let variant_configs =
self.analyze_variant_content(variant, &variant_path, &key_prefix)?;
for variant_config in variant_configs {
let mut final_config = config.clone();
final_config.merge(&variant_config)?;
result.push(final_config);
}
}
Ok(result)
}
fn analyze_struct(
&self,
struct_type: StructType,
current_path: FieldPath,
key_prefix: KeyPath,
) -> Result<Vec<Resolution>, SchemaError> {
let mut configs = vec![Resolution::new()];
for field in struct_type.fields {
configs =
self.analyze_field_into_configs(field, ¤t_path, &key_prefix, configs)?;
}
Ok(configs)
}
fn analyze_field_into_configs(
&self,
field: &'static Field,
parent_path: &FieldPath,
key_prefix: &KeyPath,
mut configs: Vec<Resolution>,
) -> Result<Vec<Resolution>, SchemaError> {
let is_flatten = field.is_flattened();
if is_flatten {
self.analyze_flattened_field_into_configs(field, parent_path, key_prefix, configs)
} else {
let field_path = parent_path.push_field(field.name);
let required = !field.has_default() && !is_option_type(field.shape());
let mut field_key_path = key_prefix.clone();
field_key_path.push(field.effective_name());
let field_info = FieldInfo {
serialized_name: field.effective_name(),
path: field_path,
required,
value_shape: field.shape(),
field,
category: if self.format == Format::Dom {
FieldCategory::from_field_dom(field).unwrap_or(FieldCategory::Element)
} else {
FieldCategory::Flat
},
};
for config in &mut configs {
config.add_field(field_info.clone())?;
config.add_key_path(field_key_path.clone());
}
configs =
self.collect_nested_key_paths_for_shape(field.shape(), &field_key_path, configs)?;
Ok(configs)
}
}
fn collect_nested_key_paths_for_shape(
&self,
shape: &'static Shape,
key_prefix: &KeyPath,
configs: Vec<Resolution>,
) -> Result<Vec<Resolution>, SchemaError> {
match shape.ty {
Type::User(UserType::Struct(struct_type)) => {
self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
}
_ => Ok(configs),
}
}
fn collect_nested_key_paths_for_struct(
&self,
struct_type: StructType,
key_prefix: &KeyPath,
mut configs: Vec<Resolution>,
) -> Result<Vec<Resolution>, SchemaError> {
for field in struct_type.fields {
let is_flatten = field.is_flattened();
let mut field_key_path = key_prefix.clone();
if is_flatten {
configs =
self.collect_nested_key_paths_for_flattened(field, key_prefix, configs)?;
} else {
field_key_path.push(field.effective_name());
for config in &mut configs {
config.add_key_path(field_key_path.clone());
}
configs = self.collect_nested_key_paths_for_shape(
field.shape(),
&field_key_path,
configs,
)?;
}
}
Ok(configs)
}
fn collect_nested_key_paths_for_flattened(
&self,
field: &'static Field,
key_prefix: &KeyPath,
configs: Vec<Resolution>,
) -> Result<Vec<Resolution>, SchemaError> {
let shape = field.shape();
match shape.ty {
Type::User(UserType::Struct(struct_type)) => {
self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
}
Type::User(UserType::Enum(enum_type)) => {
let mut result = Vec::new();
for config in configs {
let selected_variant = config
.variant_selections()
.iter()
.find(|vs| {
vs.path.segments().last() == Some(&PathSegment::Field(field.name))
})
.map(|vs| vs.variant_name);
if let Some(variant_name) = selected_variant {
if let Some(variant) =
enum_type.variants.iter().find(|v| v.name == variant_name)
{
let mut updated_config = config;
updated_config = self.collect_variant_key_paths(
variant,
key_prefix,
updated_config,
)?;
result.push(updated_config);
} else {
result.push(config);
}
} else {
result.push(config);
}
}
Ok(result)
}
_ => Ok(configs),
}
}
fn collect_variant_key_paths(
&self,
variant: &'static Variant,
key_prefix: &KeyPath,
mut config: Resolution,
) -> Result<Resolution, SchemaError> {
if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
let inner_field = &variant.data.fields[0];
let inner_shape = inner_field.shape();
if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
let configs = self.collect_nested_key_paths_for_struct(
inner_struct,
key_prefix,
vec![config],
)?;
return Ok(configs.into_iter().next().unwrap_or_else(Resolution::new));
}
}
for variant_field in variant.data.fields {
let is_flatten = variant_field.is_flattened();
if is_flatten {
let configs = self.collect_nested_key_paths_for_flattened(
variant_field,
key_prefix,
vec![config],
)?;
config = configs.into_iter().next().unwrap_or_else(Resolution::new);
} else {
let mut field_key_path = key_prefix.clone();
field_key_path.push(variant_field.effective_name());
config.add_key_path(field_key_path.clone());
let configs = self.collect_nested_key_paths_for_shape(
variant_field.shape(),
&field_key_path,
vec![config],
)?;
config = configs.into_iter().next().unwrap_or_else(Resolution::new);
}
}
Ok(config)
}
fn collect_variant_key_paths_only(
&self,
variant: &'static Variant,
key_prefix: &KeyPath,
config: &mut Resolution,
) -> Result<(), SchemaError> {
Self::collect_variant_fields_key_paths_only(variant, key_prefix, config);
Ok(())
}
fn collect_struct_key_paths_only(
struct_type: StructType,
key_prefix: &KeyPath,
config: &mut Resolution,
) {
for field in struct_type.fields {
let is_flatten = field.is_flattened();
if is_flatten {
Self::collect_shape_key_paths_only(field.shape(), key_prefix, config);
} else {
let mut field_key_path = key_prefix.clone();
field_key_path.push(field.effective_name());
config.add_key_path(field_key_path.clone());
Self::collect_shape_key_paths_only(field.shape(), &field_key_path, config);
}
}
}
fn collect_shape_key_paths_only(
shape: &'static Shape,
key_prefix: &KeyPath,
config: &mut Resolution,
) {
match shape.ty {
Type::User(UserType::Struct(inner_struct)) => {
Self::collect_struct_key_paths_only(inner_struct, key_prefix, config);
}
Type::User(UserType::Enum(enum_type)) => {
for variant in enum_type.variants {
Self::collect_variant_fields_key_paths_only(variant, key_prefix, config);
}
}
_ => {}
}
}
fn collect_variant_fields_key_paths_only(
variant: &'static Variant,
key_prefix: &KeyPath,
config: &mut Resolution,
) {
if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
let inner_field = &variant.data.fields[0];
Self::collect_shape_key_paths_only(inner_field.shape(), key_prefix, config);
return;
}
for variant_field in variant.data.fields {
let mut field_key_path = key_prefix.clone();
field_key_path.push(variant_field.effective_name());
config.add_key_path(field_key_path.clone());
Self::collect_shape_key_paths_only(variant_field.shape(), &field_key_path, config);
}
}
fn analyze_flattened_field_into_configs(
&self,
field: &'static Field,
parent_path: &FieldPath,
key_prefix: &KeyPath,
configs: Vec<Resolution>,
) -> Result<Vec<Resolution>, SchemaError> {
let field_path = parent_path.push_field(field.name);
let original_shape = field.shape();
let (shape, is_optional_flatten) = match unwrap_option_type(original_shape) {
Some(inner) => (inner, true),
None => (original_shape, false),
};
match shape.ty {
Type::User(UserType::Struct(struct_type)) => {
let mut struct_configs =
self.analyze_struct(struct_type, field_path, key_prefix.clone())?;
if is_optional_flatten {
for config in &mut struct_configs {
config.mark_all_optional();
}
}
let mut result = Vec::new();
for base_config in configs {
for struct_config in &struct_configs {
let mut merged = base_config.clone();
merged.merge(struct_config)?;
result.push(merged);
}
}
Ok(result)
}
Type::User(UserType::Enum(enum_type)) => {
let mut result = Vec::new();
let enum_name = shape.type_identifier;
let enum_repr = if self.auto_detect_enum_repr {
EnumRepr::from_shape(shape)
} else {
self.enum_repr.clone()
};
for base_config in configs {
for variant in enum_type.variants {
let mut forked = base_config.clone();
forked.add_variant_selection(field_path.clone(), enum_name, variant.name);
let variant_path = field_path.push_variant(field.name, variant.name);
match &enum_repr {
EnumRepr::ExternallyTagged => {
let mut variant_key_prefix = key_prefix.clone();
variant_key_prefix.push(variant.name);
forked.add_key_path(variant_key_prefix.clone());
let variant_field_info = FieldInfo {
serialized_name: variant.name,
path: variant_path.clone(),
required: !is_optional_flatten,
value_shape: shape, field, category: FieldCategory::Element, };
forked.add_field(variant_field_info)?;
self.collect_variant_key_paths_only(
variant,
&variant_key_prefix,
&mut forked,
)?;
result.push(forked);
}
EnumRepr::Flattened => {
let mut variant_configs = self.analyze_variant_content(
variant,
&variant_path,
key_prefix,
)?;
if is_optional_flatten {
for config in &mut variant_configs {
config.mark_all_optional();
}
}
for variant_config in variant_configs {
let mut final_config = forked.clone();
final_config.merge(&variant_config)?;
result.push(final_config);
}
}
EnumRepr::InternallyTagged { tag } => {
let mut tag_key_path = key_prefix.clone();
tag_key_path.push(tag);
forked.add_key_path(tag_key_path);
let tag_field_info = FieldInfo {
serialized_name: tag,
path: variant_path.clone(),
required: !is_optional_flatten,
value_shape: shape, field, category: FieldCategory::Element, };
forked.add_field(tag_field_info)?;
let mut variant_configs = self.analyze_variant_content(
variant,
&variant_path,
key_prefix,
)?;
if is_optional_flatten {
for config in &mut variant_configs {
config.mark_all_optional();
}
}
for variant_config in variant_configs {
let mut final_config = forked.clone();
final_config.merge(&variant_config)?;
result.push(final_config);
}
}
EnumRepr::AdjacentlyTagged { tag, content } => {
let mut tag_key_path = key_prefix.clone();
tag_key_path.push(tag);
forked.add_key_path(tag_key_path);
let tag_field_info = FieldInfo {
serialized_name: tag,
path: variant_path.clone(),
required: !is_optional_flatten,
value_shape: shape, field, category: FieldCategory::Element, };
forked.add_field(tag_field_info)?;
let mut content_key_prefix = key_prefix.clone();
content_key_prefix.push(content);
forked.add_key_path(content_key_prefix.clone());
self.collect_variant_key_paths_only(
variant,
&content_key_prefix,
&mut forked,
)?;
result.push(forked);
}
}
}
}
Ok(result)
}
_ => {
if let Def::Map(_) = &shape.def {
let field_info = FieldInfo {
serialized_name: field.effective_name(),
path: field_path,
required: false, value_shape: shape,
field,
category: if self.format == Format::Dom {
if field.is_attribute() {
FieldCategory::Attribute
} else {
FieldCategory::Element
}
} else {
FieldCategory::Flat
},
};
let mut result = configs;
for config in &mut result {
config.set_catch_all_map(field_info.category, field_info.clone());
}
return Ok(result);
}
if matches!(&shape.def, Def::DynamicValue(_)) {
let field_info = FieldInfo {
serialized_name: field.effective_name(),
path: field_path,
required: false, value_shape: shape,
field,
category: if self.format == Format::Dom {
if field.is_attribute() {
FieldCategory::Attribute
} else {
FieldCategory::Element
}
} else {
FieldCategory::Flat
},
};
let mut result = configs;
for config in &mut result {
config.set_catch_all_map(field_info.category, field_info.clone());
}
return Ok(result);
}
let required =
!field.has_default() && !is_option_type(shape) && !is_optional_flatten;
let mut field_key_path = key_prefix.clone();
field_key_path.push(field.effective_name());
let field_info = FieldInfo {
serialized_name: field.effective_name(),
path: field_path,
required,
value_shape: shape,
field,
category: if self.format == Format::Dom {
FieldCategory::from_field_dom(field).unwrap_or(FieldCategory::Element)
} else {
FieldCategory::Flat
},
};
let mut result = configs;
for config in &mut result {
config.add_field(field_info.clone())?;
config.add_key_path(field_key_path.clone());
}
Ok(result)
}
}
}
fn analyze_variant_content(
&self,
variant: &'static Variant,
variant_path: &FieldPath,
key_prefix: &KeyPath,
) -> Result<Vec<Resolution>, SchemaError> {
if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
let inner_field = &variant.data.fields[0];
let inner_shape = inner_field.shape();
let effective_shape = unwrap_to_effective_shape(inner_shape);
if let Type::User(UserType::Struct(inner_struct)) = effective_shape.ty {
let inner_path = variant_path.push_field("0");
return self.analyze_struct(inner_struct, inner_path, key_prefix.clone());
}
}
let mut configs = vec![Resolution::new()];
for variant_field in variant.data.fields {
configs =
self.analyze_field_into_configs(variant_field, variant_path, key_prefix, configs)?;
}
Ok(configs)
}
fn into_schema(self) -> Result<Schema, SchemaError> {
let resolutions = self.analyze()?;
let num_resolutions = resolutions.len();
let mut field_to_resolutions: BTreeMap<&'static str, ResolutionSet> = BTreeMap::new();
for (idx, config) in resolutions.iter().enumerate() {
for field_info in config.fields().values() {
field_to_resolutions
.entry(field_info.serialized_name)
.or_insert_with(|| ResolutionSet::empty(num_resolutions))
.insert(idx);
}
}
let mut dom_field_to_resolutions: BTreeMap<(FieldCategory, &'static str), ResolutionSet> =
BTreeMap::new();
if self.format == Format::Dom {
for (idx, config) in resolutions.iter().enumerate() {
for field_info in config.fields().values() {
dom_field_to_resolutions
.entry((field_info.category, field_info.serialized_name))
.or_insert_with(|| ResolutionSet::empty(num_resolutions))
.insert(idx);
}
}
}
Ok(Schema {
shape: self.shape,
format: self.format,
resolutions,
field_to_resolutions,
dom_field_to_resolutions,
})
}
}
const fn is_option_type(shape: &'static Shape) -> bool {
matches!(shape.def, Def::Option(_))
}
const fn unwrap_option_type(shape: &'static Shape) -> Option<&'static Shape> {
match shape.def {
Def::Option(option_def) => Some(option_def.t),
_ => None,
}
}
fn unwrap_to_effective_shape(shape: &'static Shape) -> &'static Shape {
let shape = unwrap_transparent(shape);
if let Some(proxy_def) = shape.proxy {
unwrap_to_effective_shape(proxy_def.shape)
} else {
shape
}
}
fn unwrap_transparent(shape: &'static Shape) -> &'static Shape {
if let Some(inner) = shape.inner {
unwrap_transparent(inner)
} else {
shape
}
}