actix-handler-macro 0.2.0

Helper macros for using Actix. Generates handlers, actors and address traits.
Documentation
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::punctuated::Punctuated;
use syn::{Path, PathSegment, Token, Type, TypePath};

use crate::expand_method_handlers::HandlerContext;
use crate::options::Options;

pub struct ImplContext {
    pub(crate) type_name: Type,
    pub(crate) handlers: Vec<HandlerContext>,
}

pub fn expand_addr_trait(options: Options, impl_context: ImplContext) -> TokenStream {
    let trait_type_name: Type =
        build_renamed_addr_type(&options.trait_name, &impl_context.type_name);
    let trait_block = expand_trait_declaration(
        &options,
        &impl_context,
        &impl_context.type_name,
        &trait_type_name,
    );
    let impl_block = expand_impl_declaration(
        &options,
        &impl_context,
        &impl_context.type_name,
        trait_type_name,
    );

    TokenStream::from(quote!(
        #trait_block

        #impl_block
    ))
}

fn expand_impl_declaration(
    options: &Options,
    impl_context: &ImplContext,
    type_name: &Type,
    trait_type_name: Type,
) -> TokenStream2 {
    let trait_impls = expand_trait_impls(&options, impl_context, &type_name);
    if options.no_trait_impl {
        quote!()
    } else {
        quote!(
            impl #trait_type_name for ::actix::Addr<#type_name> {
                #trait_impls
            }
        )
    }
}

fn expand_trait_declaration(
    options: &Options,
    impl_context: &ImplContext,
    type_name: &Type,
    trait_type_name: &Type,
) -> TokenStream2 {
    let trait_fns = expand_trait_methods(&options, &impl_context, &type_name);
    if options.no_trait_decl {
        quote!()
    } else {
        quote!(
            trait #trait_type_name {
                #trait_fns
            }
        )
    }
}

fn expand_trait_impls(
    options: &Options,
    impl_context: &ImplContext,
    type_name: &Type,
) -> TokenStream2 {
    impl_context
        .handlers
        .iter()
        .map(|handler_context| expand_addr_method(&options, type_name.clone(), handler_context))
        .fold(TokenStream2::new(), |mut m, i| {
            m.extend(i);
            m
        })
}

fn expand_trait_methods(
    options: &Options,
    impl_context: &ImplContext,
    type_name: &Type,
) -> TokenStream2 {
    impl_context
        .handlers
        .iter()
        .map(|handler_context| {
            let HandlerContext {
                method_name,
                message_type_name,
                ..
            } = handler_context;

            if options.use_recipient {
                quote!(
                    fn #method_name(
                        self: &Self,
                        msg: #message_type_name
                    ) -> ::actix::prelude::RecipientRequest<#message_type_name>;
                )
            } else {
                quote!(
                    fn #method_name(
                        self: &Self,
                        msg: #message_type_name
                    ) -> ::actix::prelude::Request<#type_name, #message_type_name>;
                )
            }
        })
        .fold(TokenStream2::new(), |mut m, i| {
            m.extend(i);
            m
        })
}

fn build_renamed_addr_type(trait_name: &Option<String>, type_name: &Type) -> Type {
    match type_name.clone() {
        Type::Path(path) => {
            let mut segments: Punctuated<PathSegment, Token![::]> = Punctuated::new();
            if let Some(type_segment) = path.path.segments.last() {
                let renamed_segment = PathSegment {
                    ident: trait_name
                        .as_ref()
                        .map(|tn| format_ident!("{}", tn))
                        .unwrap_or_else(|| format_ident!("{}Addr", type_segment.ident)),
                    arguments: type_segment.clone().arguments,
                };
                segments.push_value(renamed_segment);
            }
            Type::Path(TypePath {
                qself: path.qself,
                path: Path {
                    leading_colon: path.path.leading_colon,
                    segments,
                },
            })
        }
        ty => ty,
    }
}

fn expand_addr_method(
    options: &Options,
    type_name: Type,
    handler_context: &HandlerContext,
) -> TokenStream2 {
    let HandlerContext {
        method_name,
        message_type_name,
        ..
    } = handler_context;

    if options.use_recipient {
        quote!(
            fn #method_name(
                self: &Self,
                msg: #message_type_name
            ) -> ::actix::prelude::RecipientRequest<#message_type_name> {
                self.clone().recipient().send(msg)
            }
        )
    } else {
        quote!(
            fn #method_name(
                self: &Self,
                msg: #message_type_name
            ) -> ::actix::prelude::Request<#type_name, #message_type_name> {
                self.send(msg)
            }
        )
    }
}