@@ -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
229293fn 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]
372462pub 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]
388504pub 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]
404546pub 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]
420588pub 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]
436630pub fn openapi_protect_patch ( args : TokenStream , input : TokenStream ) -> TokenStream {
437631 openapi_protect_universal_impl ( args, input, "patch" )
0 commit comments