comfund_macro_impl 0.1.2

Implementation of `comfund` macros.
Documentation
mod actix_endpoint;

use quote::{format_ident, quote};

use crate::{contract::Contract, servers::actix_web::actix_endpoint::ActixEndpoint};

pub fn implement(contract: &Contract) -> proc_macro2::TokenStream {
    let service_trait_def = def_service_trait(contract);
    let configure_fn_impl = impl_configure_fn(contract);
    let attrs = contract.attrs.iter();

    quote! {
        #[cfg(
            all(
                feature = "actix-web",
                not(any(
                    feature = "axum"
                ))
            )
        )]
        pub use actix_web::*;

        #[cfg(feature = "actix-web")]
        pub mod actix_web {
            use super::*;
            #(#attrs)*
            #service_trait_def
            #configure_fn_impl
        }
    }
}

fn def_service_trait(contract: &Contract) -> impl quote::ToTokens {
    let contract_id = &contract.id;
    let actix_eps = contract
        .endpoints
        .iter()
        .map(ActixEndpoint::new)
        .collect::<Vec<_>>();

    let ep_trait_items = actix_eps.iter().map(ActixEndpoint::def_in_trait);

    quote! {
        pub trait #contract_id: 'static {
            #(#ep_trait_items)*
        }
    }
}

fn impl_configure_fn(contract: &Contract) -> impl quote::ToTokens {
    let contract_id = &contract.id;
    let configure_fn_id = get_configure_fn_id(contract_id);
    let service_trait_var = format_ident!("C");
    let routing_expressions = get_routing_expressions(contract, &service_trait_var);

    quote! {
        pub fn #configure_fn_id<#service_trait_var: #contract_id>(cfg: &mut ::actix_web::web::ServiceConfig) {
            cfg #(#routing_expressions)*;
        }
    }
}

fn get_configure_fn_id(contract_id: &syn::Ident) -> syn::Ident {
    let configure_fn_str = format!(
        "configure_{}",
        stringcase::snake_case(&contract_id.to_string())
    );
    syn::Ident::new(&configure_fn_str, contract_id.span())
}

fn get_routing_expressions(
    contract: &Contract,
    service_trait_var: &syn::Ident,
) -> impl Iterator<Item = impl quote::ToTokens> {
    use std::collections::HashMap;

    let mut ep_map = HashMap::with_capacity(contract.endpoints.len());
    for ep in &contract.endpoints {
        ep_map
            .entry(ep.meta.path_lit())
            .or_insert_with(Vec::new)
            .push(ep)
    }

    let mut exprs = Vec::with_capacity(ep_map.len());

    for (path, eps) in ep_map {
        let route_expressions = eps
            .into_iter()
            .map(|ep| ActixEndpoint::new(ep).method_router(service_trait_var));

        let expr = quote! {
            .service(
                ::actix_web::web::resource(#path)
                    #(.route(#route_expressions))*
            )
        };

        exprs.push(expr);
    }

    exprs.into_iter()
}