Search code examples
scalafuturescala-catsgithub4s

Extract value from Future using Cats


I am trying to write a small library for GitHub's API. And I am trying to simulate the behavior of Github4s's library, here is an example of this library:

val user1 = Github(accessToken).users.get("rafaparadela")
object ProgramEval {
    val u1 = user1.exec[Eval, HttpResponse[String]]().value
 }

import cats.implicits._
import github4s.GithubResponses.GHResult

ProgramEval.u1 match {
  // Here the actual value of the request is returned, 
  // not the same as Future's onComplete, where the return type is Unit
  case Right(GHResult(result, status, headers)) => result.login
  case Left(e) => e.getMessage
}

I am quoting the docs:

Every Github4s API call returns a GHIO[GHResponse[A]] which is an alias for Free[Github4s, GHResponse[A]].

GHResponse[A] is, in turn, a type alias for Either[GHException, GHResult[A]].

GHResult contains the result A given by Github as well as the status code and headers of the response:

At some point, they are making an HttpRequest using HttpClient.scala

How could I replicate this behavior myself? I have tried using Cats.Eval as in the example, but I end up having the same Future[String].

Also, I an facing with some nesting problems as I make request, for example, to get a list of contributors of an organization I need to make two HttpRequest:

  • One for getting the organization repos
  • One for each repo in order to get a list of contributors

This result on a Future[List[Future[Users]]], and I face the same problem as above, in order to obtain the results, I have to do:

(result:Future[List[Future[Users]]]) onComplete { users =>
    users.foreach {
        _  onComplete {
            // Process result
        }
    }
}

But I would like to return the value, as github4s. I've been reading about Cats's Applicative and Traversable Functors without luck.


Solution

  • You can convert List[Future] to Future[List] using Future.sequence and then squash nested futures.

    The resulting code:

    val input: Future[List[Future[Users]]] = ???
    implicit  val ec: ExecutionContext = ExecutionContext.global
    val result: Future[List[Users]] = input.flatMap(list => Future.sequence(list))