juhaku / utoipa

Simple, Fast, Code first and Compile time generated OpenAPI documentation for Rust
Apache License 2.0
2.48k stars 197 forks source link

`Overlapping method route` on routes that should not overlap #1183

Closed RubberDuckShobe closed 2 weeks ago

RubberDuckShobe commented 2 weeks ago

I'm using utoipa with the axum integration, and I have two routes, with path macros like this:

/// Get a player by ID
#[utoipa::path(
    method(get),
    path = "/{id}",
    params(
        ("id" = i32, Path, description = "ID of player to get"),
    ),
    responses( ... )
)]

/// Get the player that is currently logged in
#[utoipa::path(
    method(get),
    path = "/self",
    responses( ... )
)]

I add these routes to my router like this:

pub fn routes() -> OpenApiRouter<AppState> {
    OpenApiRouter::new()
        .routes(routes!(get_player, get_self))
}

At runtime, it panics with this error message:

thread 'main' panicked at src/api/players.rs:21:17:
Overlapping method route. Cannot add two method routes that both handle `GET`
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

The methods shouldn't overlap though, since they have different paths, which should work according to the example in the docs of the routes! macro.

This workaround seems to fix it, though:

pub fn routes() -> OpenApiRouter<AppState> {
    OpenApiRouter::new()
        .routes(routes!(get_player)).routes(routes!(get_self))
}

What could be causing this?

juhaku commented 2 weeks ago

According to the docs https://docs.rs/utoipa-axum/latest/utoipa_axum/macro.routes.html#panics.

Routes registered via routes macro or via axum::routing::* operations are bound to same rules where only one one HTTP method can can be registered once per call. This means that the following will produce runtime panic from axum code.

Both are get methods in your example above. The panic comes from axum itself, because behind the scenes it will call axum router and try to add routing::get(get_player).get(get_self) on single call which will be a panic in axum.

This is basically result of routes!() macro being a single axum MethodRouter instance and it is not split by path to separate MethodRouter instances. That is why the "workaround" will work. Normally for axum you would call route("/self", ...) and route("/:id, ...) separately as each call is its own MethodRouter.

RubberDuckShobe commented 2 weeks ago

Thank you and sorry for wasting your time, I don't know how I missed that... probably should have slept more lol

juhaku commented 2 weeks ago

Anytime :slightly_smiling_face: