scala 使用Tapir和sealed traits生成正确的模式和文档

mmvthczy  于 2024-01-08  发布在  Scala
关注(0)|答案(1)|浏览(220)

我有以下端点:

  1. val myEndpoint: PublicEndpoint[Unit, ErrorResponse, Entity, Any] = endpoint.get
  2. .in("test")
  3. .out(jsonBody[Entity])
  4. .errorOut(jsonBody[ErrorResponse])

字符串
其中EntityErrorResponse是:

  1. sealed trait ErrorResponse
  2. object ErrorResponse {
  3. final case class NotFound(id: String) extends ErrorResponse
  4. final case class UnknownError(error: String) extends ErrorResponse
  5. implicit val notFoundSchema: Schema[NotFound] = Schema.derived
  6. implicit val unknownErrorSchema: Schema[UnknownError] = Schema.derived
  7. implicit val errorResponseSchema: Schema[ErrorResponse] = Schema.derived
  8. }


然后我将端点转换为akka路由:

  1. val myEndpointRoute: Route = AkkaHttpServerInterpreter().toRoute(myEndpoint.serverLogic { _ =>
  2. val result: Either[ErrorResponse, Entity] = Right(Entity("some data of the entity"))
  3. Future.successful(result)
  4. })
  5. val swaggerEndpoint = SwaggerInterpreter().fromEndpoints[Future](List(myEndpoint), "Tapir Demo", "1.0")
  6. val swaggerRoutes: Route = AkkaHttpServerInterpreter().toRoute(swaggerEndpoint)


然后运行服务器:

  1. Http()
  2. .newServerAt("localhost", 8080)
  3. .bind(myEndpointRoute ~ swaggerRoutes)
  4. .onComplete {
  5. case Success(_) =>
  6. println(s"Started on port 8080")
  7. case Failure(e) =>
  8. println("Failed to start ... ", e)
  9. }


