use super::attrs::{ContainerAttrs, FieldAttrs, FieldType}; use darling::FromField; use heck::ToKebabCase; use quote::quote; fn wrap_option_if_no_default( value: proc_macro2::TokenStream, has_default: bool, ) -> proc_macro2::TokenStream { if has_default { value } else { quote! {Some(#value)} } } fn get_generic_type(ty: &syn::Type) -> Option<&syn::Type> { if let syn::Type::Path(syn::TypePath { path, .. }) = ty { if let Some(seg) = path.segments.last() { if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { args, .. }) = &seg.arguments { if let Some(syn::GenericArgument::Type(t)) = &args.first() { return Some(t); } } } } None } pub struct Field { pub field: syn::Field, pub attrs: FieldAttrs, pub container_attrs: ContainerAttrs, } impl Field { pub fn from_syn_field(field: syn::Field, container_attrs: ContainerAttrs) -> Self { Self { attrs: FieldAttrs::from_field(&field).unwrap(), field, container_attrs, } } /// Field name in XML pub fn xml_name(&self) -> syn::LitByteStr { self.attrs .common .rename .to_owned() .unwrap_or(syn::LitByteStr::new( self.field_ident().to_string().to_kebab_case().as_bytes(), self.field_ident().span(), )) } /// Whether to enforce the correct XML namespace pub fn ns_strict(&self) -> bool { self.attrs.common.ns_strict.is_present() || self.container_attrs.ns_strict.is_present() } /// Field identifier pub fn field_ident(&self) -> &syn::Ident { self.field .ident .as_ref() .expect("tuple structs not supported") } /// Field type pub fn ty(&self) -> &syn::Type { &self.field.ty } /// Field in the builder struct for the deserializer pub fn builder_field(&self) -> proc_macro2::TokenStream { let field_ident = self.field_ident(); let ty = self.ty(); let builder_field_type = match (self.attrs.flatten.is_present(), &self.attrs.default) { (_, Some(_default)) => quote! { #ty }, (true, None) => { let generic_type = get_generic_type(ty).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); quote! { Vec<#generic_type> } } (false, None) => quote! { Option<#ty> }, }; quote! { #field_ident: #builder_field_type } } /// Field initialiser in the builder struct for the deserializer pub fn builder_field_init(&self) -> proc_macro2::TokenStream { let field_ident = self.field_ident(); let builder_field_initialiser = match (self.attrs.flatten.is_present(), &self.attrs.default) { (_, Some(default)) => quote! { #default() }, (true, None) => quote! { vec![] }, (false, None) => quote! { None }, }; quote! { #field_ident: #builder_field_initialiser } } /// Map builder field to target field pub fn builder_field_build(&self) -> proc_macro2::TokenStream { let field_ident = self.field_ident(); let builder_value = match ( self.attrs.flatten.is_present(), self.attrs.default.is_some(), ) { (true, _) => quote! { FromIterator::from_iter(builder.#field_ident.into_iter()) }, (false, true) => quote! { builder.#field_ident }, (false, false) => quote! { builder.#field_ident.expect("todo: handle missing field") }, }; quote! { #field_ident: #builder_value } } pub fn named_branch(&self) -> Option { if self.attrs.xml_ty != FieldType::Tag { return None; } let namespace_match = if self.ns_strict() { if let Some(ns) = &self.attrs.common.ns { quote! {quick_xml::name::ResolveResult::Bound(quick_xml::name::Namespace(#ns))} } else { quote! {quick_xml::name::ResolveResult::Unbound} } } else { quote! {_} }; let field_name = self.xml_name(); let field_ident = self.field_ident(); let deserializer = self.ty(); let value = quote! { <#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)? }; let assignment = match (self.attrs.flatten.is_present(), &self.attrs.default) { (true, _) => { // TODO: Make nicer, watch out with deserializer typing let deserializer = get_generic_type(self.ty()).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); quote! { builder.#field_ident.push(<#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?); } } (false, Some(_default)) => quote! { builder.#field_ident = #value; }, (false, None) => quote! { builder.#field_ident = Some(#value); }, }; Some(quote! { (#namespace_match, #field_name) => { #assignment; } }) } pub fn untagged_branch(&self) -> Option { if self.attrs.xml_ty != FieldType::Untagged { return None; } let field_ident = self.field_ident(); let deserializer = self.ty(); Some(if self.attrs.flatten.is_present() { let deserializer = get_generic_type(self.ty()).expect("flatten attribute only implemented for explicit generics (rustical_xml will assume the first generic as the inner type)"); quote! { _ => { builder.#field_ident.push(<#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?); } } } else { quote! { _ => { builder.#field_ident = Some(<#deserializer as rustical_xml::XmlDeserialize>::deserialize(reader, &start, empty)?); } } }) } pub fn text_branch(&self) -> Option { if self.attrs.xml_ty != FieldType::Text { return None; } let field_ident = self.field_ident(); let value = wrap_option_if_no_default( quote! { rustical_xml::Value::deserialize(text.as_ref())? }, self.attrs.default.is_some(), ); Some(quote! { builder.#field_ident = #value; }) } pub fn attr_branch(&self) -> Option { if self.attrs.xml_ty != FieldType::Attr { return None; } let field_ident = self.field_ident(); let field_name = self.xml_name(); let value = wrap_option_if_no_default( quote! { rustical_xml::Value::deserialize(attr.unescape_value()?.as_ref())? }, self.attrs.default.is_some(), ); Some(quote! { #field_name => { builder.#field_ident = #value; } }) } pub fn tagname_branch(&self) -> Option { if self.attrs.xml_ty != FieldType::TagName { return None; } let field_ident = self.field_ident(); let value = wrap_option_if_no_default( quote! { rustical_xml::Value::deserialize(&String::from_utf8_lossy(name.as_ref()))? }, self.attrs.default.is_some(), ); Some(quote! { builder.#field_ident = #value; }) } pub fn tag_writer(&self) -> Option { let field_ident = self.field_ident(); let field_name = self.xml_name(); match self.attrs.xml_ty { FieldType::Attr => None, FieldType::Text => Some(quote! { writer.write_event(Event::Text(BytesText::new(&self.#field_ident)))?; }), FieldType::Tag => Some(quote! { self.#field_ident.serialize(Some(#field_name), writer)?; }), FieldType::Untagged => Some(quote! { // TODO: untag! self.#field_ident.serialize(None, writer)?; }), // TODO: Think about what to do here FieldType::TagName | FieldType::Namespace => None, } } }