Running as an akka-http server

To expose an endpoint as an akka-http server, first add the following dependency:

"com.softwaremill.tapir" %% "tapir-akka-http-server" % "0.5"

and import the package:

import tapir.server.akkahttp._

This adds extension methods to the Endpoint type: toDirective, toRoute and toRouteRecoverErrors. The first two require the logic of the endpoint to be given as a function of type:

I => Future[Either[E, O]]

The third recovers errors from failed futures, and hence requires that E is a subclass of Throwable (an exception); it expects a function of type I => Future[O].

For example:

import tapir._
import tapir.server.akkahttp._
import scala.concurrent.Future
import akka.http.scaladsl.server.Route

def countCharacters(s: String): Future[Either[Unit, Int]] = 
  Future.successful(Right[Unit, Int](s.length))

val countCharactersEndpoint: Endpoint[String, Unit, Int, Nothing] = 
  endpoint.in(stringBody).out(plainBody[Int])
  
val countCharactersRoute: Route = countCharactersEndpoint.toRoute(countCharacters)

Note that these functions take one argument, which is a tuple of type I. This means that functions which take multiple arguments need to be converted to a function using a single argument using .tupled:

def logic(s: String, i: Int): Future[Either[Unit, String]] = ???
val anEndpoint: Endpoint[(String, Int), Unit, String, Nothing] = ??? 
val aRoute: Route = anEndpoint.toRoute((logic _).tupled)

The created Route/Directive can then be further combined with other akka-http directives, for example nested within other routes. The tapir-generated Route/Directive captures from the request only what is described by the endpoint.

It’s completely feasible that some part of the input is read using akka-http directives, and the rest using tapir endpoint descriptions; or, that the tapir-generated route is wrapped in e.g. a metrics route. Moreover, “edge-case endpoints”, which require some special logic not expressible using tapir, can be always implemented directly using akka-http. For example:

val myRoute: Route = metricsDirective {
  securityDirective { user =>
    tapirEndpoint.toRoute(input => /* here we can use both `user` and `input` values */)
  }
}

Streaming

The akka-http interpreter accepts streaming bodies of type Source[ByteString, Any], which can be used both for sending response bodies and reading request bodies. Usage: streamBody[Source[ByteString, Any]](schema, mediaType).

Configuration

The interpreter can be configured by providing an implicit AkkaHttpServerOptions value and status mappers, see common server configuration for details.