use proc_macro::TokenStream;
use syn::{punctuated::Punctuated, token::Comma, FnArg, Ident, ItemFn, Pat, PatIdent, PatType, ReturnType, Signature};
pub fn parse_handler(ast: ItemFn) -> TokenStream {
const OUTPUT_TYPE_NOT_VALID: &str = "#[handler] fn must have valid output type";
let ItemFn {
sig: Signature {
ident,
output: ReturnType::Type(_, var),
inputs,
generics,
asyncness,
..
},
block,
..
} = ast
else {
panic!("{}", OUTPUT_TYPE_NOT_VALID)
};
if inputs.is_empty() {
panic!("There must be message argument!");
}
let message = inputs.first().unwrap();
let messsage_type = match message.clone() {
FnArg::Typed(PatType { ty, .. }) => *ty,
_ => panic!(""),
};
let mut args = inputs.iter().skip(1).cloned().collect::<Punctuated<FnArg, Comma>>();
let flagged_args: Vec<(FnArg, bool)> = flag_context(&mut args);
let idents = get_puntuated_idents(inputs.clone());
let injectables = take_injectables(flagged_args);
let generic_where = &generics.where_clause;
quote!(
pub #asyncness fn #ident (#message,context: ::event_driven_library::prelude::AtomicContextManager)-> #var {
#asyncness fn inner #generics (#message,#args)->#var #generic_where{
#block
};
#(
#injectables
)*
inner(#idents).await
}
)
.into()
}
fn get_puntuated_idents(inputs: Punctuated<FnArg, Comma>) -> Punctuated<Ident, Comma> {
inputs
.into_iter()
.filter_map(|i| {
if let FnArg::Typed(PatType { pat, .. }) = i {
match *pat {
Pat::Ident(PatIdent { ref ident, .. }) => Some(syn::Ident::new(&ident.to_string(), proc_macro2::Span::call_site())),
_ => panic!("Not Allowed!"),
}
} else {
None
}
})
.collect()
}
fn flag_context(args: &mut Punctuated<FnArg, Comma>) -> Vec<(FnArg, bool)> {
let mut container = vec![];
for arg in args.into_iter() {
let mut context_injection_required = false;
if let FnArg::Typed(PatType { ref mut attrs, .. }) = arg {
let init_len = attrs.len();
attrs.retain(|x| !x.path().is_ident("context"));
if init_len != attrs.len() {
context_injection_required = true;
}
}
container.push((arg.clone(), context_injection_required))
}
container
}
fn take_injectables(inputs: Vec<(FnArg, bool)>) -> Vec<proc_macro2::TokenStream> {
inputs
.into_iter()
.filter_map(|i| {
match i {
(FnArg::Typed(PatType { pat, .. }), false) => match *pat {
Pat::Ident(PatIdent { ident, .. }) => Some(quote!(
let #ident = crate::dependencies::#ident();
)),
_ => panic!("Not Allowed!"),
},
(FnArg::Typed(PatType { pat, .. }), true) => match *pat {
Pat::Ident(PatIdent { ident, .. }) => Some(quote!(
let #ident = crate::dependencies::#ident(context.clone()).await;
)),
_ => panic!("Not Allowed!"),
},
_ => None,
}
})
.collect::<Vec<_>>()
}