Running as an http4s server using ZIO¶
The tapir-zio
module defines type aliases and extension methods which make it more ergonomic to work with
ZIO and tapir. Moreover, tapir-zio-http4s-server
contains an interpreter useful when
exposing the endpoints using the http4s server.
You’ll need the following dependency for the ZServerEndpoint
type alias and helper classes:
"com.softwaremill.sttp.tapir" %% "tapir-zio" % "0.19.0-M10"
or just add the zio-http4s integration which already depends on tapir-zio
:
"com.softwaremill.sttp.tapir" %% "tapir-zio-http4s-server" % "0.19.0-M10"
Next, instead of the usual import sttp.tapir._
, you should import (or extend the ZTapir
trait, see MyTapir):
import sttp.tapir.ztapir._
This brings into scope all of the basic input/output descriptions, which can be used to define an endpoint.
Note
You should have only one of these imports in your source file. Otherwise, you’ll get naming conflicts. The
import sttp.tapir.ztapir._
import is meant as a complete replacement of import sttp.tapir._
.
Server logic¶
When defining the business logic for an endpoint, the following methods are available, which replace the standard ones:
def zServerLogic(logic: I => ZIO[R, E, O]): ZServerEndpoint[R, I, E, O, C]
def zServerLogicPart(logicPart: T => ZIO[R, E, U])
def zServerLogicForCurrent(logicPart: I => ZIO[R, E, U])
The first defines complete server logic, while the second and third allow defining server logic in parts.
Exposing endpoints using the http4s server¶
To interpret a ZServerEndpoint
as a http4s server, use the following interpreter:
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
To help with type-inference, you first need to call ZHttp4sServerInterpreter().from()
providing:
- an endpoint and logic:
def from[I, E, O, C](e: Endpoint[I, E, O, C])(logic: I => ZIO[R, E, O])
- a single server endpoint:
def from[I, E, O, C](se: ZServerEndpoint[R, I, E, O, C])
- multiple server endpoints:
def from[C](serverEndpoints: List[ZServerEndpoint[R, _, _, _, C]])
Then, call .toRoutes
to obtain the http4s HttpRoutes
instance.
Note that the resulting HttpRoutes
always require Clock
and Blocking
in the environment.
If you have multiple endpoints with different environmental requirements, the environment must be first widened
so that it is uniform across all endpoints, using the .widen
method:
import org.http4s.HttpRoutes
import sttp.tapir.ztapir._
import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter
import zio.{Has, RIO, ZIO}
import zio.blocking.Blocking
import zio.clock.Clock
import zio.interop.catz._
trait Component1
trait Component2
type Service1 = Has[Component1]
type Service2 = Has[Component2]
val serverEndpoint1: ZServerEndpoint[Service1, Unit, Unit, Unit, Any] = ???
val serverEndpoint2: ZServerEndpoint[Service2, Unit, Unit, Unit, Any] = ???
type Env = Service1 with Service2
val routes: HttpRoutes[RIO[Env with Clock with Blocking, *]] =
ZHttp4sServerInterpreter().from(List(
serverEndpoint1.widen[Env],
serverEndpoint2.widen[Env]
)).toRoutes // this is where zio-cats interop is needed
Streaming¶
The http4s interpreter accepts streaming bodies of type zio.stream.Stream[Throwable, Byte]
, as described by the ZioStreams
capability. Both response bodies and request bodies can be streamed. Usage: streamBody(ZioStreams)(schema, format)
.
The capability can be added to the classpath independently of the interpreter through the
"com.softwaremill.sttp.shared" %% "zio"
or tapir-zio
dependency.
Web sockets¶
The interpreter supports web sockets, with pipes of type zio.stream.Stream[Throwable, REQ] => zio.stream.Stream[Throwable, RESP]
.
See web sockets for more details.
Server Sent Events¶
The interpreter supports SSE (Server Sent Events).
For example, to define an endpoint that returns event stream:
import sttp.capabilities.zio.ZioStreams
import sttp.model.sse.ServerSentEvent
import sttp.tapir.server.http4s.ztapir.{ZHttp4sServerInterpreter, serverSentEventsBody}
import sttp.tapir.Endpoint
import sttp.tapir.ztapir._
import org.http4s.HttpRoutes
import zio.{UIO, RIO}
import zio.blocking.Blocking
import zio.clock.Clock
import zio.stream.Stream
val sseEndpoint: Endpoint[Unit, Unit, Stream[Throwable, ServerSentEvent], ZioStreams] = endpoint.get.out(serverSentEventsBody)
val routes: HttpRoutes[RIO[Clock with Blocking, *]] = ZHttp4sServerInterpreter()
.from(sseEndpoint.zServerLogic(_ => UIO(Stream(ServerSentEvent(Some("data"), None, None, None)))))
.toRoutes
Examples¶
Three examples of using the ZIO integration are available. The first two showcase basic functionality, while the third shows how to use partial server logic methods: