scuffle_metrics_derive/
enum_impl.rs

1use darling::FromMeta;
2use darling::ast::NestedMeta;
3use proc_macro2::TokenStream;
4
5#[derive(Debug, darling::FromMeta)]
6struct VariantAttr {
7    rename: Option<syn::LitStr>,
8}
9
10#[derive(Debug, darling::FromMeta)]
11struct EnumAttr {
12    crate_path: Option<syn::Path>,
13}
14
15pub(crate) fn metric_enum_impl(input: TokenStream) -> syn::Result<TokenStream> {
16    let input = syn::parse2::<syn::ItemEnum>(input)?;
17    let enum_ident = &input.ident;
18
19    // We only support C-style enums
20    let branches = input
21        .variants
22        .iter()
23        .map(|variant| {
24            let ident = &variant.ident;
25            if matches!(variant.fields, syn::Fields::Named(_) | syn::Fields::Unnamed(_)) {
26                return Err(syn::Error::new_spanned(ident, "only unit enums are supported"));
27            }
28
29            // #[metrics(rename = "...")]
30            let mut meta = Vec::new();
31            for attr in &variant.attrs {
32                if attr.path().is_ident("metrics") {
33                    match &attr.meta {
34                        syn::Meta::List(syn::MetaList { tokens, .. }) => {
35                            meta.extend(NestedMeta::parse_meta_list(tokens.clone())?);
36                        }
37                        _ => return Err(syn::Error::new_spanned(attr, "expected list")),
38                    }
39                }
40            }
41
42            let options = VariantAttr::from_list(&meta)?;
43
44            let name = options.rename.map(|lit| lit.value()).unwrap_or_else(|| ident.to_string());
45
46            Ok(quote::quote! {
47                #enum_ident::#ident => #name,
48            })
49        })
50        .collect::<syn::Result<Vec<_>>>()?;
51
52    let mut meta = Vec::new();
53    for attr in &input.attrs {
54        if attr.path().is_ident("metrics") {
55            match &attr.meta {
56                syn::Meta::List(syn::MetaList { tokens, .. }) => {
57                    meta.extend(NestedMeta::parse_meta_list(tokens.clone())?);
58                }
59                _ => return Err(syn::Error::new_spanned(attr, "expected list")),
60            }
61        }
62    }
63
64    let options = EnumAttr::from_list(&meta)?;
65
66    let crate_path = options.crate_path.unwrap_or_else(|| syn::parse_quote!(::scuffle_metrics));
67
68    Ok(quote::quote! {
69        impl ::core::convert::From<#enum_ident> for #crate_path::opentelemetry::Value {
70            fn from(value: #enum_ident) -> Self {
71                let value = match value {
72                    #(#branches)*
73                };
74
75                #crate_path::opentelemetry::Value::String(value.into())
76            }
77        }
78    })
79}