elif_macros/
lib.rs

1//! Procedural macros for elif.rs framework
2//!
3//! This crate provides macros that simplify common patterns in elif.rs applications,
4//! particularly around the bootstrap system and server startup.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{
9    parse::{Parse, ParseStream, Result},
10    parse_macro_input, 
11    punctuated::Punctuated,
12    token::Comma,
13    Error, Expr, ItemFn, LitStr, ReturnType, Token, Type,
14};
15
16/// Attribute macro for async main functions in elif.rs applications
17/// 
18/// This macro simplifies the bootstrap process by handling tokio runtime setup,
19/// logging initialization, and proper error conversion between bootstrap and HTTP errors.
20/// 
21/// # Examples
22/// 
23/// ## Basic Bootstrap Usage
24/// ```rust
25/// use elif::prelude::*;
26/// 
27/// #[elif::main]
28/// async fn main() -> Result<(), HttpError> {
29///     AppModule::bootstrap().listen("127.0.0.1:3000").await
30/// }
31/// ```
32/// 
33/// ## With Manual Server Setup
34/// ```rust
35/// use elif::prelude::*;
36/// 
37/// #[elif::main]
38/// async fn main() -> Result<(), HttpError> {
39///     let server = Server::new();
40///     server.listen("127.0.0.1:3000").await
41/// }
42/// ```
43#[proc_macro_attribute]
44pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream {
45    let input_fn = parse_macro_input!(input as ItemFn);
46    let fn_name = &input_fn.sig.ident;
47    let fn_block = &input_fn.block;
48    let fn_inputs = &input_fn.sig.inputs;
49    
50    // Check if function returns Result (more precise detection)
51    let returns_result = if let ReturnType::Type(_, ty) = &input_fn.sig.output {
52        if let syn::Type::Path(type_path) = &**ty {
53            type_path.path.segments.last().is_some_and(|s| s.ident == "Result")
54        } else {
55            false
56        }
57    } else {
58        false
59    };
60    
61    let expanded = if returns_result {
62        // Function returns Result - handle bootstrap/HTTP errors properly
63        quote! {
64            #[tokio::main]
65            async fn main() -> Result<(), Box<dyn std::error::Error>> {
66                // Initialize logging (if not already done)
67                if std::env::var("RUST_LOG").is_err() {
68                    std::env::set_var("RUST_LOG", "info");
69                }
70                
71                // Try to initialize logger, but don't fail if already initialized
72                let _ = env_logger::try_init();
73                
74                // Define the original async function inline
75                async fn #fn_name(#fn_inputs) -> Result<(), Box<dyn std::error::Error>> #fn_block
76                
77                // Run it and handle errors
78                match #fn_name().await {
79                    Ok(()) => Ok(()),
80                    Err(e) => {
81                        eprintln!("Application failed: {}", e);
82                        Err(e)
83                    }
84                }
85            }
86        }
87    } else {
88        // Function doesn't return Result
89        quote! {
90            #[tokio::main]
91            async fn main() {
92                // Initialize logging (if not already done)
93                if std::env::var("RUST_LOG").is_err() {
94                    std::env::set_var("RUST_LOG", "info");
95                }
96                
97                // Try to initialize logger, but don't fail if already initialized
98                let _ = env_logger::try_init();
99                
100                // Define the original async function inline
101                async fn #fn_name(#fn_inputs) #fn_block
102                
103                // Run it
104                #fn_name().await;
105            }
106        }
107    };
108    
109    TokenStream::from(expanded)
110}
111
112/// Bootstrap arguments for the bootstrap macro
113struct BootstrapArgs {
114    app_module: Option<Type>,
115    address: Option<String>,
116    config: Option<Expr>,
117    middleware: Option<Vec<Expr>>,
118}
119
120impl Parse for BootstrapArgs {
121    fn parse(input: ParseStream) -> Result<Self> {
122        let mut app_module: Option<Type> = None;
123        let mut address: Option<String> = None;
124        let mut config: Option<Expr> = None;
125        let mut middleware: Option<Vec<Expr>> = None;
126
127        // If input is empty, use auto-discovery mode
128        if input.is_empty() {
129            return Ok(BootstrapArgs {
130                app_module: None,
131                address,
132                config,
133                middleware,
134            });
135        }
136
137        // Try to parse first argument as app module type (for backward compatibility)
138        let lookahead = input.lookahead1();
139        if lookahead.peek(syn::Ident) {
140            // Check if first token is a named parameter or a type
141            let fork = input.fork();
142            let _ident: syn::Ident = fork.parse().unwrap();
143            if fork.peek(Token![=]) {
144                // This is a named parameter, not a type - use auto-discovery
145                app_module = None;
146                // Don't consume the token here, let the main parsing loop handle it
147            } else {
148                // This looks like a type - parse it for backward compatibility
149                app_module = Some(input.parse()?);
150            }
151        } else if lookahead.peek(syn::Token![::]) || lookahead.peek(syn::Token![<]) {
152            // This is definitely a type
153            app_module = Some(input.parse()?);
154        } else if lookahead.peek(LitStr) {
155            // Legacy support: first argument is address string
156            let lit: LitStr = input.parse()?;
157            address = Some(lit.value());
158        }
159        
160        // Parse optional named arguments
161        let mut first_param = true;
162        while !input.is_empty() {
163            // Only expect comma if this is not the first parameter or if we parsed a type
164            if !first_param || app_module.is_some() {
165                let _comma: Token![,] = input.parse()?;
166                
167                if input.is_empty() {
168                    break;
169                }
170            }
171            first_param = false;
172            
173            let lookahead = input.lookahead1();
174            if lookahead.peek(syn::Ident) {
175                let ident: syn::Ident = input.parse()?;
176                let _eq: Token![=] = input.parse()?;
177                
178                match ident.to_string().as_str() {
179                    "addr" => {
180                        let lit: LitStr = input.parse()?;
181                        address = Some(lit.value());
182                    }
183                    "config" => {
184                        config = Some(input.parse()?);
185                    }
186                    "middleware" => {
187                        let content;
188                        syn::bracketed!(content in input);
189                        let middleware_list: Punctuated<Expr, Comma> = 
190                            content.parse_terminated(Expr::parse, Comma)?;
191                        middleware = Some(middleware_list.into_iter().collect());
192                    }
193                    _ => {
194                        let ident_name = ident.to_string();
195                        return Err(Error::new_spanned(
196                            ident,
197                            format!(
198                                "Unknown bootstrap parameter '{}'. Valid parameters are: addr, config, middleware\n\
199                                \n\
200                                💡 Usage examples:\n\
201                                • #[elif::bootstrap] (auto-discovery)\n\
202                                • #[elif::bootstrap(addr = \"127.0.0.1:3000\")]\n\
203                                • #[elif::bootstrap(config = my_config())]\n\
204                                • #[elif::bootstrap(middleware = [cors(), auth()])]\n\
205                                • #[elif::bootstrap(AppModule)] (backward compatibility)",
206                                ident_name
207                            )
208                        ));
209                    }
210                }
211            } else if input.peek(LitStr) {
212                // Simple string for address (legacy support)
213                let lit: LitStr = input.parse()?;
214                address = Some(lit.value());
215            } else {
216                return Err(lookahead.error());
217            }
218        }
219        
220        Ok(BootstrapArgs {
221            app_module,
222            address,
223            config,
224            middleware,
225        })
226    }
227}
228
229/// Enhanced bootstrap macro for zero-boilerplate application startup
230/// 
231/// This macro provides Laravel-style "convention over configuration" by automatically
232/// generating all the server setup code using auto-discovery of modules and controllers.
233/// 
234/// # Examples
235/// 
236/// ## Zero-Boilerplate Bootstrap (NEW!)
237/// ```rust
238/// use elif::prelude::*;
239/// 
240/// #[elif::bootstrap]
241/// async fn main() -> Result<(), HttpError> {
242///     // Automatically generated:
243///     // - Module discovery from compile-time registry
244///     // - Controller auto-registration from static registry
245///     // - DI container configuration
246///     // - Router setup with all controllers
247///     // - Server startup on 127.0.0.1:3000
248/// }
249/// ```
250/// 
251/// ## With Custom Address
252/// ```rust
253/// #[elif::bootstrap(addr = "0.0.0.0:8080")]
254/// async fn main() -> Result<(), HttpError> {}
255/// ```
256/// 
257/// ## With Custom Configuration
258/// ```rust
259/// #[elif::bootstrap(config = HttpConfig::with_timeout(30))]
260/// async fn main() -> Result<(), HttpError> {}
261/// ```
262/// 
263/// ## With Middleware
264/// ```rust
265/// #[elif::bootstrap(middleware = [cors(), auth(), logging()])]
266/// async fn main() -> Result<(), HttpError> {}
267/// ```
268/// 
269/// ## Full Configuration
270/// ```rust
271/// #[elif::bootstrap(
272///     addr = "0.0.0.0:8080",
273///     config = HttpConfig::production(),
274///     middleware = [cors(), auth()]
275/// )]
276/// async fn main() -> Result<(), HttpError> {}
277/// ```
278/// 
279/// ## Backward Compatibility with AppModule
280/// ```rust
281/// #[elif::bootstrap(AppModule)]
282/// async fn main() -> Result<(), HttpError> {}
283/// ```
284#[proc_macro_attribute]
285pub fn bootstrap(args: TokenStream, input: TokenStream) -> TokenStream {
286    let bootstrap_args = match syn::parse::<BootstrapArgs>(args) {
287        Ok(args) => args,
288        Err(err) => return err.to_compile_error().into(),
289    };
290
291    let input_fn = parse_macro_input!(input as ItemFn);
292    let fn_name = &input_fn.sig.ident;
293    let fn_inputs = &input_fn.sig.inputs;
294    
295    // Validate that this is an async function
296    if input_fn.sig.asyncness.is_none() {
297        let error = Error::new_spanned(
298            input_fn.sig.fn_token,
299            "Bootstrap macro can only be applied to async functions\n\
300            \n\
301            💡 Change your function to:\n\
302            async fn main() -> Result<(), HttpError> {}"
303        );
304        return error.to_compile_error().into();
305    }
306    
307    // Check if function returns Result
308    let returns_result = if let ReturnType::Type(_, ty) = &input_fn.sig.output {
309        if let syn::Type::Path(type_path) = &**ty {
310            type_path.path.segments.last().is_some_and(|s| s.ident == "Result")
311        } else {
312            false
313        }
314    } else {
315        false
316    };
317    
318    if !returns_result {
319        let error = Error::new_spanned(
320            &input_fn.sig.output,
321            "Bootstrap macro requires functions to return Result<(), HttpError>\n\
322            \n\
323            💡 Change your function signature to:\n\
324            async fn main() -> Result<(), HttpError> {}"
325        );
326        return error.to_compile_error().into();
327    }
328    
329    // Generate bootstrap code
330    let address = bootstrap_args.address.as_deref().unwrap_or("127.0.0.1:3000");
331    let config_setup = if let Some(config) = &bootstrap_args.config {
332        quote! { .with_config(#config) }
333    } else {
334        quote! {}
335    };
336    let middleware_setup = if let Some(middleware) = &bootstrap_args.middleware {
337        quote! { .with_middleware(vec![#(Box::new(#middleware)),*]) }
338    } else {
339        quote! {}
340    };
341    
342    // Generate different bootstrap code based on whether app_module is provided
343    let bootstrap_code = if let Some(app_module) = &bootstrap_args.app_module {
344        // Backward compatibility: use AppModule::bootstrap()
345        quote! {
346            let bootstrapper = #app_module::bootstrap()
347                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?
348                #config_setup
349                #middleware_setup;
350        }
351    } else {
352        // New auto-discovery mode: use AppBootstrapper directly
353        quote! {
354            let bootstrapper = {
355                // Import AppBootstrapper from elif-http
356                use elif_http::bootstrap::AppBootstrapper;
357                AppBootstrapper::new()
358            }
359                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?
360                #config_setup
361                #middleware_setup;
362        }
363    };
364    
365    let expanded = quote! {
366        #[tokio::main]
367        async fn main() -> Result<(), Box<dyn std::error::Error>> {
368            // Initialize logging (if not already done)
369            if std::env::var("RUST_LOG").is_err() {
370                std::env::set_var("RUST_LOG", "info");
371            }
372            
373            // Try to initialize logger, but don't fail if already initialized
374            let _ = env_logger::try_init();
375            
376            // Define the original async function inline for any custom setup
377            async fn #fn_name(#fn_inputs) -> Result<(), Box<dyn std::error::Error>> {
378                // Generate bootstrap code (auto-discovery or backward compatibility)
379                #bootstrap_code
380                
381                // Start the server
382                bootstrapper
383                    .listen(#address.parse::<std::net::SocketAddr>().expect("Invalid socket address"))
384                    .await
385                    .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
386                
387                Ok(())
388            }
389            
390            // Run it and handle errors
391            match #fn_name().await {
392                Ok(()) => Ok(()),
393                Err(e) => {
394                    eprintln!("Application bootstrap failed: {}", e);
395                    Err(e)
396                }
397            }
398        }
399    };
400    
401    TokenStream::from(expanded)
402}