Skip to content

Commit

Permalink
Test error accumulation with ZIO
Browse files Browse the repository at this point in the history
  • Loading branch information
neko-kai committed Oct 16, 2023
1 parent 5c09ab7 commit 1e06c6a
Showing 1 changed file with 72 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,109 +3,119 @@ package izumi.functional.bio
import izumi.fundamentals.collections.nonempty.{NEList, NESet}
import org.scalatest.wordspec.AnyWordSpec

import scala.annotation.nowarn
import scala.annotation.{nowarn, unused}

final class ErrorAccumulatingOpsTestEither extends ErrorAccumulatingOpsTest[Either] {
override implicit def F: Error2[Either] = Root.BIOEither
override def unsafeRun[E, A](f: Either[E, A]): Either[E, A] = f
}

final class ErrorAccumulatingOpsTestZIO extends ErrorAccumulatingOpsTest[zio.IO] {
private val runner: UnsafeRun2[zio.IO] = UnsafeRun2.createZIO()

override implicit def F: Error2[zio.IO] = Root.Convert3To2(Root.BIOZIO)
override def unsafeRun[E, A](f: zio.IO[E, A]): Either[E, A] = runner.unsafeRun(f.attempt)
}

@nowarn("msg=Unused import")
class ErrorAccumulatingOpsTest extends AnyWordSpec {
abstract class ErrorAccumulatingOpsTest[F[+_, +_]] extends AnyWordSpec {
import scala.collection.compat.*

type BuilderFail
type IzType
type Result[+T] = Either[List[BuilderFail], T]
type Result[+T] = F[List[BuilderFail], T]
type TList = Result[List[IzType]]

def listTList: List[TList] = Nil
def x(t: TList): Result[Unit] = Right { val _ = t }

def F: Error2[Either] = Root.BIOEither

/** `flatSequence` with error accumulation */
def flatSequenceAccumErrors[ColR[x] <: IterableOnce[x], ColL[_], E, A](
col: ColR[Either[ColL[E], IterableOnce[A]]]
)(implicit
buildR: Factory[A, ColR[A]],
buildL: Factory[E, ColL[E]],
iterL: ColL[E] => IterableOnce[E],
): Either[ColL[E], ColR[A]] = {
F.flatSequenceAccumErrors(col)
def x(@unused t: TList): Result[Unit] = F.unit

implicit def F: Error2[F]
def unsafeRun[E, A](f: F[E, A]): Either[E, A]

implicit final class Run[+E, +A](f: F[E, A]) {
def run(): Either[E, A] = unsafeRun(f)
}

"ErrorAccumulatingOps" should {

"have biFlatAggregate callable with typealiases" in {

def test: Either[List[BuilderFail], Unit] = {
val ret = F.flatSequenceAccumErrors /*[List, List, Any, BuilderFail, IzType]*/ (listTList: List[Either[List[BuilderFail], List[IzType]]])
def test: F[List[BuilderFail], Unit] = {
val ret = F.flatSequenceAccumErrors(listTList)
x(ret)
}

assert(test.isRight)
assert(test.run().isRight)
}

"support NonEmptyCollections" in {
val nel0 = List(Right(1), Right(2), Left(NEList("error")))
val nel0 = List(F.pure(1), F.pure(2), F.fail(NEList("error")))

assert(implicitly[Factory[String, NEList[String]]] ne null)
assert(F.sequenceAccumErrors(nel0) == Left(NEList("error")))

val nes0 = List(Right(()), Left(NESet("error")))
assert(F.sequenceAccumErrors(nes0) == Left(NESet("error")))

assert(F.traverseAccumErrors_(nes0) {
case Left(value) => Left(value + "error1")
case Right(value) => Right(value)
} == Left(NESet("error", "error1")))

val nel1 = List(Right(List(1)), Left(NEList("error")))
assert(F.flatSequenceAccumErrors(nel1) == Left(NEList("error")))

assert(F.traverseAccumErrors(nel1) {
case Left(value) => Left(Set(value ++ NEList("a")))
case Right(value) => Right(Set(value ++ List(1)))
} == Left(Set(NEList("error", "a"))))

val nel2 = List(Right(1), Left(NEList("error")))
assert(F.flatTraverseAccumErrors(nel2)(_.map(i => List(i))) == Left(NEList("error")))
assert(F.flatTraverseAccumErrors(nel2)(_.map(i => List(i))).map(_.to(Set)) == Left(NEList("error")))
assert(F.sequenceAccumErrors(nel0).run() == Left(NEList("error")))

val nes0 = List(F.pure(()), F.fail(NESet("error")))
assert(F.sequenceAccumErrors(nes0).run() == Left(NESet("error")))

assert(
F.traverseAccumErrors_(nes0)(_.attempt.flatMap {
case Left(value) => F.fail(value + "error1")
case Right(value) => F.pure(value)
}).run() == Left(NESet("error", "error1"))
)

val nel1 = List(F.pure(List(1)), F.fail(NEList("error")))
assert(F.flatSequenceAccumErrors(nel1).run() == Left(NEList("error")))

assert(
F.traverseAccumErrors(nel1)(_.attempt.flatMap {
case Left(value) => F.fail(Set(value ++ NEList("a")))
case Right(value) => F.pure(Set(value ++ List(1)))
}).run() == Left(Set(NEList("error", "a")))
)

val nel2 = List(F.pure(1), F.fail(NEList("error")))
assert(F.flatTraverseAccumErrors(nel2)(_.map(i => List(i))).run() == Left(NEList("error")))
assert(F.flatTraverseAccumErrors(nel2)(_.map(i => List(i))).map(_.to(Set)).run() == Left(NEList("error")))
}

"support the happy path" in {
val l0: Seq[Either[List[String], Int]] = List(Right(1), Right(2), Right(3))
assert((F.sequenceAccumErrors(l0): Either[List[String], Seq[Int]]) == Right(Seq(1, 2, 3)))
assert(F.sequenceAccumErrors_(l0) == Right(()))
assert(F.traverseAccumErrors(l0)(identity) == Right(Seq(1, 2, 3)))
assert(F.traverseAccumErrors(l0)(identity).map(_.to(List)) == Right(List(1, 2, 3)))
assert(F.traverseAccumErrors_(l0)(_.map(_ => ())) == Right(()))
val l0: Seq[F[List[String], Int]] = List(F.pure(1), F.pure(2), F.pure(3))
assert(F.sequenceAccumErrors(l0).run() == Right(Seq(1, 2, 3)))
assert(F.sequenceAccumErrors_(l0).run() == Right(()))
assert(F.traverseAccumErrors(l0)(identity).run() == Right(Seq(1, 2, 3)))
assert(F.traverseAccumErrors(l0)(identity).map(_.to(List)).run() == Right(List(1, 2, 3)))
assert(F.traverseAccumErrors_(l0)(_.map(_ => ())).run() == Right(()))

val l1: Seq[Either[List[String], Seq[Int]]] = List(Right(Seq(1)), Right(Seq(2)), Right(Seq(3)))
assert(F.flatTraverseAccumErrors(l1)(identity) == Right(Seq(1, 2, 3)))
assert(F.flatTraverseAccumErrors(l1)(identity).map(_.to(List)) == Right(List(1, 2, 3)))
val l1: Seq[F[List[String], Seq[Int]]] = List(F.pure(Seq(1)), F.pure(Seq(2)), F.pure(Seq(3)))
assert(F.flatTraverseAccumErrors(l1)(identity).run() == Right(Seq(1, 2, 3)))
assert(F.flatTraverseAccumErrors(l1)(identity).map(_.to(List)).run() == Right(List(1, 2, 3)))

assert(F.flatSequenceAccumErrors(l1) == Right(List(1, 2, 3)))
assert(F.flatSequenceAccumErrors(l1).run() == Right(List(1, 2, 3)))

val l2: Seq[Either[String, Int]] = List(Right(1), Right(2), Right(3), Left("error"))
assert(F.sequenceAccumErrorsNEList(l2) == Left(NEList("error")))
val l2: Seq[F[String, Int]] = List(F.pure(1), F.pure(2), F.pure(3), F.fail("error"))
assert(F.sequenceAccumErrorsNEList(l2).run() == Left(NEList("error")))

val l3: Seq[Either[List[String], Int]] = List(Right(1), Right(2), Right(3), Left(List("error")))
assert(F.sequenceAccumErrors(l3) == Left(List("error")))
val l3: Seq[F[List[String], Int]] = List(F.pure(1), F.pure(2), F.pure(3), F.fail(List("error")))
assert(F.sequenceAccumErrors(l3).run() == Left(List("error")))

assert(Right(Seq(1, 2, 3)).map(_.to(List)) == Right(List(1, 2, 3)))
}

"support search" in {
assert(F.find(List(1, 2, 3))(i => Right(i == 2)) == Right(Some(2)))
assert(F.find(List(1, 2, 3))(i => Right(i == 4)) == Right(None))
assert(F.find(List(1, 2, 3))(_ => Left("error")) == Left("error"))
assert(F.find(List(1, 2, 3))(i => F.pure(i == 2)).run() == Right(Some(2)))
assert(F.find(List(1, 2, 3))(i => F.pure(i == 4)).run() == Right(None))
assert(F.find(List(1, 2, 3))(_ => F.fail("error")).run() == Left("error"))
}

"support fold" in {
assert(F.foldLeft(List(1, 2, 3))("") { case (acc, v) => Right(s"$acc.$v") } == Right(".1.2.3"))
assert(F.foldLeft(List(1, 2, 3))("") { case (_, _) => Left("error") } == Left("error"))
assert(F.foldLeft(List(1, 2, 3))("") { case (acc, v) => F.pure(s"$acc.$v") }.run() == Right(".1.2.3"))
assert(F.foldLeft(List(1, 2, 3))("") { case (_, _) => F.fail("error") }.run() == Left("error"))
}

"support partitioning" in {
val lst = List(Right(1), Right(2), Left(3))
val Right((l, r)) = F.partition(lst)
val lst = List(F.pure(1), F.pure(2), F.fail(3))
val Right((l, r)) = F.partition(lst).run(): @unchecked
assert(l == List(3))
assert(r == List(1, 2))
}
Expand Down

0 comments on commit 1e06c6a

Please sign in to comment.