Skip to content

Commit

Permalink
Add paths support for routes! macro (#1023)
Browse files Browse the repository at this point in the history
This commit adds support for defining full paths to the handler
annotated with `#[utoipa::path]` attribute macro. Prior to this commit
the `routes!` macro needed the handler path to be in same module as it
was called causing unnecessary imports.

Newly supported syntax. Of course the path could be even longer e.g.
`path::to::my::another::module::handler`.
```rust
let router: OpenApiRouter =
    OpenApiRouter::new().routes(
        routes!(pets::get_pet, pets::post_pet, pets::delete_pet)
    );
```

Update binding example to showcase use of handler paths.

Resolves #1021
  • Loading branch information
juhaku committed Sep 4, 2024
1 parent c36f877 commit a85e3f4
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 8 deletions.
21 changes: 21 additions & 0 deletions examples/axum-utoipa-bindings/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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")
}
}
}
81 changes: 73 additions & 8 deletions utoipa-axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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| {
Expand All @@ -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));
Expand All @@ -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()
}
}
};
( ) => {};
}

Expand Down Expand Up @@ -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);
}
}

0 comments on commit a85e3f4

Please sign in to comment.