densha_macros_internal/
lib.rs

1// Procedural macros for Densha framework
2// These macros provide attribute-style functionality for defining routes
3
4extern crate proc_macro;
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{
9    parse::Parse, parse_macro_input, punctuated::Punctuated, Expr, FnArg, Ident, ItemFn, Meta, Pat,
10    PatType, Token,
11};
12
13/// Custom structure to parse macro arguments
14#[derive(Default)]
15struct PageArgs {
16    render: Option<String>,
17}
18
19/// Parse arguments like (render = "static")
20impl Parse for PageArgs {
21    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
22        let mut args = Self::default();
23
24        if input.is_empty() {
25            return Ok(args);
26        }
27
28        let vars = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
29
30        for var in vars {
31            if let Meta::NameValue(meta) = var {
32                if meta.path.is_ident("render") {
33                    if let Expr::Lit(expr_lit) = &meta.value {
34                        if let syn::Lit::Str(lit_str) = &expr_lit.lit {
35                            args.render = Some(lit_str.value());
36                        }
37                    }
38                }
39            }
40        }
41
42        Ok(args)
43    }
44}
45
46/// Custom structure to parse static_props arguments
47#[derive(Default)]
48struct StaticPropsArgs {
49    revalidate: Option<u64>,
50}
51
52/// Parse arguments like (revalidate = 60)
53impl Parse for StaticPropsArgs {
54    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
55        let mut args = Self::default();
56
57        if input.is_empty() {
58            return Ok(args);
59        }
60
61        let vars = Punctuated::<Meta, Token![,]>::parse_terminated(input)?;
62
63        for var in vars {
64            if let Meta::NameValue(meta) = var {
65                if meta.path.is_ident("revalidate") {
66                    if let Expr::Lit(expr_lit) = &meta.value {
67                        if let syn::Lit::Int(lit_int) = &expr_lit.lit {
68                            if let Ok(value) = lit_int.base10_parse::<u64>() {
69                                args.revalidate = Some(value);
70                            }
71                        }
72                    }
73                }
74            }
75        }
76
77        Ok(args)
78    }
79}
80
81/// Attribute macro for defining page components
82///
83/// # Example
84///
85/// ```ignore
86/// use densha_macros::page;
87///
88/// #[page]
89/// pub fn about_page() -> String {
90///     "About page".to_string()
91/// }
92///
93/// #[page(render = "static")]
94/// pub fn static_page() -> String {
95///     "Static page".to_string()
96/// }
97/// ```
98#[proc_macro_attribute]
99pub fn page(attr: TokenStream, item: TokenStream) -> TokenStream {
100    let args = parse_macro_input!(attr as PageArgs);
101    let input = parse_macro_input!(item as ItemFn);
102
103    // Process attributes
104    let render_strategy = args.render.unwrap_or_else(|| "server".to_string());
105
106    // Get function name and path
107    let fn_name = &input.sig.ident;
108    let fn_vis = &input.vis;
109    let fn_block = &input.block;
110    let fn_generics = &input.sig.generics;
111    let fn_inputs = &input.sig.inputs;
112    let fn_output = &input.sig.output;
113
114    // Create route path from function name
115    let route_path = fn_name_to_route_path(fn_name);
116
117    // Extract path parameters
118    let _path_params = extract_path_params(fn_inputs);
119
120    // Generate output
121    let expanded = quote! {
122        #[allow(non_camel_case_types)]
123        #fn_vis struct #fn_name;
124
125        impl #fn_name {
126            #fn_vis fn new() -> Self {
127                // Register the page with the Densha router
128                println!("Registering page: {} with render strategy: {}", #route_path, #render_strategy);
129
130                Self
131            }
132
133            #fn_vis #fn_generics fn call(#fn_inputs) #fn_output {
134                #fn_block
135            }
136        }
137
138        // Create a static instance to register the route
139        #[allow(non_upper_case_globals)]
140        #fn_vis static #fn_name: () = {
141            let _ = #fn_name::new();
142            ()
143        };
144    };
145
146    TokenStream::from(expanded)
147}
148
149/// Attribute macro for defining API endpoints
150///
151/// # Example
152///
153/// ```ignore
154/// use densha_macros::api;
155///
156/// #[api]
157/// pub async fn list_users() -> String {
158///     "User list".to_string()
159/// }
160/// ```
161#[proc_macro_attribute]
162pub fn api(attr: TokenStream, item: TokenStream) -> TokenStream {
163    let _args = parse_macro_input!(attr as PageArgs);
164    let input = parse_macro_input!(item as ItemFn);
165
166    // Get function name and path
167    let fn_name = &input.sig.ident;
168    let fn_vis = &input.vis;
169    let fn_block = &input.block;
170    let fn_generics = &input.sig.generics;
171    let fn_inputs = &input.sig.inputs;
172    let fn_output = &input.sig.output;
173
174    // Create route path from function name
175    let route_path = fn_name_to_route_path(fn_name);
176
177    // Extract path parameters
178    let _path_params = extract_path_params(fn_inputs);
179
180    // Generate output
181    let expanded = quote! {
182        #[allow(non_camel_case_types)]
183        #fn_vis struct #fn_name;
184
185        impl #fn_name {
186            #fn_vis fn new() -> Self {
187                // Register the API with the Densha router
188                println!("Registering API: {}", #route_path);
189
190                Self
191            }
192
193            #fn_vis #fn_generics fn call(#fn_inputs) #fn_output {
194                #fn_block
195            }
196        }
197
198        // Create a static instance to register the route
199        #[allow(non_upper_case_globals)]
200        #fn_vis static #fn_name: () = {
201            let _ = #fn_name::new();
202            ()
203        };
204    };
205
206    TokenStream::from(expanded)
207}
208
209/// Attribute macro for defining configuration
210///
211/// # Example
212///
213/// ```ignore
214/// use densha_macros::config;
215///
216/// #[config]
217/// struct AppConfig {
218///     database_url: String,
219///     port: u16,
220/// }
221/// ```
222#[proc_macro_attribute]
223pub fn config(_attr: TokenStream, item: TokenStream) -> TokenStream {
224    let input = parse_macro_input!(item as syn::ItemStruct);
225
226    // Get struct name and fields
227    let struct_name = &input.ident;
228
229    // Generate implementation for loading configuration
230    let expanded = quote! {
231        #input
232
233        impl #struct_name {
234            /// Load configuration from environment, files, and defaults
235            pub fn load() -> Self {
236                // TODO: Implement config loading from various sources
237                println!("Loading configuration for {}", stringify!(#struct_name));
238
239                // Create a new instance with default values
240                // In a real implementation, we would load from env vars, config files, etc.
241                Self::default()
242            }
243        }
244
245        impl Default for #struct_name {
246            fn default() -> Self {
247                Self {
248                    // Default implementation - this would be replaced with actual defaults
249                    ..Default::default()
250                }
251            }
252        }
253    };
254
255    TokenStream::from(expanded)
256}
257
258/// Attribute macro for defining static props function for data fetching at build time
259///
260/// # Example
261///
262/// ```ignore
263/// use densha_macros::static_props;
264/// use serde::Deserialize;
265///
266/// #[derive(Deserialize)]
267/// struct PostParams {
268///     id: String,
269/// }
270///
271/// #[static_props(revalidate = 60)]
272/// pub async fn get_post_data(params: PostParams) -> Result<serde_json::Value> {
273///     // Fetch data at build time
274///     Ok(serde_json::json!({
275///         "post": {
276///             "id": params.id,
277///             "title": "Dynamic Post Title",
278///             "content": "Content fetched at build time"
279///         }
280///     }))
281/// }
282/// ```
283#[proc_macro_attribute]
284pub fn static_props(attr: TokenStream, item: TokenStream) -> TokenStream {
285    let args = parse_macro_input!(attr as StaticPropsArgs);
286    let input = parse_macro_input!(item as ItemFn);
287    
288    // Get function name and attributes
289    let fn_name = &input.sig.ident;
290    let fn_vis = &input.vis;
291    let fn_block = &input.block;
292    let fn_inputs = &input.sig.inputs;
293    
294    // Get revalidation time if specified
295    let revalidate = match args.revalidate {
296        Some(seconds) => quote! { Some(#seconds) },
297        None => quote! { None },
298    };
299    
300    // Generate the function registration with metadata
301    let expanded = quote! {
302        #[allow(non_upper_case_globals)]
303        #fn_vis static #fn_name: ::densha::prelude::StaticPropsFunction = ::densha::prelude::StaticPropsFunction {
304            name: stringify!(#fn_name),
305            revalidate: #revalidate,
306            func: |ctx| async move {
307                // Convert context to function parameters
308                let params = ::densha::prelude::parse_params_from_context(&ctx)
309                    .expect(&format!("Failed to parse parameters for {}", stringify!(#fn_name)));
310                
311                // Call the actual function implementation
312                async fn implementation(#fn_inputs) -> ::anyhow::Result<::serde_json::Value> {
313                    #fn_block
314                }
315                
316                implementation(params).await
317            },
318        };
319    };
320    
321    TokenStream::from(expanded)
322}
323
324/// Attribute macro for defining static paths for dynamic routes
325///
326/// # Example
327///
328/// ```ignore
329/// use densha_macros::static_paths;
330///
331/// #[static_paths]
332/// pub async fn get_post_paths() -> Result<Vec<serde_json::Value>> {
333///     // Return all possible path params to pre-render
334///     Ok(vec![
335///         serde_json::json!({"id": "1"}),
336///         serde_json::json!({"id": "2"}),
337///         serde_json::json!({"id": "3"})
338///     ])
339/// }
340/// ```
341#[proc_macro_attribute]
342pub fn static_paths(_attr: TokenStream, item: TokenStream) -> TokenStream {
343    let input = parse_macro_input!(item as ItemFn);
344    
345    // Get function name and attributes
346    let fn_name = &input.sig.ident;
347    let fn_vis = &input.vis;
348    let fn_block = &input.block;
349    
350    // Generate the function registration with metadata
351    let expanded = quote! {
352        #[allow(non_upper_case_globals)]
353        #fn_vis static #fn_name: ::densha::prelude::StaticPathsFunction = ::densha::prelude::StaticPathsFunction {
354            name: stringify!(#fn_name),
355            func: || async move {
356                // Call the actual function implementation
357                async fn implementation() -> ::anyhow::Result<::std::vec::Vec<::serde_json::Value>> {
358                    #fn_block
359                }
360                
361                implementation().await
362            },
363        };
364    };
365    
366    TokenStream::from(expanded)
367}
368
369/// Convert a function name to a route path
370///
371/// Examples:
372/// - `index` -> `/`
373/// - `about` -> `/about`
374/// - `user_profile` -> `/user/profile`
375/// - `api_users_get` -> `/api/users/get`
376fn fn_name_to_route_path(ident: &Ident) -> String {
377    let name = ident.to_string();
378
379    // Special case for index
380    if name == "index" {
381        return "/".to_string();
382    }
383
384    // Convert snake_case to path segments
385    let segments: Vec<&str> = name.split('_').collect();
386    let path = segments.join("/");
387
388    format!("/{}", path)
389}
390
391/// Extract path parameters from function arguments
392fn extract_path_params(
393    inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
394) -> Vec<String> {
395    let mut params = Vec::new();
396
397    for input in inputs {
398        match input {
399            FnArg::Typed(PatType { pat, .. }) => {
400                if let Pat::Ident(pat_ident) = &**pat {
401                    let param_name = pat_ident.ident.to_string();
402                    params.push(param_name);
403                }
404            }
405            _ => {}
406        }
407    }
408
409    params
410}