event-driven-macro 0.1.49

Rust Library For Event Driven Message Handling
Documentation
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();

	//TODO Restrict message to type that implements either Command or Message OR impl Trait
	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);

	// Get idents. This will be passed to inner(or redefined) function
	let idents = get_puntuated_idents(inputs.clone());

	// injectables
	let injectables = take_injectables(flagged_args);

	let generic_where = &generics.where_clause;

	quote!(
		// * Check if the first argument is of either Command or Message


		// ::event_driven_library::static_assertions::assert_impl_any!(
		// 	#messsage_type:
		// 	::event_driven_library::prelude::Message,
		// 	::event_driven_library::prelude::Command
		// );
		pub #asyncness fn #ident (#message,context: ::event_driven_library::prelude::AtomicContextManager)-> #var {

			#asyncness fn inner #generics (#message,#args)->#var #generic_where{

				#block
			};
			// let dependency= crate::dependencies::dependency();
			#(
				#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 {
			// * Take initial length
			let init_len = attrs.len();
			attrs.retain(|x| !x.path().is_ident("context"));

			// TODO if attribute other than `context` is passed, cause panic

			// * If initial length is not the same after ratain method is applied, it means 'context' attribute has been idenfitied
			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| {
			// TODO get attributes on function parameter to sort out context, context-injectable and context-agnostic-injectables

			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<_>>()
}