Skip to content

Commit

Permalink
Change to http4s
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasl8 committed Oct 26, 2022
1 parent 3dfde4a commit 815d2d9
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 59 deletions.
1 change: 1 addition & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
version = 2.7.5
10 changes: 5 additions & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ lazy val catsEffectVersion = "3.3.10"
lazy val circeVersion = "0.14.1"
lazy val refinedVersion = "0.9.28"
lazy val attoVersion = "0.9.5"
lazy val hammockVersion = "0.11.3"
lazy val http4sVersion = "0.23.16"
lazy val scalacheckVersion = "1.15.4"
lazy val scalatestVersion = "3.2.11"
lazy val scalaUriVersion = "4.0.1"
Expand All @@ -51,10 +51,10 @@ lazy val gsheets4s = project.in(file("."))
"org.tpolecat" %% "atto-core",
"org.tpolecat" %% "atto-refined"
).map(_ % attoVersion) ++ Seq(
"com.pepegar" %% "hammock-core",
"com.pepegar" %% "hammock-apache-http",
"com.pepegar" %% "hammock-circe"
).map(_ % hammockVersion) ++ Seq(
"org.http4s" %% "http4s-dsl",
"org.http4s" %% "http4s-circe",
"org.http4s" %% "http4s-client"
).map(_ % http4sVersion) ++ Seq(
"org.scalatest" %% "scalatest" % scalatestVersion,
"org.scalacheck" %% "scalacheck" % scalacheckVersion,
"eu.timepit" %% "refined-scalacheck" % refinedVersion
Expand Down
17 changes: 8 additions & 9 deletions src/main/scala/gsheets4s/GSheets4s.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package gsheets4s

import cats.effect.Sync

import algebras._
import http._
import interpreters._
import model._
import cats.effect.Ref
import cats.effect.{Concurrent, Ref}
import gsheets4s.algebras._
import gsheets4s.http._
import gsheets4s.interpreters._
import gsheets4s.model._
import org.http4s.client.Client

case class GSheets4s[F[_]](
spreadsheetsValues: SpreadsheetsValues[F]
)

object GSheets4s {
def apply[F[_]: Sync](creds: Ref[F, Credentials]): GSheets4s[F] = {
val requester = new HammockRequester[F]()
def apply[F[_]: Concurrent](creds: Ref[F, Credentials], http4sClient: Client[F]): GSheets4s[F] = {
val requester = new Http4sRequester[F](http4sClient)
val client = new HttpClient[F](creds, requester)
val spreadsheetsValues = new RestSpreadsheetsValues(client)
GSheets4s(spreadsheetsValues)
Expand Down
8 changes: 4 additions & 4 deletions src/main/scala/gsheets4s/http/GSheets4sDefaultUrls.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package gsheets4s.http

import cats.syntax.option._
import hammock.Uri
import org.http4s.Uri
import org.http4s.implicits._

case class GSheets4sDefaultUrls(
baseUrl: Uri,
Expand All @@ -10,7 +10,7 @@ case class GSheets4sDefaultUrls(

object GSheets4sDefaultUrls {
implicit val defaultUrls: GSheets4sDefaultUrls = GSheets4sDefaultUrls(
Uri("https".some, none, "sheets.googleapis.com/v4/spreadsheets"),
Uri("https".some, none, "www.googleapis.com/oauth2/v4/token")
uri"https://sheets.googleapis.com/v4/spreadsheets",
uri"https://www.googleapis.com/oauth2/v4/token"
)
}
54 changes: 22 additions & 32 deletions src/main/scala/gsheets4s/http/HttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,52 @@ package gsheets4s
package http

import cats.Monad
import cats.data.NonEmptyList
import cats.effect.Sync
import cats.effect.{Concurrent, Ref}
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.show._
import hammock._
import hammock.apache.ApacheInterpreter
import hammock.circe._
import io.circe.{Encoder, Decoder}
import io.lemonlabs.uri.Url

import model.{Credentials, GsheetsError}
import cats.effect.Ref
import gsheets4s.model.{Credentials, GsheetsError}
import io.circe.{Decoder, Encoder}
import org.http4s.circe.CirceEntityCodec._
import org.http4s.client.Client
import org.http4s.{Method, Request, Uri}

trait HttpRequester[F[_]] {
def request[O](uri: Uri, method: Method)(implicit d: Decoder[O]): F[O]
def requestWithBody[I, O](
uri: Uri, body: I, method: Method)(implicit e: Encoder[I], d: Decoder[O]): F[O]
}

class HammockRequester[F[_]: Sync] extends HttpRequester[F] {
implicit val interpreter = ApacheInterpreter.instance[F]
class Http4sRequester[F[_]: Concurrent](client: Client[F]) extends HttpRequester[F] {

def request[O](uri: Uri, method: Method)(implicit d: Decoder[O]): F[O] = {
implicit val hammockDecoder = new HammockDecoderForCirce()
Hammock.request(method, uri, Map.empty).as[O].exec[F]
client.expect[O](Request[F](method, uri))
}

def requestWithBody[I, O](
uri: Uri, body: I, method: Method)(implicit e: Encoder[I], d: Decoder[O]): F[O] = {
implicit val hammockEncoder = new HammockEncoderForCirce()
implicit val hammockDecoder = new HammockDecoderForCirce()
Hammock.request(method, uri, Map.empty, Some(body)).as[O].exec[F]
client.expect[O](Request[F](method, uri).withEntity(body))
}
}

class HttpClient[F[_]: Monad](creds: Ref[F, Credentials], requester: HttpRequester[F])(
implicit urls: GSheets4sDefaultUrls) {
def get[O](
path: Url,
path: Uri,
params: List[(String, String)] = List.empty)(
implicit d: Decoder[O]): F[Either[GsheetsError, O]] =
req(token => requester
.request[Either[GsheetsError, O]](urlBuilder(token, path, params), Method.GET))

def put[I, O](
path: Url,
path: Uri,
body: I,
params: List[(String, String)] = List.empty)(
implicit e: Encoder[I], d: Decoder[O]): F[Either[GsheetsError, O]] =
req(token => requester.requestWithBody[I, Either[GsheetsError, O]](
urlBuilder(token, path, params), body, Method.PUT))

def post[I, O](
path: Url,
path: Uri,
body: I,
params: List[(String, String)] = List.empty)(
implicit e: Encoder[I], d: Decoder[O]): F[Either[GsheetsError, O]] =
Expand All @@ -80,21 +71,20 @@ class HttpClient[F[_]: Monad](creds: Ref[F, Credentials], requester: HttpRequest
} yield r

private def refreshToken(c: Credentials)(implicit d: Decoder[String]): F[String] = {
val url = urls.refreshTokenUrl ?
NonEmptyList(
("refresh_token" -> c.refreshToken),
List(
("client_id" -> c.clientId),
("client_secret" -> c.clientSecret),
("grant_type" -> "refresh_token")
)
)
val url = urls.refreshTokenUrl.withQueryParam("refresh_token", c.refreshToken)
.withQueryParam("client_id", c.clientId)
.withQueryParam("client_secret", c.clientSecret)
.withQueryParam("grant_type", "refresh_token")

requester.request(url, Method.POST)
}

private def urlBuilder(
accessToken: String,
path: Url,
path: Uri,
params: List[(String, String)]): Uri =
(urls.baseUrl / path.show) ? NonEmptyList(("access_token" -> accessToken), params)
urls.baseUrl
.resolve(path)
.withQueryParam("access_token", accessToken)
.withQueryParams(params.toMap)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ package gsheets4s
package interpreters

import cats.syntax.show._
import gsheets4s.algebras.SpreadsheetsValues
import gsheets4s.http._
import gsheets4s.model._
import io.circe.generic.auto._

import algebras.SpreadsheetsValues
import http._
import model._
import org.http4s.Uri

class RestSpreadsheetsValues[F[_]](client: HttpClient[F]) extends SpreadsheetsValues[F] {
def get(spreadsheetID: String, range: A1Notation): F[Either[GsheetsError, ValueRange]] =
client.get(io.lemonlabs.uri.Url.parse(s"$spreadsheetID/values/${range.show}"))
client.get(Uri.unsafeFromString(s"$spreadsheetID/values/${range.show}"))

def update(
spreadsheetID: String,
range: A1Notation,
updates: ValueRange,
valueInputOption: ValueInputOption
): F[Either[GsheetsError, UpdateValuesResponse]] =
client.put(io.lemonlabs.uri.Url.parse(s"$spreadsheetID/values/${range.show}"), updates,
client.put(Uri.unsafeFromString(s"$spreadsheetID/values/${range.show}"), updates,
List(("valueInputOption", valueInputOption.value)))
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import model._
import org.scalatest.flatspec.AnyFlatSpec
import cats.effect.Ref
import cats.effect.unsafe.implicits.global
import org.http4s.client.JavaNetClientBuilder

object Integration extends Tag (
if (sys.env.get("GSHEETS4S_ACCESS_TOKEN").isDefined) "" else classOf[Ignore].getName)

class SpreadsheetsValuesSpec extends AnyFlatSpec {

val client = JavaNetClientBuilder[IO].create

val creds = for {
accessToken <- sys.env.get("GSHEETS4S_ACCESS_TOKEN")
refreshToken <- sys.env.get("GSHEETS4S_REFRESH_TOKEN")
Expand All @@ -30,7 +33,7 @@ class SpreadsheetsValuesSpec extends AnyFlatSpec {
"RestSpreadsheetsValues" should "update and get values" taggedAs Integration in {
val res = (for {
credsRef <- Ref.of[IO, Credentials](creds.get)
spreadsheetsValues = GSheets4s(credsRef).spreadsheetsValues
spreadsheetsValues = GSheets4s(credsRef, client).spreadsheetsValues
prog <- new TestPrograms(spreadsheetsValues)
.updateAndGet(spreadsheetID, vr, vio)
} yield prog).unsafeRunSync()
Expand All @@ -45,7 +48,7 @@ class SpreadsheetsValuesSpec extends AnyFlatSpec {
it should "report an error if the spreadsheet it doesn't exist" taggedAs Integration in {
val res = (for {
credsRef <- Ref.of[IO, Credentials](creds.get)
spreadsheetsValues = GSheets4s(credsRef).spreadsheetsValues
spreadsheetsValues = GSheets4s(credsRef, client).spreadsheetsValues
prog <- new TestPrograms(spreadsheetsValues)
.updateAndGet("not-existing-spreadsheetid", vr, vio)
} yield prog).unsafeRunSync()
Expand All @@ -59,7 +62,7 @@ class SpreadsheetsValuesSpec extends AnyFlatSpec {
it should "work with a faulty access token" taggedAs Integration in {
val res = (for {
credsRef <- Ref.of[IO, Credentials](creds.get.copy(accessToken = "faulty"))
spreadsheetsValues = GSheets4s(credsRef).spreadsheetsValues
spreadsheetsValues = GSheets4s(credsRef, client).spreadsheetsValues
prog <- new TestPrograms(spreadsheetsValues)
.updateAndGet(spreadsheetID, vr, vio)
} yield prog).unsafeRunSync()
Expand Down

0 comments on commit 815d2d9

Please sign in to comment.