use crate::debug::get_debug_level;
use crate::macros::common_output::{ create_dependency, create_service_depends };
use crate::structures::service::{ Property, PropertyDefault, ServiceData };
use proc_macro2::TokenStream;
use syn::{ DeriveInput, Ident, Visibility };
pub fn expand_derive_component(input: &DeriveInput) -> syn::Result<TokenStream> {
let service = ServiceData::from_derive_input(input)?;
let debug_level = get_debug_level();
if debug_level > 1 {
println!("Service data parsed from Component input: {:#?}", service);
}
let resolve_properties: Vec<TokenStream> = service.properties
.iter()
.map(create_resolve_property)
.collect();
let dependencies: Vec<TokenStream> = service.properties
.iter()
.filter_map(create_dependency)
.collect();
let service_depends: Vec<TokenStream> = service.properties
.iter()
.filter_map(create_service_depends)
.collect();
let visibility = &service.metadata.visibility;
let parameters_properties: Vec<TokenStream> = service.properties
.iter()
.filter_map(|property| create_parameters_property(property, visibility))
.collect();
let parameters_defaults: Vec<TokenStream> = service.properties
.iter()
.filter_map(|property| create_parameters_default(property, &service.metadata.identifier))
.collect();
let component_name = service.metadata.identifier;
let parameters_name = format_ident!("{}Parameters", component_name);
let parameters_doc = format!(" Parameters for {}", component_name);
let interface = service.metadata.interface;
let (generic_impls, generic_tys, generic_where) = service.metadata.generics.split_for_impl();
let generic_impls_no_parens = &service.metadata.generics.params;
let output =
quote! {
impl<
M: ::modi::dyn_mod::Module #(+ #dependencies)*,
#generic_impls_no_parens
> ::modi::dyn_mod::Component<M> for #component_name #generic_tys #generic_where {
type Interface = dyn #interface;
type Parameters = #parameters_name #generic_tys;
fn build(context: &mut ::modi::dyn_mod::ModuleBuildContext<M>, params: Self::Parameters) -> Box<Self::Interface> {
Box::new(Self {
#(#resolve_properties),*
})
}
}
impl #generic_tys ::modi::dyn_mod::Depends for #component_name #generic_tys #generic_where {
fn depends(&self) -> ::std::vec::Vec<::modi::dyn_mod::Manifest> {
vec![
#(#service_depends,)*
]
}
}
#[doc = #parameters_doc]
#visibility struct #parameters_name #generic_impls #generic_where {
#(#parameters_properties),*
}
impl #generic_impls ::std::default::Default for #parameters_name #generic_tys #generic_where {
#[allow(unreachable_code)]
fn default() -> Self {
Self {
#(#parameters_defaults),*
}
}
}
};
if debug_level > 0 {
println!("{}", output);
}
Ok(output)
}
fn create_resolve_property(property: &Property) -> TokenStream {
let property_name = &property.property_name;
if property.is_service() {
quote! {
#property_name: M::build_component(context)
}
} else {
quote! {
#property_name: params.#property_name
}
}
}
fn create_parameters_property(property: &Property, vis: &Visibility) -> Option<TokenStream> {
if property.is_service() {
return None;
}
let property_name = &property.property_name;
let property_type = &property.ty;
let doc_comment = &property.doc_comment;
Some(quote! {
#(#doc_comment)*
#vis #property_name: #property_type
})
}
fn create_parameters_default(property: &Property, component_ident: &Ident) -> Option<TokenStream> {
if property.is_service() {
return None;
}
let property_name = &property.property_name;
match &property.default {
PropertyDefault::Provided(default_expr) =>
Some(quote! {
#property_name: #default_expr
}),
PropertyDefault::NotProvided =>
Some(quote! {
#property_name: Default::default()
}),
PropertyDefault::NoDefault => {
let unreachable_msg = format!(
"There is no default value for `{}::{}`",
component_ident,
property_name
);
Some(
quote! {
#property_name: unreachable!(#unreachable_msg)
}
)
}
}
}