3

I have a misunderstanding with exception handling in ZIO. I followed the ZIO-Documentation.

In a test class I run the following code:

  new DefaultRuntime {}.unsafeRun(
    (for {
      peopleRef <- Ref.make(Vector(People()))
      _ <- people(2).provide(Test(peopleRef)) // this throws the exception
    } yield ())
      .fold(
        err =>
          err shouldBe ServiceException("No People with id 2"), 
        _ => fail("line above should fail")
      )
  )

I thought with the fold function I could handle this exception, but it does not even get there. I get this Exception on the console:

Fiber failed.
An unchecked error was produced.
pme123.zio.examples.swapi.package$ServiceException
    at pme123.zio.examples.swapi.Swapi$Test$$anon$4.$anonfun$people$6(Swapi.scala:57)
    at zio.ZIO$MapFn.apply(ZIO.scala:2590)
    at zio.ZIO$MapFn.apply(ZIO.scala:2588)
    at zio.internal.FiberContext.evaluateNow(FiberContext.scala:709)
    at zio.Runtime.unsafeRunAsync(Runtime.scala:93)
    ....

What do I miss?

Here is a minimal example:

object MyApp
  extends App {

  def run(args: List[String]): ZIO[Environment, Nothing, Int] =
    program
      .fold({ error => 1 // this is not reached
      }, _ => 0)

  private lazy val program = for {
    peopleRef <- Ref.make(Vector(22))
    _ <- Test(peopleRef).people(12)
  } yield ()

  case class Test(ref: Ref[Vector[Int]]) {

    def people(id: Int): Task[Int] =
      for {
        ints <- ref.get
      } yield ints.find(_ == id) match {
        case None => throw new IllegalArgumentException(s"No People with id $id") // this is thrown
        case Some(p) =>  p
      }
  }
}

Here is the whole code: SwapiTest.scala

pme
  • 14,156
  • 3
  • 52
  • 95

1 Answers1

3

ZIO doesn't catch exception unless you wrap it in ZIO. You can use ZIO.effect to wrap the entire block of legacy unsafe code throwing exceptions or just use IO.fail for specific exception.

I refactored your code to be more ZIO-like and produce failed Task instead of throwing exception:

case class Test(ref: Ref[Vector[Int]]) {

  def people(id: Int):Task[Int]=
    for {
      ints <- ref.get
      int  <- ZIO.effectTotal(ints.find(_ == id))
      res  <- int match {
        case None => IO.fail(new IllegalArgumentException(s"No People with id $id"))
        case Some(p) =>  ZIO.effectTotal(p)
      }
    }  yield res

}

In order to catch IllegalArgumentException you need the following fold:

...

.fold(
    err => err shouldBe IllegalArgumentException("No People with id 2"), 
    _   => fail("line above should fail")
 )

ServiceException should not appear here as nothing throwing it.

Bogdan Vakulenko
  • 3,380
  • 1
  • 10
  • 25
  • thanks, so in essence, the exception must be wrapped in a `IO.fail` and put into the `for-comprehension` – pme Oct 20 '19 at 16:44