use std::borrow::Cow;
use std::collections::HashMap;
use facet_core::{Def, Field, StructKind, StructType, Type, UserType};
use crate::naming::{apply_rename_all, dom_key};
use facet_singularize::singularize;
#[derive(Clone)]
pub(crate) struct FieldInfo {
pub idx: usize,
#[allow(dead_code)]
pub field: &'static Field,
pub is_list: bool,
pub is_array: bool,
pub is_set: bool,
pub is_tuple: bool,
pub namespace: Option<&'static str>,
}
#[derive(Clone)]
pub(crate) struct FlattenedChildInfo {
pub parent_idx: usize,
pub child_idx: usize,
pub child_info: FieldInfo,
pub parent_is_option: bool,
}
#[derive(Clone)]
pub(crate) struct FlattenedEnumInfo {
pub field_idx: usize,
#[allow(dead_code)]
pub field_info: FieldInfo,
}
#[derive(Clone)]
pub(crate) struct NestedFlattenedMapInfo {
pub parent_idx: usize,
pub child_idx: usize,
pub child_info: FieldInfo,
pub parent_is_option: bool,
}
pub(crate) struct StructFieldMap {
attribute_fields: HashMap<String, Vec<FieldInfo>>,
element_fields: HashMap<String, Vec<FieldInfo>>,
pub elements_fields: HashMap<String, FieldInfo>,
pub attributes_field: Option<FieldInfo>,
pub text_field: Option<FieldInfo>,
pub tag_field: Option<FieldInfo>,
pub doctype_field: Option<FieldInfo>,
pub other_field: Option<FieldInfo>,
pub tuple_fields: Option<Vec<FieldInfo>>,
flattened_children: HashMap<String, Vec<FlattenedChildInfo>>,
flattened_attributes: HashMap<String, Vec<FlattenedChildInfo>>,
pub flattened_enum: Option<FlattenedEnumInfo>,
pub flattened_maps: Vec<FieldInfo>,
pub flattened_attr_maps: Vec<FieldInfo>,
pub nested_flattened_attr_maps: Vec<NestedFlattenedMapInfo>,
pub has_flatten: bool,
pub catch_all_elements_field: Option<FieldInfo>,
}
fn field_dom_key<'a>(
field_name: &'a str,
field_rename: Option<&'a str>,
rename_all: Option<&str>,
) -> Cow<'a, str> {
if let Some(rename) = field_rename {
Cow::Borrowed(rename)
} else if let Some(rename_all) = rename_all {
Cow::Owned(apply_rename_all(field_name, rename_all))
} else {
dom_key(field_name, None)
}
}
impl StructFieldMap {
pub fn new(
struct_def: &'static StructType,
ns_all: Option<&'static str>,
rename_all: Option<&'static str>,
format_ns: Option<&'static str>,
) -> Self {
let mut attribute_fields: HashMap<String, Vec<FieldInfo>> = HashMap::new();
let mut element_fields: HashMap<String, Vec<FieldInfo>> = HashMap::new();
let mut elements_fields: HashMap<String, FieldInfo> = HashMap::new();
let mut attributes_field = None;
let mut text_field = None;
let mut tag_field = None;
let mut doctype_field = None;
let mut other_field = None;
let mut flattened_children: HashMap<String, Vec<FlattenedChildInfo>> = HashMap::new();
let mut flattened_attributes: HashMap<String, Vec<FlattenedChildInfo>> = HashMap::new();
let mut flattened_enum: Option<FlattenedEnumInfo> = None;
let mut flattened_maps: Vec<FieldInfo> = Vec::new();
let mut flattened_attr_maps: Vec<FieldInfo> = Vec::new();
let mut nested_flattened_attr_maps: Vec<NestedFlattenedMapInfo> = Vec::new();
let mut has_flatten = false;
let mut catch_all_elements_field: Option<FieldInfo> = None;
for (idx, field) in struct_def.fields.iter().enumerate() {
if field.is_flattened() {
has_flatten = true;
let parent_is_option = matches!(field.shape().def, Def::Option(_));
if is_flattened_enum(field) {
let shape = field.shape();
let (is_list, is_array, is_set, is_tuple) = classify_sequence_shape(shape);
let namespace: Option<&'static str> = field
.get_attr(Some("xml"), "ns")
.and_then(|attr| attr.get_as::<&str>().copied());
flattened_enum = Some(FlattenedEnumInfo {
field_idx: idx,
field_info: FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
},
});
continue;
}
if let Some(inner_struct_def) = get_flattened_struct_def(field) {
for (child_idx, child_field) in inner_struct_def.fields.iter().enumerate() {
if child_field.is_flattened() && is_flattened_map(child_field) {
let namespace: Option<&'static str> = child_field
.get_attr(Some("xml"), "ns")
.and_then(|attr| attr.get_as::<&str>().copied());
let info = FieldInfo {
idx: child_idx,
field: child_field,
is_list: false,
is_array: false,
is_set: false,
is_tuple: false,
namespace,
};
let nested_info = NestedFlattenedMapInfo {
parent_idx: idx,
child_idx,
child_info: info.clone(),
parent_is_option,
};
nested_flattened_attr_maps.push(nested_info);
continue;
}
let child_shape = child_field.shape();
let (is_list, is_array, is_set, is_tuple) =
classify_sequence_shape(child_shape);
let namespace: Option<&'static str> = child_field
.get_attr(Some("xml"), "ns")
.and_then(|attr| attr.get_as::<&str>().copied());
let child_key = dom_key(child_field.name, child_field.rename);
let child_info = FieldInfo {
idx: child_idx,
field: child_field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
let flattened_child = FlattenedChildInfo {
parent_idx: idx,
child_idx,
child_info,
parent_is_option,
};
let is_attribute = child_field.is_attribute();
if is_attribute {
flattened_attributes
.entry(child_key.clone().into_owned())
.or_default()
.push(flattened_child.clone());
if let Some(alias) = child_field.alias {
flattened_attributes
.entry(alias.to_string())
.or_default()
.push(flattened_child);
}
} else {
flattened_children
.entry(child_key.clone().into_owned())
.or_default()
.push(flattened_child.clone());
if (is_list || is_set) && !is_tuple && child_field.rename.is_none() {
let singular_key = singularize(&child_key);
if singular_key != *child_key {
flattened_children
.entry(singular_key)
.or_default()
.push(flattened_child.clone());
}
}
if let Some(alias) = child_field.alias {
flattened_children
.entry(alias.to_string())
.or_default()
.push(flattened_child);
}
}
}
} else if is_flattened_map(field) {
let _shape = field.shape();
let namespace: Option<&'static str> = field
.get_attr(Some("xml"), "ns")
.and_then(|attr| attr.get_as::<&str>().copied());
let info = FieldInfo {
idx,
field,
is_list: false,
is_array: false,
is_set: false,
is_tuple: false,
namespace,
};
flattened_maps.push(info.clone());
flattened_attr_maps.push(info);
}
continue; }
let shape = field.shape();
let (is_list, is_array, is_set, is_tuple) = classify_sequence_shape(shape);
let namespace: Option<&'static str> = field
.get_attr(Some("xml"), "ns")
.and_then(|attr| attr.get_as::<&str>().copied());
let element_key = field_dom_key(field.name, field.rename, rename_all);
if field.is_attribute() {
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
if (is_list || is_set) && field.rename.is_none() {
attributes_field = Some(info);
} else {
let attr_key = field_dom_key(field.name, field.rename, rename_all);
attribute_fields
.entry(attr_key.into_owned())
.or_default()
.push(info.clone());
if let Some(alias) = field.alias {
attribute_fields
.entry(alias.to_string())
.or_default()
.push(info);
}
}
} else if field.is_elements() {
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
if item_type_has_tag_field(shape) {
catch_all_elements_field = Some(info);
} else if let Some(rename) = field.rename {
elements_fields.insert(rename.to_string(), info);
} else if let Some(enum_def) =
get_item_type_enum(shape).or_else(|| get_item_type_proxy_enum(shape, format_ns))
{
for variant in enum_def.variants.iter() {
let variant_key: Cow<'_, str> = if variant.rename.is_some() {
Cow::Borrowed(variant.effective_name())
} else {
dom_key(variant.name, None)
};
elements_fields.insert(variant_key.into_owned(), info.clone());
}
} else if let Some(item_rename) = get_item_type_rename(shape) {
elements_fields.insert(item_rename.to_string(), info);
} else if let Some(item_element_name) = get_item_type_default_element_name(shape) {
elements_fields.insert(item_element_name, info);
} else {
let element_key = singularize(&field_dom_key(field.name, None, rename_all));
elements_fields.insert(element_key, info);
};
} else if field.is_text() {
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
text_field = Some(info);
} else if field.is_tag() {
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
tag_field = Some(info);
} else if field.is_doctype() {
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
doctype_field = Some(info);
} else {
if field.is_other() {
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace,
};
other_field = Some(info);
}
let effective_namespace = namespace.or(ns_all);
let info = FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace: effective_namespace,
};
element_fields
.entry(element_key.clone().into_owned())
.or_default()
.push(info.clone());
if (is_list || is_set) && !is_tuple && field.rename.is_none() {
let singular_key = singularize(&element_key);
if singular_key != element_key {
element_fields
.entry(singular_key)
.or_default()
.push(info.clone());
}
}
if let Some(alias) = field.alias {
element_fields
.entry(alias.to_string())
.or_default()
.push(info);
}
}
}
let tuple_fields = if matches!(struct_def.kind, StructKind::TupleStruct | StructKind::Tuple)
{
let fields: Vec<FieldInfo> = struct_def
.fields
.iter()
.enumerate()
.map(|(idx, field)| {
let shape = field.shape();
let (is_list, is_array, is_set, is_tuple) = classify_sequence_shape(shape);
FieldInfo {
idx,
field,
is_list,
is_array,
is_set,
is_tuple,
namespace: None,
}
})
.collect();
Some(fields)
} else {
None
};
Self {
attribute_fields,
element_fields,
elements_fields,
attributes_field,
text_field,
tag_field,
doctype_field,
other_field,
tuple_fields,
flattened_children,
flattened_attributes,
flattened_enum,
flattened_maps,
flattened_attr_maps,
nested_flattened_attr_maps,
has_flatten,
catch_all_elements_field,
}
}
pub fn find_attribute(&self, name: &str, namespace: Option<&str>) -> Option<&FieldInfo> {
self.attribute_fields.get(name).and_then(|fields| {
let exact_match = fields
.iter()
.find(|info| info.namespace.is_some() && info.namespace == namespace);
if exact_match.is_some() {
return exact_match;
}
fields.iter().find(|info| info.namespace.is_none())
})
}
pub fn find_element(&self, tag: &str, namespace: Option<&str>) -> Option<&FieldInfo> {
self.element_fields.get(tag).and_then(|fields| {
let exact_match = fields
.iter()
.find(|info| info.namespace.is_some() && info.namespace == namespace);
if exact_match.is_some() {
return exact_match;
}
fields.iter().find(|info| info.namespace.is_none())
})
}
pub fn find_flattened_child(
&self,
tag: &str,
namespace: Option<&str>,
) -> Option<&FlattenedChildInfo> {
self.flattened_children.get(tag).and_then(|children| {
let exact_match = children.iter().find(|info| {
info.child_info.namespace.is_some() && info.child_info.namespace == namespace
});
if exact_match.is_some() {
return exact_match;
}
children
.iter()
.find(|info| info.child_info.namespace.is_none())
})
}
pub fn find_flattened_attribute(
&self,
name: &str,
namespace: Option<&str>,
) -> Option<&FlattenedChildInfo> {
self.flattened_attributes.get(name).and_then(|children| {
let exact_match = children.iter().find(|info| {
info.child_info.namespace.is_some() && info.child_info.namespace == namespace
});
if exact_match.is_some() {
return exact_match;
}
children
.iter()
.find(|info| info.child_info.namespace.is_none())
})
}
pub fn get_tuple_field(&self, index: usize) -> Option<&FieldInfo> {
self.tuple_fields
.as_ref()
.and_then(|fields| fields.get(index))
}
pub fn is_tuple(&self) -> bool {
self.tuple_fields.is_some()
}
pub fn list_set_element_fields(&self) -> impl Iterator<Item = (usize, &FieldInfo)> {
let mut seen = std::collections::HashSet::new();
self.element_fields
.values()
.flatten()
.filter(|info| info.is_list || info.is_set)
.filter(move |info| seen.insert(info.idx))
.map(|info| (info.idx, info))
}
}
fn is_flattened_enum(field: &'static Field) -> bool {
let shape = field.shape();
if let Def::Option(option_def) = &shape.def {
let inner_shape = option_def.t();
if matches!(&inner_shape.ty, Type::User(UserType::Enum(_)))
&& !matches!(&inner_shape.def, Def::Option(_))
{
return true;
}
return false;
}
if matches!(&shape.ty, Type::User(UserType::Enum(_))) {
return true;
}
if let Def::List(list_def) = &shape.def {
let item_shape = list_def.t();
if matches!(&item_shape.ty, Type::User(UserType::Enum(_)))
&& !matches!(&item_shape.def, Def::Option(_))
{
return true;
}
}
false
}
fn get_flattened_struct_def(field: &'static Field) -> Option<&'static StructType> {
let shape = field.shape();
if let Type::User(UserType::Struct(struct_def)) = &shape.ty {
return Some(struct_def);
}
if let Def::Option(option_def) = &shape.def {
let inner_shape = option_def.t();
if let Type::User(UserType::Struct(struct_def)) = &inner_shape.ty {
return Some(struct_def);
}
}
None
}
fn is_flattened_map(field: &'static Field) -> bool {
let shape = field.shape();
if matches!(&shape.def, Def::Map(_)) {
return true;
}
if let Def::Option(option_def) = &shape.def {
let inner_shape = option_def.t();
if matches!(&inner_shape.def, Def::Map(_)) {
return true;
}
}
false
}
fn classify_sequence_shape(shape: &facet_core::Shape) -> (bool, bool, bool, bool) {
match &shape.def {
Def::List(_) | Def::Slice(_) => (true, false, false, false),
Def::Array(_) => (false, true, false, false),
Def::Set(_) => (false, false, true, false),
Def::Pointer(ptr_def) => {
ptr_def
.pointee()
.map(classify_sequence_shape)
.unwrap_or((false, false, false, false))
}
_ => {
if let Type::User(UserType::Struct(struct_def)) = &shape.ty
&& struct_def.kind == StructKind::Tuple
{
return (false, false, false, true);
}
(false, false, false, false)
}
}
}
fn get_item_shape(shape: &facet_core::Shape) -> Option<&'static facet_core::Shape> {
match &shape.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
Def::Array(array_def) => Some(array_def.t()),
Def::Pointer(ptr_def) => {
ptr_def.pointee().and_then(|inner| match &inner.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
_ => None,
})
}
_ => None,
}
}
fn get_item_type_enum(shape: &facet_core::Shape) -> Option<&'static facet_core::EnumType> {
let item_shape = get_item_shape(shape)?;
match &item_shape.ty {
Type::User(UserType::Enum(enum_def)) => Some(enum_def),
_ => None,
}
}
fn get_item_type_proxy_enum(
shape: &facet_core::Shape,
format_ns: Option<&'static str>,
) -> Option<&'static facet_core::EnumType> {
let item_shape = get_item_shape(shape)?;
let proxy_def = item_shape.effective_proxy(format_ns)?;
let proxy_shape = proxy_def.shape;
match &proxy_shape.ty {
Type::User(UserType::Enum(enum_def)) => Some(enum_def),
_ => None,
}
}
pub(crate) fn get_item_type_rename(shape: &facet_core::Shape) -> Option<&'static str> {
let item_shape = match &shape.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
Def::Array(array_def) => Some(array_def.t()),
Def::Pointer(ptr_def) => {
ptr_def.pointee().and_then(|inner| match &inner.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
_ => None,
})
}
_ => None,
}?;
item_shape.get_builtin_attr_value::<&str>("rename")
}
pub(crate) fn get_item_type_default_element_name(shape: &facet_core::Shape) -> Option<String> {
let item_shape = match &shape.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
Def::Array(array_def) => Some(array_def.t()),
Def::Pointer(ptr_def) => {
ptr_def.pointee().and_then(|inner| match &inner.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
_ => None,
})
}
_ => None,
}?;
Some(crate::naming::to_element_name(item_shape.type_identifier).into_owned())
}
fn item_type_has_tag_field(shape: &facet_core::Shape) -> bool {
let item_shape = match &shape.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
Def::Array(array_def) => Some(array_def.t()),
Def::Pointer(ptr_def) => {
ptr_def.pointee().and_then(|inner| match &inner.def {
Def::List(list_def) => Some(list_def.t()),
Def::Set(set_def) => Some(set_def.t()),
Def::Slice(slice_def) => Some(slice_def.t()),
_ => None,
})
}
_ => None,
};
let Some(item_shape) = item_shape else {
return false;
};
if let Type::User(UserType::Struct(struct_def)) = &item_shape.ty {
for field in struct_def.fields.iter() {
if field.is_tag() {
return true;
}
}
}
false
}