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}