http4s

A Freestyle program can easily be used with http4s.

You can add the frees-http4s module as follows:

libraryDependencies += "io.frees" %% "frees-http4s" % "0.7.0"

Note that Freestyle only supports the http4s version based on Cats and FS2.

Integration

The frees-http4s module allows you to return a FreeS[F, A] value if there is an F.Handler[G] in scope and an EntityEncoder[H, G[A]]. Http4s provides EntityEncoder instances for cats.effect.IO, Future, Id, etc out of the box.

Example

First, lets import all the regular freestyle imports:

import freestyle.free._
import freestyle.free.implicits._

And the specific import for frees-http4s:

import freestyle.free.http.http4s._

In this example, we will create an algebra to calculate the VAT of a product’s price:

import scala.math.BigDecimal
// import scala.math.BigDecimal

@free trait CalcVAT {
  def vat(price: BigDecimal): FS[BigDecimal]
  def withVat(price: BigDecimal): FS[BigDecimal] =
    vat(price).map(_ + price)
}
// defined trait CalcVAT
// defined object CalcVAT

A handler for the CalcVAT algebra with fs2.Task as target type:

import cats.effect.IO
// import cats.effect.IO

implicit val calcIoHandler = new CalcVAT.Handler[IO] {
  private val rate: BigDecimal = BigDecimal(20) / BigDecimal(100)
  def vat(price: BigDecimal): IO[BigDecimal] =
    IO.pure(price * rate)
}
// calcIoHandler: CalcVAT.Handler[cats.effect.IO] = $anon$1@7ee5c3da

Some imports for http4s:

import org.http4s._
import org.http4s.dsl.io._

We can now create a simple HttpService:

object Price {
  def unapply(s: String): Option[BigDecimal] =
    if (s.isEmpty) None else Some(BigDecimal(s))
}
// defined object Price

val userService = HttpService[IO] {
  case GET -> Root / "calc-vat" / Price(p) =>
    Ok(CalcVAT[CalcVAT.Op].vat(p).map(vat => s"The VAT for $p is $vat"))
  case GET -> Root / "calc-total" / Price(p) =>
    Ok(CalcVAT[CalcVAT.Op].withVat(p).map(total => s"The total price including VAT is $total"))
}
// userService: org.http4s.HttpService[cats.effect.IO] = Kleisli(org.http4s.HttpService$$$Lambda$13290/1060604843@100e415f)

Trying out our HttpService:

val getVAT = Request[IO](Method.GET, uri("/calc-vat/100.50"))
// getVAT: org.http4s.Request[cats.effect.IO] = Request(method=GET, uri=/calc-vat/100.50, headers=Headers())

val getTotal = Request[IO](Method.GET, uri("/calc-total/100.50"))
// getTotal: org.http4s.Request[cats.effect.IO] = Request(method=GET, uri=/calc-total/100.50, headers=Headers())

def getResult(req: Request[IO]): IO[String] =
  userService.orNotFound(req).flatMap(EntityDecoder.decodeString[IO])
// getResult: (req: org.http4s.Request[cats.effect.IO])cats.effect.IO[String]

println(getResult(getVAT).unsafeRunSync)
// The VAT for 100.50 is 20.100

println(getResult(getTotal).unsafeRunSync)
// The total price including VAT is 120.600