Running as a Play server¶
To expose endpoint as a play-server first add the following dependencies:
"com.softwaremill.sttp.tapir" %% "tapir-play-server" % "0.17.0-M7"
and (if you don’t already depend on Play)
"com.typesafe.play" %% "play-akka-http-server" % "2.8.3"
or
"com.typesafe.play" %% "play-netty-server" % "2.8.3"
depending on whether you want to use netty or akka based http-server under the hood.
Then import the package:
import sttp.tapir.server.play._
This adds two extension methods to the Endpoint
type: toRoute
and toRoutesRecoverError
. This first requires the
logic of the endpoint to be given as a function of type:
I => Future[Either[E, O]]
The second recovers errors from failed effects, and hence requires that E
is
a subclass of Throwable
(an exception); it expects a function of type I => Future[O]
. For example:
import sttp.tapir._
import sttp.tapir.server.play._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import akka.stream.Materializer
import play.api.routing.Router.Routes
implicit val materializer: Materializer = ???
def countCharacters(s: String): Future[Either[Unit, Int]] =
Future(Right[Unit, Int](s.length))
val countCharactersEndpoint: Endpoint[String, Unit, Int, Any] =
endpoint.in(stringBody).out(plainBody[Int])
val countCharactersRoutes: Routes =
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
:
import sttp.tapir._
import sttp.tapir.server.play._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import akka.stream.Materializer
import play.api.routing.Router.Routes
implicit val materializer: Materializer = ???
def logic(s: String, i: Int): Future[Either[Unit, String]] = ???
val anEndpoint: Endpoint[(String, Int), Unit, String, Any] = ???
val aRoute: Routes = anEndpoint.toRoute((logic _).tupled)
Bind the routes¶
Creating the HTTP server manually¶
An HTTP server can then be started as in the following example:
import play.core.server._
import play.api.routing.Router.Routes
val aRoute: Routes = ???
object Main {
// JVM entry point that starts the HTTP server
def main(args: Array[String]): Unit = {
val playConfig = ServerConfig(port =
sys.props.get("http.port").map(_.toInt).orElse(Some(9000))
)
NettyServer.fromRouterWithComponents(playConfig) { components =>
aRoute
}
}
}
As part of an existing Play application¶
Or, if you already have an existing Play application, you can create a Router
class and bind it to the application.
First, add a line like following in the routes
files:
-> /api api.ApiRouter
Then create a class like this:
class ApiRouter @Inject() () extends SimpleRouter {
override def routes: Routes = {
// Routes generated by Tapir (see above)
aRoute
.orElse(anotherRoute)
}
}
Find more details about how to bind a Router
to your application in the Play framework documentation.
Configuration¶
The interpreter can be configured by providing an implicit PlayServerOptions
value and status mappers, see
server options for details.
Defining an endpoint together with the server logic¶
It’s also possible to define an endpoint together with the server logic in a single, more concise step. See server logic for details.