From 300e699d0d6ba7f3a223e766fb689009eaf2c36f Mon Sep 17 00:00:00 2001 From: Kelvin Callcott Date: Fri, 12 Aug 2022 15:51:28 +0100 Subject: [PATCH] Replayed CTP changes on top --- .github/pr-labeler.yml | 1 + .github/workflows/label_and_automerge.yml | 37 ++++++++++++ .github/workflows/scala.yml | 26 ++++++++ .gitignore | 1 + README.md | 4 +- build.sbt | 33 +++++----- project/plugins.sbt | 7 ++- .../interpreters/RestSpreadsheetsValues.scala | 6 +- src/main/scala/gsheets4s/model.scala | 60 +++++-------------- .../gsheets4s/A1NotationLiteralsSpec.scala | 16 ----- src/test/scala/gsheets4s/ModelSpec.scala | 12 ++++ .../interpreters/TestSpreadsheetsValues.scala | 2 + 12 files changed, 118 insertions(+), 87 deletions(-) create mode 100644 .github/pr-labeler.yml create mode 100644 .github/workflows/label_and_automerge.yml create mode 100644 .github/workflows/scala.yml delete mode 100644 src/test/scala/gsheets4s/A1NotationLiteralsSpec.scala diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml new file mode 100644 index 0000000..759260c --- /dev/null +++ b/.github/pr-labeler.yml @@ -0,0 +1 @@ +automerge: ['update/*'] diff --git a/.github/workflows/label_and_automerge.yml b/.github/workflows/label_and_automerge.yml new file mode 100644 index 0000000..fb73466 --- /dev/null +++ b/.github/workflows/label_and_automerge.yml @@ -0,0 +1,37 @@ +name: label_and_automerge +on: + pull_request: + types: + - labeled + - unlabeled + - synchronize + - opened + - edited + - ready_for_review + - reopened + - unlocked + pull_request_review: + types: + - submitted + check_suite: + types: + - completed + status: {} +jobs: + + automerge: + runs-on: ubuntu-latest + steps: + - id: automerge + name: automerge + uses: "pascalgn/automerge-action@v0.15.3" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + pr-labeler: + runs-on: ubuntu-latest + steps: + - uses: TimonVS/pr-labeler-action@v3 + with: + configuration-path: .github/pr-labeler.yml # optional, .github/pr-labeler.yml is the default value + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml new file mode 100644 index 0000000..19c69ea --- /dev/null +++ b/.github/workflows/scala.yml @@ -0,0 +1,26 @@ +name: Scala CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 8 + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'temurin' + cache: 'sbt' + - name: Run tests + run: sbt test diff --git a/.gitignore b/.gitignore index 2f7896d..07827cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/ +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 94b5445..aa5fa66 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,7 @@ Scala wrapper around [the Google Sheets API](https://developers.google.com/sheet gsheets4s is available for Scala 2.12 on Maven central at the following coordinates: -```bash -"com.github.benfradet" %% "gsheets4s" % "0.2.0" -``` +`"com.itv" %% "ctp-gsheets4s" % "0.14.0"` ## Credentials management diff --git a/build.sbt b/build.sbt index 73ea671..9581711 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,5 @@ -inThisBuild(List( - organization := "com.github.benfradet", - homepage := Some(url("https://github.com/BenFradet/gsheets4s")), - licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), - developers := List( - Developer( - "BenFradet", - "Ben Fradet", - "benjamin.fradet@gmail.com", - url("https://benfradet.github.io") - ) - ) -)) +name := "ctp-gsheets4s" +organization := "com.itv" lazy val compilerOptions = Seq( "-deprecation", @@ -19,12 +8,9 @@ lazy val compilerOptions = Seq( "-language:existentials", "-language:higherKinds", "-unchecked", - "-Yno-adapted-args", "-Ywarn-dead-code", "-Ywarn-numeric-widen", - "-Ywarn-unused-import", - "-Xfuture", - "-Ypartial-unification" + "-Xfuture" ) lazy val baseSettings = Seq( @@ -55,8 +41,9 @@ lazy val gsheets4s = project.in(file(".")) libraryDependencies ++= Seq( "org.typelevel" %% "cats-core" % catsVersion, "org.typelevel" %% "cats-effect" % catsEffectVersion, + "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5", "eu.timepit" %% "refined" % refinedVersion, - "io.lemonlabs" %% "scala-uri" % scalaUriVersion, + "io.lemonlabs" %% "scala-uri" % scalaUriVersion ) ++ Seq( "io.circe" %% "circe-core", "io.circe" %% "circe-generic", @@ -74,3 +61,13 @@ lazy val gsheets4s = project.in(file(".")) "eu.timepit" %% "refined-scalacheck" % refinedVersion ).map(_ % "test") ) + +gitVersioningSnapshotLowerBound in ThisBuild := "0.4.0" + +resolvers += "Artifactory Realm" at "https://itvrepos.jfrog.io/itvrepos/fp-scala-libs/" + +credentials += Credentials(Path.userHome / ".ivy2" / "fp-scala-libs.credentials") + +publishArtifact := true +publishArtifact in Test := false +publishTo := Some("Artifactory Realm" at "https://itvrepos.jfrog.io/itvrepos/fp-scala-libs/") diff --git a/project/plugins.sbt b/project/plugins.sbt index 3c115ce..399e55a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9") -addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.20") +resolvers += Resolver.bintrayIvyRepo("rallyhealth", "sbt-plugins") +addSbtPlugin("com.rallyhealth.sbt" % "sbt-git-versioning" % "1.6.0") + +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.9.3") diff --git a/src/main/scala/gsheets4s/interpreters/RestSpreadsheetsValues.scala b/src/main/scala/gsheets4s/interpreters/RestSpreadsheetsValues.scala index b102b9f..a5daa3c 100644 --- a/src/main/scala/gsheets4s/interpreters/RestSpreadsheetsValues.scala +++ b/src/main/scala/gsheets4s/interpreters/RestSpreadsheetsValues.scala @@ -1,8 +1,8 @@ package gsheets4s package interpreters +import cats.syntax.show._ import io.circe.generic.auto._ -import io.lemonlabs.uri.typesafe.dsl._ import algebras.SpreadsheetsValues import http._ @@ -10,7 +10,7 @@ import model._ class RestSpreadsheetsValues[F[_]](client: HttpClient[F]) extends SpreadsheetsValues[F] { def get(spreadsheetID: String, range: A1Notation): F[Either[GsheetsError, ValueRange]] = - client.get(spreadsheetID / "values" / range) + client.get(io.lemonlabs.uri.Url.parse(s"$spreadsheetID/values/${range.show}")) def update( spreadsheetID: String, @@ -18,6 +18,6 @@ class RestSpreadsheetsValues[F[_]](client: HttpClient[F]) extends SpreadsheetsVa updates: ValueRange, valueInputOption: ValueInputOption ): F[Either[GsheetsError, UpdateValuesResponse]] = - client.put(spreadsheetID / "values" / range, updates, + client.put(io.lemonlabs.uri.Url.parse(s"$spreadsheetID/values/${range.show}"), updates, List(("valueInputOption", valueInputOption.value))) } diff --git a/src/main/scala/gsheets4s/model.scala b/src/main/scala/gsheets4s/model.scala index 450a399..b50dd8a 100644 --- a/src/main/scala/gsheets4s/model.scala +++ b/src/main/scala/gsheets4s/model.scala @@ -7,17 +7,16 @@ import cats.Show import cats.syntax.apply._ import cats.syntax.either._ import cats.syntax.show._ +import com.typesafe.scalalogging.StrictLogging import eu.timepit.refined.api.Refined import eu.timepit.refined.boolean._ import eu.timepit.refined.char._ import eu.timepit.refined.collection._ import eu.timepit.refined.numeric._ -import gsheets4s.model.A1Notation import io.circe.{Decoder, DecodingFailure, Encoder, HCursor} import io.circe.generic.semiauto._ -import io.lemonlabs.uri.typesafe._ -object model extends A1NotationLiteralSyntax { +object model extends StrictLogging{ type ValidCol = NonEmpty And Forall[UpperCase] type Col = String Refined ValidCol type ValidRow = Positive @@ -51,7 +50,6 @@ object model extends A1NotationLiteralSyntax { sealed trait A1Notation object A1Notation { - implicit val pathPartA1Notation: PathPart[A1Notation] = _.show implicit val showA1Notation: Show[A1Notation] = Show.show { case SheetNameNotation(s) => s.toString case RangeNotation(r) => r.show @@ -62,35 +60,6 @@ object model extends A1NotationLiteralSyntax { .mapN(SheetNameRangeNotation.apply) | Range.parser.map(RangeNotation(_): A1Notation) | stringOf1(elem(_ => true)).map(SheetNameNotation(_): A1Notation) - - /** Thanks to ip4s for the singlePartInterpolator implementation - * https://github.com/Comcast/ip4s/blob/b4f01a4637f2766a8e12668492a3814c478c6a03/shared/src/main/scala/com/comcast/ip4s/LiteralSyntaxMacros.scala - */ - object LiteralSyntaxMacros { - import scala.reflect.macros.blackbox - - def a1NotationInterpolator(c: blackbox.Context)(args: c.Expr[Any]*): c.Expr[A1Notation] = - singlePartInterpolator(c)( - args, - "A1Notation", - A1Notation.parser.parseOnly(_).either, - s => c.universe.reify(A1Notation.parser.parseOnly(s.splice).option.get)) - - private def singlePartInterpolator[A](c: blackbox.Context)( - args: Seq[c.Expr[Any]], - typeName: String, - validate: String => Either[String, _], - construct: c.Expr[String] => c.Expr[A]): c.Expr[A] = { - import c.universe._ - identity(args) - c.prefix.tree match { - case Apply(_, List(Apply(_, (lcp@Literal(Constant(p: String))) :: Nil))) => - val valid = validate(p) - if (valid.isRight) construct(c.Expr(lcp)) - else c.abort(c.enclosingPosition, s"invalid $typeName: ${valid.left.get}") - } - } - } } final case class SheetNameNotation(sheetName: String) extends A1Notation final case class RangeNotation(range: Range) extends A1Notation @@ -121,7 +90,7 @@ object model extends A1NotationLiteralSyntax { final case class ValueRange( range: A1Notation, majorDimension: Dimension, - values: List[List[String]] + values: List[List[String]] // Todo this value should be optional incase the sheet is empty ) final case class UpdateValuesResponse( @@ -137,8 +106,20 @@ object model extends A1NotationLiteralSyntax { message: String, status: String ) + + val fallbackdecoder: Decoder[GsheetsError] = + new Decoder[GsheetsError] { + final def apply(c: HCursor): Decoder.Result[GsheetsError] = { + val msg = s"gsheets4s errorDecoder raw json ${c.focus.map(_.noSpaces)}. Have you added a row to the sheet? If not please add one ;)" + logger.error(msg) + GsheetsError(400, msg, "").asRight[DecodingFailure] + } + } + implicit val errorDecoder: Decoder[GsheetsError] = - deriveDecoder[GsheetsError].prepare(_.downField("error")) + deriveDecoder[GsheetsError].prepare{ j => + j.downField("error") + }.handleErrorWith(_ => fallbackdecoder) final case class Credentials( accessToken: String, @@ -150,12 +131,3 @@ object model extends A1NotationLiteralSyntax { implicit def eitherDecoder[L, R](implicit l: Decoder[L], r: Decoder[R]): Decoder[Either[L, R]] = r.map(Right(_): Either[L, R]).or(l.map(Left(_): Either[L, R])) } - -trait A1NotationLiteralSyntax { - implicit def toA1NotationLiteralOps(sc: StringContext): A1NotationLiteralOps = - new A1NotationLiteralOps(sc) -} - -final class A1NotationLiteralOps(val sc: StringContext) extends AnyVal { - def a1(args: Any*): A1Notation = macro A1Notation.LiteralSyntaxMacros.a1NotationInterpolator -} diff --git a/src/test/scala/gsheets4s/A1NotationLiteralsSpec.scala b/src/test/scala/gsheets4s/A1NotationLiteralsSpec.scala deleted file mode 100644 index 3e131b5..0000000 --- a/src/test/scala/gsheets4s/A1NotationLiteralsSpec.scala +++ /dev/null @@ -1,16 +0,0 @@ -package gsheets4s - -import eu.timepit.refined.auto._ -import gsheets4s.model._ -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class A1NotationLiteralsSpec extends AnyFlatSpec with Matchers { - - behavior of "A1Notation literals" - - it should "parse an A1 notation literal" in { - a1"Sheet1!A1:B2" should be(SheetNameRangeNotation("Sheet1", Range(ColRowPosition("A", 1), ColRowPosition("B", 2)))) - } - -} diff --git a/src/test/scala/gsheets4s/ModelSpec.scala b/src/test/scala/gsheets4s/ModelSpec.scala index 9d2b91b..746a82b 100644 --- a/src/test/scala/gsheets4s/ModelSpec.scala +++ b/src/test/scala/gsheets4s/ModelSpec.scala @@ -45,4 +45,16 @@ object ModelSpec extends Properties("model") { case Right(r) => decode[Either[GsheetsError, ValueRange]](r.asJson.noSpaces) == Right(e) } } + + property("Either decoder fallback") = forAll { e: Either[GsheetsError, ValueRange] => + e match { + case Left(l) => + val js = Json.obj(("error-V2", l.asJson)).noSpaces + val x = decode[Either[GsheetsError, ValueRange]](js) + val y = Right(Left(GsheetsError(400, s"gsheets4s errorDecoder raw json Some($js). Have you added a row to the sheet? If not please add one ;)", ""))) + x == y + case Right(r) => + decode[Either[GsheetsError, ValueRange]](r.asJson.noSpaces) == Right(e) + } + } //${c.focus.map(_.noSpaces)} } diff --git a/src/test/scala/gsheets4s/interpreters/TestSpreadsheetsValues.scala b/src/test/scala/gsheets4s/interpreters/TestSpreadsheetsValues.scala index 70b5be9..c6dded2 100644 --- a/src/test/scala/gsheets4s/interpreters/TestSpreadsheetsValues.scala +++ b/src/test/scala/gsheets4s/interpreters/TestSpreadsheetsValues.scala @@ -3,6 +3,8 @@ package interpreters import cats.Id import cats.syntax.foldable._ +import cats.instances.int._ +import cats.instances.option._ import algebras._ import model._