我遇到的问题是,当浏览ErrorResponse的模式时,我看到的是对象的层次结构(#0#1),而不是像NotFoundUnknownError这样的子类型。


的数据
我应该如何定义ErrorResponse的模式?
PS:dependencies:

  1. "com.softwaremill.sttp.tapir" %% "tapir-core" % "1.9.6",
  2. "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.9.6"
  3. "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.9.6",
  4. "com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.9.6"

nxagd54h

nxagd54h1#

这就是swagger UIOpenAPI Spec 3.1.0中渲染oneOf的方式,OpenAPI Spec 3.1.0tapir使用的默认版本。如果您将其更改为OpenAPI Spec 3.0.3或任何3.x.x,您将获得预期的结果。这里您有一个重现您的案例的POC(我使用pekko-http而不是akka-http,但它是相同的)

*build.sbt

  1. lazy val root = (project in file("."))
  2. .settings(
  3. name := "tapir-swagger-ui-poc",
  4. libraryDependencies ++= Seq(
  5. "ch.qos.logback" % "logback-classic" % "1.4.14",
  6. "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5",
  7. "org.apache.pekko" %% "pekko-actor-typed" % "1.0.2",
  8. "com.softwaremill.sttp.tapir" %% "tapir-pekko-http-server" % "1.9.6",
  9. "com.softwaremill.sttp.tapir" %% "tapir-sttp-stub-server" % "1.9.6",
  10. "com.softwaremill.sttp.tapir" %% "tapir-openapi-docs" % "1.9.6",
  11. "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.9.6",
  12. "com.softwaremill.sttp.tapir" %% "tapir-swagger-ui-bundle" % "1.9.6",
  13. "com.softwaremill.sttp.apispec" %% "openapi-circe-yaml" % "0.7.3"
  14. ),
  15. run / fork := true
  16. )

字符串

*Main.scala

  1. import com.typesafe.scalalogging.Logger
  2. import io.circe.generic.auto._
  3. import org.apache.pekko.actor.typed.ActorSystem
  4. import org.apache.pekko.actor.typed.scaladsl.Behaviors
  5. import org.apache.pekko.http.scaladsl.Http
  6. import org.apache.pekko.http.scaladsl.server.Route
  7. import sttp.tapir._
  8. import sttp.tapir.json.circe._
  9. import sttp.tapir.server.ServerEndpoint
  10. import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter
  11. import sttp.tapir.swagger.SwaggerUIOptions
  12. import sttp.tapir.swagger.bundle.SwaggerInterpreter
  13. import scala.concurrent.{ExecutionContextExecutor, Future}
  14. object Main {
  15. sealed trait ErrorResponse
  16. object ErrorResponse {
  17. final case class NotFound(id: String) extends ErrorResponse
  18. final case class UnknownError(error: String) extends ErrorResponse
  19. implicit val notFoundSchema: Schema[NotFound] = Schema.derived
  20. implicit val unknownErrorSchema: Schema[UnknownError] = Schema.derived
  21. implicit val errorResponseSchema: Schema[ErrorResponse] = Schema.derived
  22. }
  23. private val myEndpoint = endpoint.get
  24. .in("test")
  25. .out(stringBody)
  26. .errorOut(jsonBody[ErrorResponse])
  27. def main(args: Array[String]): Unit = {
  28. val logger = Logger(getClass)
  29. implicit val system: ActorSystem[Nothing] =
  30. ActorSystem(Behaviors.empty, "money-maniacs-http")
  31. implicit val executionContext: ExecutionContextExecutor =
  32. system.executionContext
  33. /**
  34. * swagger UI endpoints for OpenAPI Spec 3.0.3
  35. */
  36. val swaggerEndpoints_3_0_3: List[ServerEndpoint[Any, Future]] =
  37. SwaggerInterpreter(
  38. customiseDocsModel = openAPI => openAPI.openapi("3.0.3"), // set OpenAPI spec version
  39. swaggerUIOptions = SwaggerUIOptions.default.pathPrefix(List("docs-3.0.3")) // set path prefix for docs
  40. )
  41. .fromEndpoints[Future](
  42. List(myEndpoint), // list of endpoints
  43. "My App",
  44. "1.0"
  45. )
  46. /**
  47. * swagger UI endpoints for OpenAPI Spec 3.1.0
  48. */
  49. val swaggerEndpoints_3_1_0: List[ServerEndpoint[Any, Future]] =
  50. SwaggerInterpreter(
  51. swaggerUIOptions = SwaggerUIOptions.default.pathPrefix(List("docs-3.1.0")) // set path prefix for docs
  52. )
  53. .fromEndpoints[Future](
  54. List(myEndpoint), // list of endpoints
  55. "My App",
  56. "1.0"
  57. )
  58. val routes: Route =
  59. PekkoHttpServerInterpreter()
  60. .toRoute(swaggerEndpoints_3_0_3 ::: swaggerEndpoints_3_1_0)
  61. val interface = "0.0.0.0"
  62. val port = 9000
  63. Http()
  64. .newServerAt(interface = interface, port = port)
  65. .bind(routes)
  66. .foreach { _ =>
  67. logger.info(s"Server started at $interface:$port")
  68. logger.info(s"Press enter to stop the server")
  69. }
  70. }
  71. }


使用sbt run启动服务器后,可以检查端点是否符合OpenAPI Spec 3.1.03.0.3

  1. open http://localhost:9000/docs-3.0.3

  1. open http://localhost:9000/docs-3.1.0

Swagger中报告了一个similar issue。建议添加与对象同名的属性title
对于tapir 1.9.6(您正在使用的版本,也是目前发布的最新版本),您可以将定义的模式更改为以下内容,

  1. import scala.reflect.runtime.universe.typeOf
  2. implicit val notFoundSchema: Schema[NotFound] =
  3. Schema.derived.title(typeOf[NotFound].typeSymbol.name.toString)
  4. implicit val unknownErrorSchema: Schema[UnknownError] =
  5. Schema.derived.title(typeOf[UnknownError].typeSymbol.name.toString)
  6. implicit val errorResponseSchema: Schema[ErrorResponse] =
  7. Schema.derived.title(typeOf[UnknownError].typeSymbol.name.toString)


这样做,将产生您正在寻找的结果(带有数字的哈希仍然存在,但现在它也添加了您想要的描述)


展开查看全部

相关问题