Skip to content

Commit

Permalink
Add Logger to ErrorPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
hugo-vrijswijk committed Nov 29, 2023
1 parent 8591037 commit 3d2e2a9
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 10 deletions.
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ lazy val scala3Version = "3.3.1"
lazy val rulesCrossVersions = Seq(V.scala213)
lazy val allVersions = rulesCrossVersions :+ scala3Version

ThisBuild / tlBaseVersion := "0.34"
ThisBuild / tlBaseVersion := "0.35"
ThisBuild / tlCiReleaseBranches := Seq("master")
ThisBuild / tlJdkRelease := Some(8)
ThisBuild / githubWorkflowJavaVersions := Seq("11", "17").map(JavaSpec.temurin(_))
Expand Down Expand Up @@ -56,6 +56,7 @@ lazy val core =
Settings.Libraries.Fs2.value ++
Settings.Libraries.Log4Cats.value ++
Settings.Libraries.DisciplineMUnit.value ++
Settings.Libraries.MUnitCatsEffect.value ++
Settings.Libraries.MUnit.value
)
.dependsOn(model)
Expand Down
28 changes: 20 additions & 8 deletions core/src/main/scala/clue/ErrorPolicy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import cats.syntax.all._
import clue.model.GraphQLDataResponse
import clue.model.GraphQLErrors
import clue.model.GraphQLResponse
import org.typelevel.log4cats.Logger

