Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SwaggerZioHttp #1369

Merged
merged 11 commits into from
Jul 16, 2021
15 changes: 15 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ lazy val allAggregates = core.projectRefs ++
swaggerUiPlay.projectRefs ++
redocPlay.projectRefs ++
swaggerUiVertx.projectRefs ++
swaggerUiZioHttp.projectRefs ++
serverTests.projectRefs ++
akkaHttpServer.projectRefs ++
http4sServer.projectRefs ++
Expand Down Expand Up @@ -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 = scala2And3Versions)

// server

lazy val serverTests: ProjectMatrix = (projectMatrix in file("server/tests"))
Expand Down Expand Up @@ -1188,6 +1201,7 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
circeJson,
swaggerUiAkka,
swaggerUiHttp4s,
swaggerUiZioHttp,
zioHttp4sServer,
zioHttp,
sttpStubServer,
Expand Down Expand Up @@ -1224,6 +1238,7 @@ lazy val playground: ProjectMatrix = (projectMatrix in file("playground"))
circeJson,
swaggerUiAkka,
swaggerUiHttp4s,
swaggerUiZioHttp,
refined,
cats,
zioHttp4sServer,
Expand Down
2 changes: 2 additions & 0 deletions doc/docs/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package sttp.tapir.swagger.ziohttp

import zhttp.http._
import zio.Chunk
import zio.blocking.Blocking
import zio.stream.ZStream

import java.util.Properties

class SwaggerZioHttp(
yaml: String,
contextPath: String = "docs",
yamlName: String = "docs.yaml"
) {
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[Blocking, Throwable, Request, Response[Blocking, Throwable]] = {
Http.collect[Request] {
case Method.GET -> Root / path =>
if (path.equals(contextPath)) {
Copy link
Member

@adamw adamw Jul 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want to match in a pattern match, you can do:

case Method.GET -> Root / path if path == contextPath

or better:

case Method.GET -> Root / `contextPath`

The backticks are used to tell the pattern matcher, that you want to use the value of the given value (here: contextPath), instead of assigning the any matched value to this name

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 / resource =>
if (path.equals(contextPath)) {
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 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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should have a better name :) Plus, please add to doc/examples.md

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
}