#![crate_type = "proc-macro"]
use proc_macro2::TokenStream;
use quote::quote;
use syn::spanned::Spanned as _;
use syn::{DeriveInput, Error, Result};
mod metric_type;
mod opts;
use self::metric_type::MetricType;
#[proc_macro_derive(Metrics, attributes(prometheus))]
pub fn metrics_derive_macro(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
impl_metrics_new(item.into())
.unwrap_or_else(Error::into_compile_error)
.into()
}
fn impl_metrics_new(item: TokenStream) -> Result<TokenStream> {
let ast: DeriveInput = syn::parse2(item)?;
let syn::Data::Struct(data) = ast.data else {
return Err(Error::new(ast.span(), "Metrics only derived for structs"));
};
let syn::Fields::Named(fields) = data.fields else {
return Err(Error::new(
data.fields.span(),
"Tuple or unit structs aren't supported",
));
};
let struct_name = ast.ident;
let field_inits = field_initializers(fields)?;
Ok(quote! {
impl #struct_name {
pub fn new(registry: &::prometheus::Registry) -> ::prometheus::Result<Self> {
Ok(Self {
#(#field_inits),*
})
}
}
})
}
fn field_initializers(fields: syn::FieldsNamed) -> Result<Vec<TokenStream>> {
let mut result = vec![];
for field in fields.named {
result.push(field_initializer(&field)?);
}
Ok(result)
}
fn field_initializer(field: &syn::Field) -> Result<TokenStream> {
let syn::Type::Path(ref ty) = field.ty else {
return Err(Error::new(
field.ty.span(),
format!("Field type '{:?}' unsupported", field.ty),
));
};
let metric_type: MetricType = ty.try_into()?;
let ident = field
.ident
.clone()
.ok_or_else(|| Error::new(field.span(), "Field must be named"))?;
let metric_init = metric_type.init_expr(ident.clone(), field)?;
Ok(quote! {
#ident: {
let metric = #metric_init;
registry.register(Box::new(metric.clone()))?;
metric
}
})
}