ironic-macros 1.0.8

Procedural macros for the Ironic application framework
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
    Attribute, DeriveInput, Ident, Token, Type, bracketed, parse::Parse, parse::ParseStream, parse2,
};

#[derive(Default)]
struct ModuleArgs {
    imports: Vec<Type>,
    providers: Vec<Type>,
    controllers: Vec<Type>,
    exports: Vec<Type>,
    async_init: Vec<Type>,
    lifecycle_init: Vec<Type>,
    lifecycle_bootstrap: Vec<Type>,
    lifecycle_destroy: Vec<Type>,
    lifecycle_shutdown: Vec<Type>,
    lifecycle_configure: Vec<Type>,
    lifecycle_server_ready: Vec<Type>,
    lifecycle_request_init: Vec<Type>,
    lifecycle_request_destroy: Vec<Type>,
    lifecycle_error: Vec<Type>,
    lifecycle_guard_denied: Vec<Type>,
    lifecycle_before_shutdown: Vec<Type>,
    lifecycle_after_shutdown: Vec<Type>,
    lifecycle_module_load: Vec<Type>,
    lifecycle_module_unload: Vec<Type>,
}

impl Parse for ModuleArgs {
    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
        let mut args = Self::default();
        while !input.is_empty() {
            let key = input.parse::<Ident>()?;
            input.parse::<Token![=]>()?;
            let content;
            bracketed!(content in input);
            let values = content
                .parse_terminated(Type::parse, Token![,])?
                .into_iter()
                .collect();
            match key.to_string().as_str() {
                "imports" => args.imports = values,
                "providers" => args.providers = values,
                "controllers" => args.controllers = values,
                "exports" => args.exports = values,
                "async_init" => args.async_init = values,
                "lifecycle_init" => args.lifecycle_init = values,
                "lifecycle_bootstrap" => args.lifecycle_bootstrap = values,
                "lifecycle_destroy" => args.lifecycle_destroy = values,
                "lifecycle_shutdown" => args.lifecycle_shutdown = values,
                "lifecycle_configure" => args.lifecycle_configure = values,
                "lifecycle_server_ready" => args.lifecycle_server_ready = values,
                "lifecycle_request_init" => args.lifecycle_request_init = values,
                "lifecycle_request_destroy" => args.lifecycle_request_destroy = values,
                "lifecycle_error" => args.lifecycle_error = values,
                "lifecycle_guard_denied" => args.lifecycle_guard_denied = values,
                "lifecycle_before_shutdown" => args.lifecycle_before_shutdown = values,
                "lifecycle_after_shutdown" => args.lifecycle_after_shutdown = values,
                "lifecycle_module_load" => args.lifecycle_module_load = values,
                "lifecycle_module_unload" => args.lifecycle_module_unload = values,
                _ => {
                    return Err(syn::Error::new_spanned(
                        key,
                        "expected lifecycle hook or module field",
                    ));
                }
            }
            if input.is_empty() {
                break;
            }
            input.parse::<Token![,]>()?;
        }
        Ok(args)
    }
}

pub(crate) fn expand(input: TokenStream) -> syn::Result<TokenStream> {
    let input = parse2::<DeriveInput>(input)?;
    if !input.generics.params.is_empty() {
        return Err(syn::Error::new_spanned(
            &input.generics,
            "`Module` does not support generic module types",
        ));
    }

    let has_global = input
        .attrs
        .iter()
        .any(|attr| attr.path().is_ident("global"));

    let module_attributes: Vec<&Attribute> = input
        .attrs
        .iter()
        .filter(|attr| attr.path().is_ident("module"))
        .collect();
    if module_attributes.len() != 1 {
        return Err(syn::Error::new_spanned(
            &input.ident,
            "`Module` requires exactly one `#[module(...)]` attribute",
        ));
    }
    let args = module_attributes[0].parse_args::<ModuleArgs>()?;
    let name = &input.ident;
    let imports = args.imports.iter().map(|ty| quote!(.import::<#ty>()));
    let providers = args
        .providers
        .iter()
        .map(|ty| quote!(.provider(<#ty>::provider_definition())));
    let controllers = args
        .controllers
        .iter()
        .map(|ty| quote!(.controller(<#ty>::controller_definition())));
    let exports = args.exports.iter().map(|ty| quote!(.export::<#ty>()));
    let async_init_calls = args
        .async_init
        .iter()
        .map(|ty| quote!(.async_init::<#ty>()));
    let global_call = has_global.then(|| quote!(.global()));

    macro_rules! lifecycle_calls {
        ($vec:ident, $method:ident) => {
            {
                let mut calls: Vec<TokenStream> = Vec::new();
                for ty in &args.$vec {
                    calls.push(quote! { .lifecycle(::ironic::LifecycleDefinition::builder::<#ty>().$method().build()) });
                }
                calls
            }
        };
    }

    let mut lifecycle_all: Vec<TokenStream> = Vec::new();
    lifecycle_all.extend(lifecycle_calls!(lifecycle_init, module_init));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_bootstrap, application_bootstrap));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_destroy, module_destroy));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_shutdown, application_shutdown));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_configure, module_configure));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_server_ready, server_ready));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_request_init, request_init));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_request_destroy, request_destroy));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_error, on_error));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_guard_denied, guard_denied));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_before_shutdown, before_shutdown));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_after_shutdown, after_shutdown));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_module_load, module_load));
    lifecycle_all.extend(lifecycle_calls!(lifecycle_module_unload, module_unload));

    Ok(quote! {
        impl ::ironic::Module for #name {
            fn definition() -> ::ironic::ModuleDefinition {
                ::ironic::ModuleDefinition::builder::<Self>()
                    #(#imports)*
                    #(#providers)*
                    #(#controllers)*
                    #(#exports)*
                    #global_call
                    #(#async_init_calls)*
                    #(#lifecycle_all)*
                    .build()
            }
        }
    })
}