Hammock HTTP client

Hammock is a purely functional HTTP client for the cats ecosystem. If you want to read more documentation on Hammock, you can read the docs.

First of all, include the frees-http-client as follows:

libraryDependencies += "io.frees" %% "frees-http-client" % "0.8.2"

Integration

As always, we should start with the freestyle imports:

import freestyle.free._
import freestyle.free.implicits._
import freestyle.free.http.client._
import freestyle.free.http.client.implicits._
import cats.effect.{Sync, IO}
import _root_.hammock._
import _root_.hammock.Uri._
import _root_.hammock.hi._
import _root_.hammock.jvm.Interpreter

Let’s start with a simple algebra that will allow us to do console IO:

@free trait ConsoleIO {
  def putLine(text: String): FS[Unit]
  def getLine: FS[String]
}

implicit val consoleIOSyncHandler = new ConsoleIO.Handler[IO] {
  def putLine(text: String): IO[Unit] = Sync[IO].delay(println(text))
  def getLine: IO[String] = Sync[IO].delay("30")
}

@module trait Example {
  val hammockM: HammockM
  val consoleIO: ConsoleIO
}

There are two different ways to integrate your Hammock programs in your Freestyle flow. First of all, you can use the methods in the HammockM algebra that represent HTTP verbs:

scala> implicit val interp = Interpreter[IO]
interp: hammock.jvm.Interpreter[cats.effect.IO] = hammock.jvm.Interpreter@40fd51ef

scala> HammockM[HammockM.Op].get(Uri.unsafeParse("https://jsonplaceholder.typicode.com/posts/1"), Map.empty[String, String]).interpret[IO].unsafeRunSync
res0: hammock.HttpResponse = HttpResponse(Status(200,OK,OK),Map(Vary -> Origin, Accept-Encoding, Expect-CT -> max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct", Transfer-Encoding -> chunked, Server -> cloudflare, Access-Control-Allow-Credentials -> true, Etag -> W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU", Expires -> Thu, 12 Jul 2018 18:51:58 GMT, Connection -> keep-alive, CF-Cache-Status -> REVALIDATED, Cache-Control -> public, max-age=14400, X-Content-Type-Options -> nosniff, Via -> 1.1 vegur, Content-Type -> application/json; charset=utf-8, Pragma -> no-cache, Date -> Thu, 12 Jul 2018 14:51:58 GMT, CF-RAY -> 439454b90ef55f2b-OMA, Set-Cookie -> __cfduid=d4eb2b9fb0f290b3fdc47dfaa096cd4651531407118; expires=Fri, 12-Jul-19 14:51:58 ...

Also, you can lift any arbitrary HttpRequestIO[F] program to HammockM with HammockM.run(program).

scala> implicit val interp = Interpreter[IO]
interp: hammock.jvm.Interpreter[cats.effect.IO] = hammock.jvm.Interpreter@63edad6e

scala> val response = Hammock.getWithOpts(Uri.unsafeParse("https://jsonplaceholder.typicode.com/posts/1"), Opts.empty)
response: cats.free.Free[hammock.HttpF,hammock.HttpResponse] = Free(...)

scala> HammockM[HammockM.Op].run(response).interpret[IO].unsafeRunSync
res1: hammock.HttpResponse = HttpResponse(Status(200,OK,OK),Map(Vary -> Origin, Accept-Encoding, Expect-CT -> max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct", Transfer-Encoding -> chunked, Server -> cloudflare, Access-Control-Allow-Credentials -> true, Etag -> W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU", Expires -> Thu, 12 Jul 2018 18:52:00 GMT, Connection -> keep-alive, CF-Cache-Status -> HIT, Cache-Control -> public, max-age=14400, X-Content-Type-Options -> nosniff, Via -> 1.1 vegur, Content-Type -> application/json; charset=utf-8, Pragma -> no-cache, Date -> Thu, 12 Jul 2018 14:52:00 GMT, CF-RAY -> 439454c4dfb35f2b-OMA, X-Powered-By -> Express),StringEntity({  "userId": 1,  "id": 1,  "title": "sunt aut facere repellat providen...

Example with modules

scala> implicit val interp = Interpreter[IO]
interp: hammock.jvm.Interpreter[cats.effect.IO] = hammock.jvm.Interpreter@1e523608

scala> def example[F[_] : Example] = for {
     |   _ <- Example[F].consoleIO.putLine("")
     |   age <- Example[F].consoleIO.getLine
     |   emptyHeaders = Map.empty[String, String]
     |   response <- Example[F].hammockM.get(Uri.unsafeParse("https://jsonplaceholder.typicode.com/posts/1?age=$age"), emptyHeaders)
     | } yield response
example: [F[_]](implicit evidence$1: Example[F])cats.free.Free[[β$0$]cats.free.FreeApplicative[F,β$0$],hammock.HttpResponse]

scala> example[Example.Op].interpret[IO].unsafeRunSync

res2: hammock.HttpResponse = HttpResponse(Status(200,OK,OK),Map(Vary -> Origin, Accept-Encoding, Expect-CT -> max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct", Transfer-Encoding -> chunked, Server -> cloudflare, Access-Control-Allow-Credentials -> true, Etag -> W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU", Expires -> Thu, 12 Jul 2018 18:52:02 GMT, Connection -> keep-alive, CF-Cache-Status -> MISS, Cache-Control -> public, max-age=14400, X-Content-Type-Options -> nosniff, Via -> 1.1 vegur, Content-Type -> application/json; charset=utf-8, Pragma -> no-cache, Date -> Thu, 12 Jul 2018 14:52:02 GMT, CF-RAY -> 439454cdb8ba5f2b-OMA, X-Powered-By -> Express),StringEntity({  "userId": 1,  "id": 1,  "title": "sunt aut facere repellat provide...