softwaremill / tapir

Rapid development of self-documenting APIs
https://tapir.softwaremill.com
Apache License 2.0
1.36k stars 411 forks source link

[BUG] Context path does not seem honored when building Swagger docs route #2485

Closed LeonardMeyer closed 1 year ago

LeonardMeyer commented 2 years ago

Tapir version: 1.1.2

Scala version: 2.12.14

Describe the bug

Even though I'm setting a context path and disabling relative paths, the docs are still served without the context path. This app is behind a reverse proxy but if I go inside the container and run the following curl :

user@machine:/opt/docker$ curl http://localhost:8086/docs/
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Swagger UI</title>
    <link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
    <link rel="stylesheet" type="text/css" href="index.css" />
    <link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
    <link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
  </head>
 <body>
    <div id="swagger-ui"></div>
    <script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
    <script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
    <script src="./swagger-initializer.js" charset="UTF-8"> </script>
  </body>

Now, if my context path is context and I curl on http://localhost:8086/context/docs/, I get The requested resource cannot be found. This is my code, which retrieves all the endpoints, build the doc route, passing it all to Akka to start the server :

    for {
      rootPath <- fromConf(_.base.rootPath) // This is my context path, which is an Option[String]
      docSuffix = "docs"
      docPath = rootPath.map(_.trim + "/" + docSuffix).getOrElse(docSuffix)
      swaggerOptions = rootPath
        .map(rp => SwaggerUIOptions.default.contextPath(List(rp)).withAbsolutePaths)
        .getOrElse(SwaggerUIOptions.default)
      _ <- ZIO.logDebug(s"Documentation path is '$docPath' and Swagger UI options are $swaggerOptions")
      docRoute <- ZIO.attempt(
        AkkaHttpServerInterpreter().toRoute(
          SwaggerInterpreter(swaggerUIOptions = swaggerOptions)
            .fromEndpoints[Future](endpointRoutes.toList, serviceName, ApiSettings.apiVersion)
        )
      )
      allRoutes = endpointRoutes
        .reduceOption(_ ~ _)
        .map(docRoute ~ _)
        .getOrElse(docRoute)
      _ <- ZAkkaServer.start(allRoutes) // ZIO interop, basically does Akka's bindAndHandle(...) with a finalizer
    } yield ()

The debug log does print : 14:54:30.714 [cid: ] [ZScheduler-0] DEBUG com.xxxxx.xxxxx.utils.api.ApiService - Documentation path is 'context/docs' and Swagger UI options are SwaggerUIOptions(List(docs),docs.yaml,List(context),false)

Have I missed something ?

How to reproduce?

Use my code I guess ?

Additional information

Dependencies

    "com.softwaremill.sttp.tapir"   %% "tapir-akka-http-server"     % Versions.tapir,
    "com.softwaremill.sttp.tapir"   %% "tapir-json-circe"           % Versions.tapir,
    "com.softwaremill.sttp.tapir"   %% "tapir-swagger-ui-bundle"    % Versions.tapir
adamw commented 2 years ago

I think what you are looking for is not contextPath, but pathPrefix (also in SwaggerUIOptions).

The difference is that the path prefix is added to the documentation endpoints by the swagger interpreter (if present). On the other hand, context path is not added, it is assumed that it is somehow added externally (e.g. by adding the tapir-generated routes inside an akka path directive).

So you should probably specify pathPrefix as List(context, docs) and you'll be able to access them through http://localhost:8086/context/docs/

The context-path parameter is used when generating the addess to the yaml document, not when generating the documentation endpoints themselves.

LeonardMeyer commented 1 year ago

@adamw Thanks, that was it !