From 96e89f94ae3b1b2745447ea63a38e49a2377be33 Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Mon, 12 Jul 2021 11:07:44 +0200 Subject: [PATCH 1/9] Created module for zioHttp swagger --- build.sbt | 15 +++++++++++++++ .../tapir/swagger/ziohttp/SwaggerZioHttp.scala | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala diff --git a/build.sbt b/build.sbt index 5708b4e83e..28ac76e6f3 100644 --- a/build.sbt +++ b/build.sbt @@ -121,6 +121,7 @@ lazy val allAggregates = core.projectRefs ++ swaggerUiPlay.projectRefs ++ redocPlay.projectRefs ++ swaggerUiVertx.projectRefs ++ + swaggerUiZioHttp.projectRefs ++ serverTests.projectRefs ++ akkaHttpServer.projectRefs ++ http4sServer.projectRefs ++ @@ -785,6 +786,18 @@ lazy val swaggerUiVertx: ProjectMatrix = (projectMatrix in file("docs/swagger-ui ) .jvmPlatform(scalaVersions = scala2And3Versions) +lazy val swaggerUiZioHttp: ProjectMatrix = (projectMatrix in file("docs/swagger-ui-zio-http")) + .settings(commonJvmSettings) + .settings( + name := "tapir-swagger-ui-zio-http", + libraryDependencies ++= Seq( + "io.d11" %% "zhttp" % "1.0.0.0-RC17", + "org.webjars" % "swagger-ui" % Versions.swaggerUi, + scalaTest.value % Test + ) + ) + .jvmPlatform(scalaVersions = scala2Versions) + // server lazy val serverTests: ProjectMatrix = (projectMatrix in file("server/tests")) @@ -1188,6 +1201,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples")) circeJson, swaggerUiAkka, swaggerUiHttp4s, + swaggerUiZioHttp, zioHttp4sServer, zioHttp, sttpStubServer, @@ -1224,6 +1238,7 @@ lazy val playground: ProjectMatrix = (projectMatrix in file("playground")) circeJson, swaggerUiAkka, swaggerUiHttp4s, + swaggerUiZioHttp, refined, cats, zioHttp4sServer, diff --git a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala new file mode 100644 index 0000000000..99b3f95a8e --- /dev/null +++ b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala @@ -0,0 +1,18 @@ +package sttp.tapir.swagger.ziohttp + +import java.util.Properties + +class SwaggerZioHttp( + yaml: String, + contextPath: List[String] = List("docs"), + yamlName: String = "docs.yaml" +) { + private val swaggerVersion: String = { + val p = new Properties() + val pomProperties = getClass.getResourceAsStream("/META-INF/maven/org.webjars/swagger-ui/pom.properties") + try p.load(pomProperties) + finally pomProperties.close() + p.getProperty("version") + } + +} From de6324451dc32bb13e9161bc906e62a0d87568c0 Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Mon, 12 Jul 2021 13:03:44 +0200 Subject: [PATCH 2/9] Swagger ui for zio http --- .../swagger/ziohttp/SwaggerZioHttp.scala | 40 +++++++++++++++---- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala index 99b3f95a8e..77a58acb5a 100644 --- a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala +++ b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala @@ -1,18 +1,44 @@ package sttp.tapir.swagger.ziohttp +import zhttp.http._ +import zio.Chunk + +import java.nio.file.{Files, Paths} import java.util.Properties class SwaggerZioHttp( yaml: String, - contextPath: List[String] = List("docs"), + contextPath: String = "docs", yamlName: String = "docs.yaml" ) { - private val swaggerVersion: String = { - val p = new Properties() - val pomProperties = getClass.getResourceAsStream("/META-INF/maven/org.webjars/swagger-ui/pom.properties") - try p.load(pomProperties) - finally pomProperties.close() - p.getProperty("version") + private val resourcePathPrefix = { + val swaggerVersion: String = { + val p = new Properties() + val pomProperties = getClass.getResourceAsStream("/META-INF/maven/org.webjars/swagger-ui/pom.properties") + try p.load(pomProperties) + finally pomProperties.close() + p.getProperty("version") + } + s"META-INF/resources/webjars/swagger-ui/$swaggerVersion" } + def route: Http[Any, Throwable, Request, Response[Any, Throwable]] = { + Http.collect[Request] { + case Method.GET -> Root / path => + if (path.equals(contextPath)) { + val location = s"/$contextPath/index.html?url=/$contextPath/$yamlName" + Response.http(Status.MOVED_PERMANENTLY, List(Header.custom("Location", location))) + } else Response.http(Status.NOT_FOUND) + case Method.GET -> Root / path / yamlName => + if (path.equals(contextPath)) { + if (yamlName.equals(yamlName)) { + val body = HttpData.CompleteData(Chunk.fromArray(yaml.getBytes(HTTP_CHARSET))) + Response.http(Status.OK, List(Header.custom("content-type", "text/yaml")), body) + } else { + val content = HttpData.CompleteData(Chunk.fromArray(Files.readAllBytes(Paths.get(s"$resourcePathPrefix/$yamlName")))) + Response.http(content = content) + } + } else Response.http(Status.NOT_FOUND) + } + } } From 511d2e2c39570b4fdb6e0a9688a491c41bbc7e26 Mon Sep 17 00:00:00 2001 From: slabiakt Date: Mon, 12 Jul 2021 13:32:49 +0200 Subject: [PATCH 3/9] Add info about SwaggerZioHttp to docs --- doc/docs/openapi.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/docs/openapi.md b/doc/docs/openapi.md index 0a20dd681f..b2313de615 100644 --- a/doc/docs/openapi.md +++ b/doc/docs/openapi.md @@ -184,6 +184,8 @@ For Play, use `SwaggerPlay` or `RedocPlay` classes. For Vert.x, use `SwaggerVertx` class. +For zio-http, use `SwaggerZioHttp` class. + ### Using with sbt-assembly The `tapir-swagger-ui-*` modules rely on a file in the `META-INF` directory tree, to determine the version of the Swagger UI. From 5dcfa3d357678dbbb5833348186fffd9d618095f Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Tue, 13 Jul 2021 07:09:57 +0200 Subject: [PATCH 4/9] Added Scala 3 to zioHttpSwagger supported Scala versions --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 28ac76e6f3..872ef11653 100644 --- a/build.sbt +++ b/build.sbt @@ -796,7 +796,7 @@ lazy val swaggerUiZioHttp: ProjectMatrix = (projectMatrix in file("docs/swagger- scalaTest.value % Test ) ) - .jvmPlatform(scalaVersions = scala2Versions) + .jvmPlatform(scalaVersions = scala2And3Versions) // server From cfb42a3f784221c2559c4d82b4a89cf7083ecc0d Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Tue, 13 Jul 2021 09:20:38 +0200 Subject: [PATCH 5/9] Changed the way of retriving yaml file --- .../sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala index 77a58acb5a..c35e6cd689 100644 --- a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala +++ b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala @@ -2,8 +2,10 @@ package sttp.tapir.swagger.ziohttp import zhttp.http._ import zio.Chunk +import zio.blocking.Blocking +import zio.stream.ZStream -import java.nio.file.{Files, Paths} +import java.nio.file.Paths import java.util.Properties class SwaggerZioHttp( @@ -22,7 +24,7 @@ class SwaggerZioHttp( s"META-INF/resources/webjars/swagger-ui/$swaggerVersion" } - def route: Http[Any, Throwable, Request, Response[Any, Throwable]] = { + def route: Http[Blocking, Throwable, Request, Response[Blocking, Throwable]] = { Http.collect[Request] { case Method.GET -> Root / path => if (path.equals(contextPath)) { @@ -33,9 +35,9 @@ class SwaggerZioHttp( if (path.equals(contextPath)) { if (yamlName.equals(yamlName)) { val body = HttpData.CompleteData(Chunk.fromArray(yaml.getBytes(HTTP_CHARSET))) - Response.http(Status.OK, List(Header.custom("content-type", "text/yaml")), body) + Response.http[Blocking, Throwable](Status.OK, List(Header.custom("content-type", "text/yaml")), body) } else { - val content = HttpData.CompleteData(Chunk.fromArray(Files.readAllBytes(Paths.get(s"$resourcePathPrefix/$yamlName")))) + val content = HttpData.fromStream(ZStream.fromFile(Paths.get(s"$resourcePathPrefix/$yamlName"))) Response.http(content = content) } } else Response.http(Status.NOT_FOUND) From e12245735babe5d127845e25224f330c7ac8b9c4 Mon Sep 17 00:00:00 2001 From: slabiakt Date: Wed, 14 Jul 2021 09:41:46 +0200 Subject: [PATCH 6/9] Add ExampleZioHttpServer --- .../tapir/examples/ExampleZioHttpServer.scala | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala diff --git a/examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala new file mode 100644 index 0000000000..3127983234 --- /dev/null +++ b/examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala @@ -0,0 +1,47 @@ +package sttp.tapir.examples + +import io.circe.generic.auto._ +import sttp.tapir.generic.auto._ +import sttp.tapir.json.circe._ +import sttp.tapir.server.ziohttp.ZioHttpInterpreter +import sttp.tapir.swagger.ziohttp.SwaggerZioHttp +import sttp.tapir.ztapir._ +import zhttp.http.HttpApp +import zhttp.service.Server +import zio.{App, ExitCode, IO, UIO, URIO, ZIO} + +object ExampleZioHttpServer extends App { + case class Pet(species: String, url: String) + + // Sample endpoint, with the logic implemented directly using .toRoutes + val petEndpoint: ZEndpoint[Int, String, Pet] = + endpoint.get.in("pet" / path[Int]("petId")).errorOut(stringBody).out(jsonBody[Pet]) + + val petRoutes: HttpApp[Any, Throwable] = + ZioHttpInterpreter().toHttp(petEndpoint)(petId => + if (petId == 35) ZIO.succeed(Right(Pet("Tapirus terrestris", "https://en.wikipedia.org/wiki/Tapir"))) + else ZIO.succeed(Left("Unknown pet id")) + ) + + // Same as above, but combining endpoint description with server logic: + val petServerEndpoint: ZServerEndpoint[Any, Int, String, Pet] = petEndpoint.zServerLogic { petId => + if (petId == 35) { + UIO(Pet("Tapirus terrestris", "https://en.wikipedia.org/wiki/Tapir")) + } else { + IO.fail("Unknown pet id") + } + } + val petServerRoutes: HttpApp[Any, Throwable] = ZioHttpInterpreter().toHttp(List(petServerEndpoint)) + + // + + val yaml: String = { + import sttp.tapir.docs.openapi.OpenAPIDocsInterpreter + import sttp.tapir.openapi.circe.yaml._ + OpenAPIDocsInterpreter().toOpenAPI(petEndpoint, "Our pets", "1.0").toYaml + } + + // Starting the server + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = + Server.start(8080, petRoutes <> new SwaggerZioHttp(yaml).route).exitCode +} From 7d202e09d17bf059e2cd692fb3c2df3843da9b47 Mon Sep 17 00:00:00 2001 From: slabiakt Date: Wed, 14 Jul 2021 11:01:33 +0200 Subject: [PATCH 7/9] Fix SwaggerZioHttp --- .../scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala index c35e6cd689..adb01ccf96 100644 --- a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala +++ b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala @@ -5,7 +5,6 @@ import zio.Chunk import zio.blocking.Blocking import zio.stream.ZStream -import java.nio.file.Paths import java.util.Properties class SwaggerZioHttp( @@ -31,13 +30,14 @@ class SwaggerZioHttp( val location = s"/$contextPath/index.html?url=/$contextPath/$yamlName" Response.http(Status.MOVED_PERMANENTLY, List(Header.custom("Location", location))) } else Response.http(Status.NOT_FOUND) - case Method.GET -> Root / path / yamlName => + case Method.GET -> Root / path / resource => if (path.equals(contextPath)) { - if (yamlName.equals(yamlName)) { + if (resource.equals(yamlName)) { val body = HttpData.CompleteData(Chunk.fromArray(yaml.getBytes(HTTP_CHARSET))) Response.http[Blocking, Throwable](Status.OK, List(Header.custom("content-type", "text/yaml")), body) } else { - val content = HttpData.fromStream(ZStream.fromFile(Paths.get(s"$resourcePathPrefix/$yamlName"))) + val staticResource = this.getClass.getClassLoader.getResourceAsStream(s"$resourcePathPrefix/$resource") + val content = HttpData.fromStream(ZStream.fromInputStream(staticResource)) Response.http(content = content) } } else Response.http(Status.NOT_FOUND) From 807733f47e1f9ef1a449f28cfc8392e6221f63bd Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Thu, 15 Jul 2021 14:48:59 +0200 Subject: [PATCH 8/9] Replace ifs with --- .../swagger/ziohttp/SwaggerZioHttp.scala | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala index c35e6cd689..754e50599b 100644 --- a/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala +++ b/docs/swagger-ui-zio-http/src/main/scala/sttp/tapir/swagger/ziohttp/SwaggerZioHttp.scala @@ -26,21 +26,15 @@ class SwaggerZioHttp( def route: Http[Blocking, Throwable, Request, Response[Blocking, Throwable]] = { Http.collect[Request] { - case Method.GET -> Root / path => - if (path.equals(contextPath)) { - val location = s"/$contextPath/index.html?url=/$contextPath/$yamlName" - Response.http(Status.MOVED_PERMANENTLY, List(Header.custom("Location", location))) - } else Response.http(Status.NOT_FOUND) - case Method.GET -> Root / path / yamlName => - if (path.equals(contextPath)) { - if (yamlName.equals(yamlName)) { - val body = HttpData.CompleteData(Chunk.fromArray(yaml.getBytes(HTTP_CHARSET))) - Response.http[Blocking, Throwable](Status.OK, List(Header.custom("content-type", "text/yaml")), body) - } else { - val content = HttpData.fromStream(ZStream.fromFile(Paths.get(s"$resourcePathPrefix/$yamlName"))) - Response.http(content = content) - } - } else Response.http(Status.NOT_FOUND) + case Method.GET -> Root / `contextPath` => + val location = s"/$contextPath/index.html?url=/$contextPath/$yamlName" + Response.http(Status.MOVED_PERMANENTLY, List(Header.custom("Location", location))) + case Method.GET -> Root / `contextPath` / `yamlName` => + val body = HttpData.CompleteData(Chunk.fromArray(yaml.getBytes(HTTP_CHARSET))) + Response.http[Blocking, Throwable](Status.OK, List(Header.custom("content-type", "text/yaml")), body) + case Method.GET -> Root / `contextPath` / swaggerResource => + val content = HttpData.fromStream(ZStream.fromFile(Paths.get(s"$resourcePathPrefix/$swaggerResource"))) + Response.http(content = content) } } } From 27433de12494c81d6b24da29d6f42041ad18ad91 Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Fri, 16 Jul 2021 08:22:43 +0200 Subject: [PATCH 9/9] Rename and added to doc/examples.md --- doc/examples.md | 1 + ...ExampleZioHttpServer.scala => ZioExampleZioHttpServer.scala} | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename examples/src/main/scala/sttp/tapir/examples/{ExampleZioHttpServer.scala => ZioExampleZioHttpServer.scala} (97%) diff --git a/doc/examples.md b/doc/examples.md index d64cbb7e58..6c4ae00b04 100644 --- a/doc/examples.md +++ b/doc/examples.md @@ -20,6 +20,7 @@ The [`examples`](https://github.com/softwaremill/tapir/tree/master/examples/src/ * [ZIO example, using http4s](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/ZioExampleHttp4sServer.scala) * [ZIO example with environment, using http4s](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/ZioEnvExampleHttp4sServer.scala) * [ZIO partial server logic example, using http4s](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/ZioPartialServerLogicHttp4s.scala) +* [ZIO example using ZIO http](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala) * [Streaming body, using akka-http](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/StreamingAkkaServer.scala) * [Streaming body, using http4s + fs2](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/StreamingHttp4sFs2Server.scala) * [Web sockets server, using akka-http](https://github.com/softwaremill/tapir/blob/master/examples/src/main/scala/sttp/tapir/examples/WebSocketAkkaServer.scala) diff --git a/examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala similarity index 97% rename from examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala rename to examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala index 3127983234..a885a37cf9 100644 --- a/examples/src/main/scala/sttp/tapir/examples/ExampleZioHttpServer.scala +++ b/examples/src/main/scala/sttp/tapir/examples/ZioExampleZioHttpServer.scala @@ -10,7 +10,7 @@ import zhttp.http.HttpApp import zhttp.service.Server import zio.{App, ExitCode, IO, UIO, URIO, ZIO} -object ExampleZioHttpServer extends App { +object ZioExampleZioHttpServer extends App { case class Pet(species: String, url: String) // Sample endpoint, with the logic implemented directly using .toRoutes