densha_macros_internal/
lib.rs1extern 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#[derive(Default)]
15struct PageArgs {
16 render: Option<String>,
17}
18
19impl 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#[derive(Default)]
48struct StaticPropsArgs {
49 revalidate: Option<u64>,
50}
51
52impl 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#[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 let render_strategy = args.render.unwrap_or_else(|| "server".to_string());
105
106 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 let route_path = fn_name_to_route_path(fn_name);
116
117 let _path_params = extract_path_params(fn_inputs);
119
120 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 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 #[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#[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 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 let route_path = fn_name_to_route_path(fn_name);
176
177 let _path_params = extract_path_params(fn_inputs);
179
180 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 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 #[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#[proc_macro_attribute]
223pub fn config(_attr: TokenStream, item: TokenStream) -> TokenStream {
224 let input = parse_macro_input!(item as syn::ItemStruct);
225
226 let struct_name = &input.ident;
228
229 let expanded = quote! {
231 #input
232
233 impl #struct_name {
234 pub fn load() -> Self {
236 println!("Loading configuration for {}", stringify!(#struct_name));
238
239 Self::default()
242 }
243 }
244
245 impl Default for #struct_name {
246 fn default() -> Self {
247 Self {
248 ..Default::default()
250 }
251 }
252 }
253 };
254
255 TokenStream::from(expanded)
256}
257
258#[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 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 let revalidate = match args.revalidate {
296 Some(seconds) => quote! { Some(#seconds) },
297 None => quote! { None },
298 };
299
300 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 let params = ::densha::prelude::parse_params_from_context(&ctx)
309 .expect(&format!("Failed to parse parameters for {}", stringify!(#fn_name)));
310
311 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#[proc_macro_attribute]
342pub fn static_paths(_attr: TokenStream, item: TokenStream) -> TokenStream {
343 let input = parse_macro_input!(item as ItemFn);
344
345 let fn_name = &input.sig.ident;
347 let fn_vis = &input.vis;
348 let fn_block = &input.block;
349
350 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 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
369fn fn_name_to_route_path(ident: &Ident) -> String {
377 let name = ident.to_string();
378
379 if name == "index" {
381 return "/".to_string();
382 }
383
384 let segments: Vec<&str> = name.split('_').collect();
386 let path = segments.join("/");
387
388 format!("/{}", path)
389}
390
391fn 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}