xmlserde_derives 0.14.0

macros that help xmlserde serde the xml files
Documentation
use syn::DeriveInput;
use syn::Meta;

use crate::symbol::OTHER;
use crate::symbol::{MAP, RENAME};
use crate::utils::get_array_lit_str;
use crate::utils::get_lit_str;
use crate::utils::get_xmlserde_meta_items;

pub fn get_enum_value_impl_block(input: DeriveInput) -> proc_macro2::TokenStream {
    let data = match input.data {
        syn::Data::Enum(e) => e,
        _ => panic!("expect enum type"),
    };
    let ident = input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
    let variants = data
        .variants
        .iter()
        .filter_map(|v| EnumValueVariant::from_variant(v))
        .collect::<Vec<_>>();

    let ser_branches = variants.iter().map(get_ser_branch).collect::<Vec<_>>();
    let de_branches = variants.iter().map(get_de_branch).collect::<Vec<_>>();
    quote! {
        impl #impl_generics ::xmlserde::XmlValue for #ident #type_generics #where_clause {
            fn serialize(&self) -> String {
                match &self {
                    #(#ser_branches),*
                }
            }
            fn deserialize(s: &str) -> Result<Self, String> {
                match s {
                    #(#de_branches),*
                }
            }
        }
    }
}

fn get_de_branch(var: &EnumValueVariant) -> proc_macro2::TokenStream {
    let ident = var.ident;
    if var.is_other {
        if let Some(field) = &var.is_other_field {
            let ty = &field.ty;
            return quote! {
                _ => Ok(Self::#ident(#ty::deserialize(s)?))
            };
        }
    }
    if !var.map.is_empty() {
        let values = var.map.iter().map(|s| quote! {#s => Ok(Self::#ident)});
        return quote! {
            #(#values),*
        };
    }
    if let Some(rename) = &var.rename {
        return quote! {
            #rename => Ok(Self::#ident)
        };
    }
    panic!("unexpected situation")
}

fn get_ser_branch(var: &EnumValueVariant) -> proc_macro2::TokenStream {
    let ident = var.ident;
    if var.is_other {
        if let Some(field) = &var.is_other_field {
            let ty = &field.ty;
            return quote! {
                Self::#ident(v) => {
                    <#ty as ::xmlserde::XmlValue>::serialize(v)
                }
            };
        }
    }
    if !var.map.is_empty() {
        let first = var.map.first().unwrap();
        return quote! {
            Self::#ident => #first.to_string()
        };
    }
    if let Some(rename) = &var.rename {
        return quote! {
            Self::#ident => #rename.to_string()
        };
    }

    panic!("unexpected situation")
}

struct EnumValueVariant<'a> {
    ident: &'a syn::Ident,
    is_other: bool,
    is_other_field: Option<syn::Field>,
    rename: Option<syn::LitStr>,
    map: Vec<syn::LitStr>,
}

impl<'a> EnumValueVariant<'a> {
    pub fn from_variant(v: &'a syn::Variant) -> Option<Self> {
        for meta_item in v
            .attrs
            .iter()
            .flat_map(|attr| get_xmlserde_meta_items(attr))
            .flatten()
        {
            match meta_item {
                Meta::Path(path) if path == OTHER => {
                    let field = match &v.fields {
                        syn::Fields::Named(_) => panic!("unsupported named fields"),
                        syn::Fields::Unnamed(fields_unnamed) => {
                            fields_unnamed.unnamed.iter().next().cloned()
                        }
                        syn::Fields::Unit => None,
                    };
                    if field.is_none() {
                        panic!("other field should not have no field!")
                    }
                    return Some(Self {
                        rename: None,
                        ident: &v.ident,
                        is_other: true,
                        is_other_field: field,
                        map: Vec::new(),
                    });
                }
                Meta::NameValue(m) if m.path == RENAME => {
                    if let Ok(s) = get_lit_str(&m.value) {
                        return Some(Self {
                            rename: Some(s.clone()),
                            ident: &v.ident,
                            is_other: false,
                            map: Vec::new(),
                            is_other_field: None,
                        });
                    }
                    panic!(r#"please use `#[rename = "..."]`"#);
                }
                Meta::NameValue(m) if m.path == MAP => {
                    if let Ok(s) = get_array_lit_str(&m.value) {
                        return Some(Self {
                            rename: None,
                            ident: &v.ident,
                            is_other: false,
                            map: s.into_iter().map(|s| s.clone()).collect(),
                            is_other_field: None,
                        });
                    }
                    panic!(r#"please use `#[map = ["..."]`"#);
                }
                _ => panic!("unexpected attribute"),
            }
        }
        None
    }
}