Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fallback in nested router isn't used when other routes on the root router match #3138

Open
1 task done
syphar opened this issue Jan 2, 2025 · 3 comments · May be fixed by #3158
Open
1 task done

fallback in nested router isn't used when other routes on the root router match #3138

syphar opened this issue Jan 2, 2025 · 3 comments · May be fixed by #3158

Comments

@syphar
Copy link

syphar commented Jan 2, 2025

  • I have looked for existing issues (including closed) about this

Bug Report

Version

├── axum v0.8.1
│   ├── axum-core v0.5.0

Platform

Darwin mondo-0984 23.5.0 Darwin Kernel Version 23.5.0: Wed May 1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000 arm64

Description

When trying to migrate docs.rs to axum 0.8 I stumbled onto an issue with nested routing and fallbacks. In the breaking changes from the changelog I didn't see anything obvious that I did wrong, and the documentation still states the behaviour that I expect.

I have this example code:

use axum::{
    http::{StatusCode, Uri},
    routing::get,
    Json, Router,
};

async fn fallback() -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "Not Found Root")
}

async fn api_fallback() -> (StatusCode, Json<serde_json::Value>) {
    (
        StatusCode::NOT_FOUND,
        Json(serde_json::json!({ "status": "Not Found API" })),
    )
}

#[tokio::main]
async fn main() {
    let api_routes = Router::new()
        .route("/users", get(|| async {}))
        .fallback(api_fallback);

    let app = Router::new()
        .nest("/api", api_routes)
        .route(
            "/{name}/{version}",
            get(|uri: Uri| async move { uri.to_string() }),
        )
        .fallback(fallback);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Following the documentation about fallbacks and a nested router I would expect that a request to /api/something would lead to api_fallback being called.

But what happens is that the route behind /{name}/{version} is called.

In our case I have a nested router for static assets behind /-/static and want a custom static fallback if the first static routes don't match.

with axum ~0.7 we used get_service here, which is not allowed any more, which is why I wanted to migrate to fallback_service.

@jplatte
Copy link
Member

jplatte commented Jan 2, 2025

This seems like a matchit thing.. I think.
Can you try (independently):

  • using .nest_service for nesting the API router (requires calling .with_state on it first if you're using state in the API handlers)
  • using .route("/{*rest}", any(api_fallback)) for the inner fallback

@syphar
Copy link
Author

syphar commented Jan 2, 2025

now that's interesting:

  1. .nest_service: fixes the issue in the test-app.
  2. .route(...): fixes the issue when requesting /api/doesnt_exist, but doesn't return the API 404 when requesting the /api root. Another router with / fixes this for /api, but not for /api/

@syphar
Copy link
Author

syphar commented Jan 2, 2025

changing .nest to .nest_service also fixes the issue in docs.rs

@mladedav mladedav linked a pull request Jan 7, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants