juhaku / utoipa

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

OpenAPI not being served according to Actix scopes #283

Open JasterV opened 2 years ago

JasterV commented 2 years ago

Hi!

I'm trying to expose an OpenAPI spec this way:

...
.service(
    web::scope("/docs")
        .wrap(auth)
        .service(
            SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-doc/openapi.json", openapi.clone()),
))

The problem is that when navigating to localhost:8000/docs/swagger-ui we get the following error:

screenshot_2022-09-14_at_11 24 17

And the reason is that the openapi.json is now under /docs/api-doc/openapi.json but by default the UI tries to point to /api-doc/openapi.json.

In fact, if I search on the bar for /docs/api-doc/openapi.json it renders the correct page

juhaku commented 2 years ago

In general the scopes are quite hard topic, and most likely there is some room to improve. It's just a really hard nut to crack since the web libraries are not exposing the context path or scope outside.

As what comes to the example, the SwaggerUi::new(...) expects a relative path from root of the app and similarly the .url(...) method does.

SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-doc/openapi.json", openapi.clone())

For what comes to scopes now maybe this example might help you: https://github.com/juhaku/utoipa/tree/master/examples/actix-web-multiple-api-docs-with-scopes

And there is an old issue regarding the sopes as well: https://github.com/juhaku/utoipa/issues/121 And also there is this PR https://github.com/juhaku/utoipa/pull/274 which is still unresolved but concerning axum framework but concerns same topic and actually if something will be done then the changes here should be also done for actix-web and rocket framework's as well.

JasterV commented 2 years ago

@juhaku Thank you, I'm going to try the example

As a workaround, I've done this:

// On the server
web::scope("/docs")
     .wrap(HttpAuthentication::basic(validator))
     .route(
         "/openapi.json",
          web::get().to(crate::controller::open_api_spec),
      )
      .service(
          SwaggerUi::new("swagger/{_:.*}")
          .url("/docs/openapi.json", Default::default()),
);

// open_api_spec
pub async fn open_api_spec(_: web::Data<ServerData>) -> HttpResponse<BoxBody> {
    let openapi = ApiDoc::openapi();
    HttpResponse::Ok().json(openapi)
}

And it actually works :smile: The idea is to expose the json file on a separate route and then point to it with the SwaggerUI service. But this is a bit tricky and we are exposing a dummy default OpenAPI spec

JasterV commented 2 years ago

@juhaku The example did not work for us as what we want is to modify the endpoint where the json file is served, that is a bit different from what is being done in the example which is related to modifying the base endpoint of the exposed paths.

I hope it makes sense 😵‍💫 hahah

Thank you anyway!

juhaku commented 2 years ago

Aa yeah, I see.. So you would like to have custom functionality over the handler of the OpenAPI doc endpoint? If I get you right :smile: That is indeed not supported via the SwaggerUi struct unfortunately since that actually creates simple handlers behind the scenes without offering capability to enhance them in anyways. One alternative is to look the warp or tide example (you can find them in the examples folder) of how to implement the manual serving of Swagger UI and OpenAPI doc.

That is something that could be added in future indeed, but needs some desing thinking for to get it right. And in tandem the support should also be added for all the frameworks like axum and rocket not just actix-web.

ThomasVille commented 2 days ago

Hi everyone!

We've encountered this issue today and this workaround works fine for the moment but here is another idea, why not provide SwaggerUi both:

Maybe something like this?

...
.service(
    web::scope("/docs")
        .wrap(auth)
        .service(
            SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-doc/openapi.json", openapi.clone()).url_override("/docs/api-doc/openapi.json"),
))

Or give it just the scope?

...
.service(
    web::scope("/docs")
        .wrap(auth)
        .service(
            SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-doc/openapi.json", openapi.clone()).scope("/docs"),
))

These solutions introduce a redundancy in the scope definition but at least they allow covering this use-case without resorting to a workaround.

juhaku commented 1 day ago

@ThomasVille There is also another way as elaborated here in more detail https://github.com/juhaku/utoipa/issues/964#issuecomment-2351543394. In general you could try to give custom config for the Swagger UI.

let config = Config::from("/api/path/swagger/should/find/openapi.json");
SwaggerUi::new("/swgger-ui/{_:.*}").url("/api-doc/openapi.json", openapi.clone()).config(config);

The is not a bad idea, but the first approach is already there by way of providing custom config.