Skip to content

Identical names in different modules causes problems in the SwaggerUI #1373

@happybeing

Description

@happybeing

Thanks so much for utoipa and its support for actix and swagger. They've made such a positive difference to building by first REST API. 🙏

NOTE: I have found the apparent cause of this behaviour and a workaround, documented at the end of this post. TL;DR: every API function must have a unique name when using utoipa-swagger-ui. Identical names in different modules causes problems in the SwaggerUI.

Problem 1 - UI calls the wrong route

To begin with everything was working fine - I'd add a route and test it in the UI, and use earlier routes to help do that in combination. I now have 15 routes (POST, PUT, GET) in five categories (see openapi.json attached). After changing and adding some routes, and then updating the implementation of an older one I saw this:

  • When testing /ant-0/data GET using the UI, the 'try out' was calling /ant-0/archive GET instead. This shows in both the response I got and in the curl example. I checked this multiple times and it is definitely the behaviour.

Problem 2 - UI conflates open/close details

Today, preparing to investigate I noticed that when I reload the UI and all routes show as just one line:

  • if I click on the /ant-0/data GET route to open for testing, it opens both that and the /ant-0/archive GET panel as well. In fact clicking on either route closes or re-opens both. When I click on either route and both are open, the browser address shows as http://127.0.0.1:8080/swagger-ui/#/Autonomi/get or if closed reverts to http://127.0.0.1:8080/swagger-ui/#/.

These two routes are adjacent in the UI as you can see and whether I 'Try it out' on either route it executes the /ant-0/archive GET route and shows that in the curl example.

Details

I'm using:

actix-web = "4.9.0"
actix-cors = "0.7.1"
actix-multipart = "0.7.2"
utoipa = { version = "5.3.1", features = ["actix_extras", "non_strict_integers"] }
utoipa-actix-web = "0.1.2"
utoipa-swagger-ui = { version = "9.0.1", features = ["reqwest", "actix-web"] }

I've looked at the openapi.json and it seems fine, but looking at the behaviour it seems like something is preventing it from distinguishing some routes and the browser address bar shows that the UI is not addressing them uniquely enough. That is, both are at ``http://127.0.0.1:8080/swagger-ui/#/Autonomi/get` and don't show anything about the route.

The JSON looks ok to me, but I wonder if my use of the macros is causing some stupidity in the openapi.json. I'm not sure what it should look like or where I might be misusing the macros, or if the is a bug in one of the dependencies. I've attached the openapi.json at the end.

Here is the code which generates the openapi.json for these routes:

/ant-0/archive GET

/// Get a directory tree (from PublicArchive or PrivateArchive)
///
/// Returns a DwebArchive schema containing metadata for files and directories
#[utoipa::path(
    responses(
        (status = 200,
            description = "The JSON representation (DwebArchive schema) of an Autonomi PublicArchive or PrivateArchive.", body = [DwebArchive])
        ),
    tags = ["Autonomi"],
    params(
        ("datamap_or_address", description = "the hex encoded datamap chunk or data address of an Autonomi archive"),
    )
)]
#[get("/archive/{datamap_or_address}")]
pub async fn get(
    request: HttpRequest,
    datamap_or_address: web::Path<String>,
    client: Data<dweb::client::DwebClient>,
) -> HttpResponse {
...

/ant-0/data GET

/// Get data from the network using a hex encoded datamap or data address
#[utoipa::path(
    responses(
        (status = 200, description = "Success"),
        (status = StatusCode::BAD_REQUEST, description = "The datamap_or_address is not a valid address"),
        (status = StatusCode::NOT_FOUND, description = "The data was not found or a network error occured"),
        ),
    tags = ["Autonomi"],
    params(
        ("datamap_or_address", description = "the hex encoded datamap or data address of public or private data"),
    )
)]
#[get("/data/{datamap_or_address}")]
pub async fn get(
    request: HttpRequest,
    params: web::Path<String>,
    client: Data<dweb::client::DwebClient>,
) -> HttpResponse
...

Workaround

I had a hunch that it is because the function names are the same. In Rust it is idiomatic to use the module paths to differentiate things so in the above examples, here is the code which adds these functions, both of which are called get():

            // Autonomi APIs
            .service(
                scope(dweb::api::ANT_API_ROUTE)
                    .service(api_ant::v0::archive::post_public)
                    .service(api_ant::v0::archive::post_private)
                    .service(api_ant::v0::data::get)
                    .service(api_ant::v0::archive::get)
                    .service(api_ant::v0::archive::get_version),
            )
            // dweb APIs
            .service(
                scope(dweb::api::DWEB_API_ROUTE)
                    .service(api_dweb::v0::name::api_register_name)
                    .service(api_dweb::v0::name::api_dwebname_list)
                    .service(api_dweb::v0::file::file_get)
                    .service(api_dweb::v0::form::data_put)
                    .service(api_dweb::v0::form::data_put_list),
            )

And if I rename them to be unique as below, the above issues go away:

                    .service(api_ant::v0::data::data_get)
                    .service(api_ant::v0::archive::archive_get)

A bug?

I don't know if or where this could be corrected but think it is undesirable behaviour and probably a bug. The issue may be to do with how the UI uses the operation id to find the route, rather than the route itself:

   "/ant-0/data/{datamap_or_address}": {
      "get": {
        "tags": [
          "Autonomi"
        ],
        "summary": "Get data from the network using a hex encoded datamap or data address",
        "operationId": "data_get",

The Fix?

Maybe a fix would be to extend the operationId to include the route, but I'm not sure what the expectations are about this from other tools so don't propose to look at that. For me it will be good enough to keep the function names unique.

And again, thanks very much for creating and maintaining these fantastic resources.

openapi.json

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions