Running as a Finatra server

To expose an endpoint as an finatra server, first add the following dependency:

"com.softwaremill.tapir" %% "tapir-finatra-server" % "0.7.9"

and import the package:

import tapir.server.finatra._

This adds extension methods to the Endpoint type: toRoute and toRouteRecoverErrors. The first one requires the logic of the endpoint to be given as a function of type (note this is a Twitter Future):

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

The second 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.value(Right[Unit, Int](s.length))

val countCharactersEndpoint: Endpoint[String, Unit, Int, Nothing] = 
  endpoint.in(stringBody).out(plainBody[Int])
  
val countCharactersRoute: FinatraRoute = 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: FinatraRoute = anEndpoint.toRoute((logic _).tupled)

Now that you’ve created the FinatraRoute, add TapirController as a trait to your Controller. You can then add the created route with addTapirRoute.

class MyController extends Controller with TapirController {
  addTapirRoute(endpoint.toRoute { (s: String, i: Int) => ??? }
}