Add book_bot_macros proc-macro crate
Some checks are pending
Build docker image / Build-Docker-Image (push) Waiting to run
rust-clippy analyze / Run rust-clippy analyzing (push) Waiting to run

Introduce log_handler attribute macro that injects tracing logs into
handler functions. It extracts user_id from Message (optionally) or
CallbackQuery and logs handler name; falls back to logging handler only.
This commit is contained in:
2026-03-02 20:11:41 +01:00
parent ee3a2cdfc7
commit 155dc9b1ee
2 changed files with 87 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
[package]
name = "book_bot_macros"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = { workspace = true }
quote = { workspace = true }
proc-macro2 = { workspace = true }

View File

@@ -0,0 +1,75 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, FnArg, ItemFn, LitStr, Pat, PatType, Type, TypePath};
#[proc_macro_attribute]
pub fn log_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
let handler_name = parse_macro_input!(attr as LitStr);
let input_fn = parse_macro_input!(item as ItemFn);
let fn_vis = &input_fn.vis;
let fn_sig = &input_fn.sig;
let fn_block = &input_fn.block;
let fn_attrs = &input_fn.attrs;
let log_stmt = generate_log_stmt(&input_fn, &handler_name);
quote! {
#(#fn_attrs)*
#fn_vis #fn_sig {
#log_stmt
#fn_block
}
}
.into()
}
fn get_type_ident(ty: &Type) -> Option<String> {
if let Type::Path(TypePath { path, .. }) = ty {
path.segments.last().map(|s| s.ident.to_string())
} else {
None
}
}
fn get_param_ident(fn_arg: &FnArg) -> Option<(proc_macro2::Ident, String)> {
if let FnArg::Typed(PatType { pat, ty, .. }) = fn_arg {
if let Pat::Ident(pat_ident) = pat.as_ref() {
if let Some(type_name) = get_type_ident(ty) {
return Some((pat_ident.ident.clone(), type_name));
}
}
}
None
}
fn generate_log_stmt(input_fn: &ItemFn, handler_name: &LitStr) -> proc_macro2::TokenStream {
for fn_arg in &input_fn.sig.inputs {
if let Some((ident, type_name)) = get_param_ident(fn_arg) {
match type_name.as_str() {
"Message" => {
return quote! {
tracing::info!(
handler = #handler_name,
user_id = ?#ident.from.as_ref().map(|u| u.id.0)
);
};
}
"CallbackQuery" => {
return quote! {
tracing::info!(
handler = #handler_name,
user_id = #ident.from.id.0
);
};
}
_ => continue,
}
}
}
// Fallback: log without user_id if no known type found
quote! {
tracing::info!(handler = #handler_name);
}
}