La traduction de l'article a été préparée en prévision du début du cours "Scala developer"
, tapir HTTP. API, , , , .
.
, .
, . , . baseEndpoint, : . , , , .
import java.util.UUID
import sttp.tapir._
import sttp.tapir.json.circe._
import io.circe.generic.auto._
import sttp.model.StatusCode
case class AuthToken(token: String)
case class Error(msg: String, statusCode: StatusCode) extends Exception
val error: EndpointOutput[Error] = stringBody.and(statusCode).mapTo(Error)
val baseEndpoint: Endpoint[AuthToken, Error, Unit, Nothing] = endpoint
.in(header[String]("X-Authorization")
.description("Only authorized users can add pets")
.example("1234")
.mapTo(AuthToken))
.in("api" / "1.0")
.errorOut(error)
, . -:
case class User(id: UUID, name: String)
case class Pet(id: UUID, kind: String, name: String)
val getPet: Endpoint[(AuthToken, UUID), Error, Pet, Nothing] =
baseEndpoint
.get
.in(query[UUID]("id").description("The id of the pet to find"))
.out(jsonBody[Pet])
.description("Finds a pet by id")
val addPet: Endpoint[(AuthToken, Pet), Error, Unit, Nothing] =
baseEndpoint
.post
.in(jsonBody[Pet])
.description("Adds a pet")
, , . , . Future -, - Future.
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
// Error,
def authorize(authToken: AuthToken): Future[User] = ???
def findPetForUser(user: User, id: UUID): Future[Option[Pet]] = ???
def addPetToUser(user: User, pet: Pet): Future[Unit] = ???
val getPetWithLogic = getPet.serverLogicRecoverErrors {
case (authToken, id) =>
authorize(authToken).flatMap { user =>
findPetForUser(user, id).flatMap {
case Some(pet) => Future.successful(pet)
case None => Future.failed(Error("Not found", StatusCode.NotFound))
}
}
}
val addPetWithLogic = addPet.serverLogicRecoverErrors {
case (authToken, pet) =>
authorize(authToken).flatMap { user =>
addPetToUser(user, pet)
}
}
, , , Exception, - , , .
, , , akka-http Route, akka-http .
//
akka.http.scaladsl.Route
import akka.http.scaladsl.server.Route
import sttp.tapir.server.akkahttp._
val routes: Route = List(getPetWithLogic, addPetWithLogic).toRoute
// ,
akka-http
. , , . , Authorization. , , . , , , .
import java.util.UUID
import cats.effect.{ContextShift, IO, Timer}
import sttp.tapir._
import sttp.model.StatusCode
case class AuthToken(token: String)
case class Error(msg: String, statusCode: StatusCode)
case class User(id: UUID, name: String)
case class Pet(id: UUID, kind: String, name: String)
def authorize(authToken: AuthToken): IO[Either[Error, User]] = ???
def findPetForUser(user: User, id: UUID): IO[Either[Error, Option[Pet]]] = ???
def addPetToUser(user: User, pet: Pet): IO[Either[Error, Unit]] = ???
,
-. , ( : serverLogicForCurrent), ( ):
import sttp.tapir.json.circe._
import io.circe.generic.auto._
import sttp.tapir.server.PartialServerEndpoint
val error: EndpointOutput[Error] = stringBody.and(statusCode).mapTo(Error)
val secureEndpoint: PartialServerEndpoint[User, Unit, Error, Unit, Nothing, IO] =
endpoint
.in(auth.bearer[String].mapTo(AuthToken))
.in("api" / "1.0")
.errorOut(error)
.serverLogicForCurrent(authorize)
, PartialServerEndpoint. / ( , ).
! , , , — , .
, /, , , : (: User) .
val getPetWithLogic =
secureEndpoint
.get
.in(query[UUID]("id").description("The id of the pet to find"))
.out(jsonBody[Pet])
.description("Finds a pet by id")
.serverLogic {
case (user, id) =>
findPetForUser(user, id).map {
case Right(Some(pet)) => Right(pet)
case Right(None) => Left(Error("Not found", StatusCode.NotFound))
case Left(error) => Left(error)
}
}
val addPetWithLogic =
secureEndpoint
.post
.in(jsonBody[Pet])
.description("Adds a pet")
.serverLogic((addPetToUser _).tupled)
, , , case . , IO . , IO[Either[Error, _]].
ServerEndpoint ( , ). , , IO , http4s.
// the endpoints are interpreted as an http4s.HttpRoutes[IO]
import sttp.tapir.server.http4s._
import org.http4s.HttpRoutes
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
implicit val contextShift: ContextShift[IO] = IO.contextShift(ec)
implicit val timer: Timer[IO] = IO.timer(ec)
val routes: HttpRoutes[IO] = List(getPetWithLogic, addPetWithLogic).toRoutes
// expose routes using http4s
tapir , . , tapir-core tapir-json-circe.
- - . . . , (, cookies):
import java.util.UUID
import io.circe.generic.auto._
import sttp.model.StatusCode
import sttp.tapir._
import sttp.tapir.json.circe._
object Endpoints {
case class AuthToken(token: String)
case class Error(msg: String, statusCode: StatusCode) extends Exception
case class User(id: UUID, name: String)
case class Pet(id: UUID, kind: String, name: String)
val error: EndpointOutput[Error] = stringBody.and(statusCode).mapTo(Error)
val baseEndpoint: Endpoint[AuthToken, Error, Unit, Nothing] =
endpoint
.in(auth.apiKey(cookie[String]("Token")).mapTo(AuthToken))
.in("api" / "1.0")
.errorOut(error)
val getPet: Endpoint[(AuthToken, UUID), Error, Pet, Nothing] =
baseEndpoint
.get
.in(query[UUID]("id").description("The id of the pet to find"))
.out(jsonBody[Pet])
.description("Finds a pet by id")
val addPet: Endpoint[(AuthToken, Pet), Error, Unit, Nothing] =
baseEndpoint
.post
.in(jsonBody[Pet])
.description("Adds a pet")
}
, , . .
( ), serverLogicPart, ( ), .
serverLogicPart , ( , ). ServerEndpointInParts .
, , , , ( ) , , .
, - , , User:
object Server {
import Endpoints._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
// should fail with Error if user not found
def authorize(authToken: AuthToken): Future[Either[Error, User]] = ???
def findPetForUser(user: User, id: UUID): Future[Either[Error, Option[Pet]]] = ???
def addPetToUser(user: User, pet: Pet): Future[Either[Error, Unit]] = ???
val getPetWithLogic = getPet.serverLogicPart(authorize).andThen {
case (user, id) =>
findPetForUser(user, id).map {
case Right(Some(pet)) => Right(pet)
case Right(None) => Left(Error("Not found", StatusCode.NotFound))
case Left(error) => Left(error)
}
}
val addPetWithLogic = addPet.serverLogicPart(authorize)
.andThen((addPetToUser _).tupled)
// the endpoints are now interpreted as an akka.http.scaladsl.Route
import akka.http.scaladsl.server.Route
import sttp.tapir.server.akkahttp._
val routes: Route = List(getPetWithLogic, addPetWithLogic).toRoute
// , akka-http
}
, ServerEndpoint. , Future akka-http.
:
- .
- , . / ( ), .
- , . , .
, , . . , , .
. , , , .
, , tapir, HTTP!