use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
token::{At, Comma, FatArrow},
Error, ItemStruct, LitStr, Token, Type,
};
pub fn module_impl(args: TokenStream, input: TokenStream) -> TokenStream {
let module_args = match syn::parse::<ModuleArgs>(args) {
Ok(args) => args,
Err(err) => return err.to_compile_error().into(),
};
let mut item_struct = parse_macro_input!(input as ItemStruct);
match process_module_attribute(&mut item_struct, module_args) {
Ok(result) => result.into(),
Err(err) => err.to_compile_error().into(),
}
}
pub fn module_composition_impl(input: TokenStream) -> TokenStream {
let composition_args = match syn::parse::<ModuleCompositionArgs>(input) {
Ok(args) => args,
Err(err) => return err.to_compile_error().into(),
};
match generate_application_composition(composition_args) {
Ok(result) => result.into(),
Err(err) => err.to_compile_error().into(),
}
}
pub fn demo_dsl_impl(input: TokenStream) -> TokenStream {
let demo_args = match syn::parse::<DemoDslArgs>(input) {
Ok(args) => args,
Err(err) => return err.to_compile_error().into(),
};
match generate_demo_dsl_expansion(demo_args) {
Ok(result) => result.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[derive(Debug, Clone)]
pub struct ModuleArgs {
pub providers: Vec<ProviderDef>,
pub controllers: Vec<Type>,
pub imports: Vec<Type>,
pub exports: Vec<Type>,
pub is_app_module: bool,
}
impl Parse for ModuleArgs {
fn parse(input: ParseStream) -> Result<Self> {
let mut providers = Vec::new();
let mut controllers = Vec::new();
let mut imports = Vec::new();
let mut exports = Vec::new();
let mut is_app_module = false;
while !input.is_empty() {
let key: Ident = input.parse()?;
let key_str = key.to_string();
if key_str == "is_app" && !input.peek(Token![:]) {
is_app_module = true;
} else {
let _colon: Token![:] = input.parse()?;
match key_str.as_str() {
"providers" => {
providers = parse_provider_list(input)?;
}
"controllers" => {
controllers = parse_type_list(input)?;
}
"imports" => {
imports = parse_type_list(input)?;
}
"exports" => {
exports = parse_type_list(input)?;
}
_ => {
return Err(Error::new_spanned(
key,
format!(
"Unknown module section '{}'. Valid sections are: providers, controllers, imports, exports, is_app.\n\
\n\
💡 Suggestions:\n\
• Use 'providers: [ServiceType]' for concrete services\n\
• Use 'providers: [dyn Trait => Implementation]' for trait mappings\n\
• Use 'controllers: [ControllerType]' for HTTP controllers\n\
• Use 'imports: [ModuleType]' for module dependencies\n\
• Use 'exports: [ServiceType]' for services available to other modules\n\
• Use 'is_app' (without colon) to mark this module as an app module that can bootstrap\n\
\n\
📖 See: https://docs.elif.rs/modules/module-definition",
key_str
)
));
}
}
}
if !input.is_empty() {
let _comma: Option<Comma> = input.parse().ok();
}
}
Ok(ModuleArgs {
providers,
controllers,
imports,
exports,
is_app_module,
})
}
}
#[derive(Debug, Clone)]
pub struct ProviderDef {
pub service_type: ProviderType,
pub implementation: Option<Type>,
pub name: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ProviderType {
Concrete(Type),
Trait(Type),
}
impl Parse for ProviderDef {
fn parse(input: ParseStream) -> Result<Self> {
let service_type = if input.peek(Token![dyn]) {
let _dyn: Token![dyn] = input.parse()?;
let trait_type: Type = input.parse()?;
ProviderType::Trait(trait_type)
} else {
let parsed_type: Type = input.parse()?;
if input.peek(FatArrow) {
ProviderType::Trait(parsed_type)
} else {
ProviderType::Concrete(parsed_type)
}
};
let mut implementation = None;
let mut name = None;
if input.peek(FatArrow) {
let _arrow: FatArrow = input.parse()?;
implementation = Some(input.parse()?);
if input.peek(At) {
let _at: At = input.parse()?;
let name_lit: LitStr = input.parse()?;
name = Some(name_lit.value());
}
}
Ok(ProviderDef {
service_type,
implementation,
name,
})
}
}
fn parse_provider_list(input: ParseStream) -> Result<Vec<ProviderDef>> {
let content;
let _bracket = syn::bracketed!(content in input);
let providers: Punctuated<ProviderDef, Comma> =
content.parse_terminated(ProviderDef::parse, Comma)?;
Ok(providers.into_iter().collect())
}
fn parse_type_list(input: ParseStream) -> Result<Vec<Type>> {
let content;
let _bracket = syn::bracketed!(content in input);
let types: Punctuated<Type, Comma> = content.parse_terminated(Type::parse, Comma)?;
Ok(types.into_iter().collect())
}
fn parse_string_list(input: ParseStream) -> Result<Vec<String>> {
let content;
let _bracket = syn::bracketed!(content in input);
let strings: Punctuated<LitStr, Comma> =
content.parse_terminated(|input| input.parse::<LitStr>(), Comma)?;
Ok(strings.into_iter().map(|s| s.value()).collect())
}
#[derive(Debug, Clone)]
pub struct ModuleCompositionArgs {
pub modules: Vec<Type>,
pub overrides: Vec<ProviderDef>,
}
#[derive(Debug, Clone, Default)]
pub struct DemoDslArgs {
pub services: Vec<Type>,
pub controllers: Vec<Type>,
pub middleware: Vec<String>,
}
impl Parse for ModuleCompositionArgs {
fn parse(input: ParseStream) -> Result<Self> {
let mut modules = Vec::new();
let mut overrides = Vec::new();
while !input.is_empty() {
let key: Ident = input.parse()?;
let _colon: Token![:] = input.parse()?;
let key_str = key.to_string();
match key_str.as_str() {
"modules" => {
modules = parse_type_list(input)?;
}
"overrides" => {
overrides = parse_provider_list(input)?;
}
_ => {
return Err(Error::new_spanned(
key,
format!(
"Unknown composition section '{}'. Valid sections are: modules, overrides.\n\
\n\
💡 Suggestions:\n\
• Use 'modules: [ModuleType1, ModuleType2]' to compose multiple modules\n\
• Use 'overrides: [Service => Implementation]' to override module bindings\n\
\n\
📖 Example:\n\
module_composition! {{\n\
modules: [UserModule, AuthModule],\n\
overrides: [dyn EmailService => MockEmailService @ \"test\"]\n\
}}\n\
\n\
📖 See: https://docs.elif.rs/modules/application-composition",
key_str
)
));
}
}
if !input.is_empty() {
let _comma: Option<Comma> = input.parse().ok();
}
}
if modules.is_empty() {
return Err(Error::new(
Span::call_site(),
"module! composition requires at least one module in the 'modules' section",
));
}
Ok(ModuleCompositionArgs { modules, overrides })
}
}
impl Parse for DemoDslArgs {
fn parse(input: ParseStream) -> Result<Self> {
let mut services = Vec::new();
let mut controllers = Vec::new();
let mut middleware = Vec::new();
while !input.is_empty() {
let key: Ident = input.parse()?;
let _colon: Token![:] = input.parse()?;
let key_str = key.to_string();
match key_str.as_str() {
"services" => {
services = parse_type_list(input)?;
}
"controllers" => {
controllers = parse_type_list(input)?;
}
"middleware" => {
middleware = parse_string_list(input)?;
}
_ => {
return Err(Error::new_spanned(
key,
format!(
"Unknown demo DSL section '{}'. Valid sections are: services, controllers, middleware.\n\
\n\
💡 Demo DSL Suggestions:\n\
• Use 'services: [ServiceType1, ServiceType2]' for concrete services\n\
• Use 'controllers: [ControllerType1, ControllerType2]' for HTTP controllers\n\
• Use 'middleware: [\"cors\", \"auth\", \"logging\"]' for middleware stack\n\
\n\
📖 Example:\n\
demo_module! {{\n\
services: [UserService, EmailService],\n\
controllers: [UserController],\n\
middleware: [\"cors\", \"auth\"]\n\
}}\n\
\n\
⚠️ Note: Demo DSL is simplified syntax. For trait mappings and imports/exports,\n\
use the full #[module(...)] attribute syntax instead.\n\
\n\
📖 See: https://docs.elif.rs/modules/demo-dsl-guide",
key_str
)
));
}
}
if !input.is_empty() {
let _comma: Option<Comma> = input.parse().ok();
}
}
Ok(DemoDslArgs {
services,
controllers,
middleware,
})
}
}
fn process_module_attribute(
item_struct: &mut ItemStruct,
module_args: ModuleArgs,
) -> Result<proc_macro2::TokenStream> {
let struct_name = &item_struct.ident;
let module_descriptor_impl = generate_module_descriptor_method(struct_name, &module_args)?;
let app_bootstrap_impl = if module_args.is_app_module {
generate_app_bootstrap_impl(struct_name, &module_args)?
} else {
quote! {}
};
Ok(quote! {
#item_struct
#module_descriptor_impl
#app_bootstrap_impl
})
}
fn generate_module_descriptor_method(
struct_name: &Ident,
module_args: &ModuleArgs,
) -> Result<proc_macro2::TokenStream> {
let providers_code = generate_providers_descriptors(&module_args.providers)?;
let controllers_code = generate_controllers_descriptors(&module_args.controllers)?;
let imports_list = generate_imports_list(&module_args.imports)?;
let exports_list = generate_exports_list(&module_args.exports)?;
let auto_configure_code = generate_auto_configure_function(struct_name, module_args)?;
let registry_registration_code = generate_registry_registration_code(struct_name, module_args)?;
Ok(quote! {
impl #struct_name {
pub fn module_descriptor() -> elif_core::modules::ModuleDescriptor {
use elif_core::modules::{ModuleDescriptor, ServiceDescriptor, ControllerDescriptor, ServiceLifecycle};
use std::any::TypeId;
let mut descriptor = ModuleDescriptor::new(stringify!(#struct_name));
#providers_code
#controllers_code
descriptor = descriptor
.with_imports(#imports_list)
.with_exports(#exports_list);
descriptor
}
}
impl elif_core::modules::ModuleAutoConfiguration for #struct_name {
fn module_descriptor() -> elif_core::modules::ModuleDescriptor {
Self::module_descriptor()
}
fn auto_configure(container: &mut elif_core::container::IocContainer) -> Result<(), elif_core::modules::ModuleError> {
#auto_configure_code
}
}
#registry_registration_code
})
}
fn generate_providers_descriptors(providers: &[ProviderDef]) -> Result<proc_macro2::TokenStream> {
if providers.is_empty() {
return Ok(quote! {
});
}
let mut descriptor_calls = Vec::new();
for provider in providers {
let descriptor_call = match &provider.service_type {
ProviderType::Concrete(service_type) => match &provider.name {
Some(name) => {
quote! {
descriptor = descriptor.with_provider(
ServiceDescriptor::new::<#service_type>(stringify!(#service_type), ServiceLifecycle::default())
.with_name(#name)
);
}
}
None => {
quote! {
descriptor = descriptor.with_provider(
ServiceDescriptor::new::<#service_type>(stringify!(#service_type), ServiceLifecycle::default())
);
}
}
},
ProviderType::Trait(trait_type) => match &provider.implementation {
Some(impl_type) => match &provider.name {
Some(name) => {
quote! {
descriptor = descriptor.with_provider(
ServiceDescriptor::trait_mapping::<#trait_type, #impl_type>(
stringify!(#trait_type), stringify!(#impl_type), ServiceLifecycle::default()
).with_name(#name)
);
}
}
None => {
quote! {
descriptor = descriptor.with_provider(
ServiceDescriptor::trait_mapping::<#trait_type, #impl_type>(
stringify!(#trait_type), stringify!(#impl_type), ServiceLifecycle::default()
)
);
}
}
},
None => {
return Err(Error::new_spanned(
trait_type,
"Trait providers must specify implementation type: dyn Trait => Implementation.\n\
\n\
💡 Suggestions:\n\
• Use 'dyn EmailService => SmtpEmailService' for trait mapping\n\
• Use 'dyn EmailService => SmtpEmailService @ \"smtp\"' for named mapping\n\
• Use 'EmailService => SmtpEmailService' (dyn is optional in simplified syntax)\n\
\n\
📖 Examples:\n\
#[module(\n\
providers: [\n\
UserService, // Concrete service\n\
dyn EmailService => SmtpEmailService, // Trait mapping\n\
dyn EmailService => MockEmailService @ \"test\" // Named mapping\n\
]\n\
)]\n\
\n\
📖 See: https://docs.elif.rs/modules/dependency-injection"
));
}
},
};
descriptor_calls.push(descriptor_call);
}
Ok(quote! {
#(#descriptor_calls)*
})
}
fn generate_controllers_descriptors(controllers: &[Type]) -> Result<proc_macro2::TokenStream> {
if controllers.is_empty() {
return Ok(quote! {
});
}
let descriptor_calls: Vec<_> = controllers
.iter()
.map(|controller| {
quote! {
descriptor = descriptor.with_controller(
ControllerDescriptor::new::<#controller>(stringify!(#controller))
);
}
})
.collect();
Ok(quote! {
#(#descriptor_calls)*
})
}
fn generate_imports_list(imports: &[Type]) -> Result<proc_macro2::TokenStream> {
if imports.is_empty() {
return Ok(quote! { vec![] });
}
let import_strings: Vec<_> = imports
.iter()
.map(|import| {
quote! { stringify!(#import).to_string() }
})
.collect();
Ok(quote! {
vec![#(#import_strings),*]
})
}
fn generate_exports_list(exports: &[Type]) -> Result<proc_macro2::TokenStream> {
if exports.is_empty() {
return Ok(quote! { vec![] });
}
let export_strings: Vec<_> = exports
.iter()
.map(|export| {
quote! { stringify!(#export).to_string() }
})
.collect();
Ok(quote! {
vec![#(#export_strings),*]
})
}
fn generate_registry_registration_code(
struct_name: &Ident,
module_args: &ModuleArgs,
) -> Result<proc_macro2::TokenStream> {
let controller_names: Vec<String> = module_args.controllers
.iter()
.map(|controller| controller.to_token_stream().to_string())
.collect();
let provider_names: Vec<String> = module_args.providers
.iter()
.map(|provider| match &provider.service_type {
ProviderType::Concrete(service_type) => service_type.to_token_stream().to_string(),
ProviderType::Trait(trait_type) => {
if let Some(impl_type) = &provider.implementation {
impl_type.to_token_stream().to_string()
} else {
trait_type.to_token_stream().to_string()
}
}
})
.collect();
let import_names: Vec<String> = module_args.imports
.iter()
.map(|import| import.to_token_stream().to_string())
.collect();
let export_names: Vec<String> = module_args.exports
.iter()
.map(|export| export.to_token_stream().to_string())
.collect();
Ok(quote! {
impl #struct_name {
pub fn ensure_registered() {
use elif_core::modules::{CompileTimeModuleMetadata, register_module_globally};
static REGISTER_MODULE: std::sync::Once = std::sync::Once::new();
REGISTER_MODULE.call_once(|| {
let metadata = CompileTimeModuleMetadata::new(stringify!(#struct_name).to_string())
.with_controllers(vec![#(#controller_names.to_string()),*])
.with_providers(vec![#(#provider_names.to_string()),*])
.with_imports(vec![#(#import_names.to_string()),*])
.with_exports(vec![#(#export_names.to_string()),*]);
register_module_globally(metadata);
});
}
}
#[::ctor::ctor]
fn __register_module() {
#struct_name::ensure_registered();
}
})
}
fn generate_auto_configure_function(
_struct_name: &Ident,
module_args: &ModuleArgs,
) -> Result<proc_macro2::TokenStream> {
let mut configure_calls = Vec::new();
for import in &module_args.imports {
configure_calls.push(quote! {
<#import as elif_core::modules::ModuleAutoConfiguration>::auto_configure(container)?;
});
}
for provider in &module_args.providers {
let configure_call = match &provider.service_type {
ProviderType::Concrete(service_type) => {
match &provider.name {
Some(name) => {
quote! {
container.bind_named::<#service_type, #service_type>(#name);
}
}
None => {
quote! {
container.bind::<#service_type, #service_type>();
}
}
}
}
ProviderType::Trait(trait_type) => {
if let Some(impl_type) = &provider.implementation {
match &provider.name {
Some(name) => {
let _token_name = quote::format_ident!(
"{}Token",
trait_type.to_token_stream().to_string().replace(" ", "")
);
quote! {
container.bind_named::<#impl_type, #impl_type>(#name);
}
}
None => {
let _token_name = quote::format_ident!(
"{}Token",
trait_type.to_token_stream().to_string().replace(" ", "")
);
quote! {
container.bind::<#impl_type, #impl_type>();
}
}
}
} else {
return Err(Error::new_spanned(
trait_type,
"Trait providers must specify implementation type: dyn Trait => Implementation"
));
}
}
};
configure_calls.push(configure_call);
}
for controller in &module_args.controllers {
configure_calls.push(quote! {
container.bind::<#controller, #controller>();
});
}
Ok(quote! {
use elif_core::modules::{ModuleError, ModuleAutoConfiguration};
use elif_core::container::ServiceBinder;
if !container.is_built() {
}
#(#configure_calls)*
Ok(())
})
}
fn generate_application_composition(
composition_args: ModuleCompositionArgs,
) -> Result<proc_macro2::TokenStream> {
let modules_descriptors = generate_modules_descriptors(&composition_args.modules)?;
let overrides_descriptors = generate_composition_overrides(&composition_args.overrides)?;
Ok(quote! {
{
use elif_core::modules::{ModuleComposition, ModuleDescriptor, ServiceDescriptor};
let mut composition = ModuleComposition::new();
#modules_descriptors
#overrides_descriptors
composition.compose().unwrap()
}
})
}
fn generate_modules_descriptors(modules: &[Type]) -> Result<proc_macro2::TokenStream> {
if modules.is_empty() {
return Ok(quote! {
});
}
let descriptor_calls: Vec<_> = modules
.iter()
.map(|module| {
quote! {
composition = composition.with_module(#module::module_descriptor());
}
})
.collect();
Ok(quote! {
#(#descriptor_calls)*
})
}
fn generate_composition_overrides(overrides: &[ProviderDef]) -> Result<proc_macro2::TokenStream> {
if overrides.is_empty() {
return Ok(quote! {
});
}
let mut override_descriptors = Vec::new();
for override_def in overrides {
let override_descriptor = match &override_def.service_type {
ProviderType::Concrete(service_type) => {
let service_name = quote! { stringify!(#service_type) }.to_string();
match &override_def.name {
Some(name) => {
quote! {
ServiceDescriptor::new::<#service_type>(#service_name, ServiceLifecycle::default())
.with_name(#name)
}
}
None => {
quote! {
ServiceDescriptor::new::<#service_type>(#service_name, ServiceLifecycle::default())
}
}
}
}
ProviderType::Trait(trait_type) => {
if let Some(impl_type) = &override_def.implementation {
let service_name = quote! { stringify!(#trait_type) }.to_string();
let impl_name = quote! { stringify!(#impl_type) }.to_string();
match &override_def.name {
Some(name) => {
quote! {
ServiceDescriptor::trait_mapping::<#trait_type, #impl_type>(
#service_name, #impl_name, ServiceLifecycle::default()
).with_name(#name)
}
}
None => {
quote! {
ServiceDescriptor::trait_mapping::<#trait_type, #impl_type>(
#service_name, #impl_name, ServiceLifecycle::default()
)
}
}
}
} else {
return Err(Error::new_spanned(
trait_type,
"Trait overrides must specify implementation type: dyn Trait => Implementation"
));
}
}
};
override_descriptors.push(override_descriptor);
}
Ok(quote! {
use elif_core::modules::ServiceLifecycle;
let overrides = vec![
#(#override_descriptors),*
];
composition = composition.with_overrides(overrides);
})
}
fn generate_app_bootstrap_impl(struct_name: &Ident, module_args: &ModuleArgs) -> Result<proc_macro2::TokenStream> {
let import_references: Vec<_> = module_args.imports
.iter()
.map(|import| {
quote! {
let _ = std::marker::PhantomData::<#import>;
#import::ensure_registered();
}
})
.collect();
Ok(quote! {
impl elif_http::AppBootstrap for #struct_name {
fn bootstrap() -> elif_http::BootstrapResult<elif_http::AppBootstrapper> {
println!("🚀 AppModule::bootstrap() called for {}", stringify!(#struct_name));
#(#import_references)*
Self::ensure_registered();
println!("📋 Module registration completed for {}", stringify!(#struct_name));
elif_http::AppBootstrapper::new()
}
}
impl #struct_name {
fn force_module_inclusion() {
Self::ensure_registered();
}
}
})
}
fn generate_demo_dsl_expansion(demo_args: DemoDslArgs) -> Result<proc_macro2::TokenStream> {
let providers: Vec<ProviderDef> = demo_args
.services
.into_iter()
.map(|service| ProviderDef {
service_type: ProviderType::Concrete(service),
implementation: None,
name: None,
})
.collect();
let module_args = ModuleArgs {
providers,
controllers: demo_args.controllers,
imports: Vec::new(), exports: Vec::new(), is_app_module: false, };
let struct_name = Ident::new("DemoDslModule", Span::call_site());
let module_descriptor_impl = generate_module_descriptor_method(&struct_name, &module_args)?;
let middleware_code = if demo_args.middleware.is_empty() {
quote! { }
} else {
let middleware_names = &demo_args.middleware;
quote! {
let middleware_stack = vec![#(#middleware_names.to_string()),*];
println!("Demo DSL: Would apply middleware: {:?}", middleware_stack);
}
};
Ok(quote! {
{
struct #struct_name;
#module_descriptor_impl
let descriptor = #struct_name::module_descriptor();
#middleware_code
descriptor
}
})
}