Skip to content

Commit

Permalink
fix: remove trailing / from nested paths
Browse files Browse the repository at this point in the history
  • Loading branch information
0xf09f95b4 committed Jan 10, 2025
1 parent 2fa7282 commit 1687948
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 8 deletions.
30 changes: 22 additions & 8 deletions crates/aide/src/axum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ use crate::{
gen::{self, in_context},
openapi::{OpenApi, PathItem, ReferenceOr, SchemaObject},
operation::OperationHandler,
util::merge_paths,
util::{merge_paths, path_for_nested_route},
OperationInput, OperationOutput,
};
#[cfg(feature = "axum-tokio")]
Expand Down Expand Up @@ -529,16 +529,14 @@ where
///
/// The generated documentations are nested as well.
#[tracing::instrument(skip_all)]
pub fn nest(mut self, mut path: &str, router: ApiRouter<S>) -> Self {
pub fn nest(mut self, path: &str, router: ApiRouter<S>) -> Self {
self.router = self.router.nest(path, router.router);

path = path.trim_end_matches('/');

self.paths.extend(
router
.paths
.into_iter()
.map(|(route, path_item)| (path.to_string() + &route, path_item)),
.map(|(route, path_item)| (path_for_nested_route(&path, &route), path_item)),
);

self
Expand All @@ -553,15 +551,14 @@ where
///
/// Thus the primary and probably the only use-case
/// of this function is nesting routers with different states.
pub fn nest_api_service(mut self, mut path: &str, service: impl Into<ApiRouter<()>>) -> Self {
pub fn nest_api_service(mut self, path: &str, service: impl Into<ApiRouter<()>>) -> Self {
let router: ApiRouter<()> = service.into();

path = path.trim_end_matches('/');
self.paths.extend(
router
.paths
.into_iter()
.map(|(route, path_item)| (path.to_string() + &route, path_item)),
.map(|(route, path_item)| (path_for_nested_route(&path, &route), path_item)),
);
self.router = self.router.nest_service(path, router.router);
self
Expand Down Expand Up @@ -846,6 +843,13 @@ mod tests {

async fn test_handler3() {}

fn nested_route() -> ApiRouter {
ApiRouter::new()
.api_route_with("/", routing::post(test_handler3), |t| t)
.api_route_with("/test1", routing::post(test_handler3), |t| t)
.api_route_with("/test2/", routing::post(test_handler3), |t| t)
}

#[derive(Clone, Copy)]
struct TestState {
field1: u8,
Expand Down Expand Up @@ -895,6 +899,16 @@ mod tests {
assert!(item.post.is_some());
}

#[test]
fn test_nested_routing() {
let app: ApiRouter = ApiRouter::new().nest("/app", nested_route());

assert!(app.paths.contains_key("/app"));
assert!(!app.paths.contains_key("/app/"));
assert!(app.paths.contains_key("/app/test1"));
assert!(app.paths.contains_key("/app/test2/"));
}

#[test]
fn test_layered_handler() {
let _app: ApiRouter = ApiRouter::new().api_route(
Expand Down
12 changes: 12 additions & 0 deletions crates/aide/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ pub fn iter_operations_mut(
vec.into_iter()
}

/// Helper function for nesting functions in Axum.
/// Based on Axum's own implementation of nested paths.
pub(crate) fn path_for_nested_route<'a>(path: &'a str, route: &'a str) -> String {
if path.ends_with('/') {
format!("{path}{}", route.trim_start_matches('/')).into()
} else if route == "/" {
path.into()
} else {
format!("{path}{route}").into()
}
}

pub(crate) fn merge_paths(ctx: &mut GenContext, path: &str, target: &mut PathItem, from: PathItem) {
if let Some(op) = from.get {
if target.get.is_some() {
Expand Down

0 comments on commit 1687948

Please sign in to comment.