derive_path_str.rs

  1use convert_case::{Case, Casing};
  2use proc_macro::TokenStream;
  3use quote::quote;
  4use syn::{Attribute, Data, DeriveInput, Lit, Meta, NestedMeta, parse_macro_input};
  5
  6pub fn derive_path_str(input: TokenStream) -> TokenStream {
  7    let input = parse_macro_input!(input as DeriveInput);
  8    let name = &input.ident;
  9
 10    let prefix = get_attr_value(&input.attrs, "prefix").expect("prefix attribute is required");
 11    let suffix = get_attr_value(&input.attrs, "suffix").unwrap_or_else(|| "".to_string());
 12
 13    let serialize_all = get_strum_serialize_all(&input.attrs);
 14    let path_str_impl = impl_path_str(name, &input.data, &prefix, &suffix, serialize_all);
 15
 16    let expanded = quote! {
 17        impl #name {
 18            pub fn path(&self) -> &'static str {
 19                #path_str_impl
 20            }
 21        }
 22    };
 23
 24    TokenStream::from(expanded)
 25}
 26
 27fn impl_path_str(
 28    name: &syn::Ident,
 29    data: &Data,
 30    prefix: &str,
 31    suffix: &str,
 32    serialize_all: Option<String>,
 33) -> proc_macro2::TokenStream {
 34    match *data {
 35        Data::Enum(ref data) => {
 36            let match_arms = data.variants.iter().map(|variant| {
 37                let ident = &variant.ident;
 38                let variant_name = if let Some(ref case) = serialize_all {
 39                    match case.as_str() {
 40                        "snake_case" => ident.to_string().to_case(Case::Snake),
 41                        "lowercase" => ident.to_string().to_lowercase(),
 42                        _ => ident.to_string(),
 43                    }
 44                } else {
 45                    ident.to_string()
 46                };
 47                let path = format!("{}/{}{}", prefix, variant_name, suffix);
 48                quote! {
 49                    #name::#ident => #path,
 50                }
 51            });
 52
 53            quote! {
 54                match self {
 55                    #(#match_arms)*
 56                }
 57            }
 58        }
 59        _ => panic!("DerivePathStr only supports enums"),
 60    }
 61}
 62
 63fn get_strum_serialize_all(attrs: &[Attribute]) -> Option<String> {
 64    attrs
 65        .iter()
 66        .filter(|attr| attr.path.is_ident("strum"))
 67        .find_map(|attr| {
 68            if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
 69                meta_list.nested.iter().find_map(|nested_meta| {
 70                    if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
 71                        if name_value.path.is_ident("serialize_all") {
 72                            if let Lit::Str(lit_str) = &name_value.lit {
 73                                return Some(lit_str.value());
 74                            }
 75                        }
 76                    }
 77                    None
 78                })
 79            } else {
 80                None
 81            }
 82        })
 83}
 84
 85fn get_attr_value(attrs: &[Attribute], key: &str) -> Option<String> {
 86    attrs
 87        .iter()
 88        .filter(|attr| attr.path.is_ident("path_str"))
 89        .find_map(|attr| {
 90            if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
 91                meta_list.nested.iter().find_map(|nested_meta| {
 92                    if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
 93                        if name_value.path.is_ident(key) {
 94                            if let Lit::Str(lit_str) = &name_value.lit {
 95                                return Some(lit_str.value());
 96                            }
 97                        }
 98                    }
 99                    None
100                })
101            } else {
102                None
103            }
104        })
105}