The basic type in tAPIr, a library for describing HTTP endpoints, is Endpoint[I, E, O, S] . The first type parameter is the type of the input ( I ), the second and third — types of output in case of an error ( E )and in case of success ( O ). The final type parameter defines the streaming requirements ( S ).

For example, a value:

describes an endpoint, where the request path is mapped to an Int (the input), in case of an error the response is mapped to a String , and in case of success to the json representation of the Pet class.

A real tapir

The second datatype that we’ll look at in this blog is ZIO’s IO[E, A] . An IO describes a process, or a set of processes, that might run concurrently and interact. The first type parameter, E , is the type of the result in case of an “expected” error, that might occur when evaluating the computation. The second type, A , is the type of the result in case of success.

Hence on one hand we have tAPIr’s Endpoint with dedicated, separate outputs which correspond to error and success; on the other, we have ZIO’s IO which can result either in a specific, typed error or success.

Coincidence? I don’t think so!

Bridging the gap

Let’s leverage this similarity. tAPIr’s Endpoint[I, E, O, S] can be interpreted as a server, a client or documentation.

To interpret an endpoint as a server, a function, of type I => F[Either[E, O]] , has to be provided for some side-effects wrapper such as Future or IO .

tAPIr comes with a http4s server interpreter (among others), which can be adapted to work with ZIO. You’ll say — wait! — isn’t http4s designed to work with cats-effect? (cats-effect contains another, different implementation of IO). Yes! However, the implementation of http4s uses only the typeclasses defined by cats-effect and cats, not the IO implementation itself. This makes it possible to use http4s with any library, which defines implementations for these typeclasses.

And luckily, ZIO is one such library; http4s works with ZIO without problems! Twitter confirms that:

Along with fully working examples:

Still, we might adapt the API of tAPIr’s http4s interpreter to work better with ZIO’s bifunctor IO , and leverage the fact that both libraries have dedicated error channels. This will be as easy as defining an extension method for Endpoint :

How does this work? We add a method, toZioRoutes(logic) , to any Endpoint instance. Then, we convert the logic: I => IO[E, O] function, to a function of type I => IO[Nothing, Either[E, O]] , using the .either method provided by ZIO. And that’s a function that we can pass to the existing http4s interpreter — done!

Note that the result of the method is HttpRoutes[Task] ; Task is an alias for IO , which has the error type fixed to Throwable . That is, it can throw any kind of errors. This is needed to integrate with the cats-effect typeclasses, which require the error case to be typed this way.

Let’s find out how it looks like in practice! We’ll define a single endpoint, for getting data for a pet with the given id; for errors a string is returned, and in case of success the pet’s data in json format:

Apart from the core tAPIr APIs, we are also using tapir-circe integration.

Next, we can interpret this endpoint as a server, converting the tapir description, coupled with the logic function, to http4s’s HttpRoutes instance:

And that’s it! Nice & clean. We can now expose our endpoint as a server:

The code is available in tAPIr’s repository, try it out!

You might have noticed that we have also defined another extension method, zioServerLogic . This variant is useful for creating objects, which contain both the endpoint description, and the server logic. A List of such endpoints can then be converted to a server by invoking .toRoutes .

As a bonus, as tAPIr describes endpoints, apart from generating a server, it can also generate a client or OpenAPI/Swagger documentation. Let’s try that.

Here’s how you can generate the OpenAPI yaml:

Then it’s a matter of exposing the generated documentation, along with the Swagger UI. Take a look at the code in the repository on how to do that.

Summary

Both tapir and ZIO have separate, typed error channels. Both datatypes describe — either an endpoint, or a process — which might end with an error, or with success.

Using a simple extension method it is possible to leverage ZIO’s typed error/success channels and use them directly when interpreting a tAPIr endpoint as a server.