hessra_macros/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6    parse::Parse, parse::ParseStream, parse_macro_input, FnArg, Ident, ItemFn, LitStr, Pat,
7    PatIdent, Token,
8};
9
10struct MacroArgs {
11    resource: LitStr,
12    config_param: Option<Ident>,
13}
14
15impl Parse for MacroArgs {
16    fn parse(input: ParseStream) -> syn::Result<Self> {
17        let resource = input.parse::<LitStr>()?;
18
19        let config_param = if input.peek(Token![,]) {
20            input.parse::<Token![,]>()?;
21            Some(input.parse::<Ident>()?)
22        } else {
23            None
24        };
25
26        Ok(MacroArgs {
27            resource,
28            config_param,
29        })
30    }
31}
32
33/// Macro to wrap a function with authorization token request logic
34///
35/// This macro will request an authorization token for a given resource
36/// before executing the wrapped function. It supports both synchronous
37/// and asynchronous functions.
38///
39/// # Example
40///
41/// ```
42/// use hessra_macros::request_authorization;
43///
44/// // With client config parameter
45/// #[request_authorization("my-resource", client_config)]
46/// async fn protected_function(client_config: HessraConfig) {
47///     // This function will be called after token is obtained
48/// }
49///
50/// // Using global configuration
51/// #[request_authorization("my-resource")]
52/// async fn simple_protected_function() {
53///     // This function will be called after token is obtained using global config
54/// }
55///
56/// // With individual connection parameters
57/// #[request_authorization("my-resource")]
58/// async fn custom_protected_function(base_url: String, mtls_cert: String, mtls_key: String, server_ca: String) {
59///     // This function will be called after token is obtained using provided parameters
60/// }
61/// ```
62#[proc_macro_attribute]
63pub fn request_authorization(attr: TokenStream, item: TokenStream) -> TokenStream {
64    let args = parse_macro_input!(attr as MacroArgs);
65    let input = parse_macro_input!(item as ItemFn);
66
67    let fn_name = &input.sig.ident;
68    let fn_args = &input.sig.inputs;
69    let fn_generics = &input.sig.generics;
70    let fn_output = &input.sig.output;
71    let fn_body = &input.block;
72    let fn_vis = &input.vis;
73
74    let is_async = input.sig.asyncness.is_some();
75    let resource = &args.resource;
76
77    // Check if the function has parameters needed for client config
78    let has_config_param = args.config_param.is_some();
79    let config_param = args.config_param;
80
81    // Check if any of the function parameters can be used for client config
82    let has_base_url_param = fn_args.iter().any(|arg| {
83        if let FnArg::Typed(pat_type) = arg {
84            if let Pat::Ident(PatIdent { ident, .. }) = &*pat_type.pat {
85                return ident == "base_url";
86            }
87        }
88        false
89    });
90
91    // Create parameter list for the forwarding call
92    let _args: Vec<_> = fn_args
93        .iter()
94        .filter_map(|arg| {
95            if let FnArg::Typed(pat_type) = arg {
96                if let Pat::Ident(PatIdent { ident, .. }) = &*pat_type.pat {
97                    return Some(ident);
98                }
99            }
100            None
101        })
102        .collect();
103
104    let expanded = if is_async {
105        if has_config_param {
106            // Use the provided client config parameter - now using cloned ownership
107            quote! {
108                #fn_vis #fn_generics async fn #fn_name(#fn_args) #fn_output {
109                    // Create client from the provided configuration (clone to avoid borrowing issues)
110                    let config_clone = #config_param.clone();
111                    let client = config_clone.create_client()
112                        .expect("Failed to create Hessra client from configuration");
113
114                    // Request a token for the resource
115                    let resource = #resource.to_string();
116                    let token = client.request_token(resource)
117                        .await
118                        .expect("Failed to request authorization token");
119
120                    // Call the original function
121                    #fn_body
122                }
123            }
124        } else if has_base_url_param {
125            // Create a new client from function parameters - using owned values
126            quote! {
127                #fn_vis #fn_generics async fn #fn_name(#fn_args) #fn_output {
128                    // Create a temporary configuration from parameters
129                    let config = hessra_sdk::HessraConfig::new(
130                        base_url.clone(),
131                        None, // default port
132                        hessra_sdk::Protocol::Http1,
133                        mtls_cert.clone(),
134                        mtls_key.clone(),
135                        server_ca.clone()
136                    );
137
138                    // Create client from the config
139                    let client = config.create_client()
140                        .expect("Failed to create Hessra client from parameters");
141
142                    // Request a token for the resource
143                    let resource = #resource.to_string();
144                    let token = client.request_token(resource)
145                        .await
146                        .expect("Failed to request authorization token");
147
148                    // Call the original function
149                    #fn_body
150                }
151            }
152        } else {
153            // Use global configuration
154            quote! {
155                #fn_vis #fn_generics async fn #fn_name(#fn_args) #fn_output {
156                    // Get the global configuration (clone to avoid reference issues)
157                    let config = hessra_sdk::get_default_config()
158                        .cloned()
159                        .or_else(|| hessra_sdk::try_load_default_config())
160                        .expect("No Hessra configuration found. Set a default configuration or provide parameters.");
161
162                    // Create client from the config
163                    let client = config.create_client()
164                        .expect("Failed to create Hessra client from global configuration");
165
166                    // Request a token for the resource
167                    let resource = #resource.to_string();
168                    let token = client.request_token(resource)
169                        .await
170                        .expect("Failed to request authorization token");
171
172                    // Call the original function
173                    #fn_body
174                }
175            }
176        }
177    } else {
178        // For synchronous functions
179        if has_config_param {
180            quote! {
181                #fn_vis #fn_generics fn #fn_name(#fn_args) #fn_output {
182                    // Create a runtime for the asynchronous token request
183                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
184
185                    // Create client from the provided configuration (clone to avoid borrowing issues)
186                    let config_clone = #config_param.clone();
187                    let client = config_clone.create_client()
188                        .expect("Failed to create Hessra client from configuration");
189
190                    // Request a token for the resource
191                    let resource = #resource.to_string();
192                    let token = rt.block_on(client.request_token(resource))
193                        .expect("Failed to request authorization token");
194
195                    // Call the original function
196                    #fn_body
197                }
198            }
199        } else if has_base_url_param {
200            quote! {
201                #fn_vis #fn_generics fn #fn_name(#fn_args) #fn_output {
202                    // Create a runtime for the asynchronous token request
203                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
204
205                    // Create a temporary configuration from parameters
206                    let config = hessra_sdk::HessraConfig::new(
207                        base_url.clone(),
208                        None, // default port
209                        hessra_sdk::Protocol::Http1,
210                        mtls_cert.clone(),
211                        mtls_key.clone(),
212                        server_ca.clone()
213                    );
214
215                    // Create client from the config
216                    let client = config.create_client()
217                        .expect("Failed to create Hessra client from parameters");
218
219                    // Request a token for the resource
220                    let resource = #resource.to_string();
221                    let token = rt.block_on(client.request_token(resource))
222                        .expect("Failed to request authorization token");
223
224                    // Call the original function
225                    #fn_body
226                }
227            }
228        } else {
229            // Use global configuration
230            quote! {
231                #fn_vis #fn_generics fn #fn_name(#fn_args) #fn_output {
232                    // Create a runtime for the asynchronous token request
233                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
234
235                    // Get the global configuration (clone to avoid reference issues)
236                    let config = hessra_sdk::get_default_config()
237                        .cloned()
238                        .or_else(|| hessra_sdk::try_load_default_config())
239                        .expect("No Hessra configuration found. Set a default configuration or provide parameters.");
240
241                    // Create client from the config
242                    let client = config.create_client()
243                        .expect("Failed to create Hessra client from global configuration");
244
245                    // Request a token for the resource
246                    let resource = #resource.to_string();
247                    let token = rt.block_on(client.request_token(resource))
248                        .expect("Failed to request authorization token");
249
250                    // Call the original function
251                    #fn_body
252                }
253            }
254        }
255    };
256
257    TokenStream::from(expanded)
258}
259
260/// Macro to wrap a function with authorization verification logic
261///
262/// This macro will verify an authorization token for a given resource
263/// before executing the wrapped function. It supports both synchronous
264/// and asynchronous functions.
265///
266/// # Example
267///
268/// ```
269/// use hessra_macros::authorize;
270///
271/// // With client config parameter
272/// #[authorize("my-resource", client_config)]
273/// async fn protected_function(token: String, client_config: HessraConfig) {
274///     // This function will be called if token is valid
275/// }
276///
277/// // Using global configuration
278/// #[authorize("my-resource")]
279/// async fn simple_protected_function(token: String) {
280///     // This function will be called if token is valid using global config
281/// }
282///
283/// // With individual connection parameters
284/// #[authorize("my-resource")]
285/// async fn custom_protected_function(token: String, base_url: String, mtls_cert: String, mtls_key: String, server_ca: String) {
286///     // This function will be called if token is valid using provided parameters
287/// }
288/// ```
289#[proc_macro_attribute]
290pub fn authorize(attr: TokenStream, item: TokenStream) -> TokenStream {
291    let args = parse_macro_input!(attr as MacroArgs);
292    let input = parse_macro_input!(item as ItemFn);
293
294    let fn_name = &input.sig.ident;
295    let fn_args = &input.sig.inputs;
296    let fn_generics = &input.sig.generics;
297    let fn_output = &input.sig.output;
298    let fn_body = &input.block;
299    let fn_vis = &input.vis;
300
301    let is_async = input.sig.asyncness.is_some();
302    let resource = &args.resource;
303
304    // Check if the function has a dedicated client config parameter
305    let has_config_param = args.config_param.is_some();
306    let config_param = args.config_param;
307
308    // Check if any of the function parameters can be used for client config
309    let has_base_url_param = fn_args.iter().any(|arg| {
310        if let FnArg::Typed(pat_type) = arg {
311            if let Pat::Ident(PatIdent { ident, .. }) = &*pat_type.pat {
312                return ident == "base_url";
313            }
314        }
315        false
316    });
317
318    // Find the token parameter
319    let token_param = fn_args.iter().find_map(|arg| {
320        if let FnArg::Typed(pat_type) = arg {
321            if let Pat::Ident(PatIdent { ident, .. }) = &*pat_type.pat {
322                if ident == "token" {
323                    return Some(ident);
324                }
325            }
326        }
327        None
328    });
329
330    let token_ident = match token_param {
331        Some(ident) => ident,
332        None => {
333            return syn::Error::new_spanned(
334                &input.sig,
335                "The function must have a 'token' parameter to use the authorize macro",
336            )
337            .to_compile_error()
338            .into();
339        }
340    };
341
342    let expanded = if is_async {
343        if has_config_param {
344            quote! {
345                #fn_vis #fn_generics async fn #fn_name(#fn_args) #fn_output {
346                    // Create client from the provided configuration (clone to avoid borrowing issues)
347                    let config_clone = #config_param.clone();
348                    let client = config_clone.create_client()
349                        .expect("Failed to create Hessra client from configuration");
350
351                    // Verify the token for the specified resource
352                    let resource = #resource.to_string();
353                    let verification_result = client.verify_token(#token_ident.clone(), resource).await;
354
355                    match verification_result {
356                        Ok(_) => {
357                            // Token is valid, proceed with the function
358                            #fn_body
359                        },
360                        Err(e) => {
361                            // Token is invalid, return an error
362                            panic!("Authorization failed: {}", e);
363                        }
364                    }
365                }
366            }
367        } else if has_base_url_param {
368            quote! {
369                #fn_vis #fn_generics async fn #fn_name(#fn_args) #fn_output {
370                    // Create a temporary configuration from parameters
371                    let config = hessra_sdk::HessraConfig::new(
372                        base_url.clone(),
373                        None, // default port
374                        hessra_sdk::Protocol::Http1,
375                        mtls_cert.clone(),
376                        mtls_key.clone(),
377                        server_ca.clone()
378                    );
379
380                    // Create client from the config
381                    let client = config.create_client()
382                        .expect("Failed to create Hessra client from parameters");
383
384                    // Verify the token for the specified resource
385                    let resource = #resource.to_string();
386                    let verification_result = client.verify_token(#token_ident.clone(), resource).await;
387
388                    match verification_result {
389                        Ok(_) => {
390                            // Token is valid, proceed with the function
391                            #fn_body
392                        },
393                        Err(e) => {
394                            // Token is invalid, return an error
395                            panic!("Authorization failed: {}", e);
396                        }
397                    }
398                }
399            }
400        } else {
401            // Use global configuration
402            quote! {
403                #fn_vis #fn_generics async fn #fn_name(#fn_args) #fn_output {
404                    // Get the global configuration (clone to avoid reference issues)
405                    let config = hessra_sdk::get_default_config()
406                        .cloned()
407                        .or_else(|| hessra_sdk::try_load_default_config())
408                        .expect("No Hessra configuration found. Set a default configuration or provide parameters.");
409
410                    // Create client from the config
411                    let client = config.create_client()
412                        .expect("Failed to create Hessra client from global configuration");
413
414                    // Verify the token for the specified resource
415                    let resource = #resource.to_string();
416                    let verification_result = client.verify_token(#token_ident.clone(), resource).await;
417
418                    match verification_result {
419                        Ok(_) => {
420                            // Token is valid, proceed with the function
421                            #fn_body
422                        },
423                        Err(e) => {
424                            // Token is invalid, return an error
425                            panic!("Authorization failed: {}", e);
426                        }
427                    }
428                }
429            }
430        }
431    } else {
432        // For synchronous functions
433        if has_config_param {
434            quote! {
435                #fn_vis #fn_generics fn #fn_name(#fn_args) #fn_output {
436                    // Create a runtime for the asynchronous token verification
437                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
438
439                    // Create client from the provided configuration (clone to avoid borrowing issues)
440                    let config_clone = #config_param.clone();
441                    let client = config_clone.create_client()
442                        .expect("Failed to create Hessra client from configuration");
443
444                    // Verify the token for the specified resource
445                    let resource = #resource.to_string();
446                    let verification_result = rt.block_on(client.verify_token(#token_ident.clone(), resource));
447
448                    match verification_result {
449                        Ok(_) => {
450                            // Token is valid, proceed with the function
451                            #fn_body
452                        },
453                        Err(e) => {
454                            // Token is invalid, return an error
455                            panic!("Authorization failed: {}", e);
456                        }
457                    }
458                }
459            }
460        } else if has_base_url_param {
461            quote! {
462                #fn_vis #fn_generics fn #fn_name(#fn_args) #fn_output {
463                    // Create a runtime for the asynchronous token verification
464                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
465
466                    // Create a temporary configuration from parameters
467                    let config = hessra_sdk::HessraConfig::new(
468                        base_url.clone(),
469                        None, // default port
470                        hessra_sdk::Protocol::Http1,
471                        mtls_cert.clone(),
472                        mtls_key.clone(),
473                        server_ca.clone()
474                    );
475
476                    // Create client from the config
477                    let client = config.create_client()
478                        .expect("Failed to create Hessra client from parameters");
479
480                    // Verify the token for the specified resource
481                    let resource = #resource.to_string();
482                    let verification_result = rt.block_on(client.verify_token(#token_ident.clone(), resource));
483
484                    match verification_result {
485                        Ok(_) => {
486                            // Token is valid, proceed with the function
487                            #fn_body
488                        },
489                        Err(e) => {
490                            // Token is invalid, return an error
491                            panic!("Authorization failed: {}", e);
492                        }
493                    }
494                }
495            }
496        } else {
497            // Use global configuration
498            quote! {
499                #fn_vis #fn_generics fn #fn_name(#fn_args) #fn_output {
500                    // Create a runtime for the asynchronous token verification
501                    let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
502
503                    // Get the global configuration (clone to avoid reference issues)
504                    let config = hessra_sdk::get_default_config()
505                        .cloned()
506                        .or_else(|| hessra_sdk::try_load_default_config())
507                        .expect("No Hessra configuration found. Set a default configuration or provide parameters.");
508
509                    // Create client from the config
510                    let client = config.create_client()
511                        .expect("Failed to create Hessra client from global configuration");
512
513                    // Verify the token for the specified resource
514                    let resource = #resource.to_string();
515                    let verification_result = rt.block_on(client.verify_token(#token_ident.clone(), resource));
516
517                    match verification_result {
518                        Ok(_) => {
519                            // Token is valid, proceed with the function
520                            #fn_body
521                        },
522                        Err(e) => {
523                            // Token is invalid, return an error
524                            panic!("Authorization failed: {}", e);
525                        }
526                    }
527                }
528            }
529        }
530    };
531
532    TokenStream::from(expanded)
533}