dialtone_sqlx_macros 0.1.0

Dialtone SQLx Macros
Documentation
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
                }
            }
        }

    })
}