sealed trait ErrorPolicy {
type ReturnType[D]
Expand All @@ -18,7 +19,7 @@ sealed trait ErrorPolicy {
}

sealed trait ErrorPolicyProcessor[D, R] {
def process[F[_]: ApplicativeThrow](response: GraphQLResponse[D]): F[R]
def process[F[_]: ApplicativeThrow](response: GraphQLResponse[D])(implicit log: Logger[F]): F[R]
}

object ErrorPolicy {
Expand All @@ -31,15 +32,24 @@ object ErrorPolicy {
ApplicativeThrow[F].raiseError(ResponseException(errors, data))
}

/**
* If the response contains data, return it. If the response contains errors, raise an exception.
* If the response contains both data and errors, log the errors and return the data.
*/
object IgnoreOnData extends ErrorPolicy {
type ReturnType[D] = D

def processor[D]: ErrorPolicyProcessor[D, D] = new Distinct[D] {
def process[F[_]: ApplicativeThrow](response: GraphQLResponse[D]): F[D] =
def process[F[_]: ApplicativeThrow](
response: GraphQLResponse[D]
)(implicit log: Logger[F]): F[D] =
response.result match {
case Ior.Left(errors) => processErrors(errors)
case Ior.Right(data) => processData(data)
case Ior.Both(_, data) => processData(data)
case Ior.Left(errors) => processErrors(errors)
case Ior.Right(data) => processData(data)
case Ior.Both(errors, data) =>
log.warn(ResponseException(errors, data.some))(
"Received both data and errors"
) *> processData(data)
}
}
}
Expand All @@ -48,7 +58,9 @@ object ErrorPolicy {
type ReturnType[D] = D

def processor[D]: ErrorPolicyProcessor[D, D] = new Distinct[D] {
def process[F[_]: ApplicativeThrow](response: GraphQLResponse[D]): F[ReturnType[D]] =
def process[F[_]: ApplicativeThrow](
response: GraphQLResponse[D]
)(implicit log: Logger[F]): F[ReturnType[D]] =
response.result match {
case Ior.Left(errors) => processErrors(errors)
case Ior.Right(data) => processData(data)
Expand All @@ -65,7 +77,7 @@ object ErrorPolicy {
new ErrorPolicyProcessor[D, GraphQLResponse[D]] {
def process[F[_]: ApplicativeThrow](
response: GraphQLResponse[D]
): F[ReturnType[D]] =
)(implicit log: Logger[F]): F[ReturnType[D]] =
Applicative[F].pure(response)
}
}
Expand All @@ -78,7 +90,7 @@ object ErrorPolicy {

def process[F[_]: ApplicativeThrow](
response: GraphQLResponse[D]
): F[ReturnType[D]] =
)(implicit log: Logger[F]): F[ReturnType[D]] =
response.result match {
case Ior.Left(errors) => ApplicativeThrow[F].raiseError(ResponseException(errors, none))
case Ior.Right(data) => Applicative[F].pure(GraphQLDataResponse(data, none, none))
Expand Down
123 changes: 123 additions & 0 deletions core/src/test/scala/clue/ErrorPolicySpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package clue

import cats.data.Ior
import cats.data.NonEmptyList
import cats.effect.IO
import cats.syntax.all.*
import clue.model.GraphQLDataResponse
import clue.model.GraphQLError
import clue.model.GraphQLResponse
import org.scalacheck.Arbitrary
import org.typelevel.log4cats.testing.StructuredTestingLogger

class ErrorPolicySpec extends munit.CatsEffectSuite {

private val loggerFixture =
FunFixture[StructuredTestingLogger[IO]](_ => StructuredTestingLogger.impl[IO](), _ => ())

loggerFixture.test("IgnoreOnData only errors") { implicit logger =>
val policy = ErrorPolicy.IgnoreOnData.processor[Int]

val result = policy.process[IO](leftResponse)

result.intercept[ResponseException[Int]] *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("IgnoreOnData only data") { implicit logger =>
val policy = ErrorPolicy.IgnoreOnData.processor[Int]

val result = policy.process[IO](rightResponse)

result.assertEquals(1) *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("IgnoreOnData errors and data") { implicit logger =>
val policy = ErrorPolicy.IgnoreOnData.processor[Int]

val result = policy.process[IO](bothResponse)

result.assertEquals(1) *>
logger.logged.assertEquals(
Vector(
StructuredTestingLogger.WARN(
"Received both data and errors",
throwOpt = ResponseException(NonEmptyList.one(graphQlError), 1.some).some
)
)
)
}

loggerFixture.test("RaiseAlways only errors") { implicit logger =>
val policy = ErrorPolicy.RaiseAlways.processor[Int]

val result = policy.process[IO](leftResponse)

result.intercept[ResponseException[Int]] *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("RaiseAlways only data") { implicit logger =>
val policy = ErrorPolicy.RaiseAlways.processor[Int]

val result = policy.process[IO](rightResponse)

result.assertEquals(1) *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("RaiseAlways errors and data") { implicit logger =>
val policy = ErrorPolicy.RaiseAlways.processor[Int]

val result = policy.process[IO](bothResponse)

result.intercept[ResponseException[Int]] *> logger.logged.assert(_.isEmpty)
}

loggerFixture.test("ReturnAlways always returns whole response") { implicit logger =>
val policy = ErrorPolicy.ReturnAlways.processor[Int]

val result = policy.process[IO](bothResponse)

result.assertEquals(bothResponse) *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("RaiseOnNoData only errors") { implicit logger =>
val policy = ErrorPolicy.RaiseOnNoData.processor[Int]

val result = policy.process[IO](leftResponse)

result.intercept[ResponseException[Int]] *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("RaiseOnNoData only data") { implicit logger =>
val policy = ErrorPolicy.RaiseOnNoData.processor[Int]

val result = policy.process[IO](rightResponse)

result.assertEquals(GraphQLDataResponse(1, None, None)) *>
logger.logged.assert(_.isEmpty)
}

loggerFixture.test("RaiseOnNoData errors and data") { implicit logger =>
val policy = ErrorPolicy.RaiseOnNoData.processor[Int]

val result = policy.process[IO](bothResponse)

result.assertEquals(GraphQLDataResponse(1, NonEmptyList.one(graphQlError).some, None)) *>
logger.logged.assert(_.isEmpty)
}

val graphQlError = Arbitrary.arbString.arbitrary.map(GraphQLError(_)).sample.get

def leftResponse = GraphQLResponse.errors[Int](NonEmptyList.one(graphQlError))
def rightResponse = GraphQLResponse[Int](result = Ior.right(1))
def bothResponse = GraphQLResponse[Int](result = Ior.both(NonEmptyList.one(graphQlError), 1))

}
10 changes: 9 additions & 1 deletion project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object Settings {
val log4Cats = "2.6.0"
val monocle = "3.2.0"
val munit = "0.7.29"
val munitCatsEffect = "2.0.0-M4"
val scalaFix = scalafix.sbt.BuildInfo.scalafixVersion
val scalaJSDom = "2.8.0"
val scalaJSMacrotaskExecutor = "1.1.1"
Expand Down Expand Up @@ -109,7 +110,8 @@ object Settings {

val Log4Cats = Def.setting(
Seq(
"org.typelevel" %%% "log4cats-core" % log4Cats
"org.typelevel" %%% "log4cats-core" % log4Cats,
"org.typelevel" %%% "log4cats-testing" % log4Cats % "test"
)
)

Expand All @@ -126,6 +128,12 @@ object Settings {
)
)

val MUnitCatsEffect = Def.setting(
Seq[ModuleID](
"org.typelevel" %%% "munit-cats-effect" % munitCatsEffect % "test"
)
)

val ScalaFix = Def.setting(
Seq(
"ch.epfl.scala" %%% "scalafix-core" % scalaFix
Expand Down

0 comments on commit 3d2e2a9

Please sign in to comment.