use proc_macro2::TokenStream;
use quote::quote;
use syn::{AttrStyle, Attribute, Data, DataEnum, DeriveInput, Meta};
pub fn expand_sqlx_enum_proxy(input: DeriveInput) -> syn::Result<TokenStream> {
let enum_name = &input.ident;
let attrs: Vec<&Attribute> = input
.attrs
.iter()
.filter(|attr| attr.path.is_ident("proxy_for"))
.collect();
if attrs.len() != 1 {
return Err(syn::Error::new_spanned(
&input,
"one and only one proxy_for attribute required",
));
}
let proxy_for = attrs.get(0).unwrap();
if let AttrStyle::Inner(_) = &proxy_for.style {
return Err(syn::Error::new_spanned(
&attrs[0],
"proxy_for attribute must be on the enum",
));
}
let meta = proxy_for.parse_meta().unwrap();
let meta_list = match meta {
Meta::List(v) => v,
_ => return Err(syn::Error::new_spanned(meta, "unknown meta attributes")),
};
let base = match &meta_list.nested.len() {
1 => &meta_list.nested[0],
_ => {
return Err(syn::Error::new_spanned(
&attrs[0],
"attribute must supply one names in list",
))
}
};
let variants = match &input.data {
Data::Enum(DataEnum { variants, .. }) => variants,
_ => {
return Err(syn::Error::new_spanned(
&input,
"this only works with enums",
))
}
};
let from_str = variants
.iter()
.map(|v| {
let v_name = &v.ident;
Ok(quote! {
a if a == #enum_name::#v_name.to_string() => Ok(#enum_name::#v_name),
})
})
.collect::<syn::Result<TokenStream>>()?;
let to_str = variants
.iter()
.map(|v| {
let v_name = &v.ident;
Ok(quote! {
#enum_name::#v_name => {
let t = #base::#v_name;
to_variant_name(&t).unwrap().to_string()
}
})
})
.collect::<syn::Result<TokenStream>>()?;
let into = variants
.iter()
.map(|v| {
let v_name = &v.ident;
Ok(quote! {
#enum_name::#v_name => #base::#v_name,
})
})
.collect::<syn::Result<TokenStream>>()?;
let from = variants
.iter()
.map(|v| {
let v_name = &v.ident;
Ok(quote! {
#base::#v_name => #enum_name::#v_name,
})
})
.collect::<syn::Result<TokenStream>>()?;
let no_match_msg = format!("no match for {}", enum_name);
Ok(quote! {
impl FromStr for #enum_name {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
#from_str
_ => Err(#no_match_msg)
}
}
}
impl ToString for #enum_name {
fn to_string(&self) -> String {
match &self {
#to_str
}
}
}
impl Into<#base> for #enum_name {
fn into(self) -> #base {
match self {
#into
}
}
}
impl From<&#base> for #enum_name {
fn from(base: &#base) -> Self {
match base {
#from
}
}
}
})
}