metrique-macro 0.1.3

Library for working with unit of work metrics - #[metrics] macro
Documentation
use crate::{
    MetricsField, MetricsFieldKind, MetricsVariant, NameStyle, RootAttributes, metric_name,
};

use proc_macro2::{Span, TokenStream as Ts2};
use quote::{quote, quote_spanned};
use syn::Ident;

pub(crate) fn generate_value_impl_for_enum(
    root_attrs: &RootAttributes,
    value_name: &Ident,
    parsed_variants: &[MetricsVariant],
) -> Ts2 {
    let variants_and_strings = parsed_variants.iter().map(|variant| {
        let variant_ident = &variant.ident;
        let metric_name = metric_name(root_attrs, root_attrs.rename_all, variant);
        quote_spanned!(variant.ident.span()=> #value_name::#variant_ident => #metric_name)
    });
    quote!(
        impl ::metrique::writer::Value for #value_name {
            fn write(&self, writer: impl ::metrique::writer::ValueWriter) {
                writer.string(#[allow(deprecated)] match self {
                    #(#variants_and_strings),*
                });
            }
        }
    )
}

pub fn validate_value_impl_for_struct(
    root_attrs: &RootAttributes,
    value_name: &Ident,
    parsed_fields: &[MetricsField],
) -> Result<(), syn::Error> {
    let non_ignore_fields: Vec<&MetricsField> = parsed_fields
        .iter()
        .filter(|f| !matches!(f.attrs.kind, MetricsFieldKind::Ignore(_)))
        .collect::<Vec<_>>();
    if non_ignore_fields.len() > 1 {
        return Err(syn::Error::new(
            non_ignore_fields[1].span,
            "multiple non-ignored fields for #[metrics(value)]",
        ));
    }
    if root_attrs.emf_dimensions.is_some() {
        return Err(syn::Error::new(
            value_name.span(),
            "emf_dimensions is not supported for #[metrics(value)]",
        ));
    }
    if root_attrs.prefix.is_some() {
        return Err(syn::Error::new(
            value_name.span(),
            "prefix is not supported for #[metrics(value)]",
        ));
    }
    if !matches!(root_attrs.rename_all, NameStyle::Preserve) {
        return Err(syn::Error::new(
            value_name.span(),
            "NameStyle is not supported for #[metrics(value)]",
        ));
    }

    Ok(())
}

pub(crate) fn format_value(format: &Option<syn::Path>, span: Span, field: Ts2) -> Ts2 {
    if let Some(format) = format {
        quote_spanned! { span=> &::metrique::format::FormattedValue::<_, #format, _>::new(#field)}
    } else {
        field
    }
}

pub(crate) fn generate_value_impl_for_struct(
    _root_attrs: &RootAttributes,
    value_name: &Ident,
    parsed_fields: &[MetricsField],
) -> Result<Ts2, syn::Error> {
    // support struct with only ignored fields as no value for orthogonality
    let non_ignore_fields = parsed_fields
        .iter()
        .filter(|f| !matches!(f.attrs.kind, MetricsFieldKind::Ignore(_)));
    let body: Vec<Ts2> = non_ignore_fields
        .map(|field| match &field.attrs.kind {
            MetricsFieldKind::Field {
                unit: _,
                name: None,
                format,
            } => {
                let ident = &field.ident;
                let value = format_value(format, field.span, quote! { &self.#ident });
                Ok(quote_spanned! {field.span=> ::metrique::writer::Value::write(#value, writer) })
            }
            _ => Err(syn::Error::new(
                field.span,
                "only plain fields are supported in #[metrics(value)]",
            )),
        })
        .collect::<Result<Vec<_>, _>>()?;

    Ok(quote! {
        impl ::metrique::writer::Value for #value_name {
            fn write(&self, writer: impl ::metrique::writer::ValueWriter) {
                #[allow(deprecated)] {
                    #(#body);*
                }
            }
        }
    })
}