diff --git a/modules/core-tests/shared/src/test/scala/TraceValueTest.scala b/modules/core-tests/shared/src/test/scala/TraceValueTest.scala index 2daeaebf..ee7de8d8 100644 --- a/modules/core-tests/shared/src/test/scala/TraceValueTest.scala +++ b/modules/core-tests/shared/src/test/scala/TraceValueTest.scala @@ -5,6 +5,10 @@ package natchez import cats.Id +import cats.data.* +import cats.laws.discipline.arbitrary.* +import munit.ScalaCheckSuite +import org.scalacheck.Prop object TraceValueTest { // should compile @@ -13,3 +17,64 @@ object TraceValueTest { def traceValueFloat() = Trace.Implicits.noop[Id].put(fields = ("foo", 1.0f)) def traceValueDouble() = Trace.Implicits.noop[Id].put(fields = ("foo", 1.0d)) } + +class TraceableValueSpec extends ScalaCheckSuite { + + test( + "TraceableValue[Either[String, Int]] should be a TraceValue.StringValue for Left and TraceValue.NumberValue for Right" + ) { + Prop.forAll { (input: Either[String, Int]) => + val output = TraceableValue[Either[String, Int]].toTraceValue(input) + + input match { + case Left(l) => assertEquals(output, TraceValue.StringValue(l)) + case Right(r) => assertEquals(output, TraceValue.NumberValue(r)) + } + } + } + + test( + "TraceableValue[Validated[String, Int]] should be a TraceValue.StringValue for Invalid and TraceValue.NumberValue for Valid" + ) { + Prop.forAll { (input: Validated[String, Int]) => + val output = TraceableValue[Validated[String, Int]].toTraceValue(input) + + input match { + case Validated.Invalid(l) => assertEquals(output, TraceValue.StringValue(l)) + case Validated.Valid(r) => assertEquals(output, TraceValue.NumberValue(r)) + } + } + } + + test( + "TraceableValue[Ior[String, Int]] should be a TraceValue.StringValue for Left and TraceValue.NumberValue for Right or Both" + ) { + Prop.forAll { (input: Ior[String, Int]) => + val output = TraceableValue[Ior[String, Int]].toTraceValue(input) + + input match { + case Ior.Left(l) => assertEquals(output, TraceValue.StringValue(l)) + case Ior.Right(r) => assertEquals(output, TraceValue.NumberValue(r)) + case Ior.Both(_, r) => + assertEquals(output, TraceValue.NumberValue(r)) + } + } + } + + test("TraceableValue[(String, Int)] should be a TraceValue.NumberValue") { + Prop.forAll { (input: (String, Int)) => + val output = TraceableValue[(String, Int)].toTraceValue(input) + + assertEquals(output, TraceValue.NumberValue(input._2)) + } + } + + test("TraceableValue[Const[String, *]] should be a TraceValue.StringValue") { + Prop.forAll { (input: Const[String, Int]) => + val output = TraceableValue[Const[String, Int]].toTraceValue(input) + + assertEquals(output, TraceValue.StringValue(input.getConst)) + } + } + +} diff --git a/modules/core/shared/src/main/scala/TraceValue.scala b/modules/core/shared/src/main/scala/TraceValue.scala index c388886c..8d747003 100644 --- a/modules/core/shared/src/main/scala/TraceValue.scala +++ b/modules/core/shared/src/main/scala/TraceValue.scala @@ -4,6 +4,8 @@ package natchez +import cats.* + sealed trait TraceValue extends Product with Serializable { def value: Any } @@ -41,6 +43,7 @@ object TraceValue { * * @tparam A The type to be converted to `TraceValue` */ +@FunctionalInterface trait TraceableValue[A] { outer => def toTraceValue(a: A): TraceValue @@ -57,4 +60,16 @@ object TraceableValue { implicit val longToTraceValue: TraceableValue[Long] = TraceValue.NumberValue(_) implicit val doubleToTraceValue: TraceableValue[Double] = TraceValue.NumberValue(_) implicit val floatToTraceValue: TraceableValue[Float] = TraceValue.NumberValue(_) + + implicit val traceValueIdentity: TraceableValue[TraceValue] = identity + + implicit def bifoldableTraceableValue[F[_, _]: Bifoldable, A: TraceableValue, B: TraceableValue] + : TraceableValue[F[A, B]] = + Bifoldable[F] + .bifoldRight(_, Eval.later[TraceValue](???))( + (a, _) => Eval.now(TraceableValue[A].toTraceValue(a)), + (b, _) => Eval.now(TraceableValue[B].toTraceValue(b)) + ) + .value + }