retro

retro

  • toctoc
  • enumero
  • sbt-buildo
  • tapiro
  • mailo
  • wiro
  • GitHub

›Getting started

Setup

  • Installation

Getting started

  • Introduction
  • Why?
  • Migration from Wiro
Edit

Introduction

Tapiro parses your Scala controllers to generate HTTP endpoints.

A Scala controller is a trait defined as follows:

import scala.annotation.StaticAnnotation

class path(val path: String) extends StaticAnnotation
class query extends StaticAnnotation
class command extends StaticAnnotation

case class Cat(name: String)
case class Error(msg: String)

@path("Cats")
trait Cats[F[_], AuthToken] {
  @query //translate this to a GET
  def findCutestCat(): F[Either[Error, Cat]]

  @command //translate this to a POST
  def doSomethingWithTheCat(catId: Int, token: AuthToken): F[Either[Error, Unit]]
}

For each controller tapiro generates two files:

  • CatsTapirEndpoints.scala containing the HTTP api description using https://tapir.softwaremill.com/en/latest/
  • CatsHttpEndpoints.scala which injects the api logic defined in the controller into the tapir endpoints

Complete Example

Here you have an example implementation of the Cats controller definied in the previous section:

import cats.effect._

object Cats {
  def create[F[_]](implicit F: Sync[F]) = new Cats[F, String] {
    override def findCutestCat(): F[Either[Error, Cat]] =
      F.delay(Right(Cat("Cheshire")))
    override def doSomethingWithTheCat(catId: Int, token: String): F[Either[Error, Unit]] =
      F.delay(Right(()))
  }
}

Here you have the autogenerated magic fromo tapiro (This is the content of CatsHttpEndpoints.scala that will be autogenerated).

import org.http4s.HttpRoutes

// ---- begins autogenerated code
object CatsHttpEndpoints {
  def routes(controller: Cats[IO, String]): HttpRoutes[IO] = ???
}

Here is how to run the server:

import org.http4s.blaze.server._
import org.http4s.implicits._

object Main extends IOApp {
  val catsImpl = Cats.create[IO]
  val routes = CatsHttpEndpoints.routes(catsImpl)

  override def run(args: List[String]): IO[ExitCode] =
    BlazeServerBuilder[IO]
      .bindHttp(8080, "localhost")
      .withHttpApp(routes.orNotFound)
      .serve
      .compile
      .drain
      .as(ExitCode.Success)
}

The resulting server can be queried as follows:

/GET /Cats/findCutestCat
/POST /Cats/doSomethingWithTheCat -d '{ "catId": 1 }'

Authentication

An Auth type argument is expected in each controller and is added as authorization header.

trait Cats[F[_], Auth]

The actual implementation of the Auth is left to the user. All tapiro requires is a proper tapir PlainCodec such as:

import sttp.tapir._
import sttp.tapir.Codec._

case class CustomAuth(token: String)

def decodeAuth(s: String): DecodeResult[CustomAuth] = {
  val TokenPattern = "Token token=(.+)".r
  s match {
    case TokenPattern(token) => DecodeResult.Value(CustomAuth(token))
    case _                   => DecodeResult.Error(s, new Exception("token not found"))
  }
}

def encodeAuth(auth: CustomAuth): String = auth.token

implicit val authCodec: PlainCodec[CustomAuth] = Codec.string
  .mapDecode(decodeAuth)(encodeAuth)
// authCodec: PlainCodec[CustomAuth] = sttp.tapir.Codec$$anon$1@ffbe470

The user will find the decoded token as the last argument of the method in the trait.

@command //translate this to a POST
def doSomethingWithTheCat(catId: Int, token: Auth): F[Either[Error, Unit]]
← InstallationWhy? →
  • Complete Example
  • Authentication
retro
Docs
InstallationConcepts
More
TwitterGitHubStar
Copyright © 2024 buildo