diff --git a/examples/axum-utoipa-bindings/src/main.rs b/examples/axum-utoipa-bindings/src/main.rs index 9a33e314..8de4b273 100644 --- a/examples/axum-utoipa-bindings/src/main.rs +++ b/examples/axum-utoipa-bindings/src/main.rs @@ -37,6 +37,10 @@ async fn main() -> Result<(), io::Error> { .routes(routes!(health)) .nest("/api/customer", customer::router()) .nest("/api/order", order::router()) + .routes(routes!( + inner::secret_handlers::get_secret, + inner::secret_handlers::post_secret + )) .split_for_parts(); let router = router.merge(SwaggerUi::new("/swagger-ui").url("/apidoc/openapi.json", api)); @@ -126,3 +130,20 @@ mod order { }) } } + +mod inner { + pub mod secret_handlers { + + /// This is some secret inner handler + #[utoipa::path(get, path = "/api/inner/secret", responses((status = OK, body = str)))] + pub async fn get_secret() -> &'static str { + "secret" + } + + /// Post some secret inner handler + #[utoipa::path(post, path = "/api/inner/secret", responses((status = OK)))] + pub async fn post_secret() { + println!("You posted a secret") + } + } +} diff --git a/utoipa-axum/src/lib.rs b/utoipa-axum/src/lib.rs index 135e0e29..c3db08e3 100644 --- a/utoipa-axum/src/lib.rs +++ b/utoipa-axum/src/lib.rs @@ -132,7 +132,7 @@ pub use paste::paste; /// ``` #[macro_export] macro_rules! routes { - ( $handler:ident $(, $tail:tt)* ) => { + ( $handler:path $(, $tail:path)* ) => { { use $crate::PathItemExt; let mut paths = utoipa::openapi::path::Paths::new(); @@ -148,7 +148,7 @@ macro_rules! routes { (paths, method_router) } }; - ( $router:ident: $paths:ident: $handler:ident $(, $tail:tt)* ) => { + ( $router:ident: $paths:ident: $handler:path $(, $tail:tt)* ) => { { let (path, item, types) = routes!(@resolve_types $handler); let router = types.iter().by_ref().fold($router, |router, path_type| { @@ -160,14 +160,13 @@ macro_rules! routes { router } }; - ( @resolve_types $handler:ident ) => { + ( @resolve_types $handler:path ) => { { - use utoipa::{Path, __dev::Tags}; $crate::paste! { - let path = [<__path_ $handler>]::path(); - let mut operation = [<__path_ $handler>]::operation(); - let types = [<__path_ $handler>]::methods(); - let tags = [< __path_ $handler>]::tags(); + let path = routes!( @path path of $handler ); + let mut operation = routes!( @path operation of $handler ); + let types = routes!( @path methods of $handler ); + let tags = routes!( @path tags of $handler ); if !tags.is_empty() { let operation_tags = operation.tags.get_or_insert(Vec::new()); operation_tags.extend(tags.iter().map(ToString::to_string)); @@ -176,6 +175,30 @@ macro_rules! routes { } } }; + ( @path $op:ident of $part:ident $( :: $tt:tt )* ) => { + routes!( $op [ $part $( $tt )*] ) + }; + ( $op:ident [ $first:tt $( $rest:tt )* ] $( $rev:tt )* ) => { + routes!( $op [ $( $rest )* ] $first $( $rev)* ) + }; + ( $op:ident [] $first:tt $( $rest:tt )* ) => { + routes!( @inverse $op $first $( $rest )* ) + }; + ( @inverse $op:ident $tt:tt $( $rest:tt )* ) => { + routes!( @rev $op $tt [$($rest)*] ) + }; + ( @rev $op:ident $tt:tt [ $first:tt $( $rest:tt)* ] $( $reversed:tt )* ) => { + routes!( @rev $op $tt [ $( $rest )* ] $first $( $reversed )* ) + }; + ( @rev $op:ident $handler:tt [] $($tt:tt)* ) => { + { + #[allow(unused_imports)] + use utoipa::{Path, __dev::Tags}; + $crate::paste! { + $( $tt :: )* [<__path_ $handler>]::$op() + } + } + }; ( ) => {}; } @@ -314,4 +337,46 @@ mod tests { ); assert_eq!(expected_paths.build(), paths); } + + mod pets { + + #[utoipa::path(get, path = "/")] + pub async fn get_pet() {} + + #[utoipa::path(post, path = "/")] + pub async fn post_pet() {} + + #[utoipa::path(delete, path = "/")] + pub async fn delete_pet() {} + } + #[test] + fn openapi_routes_from_another_path() { + let mut router: OpenApiRouter = + OpenApiRouter::new().routes(routes!(pets::get_pet, pets::post_pet, pets::delete_pet)); + let paths = router.to_openapi().paths; + + let expected_paths = utoipa::openapi::path::PathsBuilder::new() + .path( + "/", + utoipa::openapi::PathItem::new( + utoipa::openapi::path::HttpMethod::Get, + utoipa::openapi::path::OperationBuilder::new().operation_id(Some("get_pet")), + ), + ) + .path( + "/", + utoipa::openapi::PathItem::new( + utoipa::openapi::path::HttpMethod::Post, + utoipa::openapi::path::OperationBuilder::new().operation_id(Some("post_pet")), + ), + ) + .path( + "/", + utoipa::openapi::PathItem::new( + utoipa::openapi::path::HttpMethod::Delete, + utoipa::openapi::path::OperationBuilder::new().operation_id(Some("delete_pet")), + ), + ); + assert_eq!(expected_paths.build(), paths); + } }