juhaku / utoipa

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

Unable to host swagger-ui on root URL with Axum #628

Open Sh1nku opened 1 year ago

Sh1nku commented 1 year ago

Seems that there is an problem when trying to host swagger-ui on the root URL with axum.

let app = Router::new()
        .merge(SwaggerUi::new("/").url("/openapi.json", ApiDoc::openapi()))

https://github.com/juhaku/utoipa/blob/970e10f4203edc8af68c78a009dbb8dc935703bb/utoipa-swagger-ui/src/axum.rs#L49-L57

A redirect from / to // is created, and the other routes also become incorrect

qsantos commented 10 months ago

Minimal working example:

// src/main.rs

use axum::{routing::get, Router};
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;

#[utoipa::path(get, path = "/hello-world")]
pub async fn hello_world() -> &'static str {
    "Hello, World!"
}

#[tokio::main]
async fn main() {
    #[derive(OpenApi)]
    #[openapi(paths(hello_world))]
    struct ApiDoc;

    let app = Router::new()
        .merge(SwaggerUi::new("/").url("/openapi.json", ApiDoc::openapi()))
        .route("/hello-world", get(hello_world));

    axum::Server::bind(&"127.0.0.1:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

and

# Cargo.toml

[package]
name = "a"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = { version = "0.6.18" }
tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread", "fs"] }
utoipa = { version = "3.3.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "3.1.3", features = ["axum"] }

The Swagger UI is served on http://127.0.0.1:3000//, but http://127.0.0.1:3000/ returns 303 See Other with location: //. Note that this does not count as a redirection and is not followed by user agents.

Using SwaggerUi::new("/") or url("/openapi.json", ApiDoc::openapi()) result respectively in:

Paths must start with a `/`. Use "/" for root routes

and

Paths must start with a `/`
qsantos commented 9 months ago

It's worse than just allowing to serve the Swagger UI at //: utoipa-swagger-ui actually captures everything under the provided path.

leebenson commented 1 month ago

@juhaku, is there a plan to fix this? AFAICT, there's 2 issues here:

  1. It's impossible to host on root, because of the greedy capture. It conflicts with any other GET route.
  2. The // issue coupled with the lack of a redirect non-/ ending routes. Meaning, if we host Swagger UI on, say, /v1, then visiting /v1 will produce a 404 because all static assets are loaded from ./* (which in this case, would be root) -- but hosting on /v1/ also produces a 404, because Utoipa adds an implicit /, making the full route /v1//*.
juhaku commented 1 month ago
  1. It's impossible to host on root, because of the greedy capture. It conflicts with any other GET route.

This is impossible to fix, since the Swagger UI is a dir with bunch of files so it needs to be a greedy capture in order to serve everything under the Swagger UI. It is same as effectively serving a static files assets IMO. If there is a way around this I am open for PRs and improvements.

  1. The // issue coupled with the lack of a redirect non-/ ending routes. Meaning, if we host Swagger UI on, say, /v1, then visiting /v1 will produce a 404 because all static assets are loaded from ./ (which in this case, would be root) -- but hosting on /v1/ also produces a 404, because Utoipa adds an implicit /, making the full route /v1//.

Maybe there is something that can be done to optimize the path but I am not able to focus on this at the moment.

If someone know how to improve this you can also submit a PR :slightly_smiling_face: