http4s

A Freestyle program can easily be used with http4s.

You can add the freestyle-http4s module as follows:

libraryDependencies += "io.frees" %% "freestyle-http-http4s" % "0.3.1"

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

Integration

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

Example

First, lets import all the regular freestyle imports:

import freestyle._
import freestyle.implicits._

And the specific import for freestyle-http4s:

import freestyle.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 _root_.fs2.Task
// import _root_.fs2.Task

import _root_.fs2.interop.cats._
// import _root_.fs2.interop.cats._

implicit val taskHandler = new CalcVAT.Handler[Task] {
  private val rate: BigDecimal = BigDecimal(20) / BigDecimal(100)
  def vat(price: BigDecimal): Task[BigDecimal] =
    Task.now(price * rate)
}
// taskHandler: CalcVAT.Handler[fs2.Task] = $anon$1@74cc16bf

Some imports for http4s:

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

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 {
  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 = Kleisli(org.http4s.package$HttpService$$$Lambda$6548/1074363043@1cb9a484)

Trying out our HttpService:

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

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

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

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

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