sword-macros 0.3.0

Structured web framework built on top of tokio ecosystem, providing powerful features for building robust web applications.
Documentation
use crate::controllers::shared::CMetaStack;
use heck::ToSnakeCase;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{FnArg, ItemFn, LitStr, Type};

pub fn expand_handle(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
    if CMetaStack::get("event_handler", "controller_name").is_none() {
        return Ok(item);
    }

    let event_lit = syn::parse::<LitStr>(attr)?;
    let event_key = event_lit.value();
    let input_fn = syn::parse::<ItemFn>(item)?;

    if event_key.is_empty() {
        return Err(syn::Error::new_spanned(
            &event_lit,
            "event key must not be empty; specify the full key, e.g. #[handle(\"user.created\")]",
        ));
    }

    let controller_name = CMetaStack::get("event_handler", "controller_name").unwrap();

    let fn_name = &input_fn.sig.ident;
    let controller_ident: syn::Ident = syn::parse_str(&controller_name)?;

    let event_type = extract_event_type(&input_fn)?;

    let registration_name = format_ident!(
        "__SWORD_EVENT_HANDLER_{}_{}",
        controller_name.replace("::", "_"),
        fn_name
    );

    let build_fn_name = format_ident!(
        "__sword_event_build_{}_{}",
        controller_ident.to_string().to_snake_case(),
        fn_name
    );

    let expansion = quote! {
        #input_fn

        #[allow(non_upper_case_globals)]
        #[doc(hidden)]
        const #registration_name: () = {
            fn #build_fn_name(
                state: &::sword::internal::core::State,
            ) -> ::sword::internal::events::EventHandlerFn {
                let controller: ::std::sync::Arc<#controller_ident> =
                    state.borrow::<#controller_ident>().unwrap_or_else(|_| {
                        ::sword::internal::core::sword_error!(
                            title: "Failed to retrieve event handler controller from state",
                            reason: ::std::format!(
                                "Controller {} was not built for event handler",
                                ::std::stringify!(#controller_ident)
                            ),
                            context: {
                                "controller" => ::std::stringify!(#controller_ident),
                                "event_key" => #event_key,
                            },
                            hints: [
                                "Ensure the controller is registered via Module::register_controllers",
                            ],
                        )
                    });

                ::std::sync::Arc::new(
                    move |event: ::std::sync::Arc<dyn ::sword::internal::events::Event>| {
                        let controller = controller.clone();
                        let cloned = event.clone_event();

                        ::std::boxed::Box::pin(async move {
                            let typed = cloned.downcast_ref::<#event_type>().ok_or_else(|| {
                                ::std::format!(
                                    "Failed to downcast event to {} for handler {} (key: {})",
                                    ::std::any::type_name::<#event_type>(),
                                    ::std::stringify!(#fn_name),
                                    #event_key,
                                )
                            })?;

                            (controller.#fn_name(
                                ::std::clone::Clone::clone(typed),
                            )
                            .await)
                        })
                            as ::std::pin::Pin<
                                ::std::boxed::Box<
                                    dyn ::std::future::Future<
                                        Output = ::sword::internal::events::EventHandlerResult<
                                            (),
                                        >,
                                    > + ::std::marker::Send,
                                >,
                            >
                    },
                )
            }

            ::sword::internal::inventory::submit! {
                ::sword::internal::events::EventRouteRegistrar {
                    event_key: #event_key,
                    handler_type_id: ::std::any::TypeId::of::<#controller_ident>(),
                    build_and_handle: #build_fn_name,
                }
            }
        };
    };

    Ok(TokenStream::from(expansion))
}

fn extract_event_type(input_fn: &ItemFn) -> syn::Result<Type> {
    let has_receiver = input_fn
        .sig
        .inputs
        .first()
        .is_some_and(|arg| matches!(arg, FnArg::Receiver(_)));

    if !has_receiver {
        return Err(syn::Error::new_spanned(
            &input_fn.sig,
            "Event handler must have `&self` as the first parameter, \
             e.g. `async fn handle(&self, event: MyEvent) -> Result<()>`",
        ));
    }

    let typed_params: Vec<&syn::FnArg> = input_fn
        .sig
        .inputs
        .iter()
        .filter(|arg| matches!(arg, FnArg::Typed(_)))
        .collect();

    let event_arg = typed_params.first().ok_or_else(|| {
        syn::Error::new_spanned(
            &input_fn.sig,
            "Event handler must have a typed parameter for the event, \
             e.g. `async fn handle(&self, event: MyEvent) -> Result<()>`",
        )
    })?;

    match event_arg {
        FnArg::Typed(pat_type) => Ok(*pat_type.ty.clone()),
        _ => unreachable!(),
    }
}