Skip to content

Commit c4c026d

Browse files
committed
feat: add support for tag= in openapi_protect_*
1 parent 37c8d81 commit c4c026d

File tree

1 file changed

+200
-6
lines changed

1 file changed

+200
-6
lines changed

rust/auth-macros/src/lib.rs

Lines changed: 200 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,70 @@ fn parse_protect_args(args: &Punctuated<Expr, Token![,]>) -> Result<(String, Str
225225
Ok((path, permission))
226226
}
227227

228+
/// Parse the arguments for OpenAPI protection macros with optional tag
229+
fn parse_openapi_protect_args(
230+
args: &Punctuated<Expr, Token![,]>,
231+
) -> Result<(String, String, Option<String>), String> {
232+
if args.len() < 2 || args.len() > 3 {
233+
return Err(
234+
"OpenAPI protection macros require 2 or 3 arguments: path, permission, and optionally tag=\"value\"".to_string(),
235+
);
236+
}
237+
238+
let path = match &args[0] {
239+
Expr::Lit(expr_lit) => {
240+
if let Lit::Str(lit_str) = &expr_lit.lit {
241+
lit_str.value()
242+
} else {
243+
return Err("First argument (path) must be a string literal".to_string());
244+
}
245+
}
246+
_ => return Err("First argument (path) must be a string literal".to_string()),
247+
};
248+
249+
let permission = match &args[1] {
250+
Expr::Lit(expr_lit) => {
251+
if let Lit::Str(lit_str) = &expr_lit.lit {
252+
lit_str.value()
253+
} else {
254+
return Err("Second argument (permission) must be a string literal".to_string());
255+
}
256+
}
257+
_ => return Err("Second argument (permission) must be a string literal".to_string()),
258+
};
259+
260+
let tag = if args.len() == 3 {
261+
match &args[2] {
262+
Expr::Assign(assign) => {
263+
// Check if left side is "tag"
264+
if let Expr::Path(path) = &*assign.left {
265+
if path.path.segments.len() == 1 && path.path.segments[0].ident == "tag" {
266+
// Check if right side is a string literal
267+
if let Expr::Lit(expr_lit) = &*assign.right {
268+
if let Lit::Str(lit_str) = &expr_lit.lit {
269+
Some(lit_str.value())
270+
} else {
271+
return Err("tag value must be a string literal".to_string());
272+
}
273+
} else {
274+
return Err("tag value must be a string literal".to_string());
275+
}
276+
} else {
277+
return Err("Third argument must be tag=\"value\"".to_string());
278+
}
279+
} else {
280+
return Err("Third argument must be tag=\"value\"".to_string());
281+
}
282+
}
283+
_ => return Err("Third argument must be tag=\"value\"".to_string()),
284+
}
285+
} else {
286+
None
287+
};
288+
289+
Ok((path, permission, tag))
290+
}
291+
228292
/// Internal function that implements the combined OpenAPI + protection logic for all HTTP methods
229293
fn openapi_protect_universal_impl(
230294
args: TokenStream,
@@ -234,9 +298,9 @@ fn openapi_protect_universal_impl(
234298
let args = parse_macro_input!(args with Punctuated::<Expr, Token![,]>::parse_terminated);
235299
let input_fn = parse_macro_input!(input as ItemFn);
236300

237-
// Parse arguments: path and permission
238-
let (path, permission) = match parse_protect_args(&args) {
239-
Ok((p, perm)) => (p, perm),
301+
// Parse arguments: path, permission, and optional tag
302+
let (path, permission, tag) = match parse_openapi_protect_args(&args) {
303+
Ok((p, perm, t)) => (p, perm, t),
240304
Err(err) => {
241305
return syn::Error::new_spanned(&input_fn, err)
242306
.to_compile_error()
@@ -290,13 +354,20 @@ fn openapi_protect_universal_impl(
290354
}
291355
};
292356

357+
// Generate the OpenAPI attribute with optional tag
358+
let openapi_attr = if let Some(tag_value) = tag {
359+
quote! { #[rocket_okapi::openapi(tag = #tag_value)] }
360+
} else {
361+
quote! { #[rocket_okapi::openapi] }
362+
};
363+
293364
// Generate the combined function with OpenAPI + Either return type
294365
let expanded = if has_bearer_param {
295366
// If OAuthBearer is already in signature, just add permission check
296367
quote! {
297368
// Actual function implementation with OpenAPI attribute BEFORE route attribute
298369
#(#fn_attrs)*
299-
#[rocket_okapi::openapi]
370+
#openapi_attr
300371
#rocket_attr
301372
#fn_vis #fn_asyncness fn #fn_name(#fn_inputs) -> rocket::Either<rocket::response::status::Forbidden<&'static str>, #return_type> {
302373
// Check permission first
@@ -313,7 +384,7 @@ fn openapi_protect_universal_impl(
313384
quote! {
314385
// Actual function implementation with OpenAPI attribute BEFORE route attribute
315386
#(#fn_attrs)*
316-
#[rocket_okapi::openapi]
387+
#openapi_attr
317388
#rocket_attr
318389
#fn_vis #fn_asyncness fn #fn_name(
319390
bearer: crate::visualization::auth::guards::OAuthBearer,
@@ -347,17 +418,31 @@ fn openapi_protect_universal_impl(
347418
/// // Your handler code here
348419
/// // The 'bearer' variable is automatically available
349420
/// }
421+
///
422+
/// // With optional tag for OpenAPI documentation
423+
/// #[openapi_protect_get("/path", "permission:scope", tag="Custom Tag")]
424+
/// fn handler_name() -> SomeResponse {
425+
/// // Your handler code here
426+
/// // The 'bearer' variable is automatically available
427+
/// }
350428
/// ```
351429
///
430+
/// ### Parameters
431+
///
432+
/// - `path`: The route path (required)
433+
/// - `permission`: The required permission string (required)
434+
/// - `tag`: Optional OpenAPI tag for grouping endpoints in documentation
435+
///
352436
/// ### Features
353437
///
354438
/// - Automatically adds `OAuthBearer` request guard to function signature
355439
/// - Generates proper OpenAPI documentation including authentication requirements
440+
/// - Supports optional tag parameter for OpenAPI documentation organization
356441
/// - Adds permission checking logic
357442
/// - Returns HTTP 403 Forbidden if permission is denied
358443
/// - Uses `rocket::Either` for proper response type handling
359444
///
360-
/// ### Example
445+
/// ### Examples
361446
///
362447
/// ```rust,ignore
363448
/// use auth_macros::openapi_protect_get;
@@ -367,6 +452,11 @@ fn openapi_protect_universal_impl(
367452
/// fn get_data() -> Json<&'static str> {
368453
/// Json("Protected data")
369454
/// }
455+
///
456+
/// #[openapi_protect_get("/api/users", "read:users", tag="User Management")]
457+
/// fn get_users() -> Json<Vec<User>> {
458+
/// Json(vec![])
459+
/// }
370460
/// ```
371461
#[proc_macro_attribute]
372462
pub fn openapi_protect_get(args: TokenStream, input: TokenStream) -> TokenStream {
@@ -375,6 +465,10 @@ pub fn openapi_protect_get(args: TokenStream, input: TokenStream) -> TokenStream
375465

376466
/// Combined OpenAPI and protection macro for POST routes
377467
///
468+
/// This macro combines the functionality of `#[openapi]` and `#[protect_post]` into a single
469+
/// attribute that automatically generates OpenAPI documentation and adds Bearer token validation
470+
/// with permission checking.
471+
///
378472
/// ### Syntax
379473
///
380474
/// ```rust,ignore
@@ -383,14 +477,40 @@ pub fn openapi_protect_get(args: TokenStream, input: TokenStream) -> TokenStream
383477
/// // Your handler code here
384478
/// // The 'bearer' variable is automatically available
385479
/// }
480+
///
481+
/// // With optional tag for OpenAPI documentation
482+
/// #[openapi_protect_post("/path", "permission:scope", tag="Custom Tag")]
483+
/// fn handler_name() -> SomeResponse {
484+
/// // Your handler code here
485+
/// // The 'bearer' variable is automatically available
486+
/// }
386487
/// ```
488+
///
489+
/// ### Parameters
490+
///
491+
/// - `path`: The route path (required)
492+
/// - `permission`: The required permission string (required)
493+
/// - `tag`: Optional OpenAPI tag for grouping endpoints in documentation
494+
///
495+
/// ### Features
496+
///
497+
/// - Automatically adds `OAuthBearer` request guard to function signature
498+
/// - Generates proper OpenAPI documentation including authentication requirements
499+
/// - Supports optional tag parameter for OpenAPI documentation organization
500+
/// - Adds permission checking logic
501+
/// - Returns HTTP 403 Forbidden if permission is denied
502+
/// - Uses `rocket::Either` for proper response type handling
387503
#[proc_macro_attribute]
388504
pub fn openapi_protect_post(args: TokenStream, input: TokenStream) -> TokenStream {
389505
openapi_protect_universal_impl(args, input, "post")
390506
}
391507

392508
/// Combined OpenAPI and protection macro for PUT routes
393509
///
510+
/// This macro combines the functionality of `#[openapi]` and `#[protect_put]` into a single
511+
/// attribute that automatically generates OpenAPI documentation and adds Bearer token validation
512+
/// with permission checking.
513+
///
394514
/// ### Syntax
395515
///
396516
/// ```rust,ignore
@@ -399,14 +519,40 @@ pub fn openapi_protect_post(args: TokenStream, input: TokenStream) -> TokenStrea
399519
/// // Your handler code here
400520
/// // The 'bearer' variable is automatically available
401521
/// }
522+
///
523+
/// // With optional tag for OpenAPI documentation
524+
/// #[openapi_protect_put("/path", "permission:scope", tag="Custom Tag")]
525+
/// fn handler_name() -> SomeResponse {
526+
/// // Your handler code here
527+
/// // The 'bearer' variable is automatically available
528+
/// }
402529
/// ```
530+
///
531+
/// ### Parameters
532+
///
533+
/// - `path`: The route path (required)
534+
/// - `permission`: The required permission string (required)
535+
/// - `tag`: Optional OpenAPI tag for grouping endpoints in documentation
536+
///
537+
/// ### Features
538+
///
539+
/// - Automatically adds `OAuthBearer` request guard to function signature
540+
/// - Generates proper OpenAPI documentation including authentication requirements
541+
/// - Supports optional tag parameter for OpenAPI documentation organization
542+
/// - Adds permission checking logic
543+
/// - Returns HTTP 403 Forbidden if permission is denied
544+
/// - Uses `rocket::Either` for proper response type handling
403545
#[proc_macro_attribute]
404546
pub fn openapi_protect_put(args: TokenStream, input: TokenStream) -> TokenStream {
405547
openapi_protect_universal_impl(args, input, "put")
406548
}
407549

408550
/// Combined OpenAPI and protection macro for DELETE routes
409551
///
552+
/// This macro combines the functionality of `#[openapi]` and `#[protect_delete]` into a single
553+
/// attribute that automatically generates OpenAPI documentation and adds Bearer token validation
554+
/// with permission checking.
555+
///
410556
/// ### Syntax
411557
///
412558
/// ```rust,ignore
@@ -415,14 +561,40 @@ pub fn openapi_protect_put(args: TokenStream, input: TokenStream) -> TokenStream
415561
/// // Your handler code here
416562
/// // The 'bearer' variable is automatically available
417563
/// }
564+
///
565+
/// // With optional tag for OpenAPI documentation
566+
/// #[openapi_protect_delete("/path", "permission:scope", tag="Custom Tag")]
567+
/// fn handler_name() -> SomeResponse {
568+
/// // Your handler code here
569+
/// // The 'bearer' variable is automatically available
570+
/// }
418571
/// ```
572+
///
573+
/// ### Parameters
574+
///
575+
/// - `path`: The route path (required)
576+
/// - `permission`: The required permission string (required)
577+
/// - `tag`: Optional OpenAPI tag for grouping endpoints in documentation
578+
///
579+
/// ### Features
580+
///
581+
/// - Automatically adds `OAuthBearer` request guard to function signature
582+
/// - Generates proper OpenAPI documentation including authentication requirements
583+
/// - Supports optional tag parameter for OpenAPI documentation organization
584+
/// - Adds permission checking logic
585+
/// - Returns HTTP 403 Forbidden if permission is denied
586+
/// - Uses `rocket::Either` for proper response type handling
419587
#[proc_macro_attribute]
420588
pub fn openapi_protect_delete(args: TokenStream, input: TokenStream) -> TokenStream {
421589
openapi_protect_universal_impl(args, input, "delete")
422590
}
423591

424592
/// Combined OpenAPI and protection macro for PATCH routes
425593
///
594+
/// This macro combines the functionality of `#[openapi]` and `#[protect_patch]` into a single
595+
/// attribute that automatically generates OpenAPI documentation and adds Bearer token validation
596+
/// with permission checking.
597+
///
426598
/// ### Syntax
427599
///
428600
/// ```rust,ignore
@@ -431,7 +603,29 @@ pub fn openapi_protect_delete(args: TokenStream, input: TokenStream) -> TokenStr
431603
/// // Your handler code here
432604
/// // The 'bearer' variable is automatically available
433605
/// }
606+
///
607+
/// // With optional tag for OpenAPI documentation
608+
/// #[openapi_protect_patch("/path", "permission:scope", tag="Custom Tag")]
609+
/// fn handler_name() -> SomeResponse {
610+
/// // Your handler code here
611+
/// // The 'bearer' variable is automatically available
612+
/// }
434613
/// ```
614+
///
615+
/// ### Parameters
616+
///
617+
/// - `path`: The route path (required)
618+
/// - `permission`: The required permission string (required)
619+
/// - `tag`: Optional OpenAPI tag for grouping endpoints in documentation
620+
///
621+
/// ### Features
622+
///
623+
/// - Automatically adds `OAuthBearer` request guard to function signature
624+
/// - Generates proper OpenAPI documentation including authentication requirements
625+
/// - Supports optional tag parameter for OpenAPI documentation organization
626+
/// - Adds permission checking logic
627+
/// - Returns HTTP 403 Forbidden if permission is denied
628+
/// - Uses `rocket::Either` for proper response type handling
435629
#[proc_macro_attribute]
436630
pub fn openapi_protect_patch(args: TokenStream, input: TokenStream) -> TokenStream {
437631
openapi_protect_universal_impl(args, input, "patch")

0 commit comments

Comments
 (0)