Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import cats.implicits._
import fs2._
import org.http4s._
import org.http4s.implicits._
import org.http4s.client.Client
import org.http4s.client.{Client, UnexpectedStatus}
import org.http4s.headers.Authorization

import _root_.io.chrisdavenport.github._
import cats.data.Kleisli
import org.http4s.headers.Link
import org.http4s._

import scala.util.matching.Regex


object RequestConstructor {
Expand Down Expand Up @@ -46,44 +47,49 @@ object RequestConstructor {
c.expect[B](req)
}

private val RE_LINK: Regex = "[\\s]*<(.*)>; rel=\"(.*)\"".r

private def getNextUri[F[_]: MonadError[*[_], Throwable]](r: Response[F]): Option[Uri] = {
for {
next <- r.headers.toList.map(Link.matchHeader).collect{
case Some(Link(uri, Some("next"), _, _, _)) => uri
}.headOption
} yield next
r.headers.toList.flatMap {
case Header(name, value) if name == "Link".ci => value.split(",").toList
case _ => Nil
}.collectFirstSome {
case RE_LINK(uri, "next") => Uri.fromString(uri).toOption
case _ => None
}
}

def runPaginatedRequest[F[_]: Sync, B: EntityDecoder[F, ?]](
auth: Option[Auth],
extendedUri: Uri,
): Kleisli[Stream[F, ?], Client[F], B] = Kleisli{ c =>
val uri = Uri.resolve(baseUrl(auth), extendedUri)

unfoldLoopEval(uri){uri =>
unfoldLoopEval(uri){ uri =>
val baseReq = Request[F](method = Method.GET, uri = uri)
.withHeaders(extraHeaders)
val req = auth.fold(baseReq)(setAuth(_)(baseReq))
c.run(req).use{resp =>
resp.as[B].map{
c.fetch(req) { resp =>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we switch from run to fetch. run is clearer use semantics in my opinion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No clear reason - I just didn't know how to do it using f.
But looking at it now, I think it should also work using use as well.
I guess this part can be changed back - I can do it if you want.

if(resp.status.isSuccess)
resp.as[B].map {
(_, getNextUri(resp))
}
}
else
MonadError[F, Throwable].raiseError[(B, Option[Uri])](UnexpectedStatus(resp.status))
}
}
}


//These two will be in the next version of fs2



/** Like [[unfoldLoop]], but takes an effectful function. */
private def unfoldLoopEval[F[_], S, O](s: S)(f: S => F[(O, Option[S])]): Stream[F, O] =
Pull
.loop[F, O, S](
s =>
Pull.eval(f(s)).flatMap {
case (o, sOpt) => Pull.output1(o) >> Pull.pure(sOpt)
case (o, sOpt) =>
Pull.output1(o) >> Pull.pure(sOpt)
}
)(s)
.void
Expand All @@ -109,4 +115,5 @@ object RequestConstructor {
Header("User-Agent", "github.scala/" ++ _root_.io.chrisdavenport.github.BuildInfo.version),
Header("Accept", "application/vnd.github.v3+json")
)

}