From 4d1e1e7922195e1daa6760e602476e4648f62319 Mon Sep 17 00:00:00 2001 From: Koisell Date: Sun, 26 May 2019 21:47:29 +0200 Subject: [PATCH] Zio interoperability It order to empowered more taggeless final users and simplify migrations from/to ZIO we introduce some interoperability library for it. This library works the same way that Monix interoperability. It's currently working only on JVM, ScalaJS support will follow (for now it won't compile). Be aware that if you are completely commit to either Monix or ZIO both libraries already have their internal support for retry policies. --- README.md | 3 +- build.sbt | 23 ++++++- modules/docs/src/main/tut/docs/sleep.md | 2 + modules/docs/src/main/tut/index.md | 3 +- .../src/main/scala/retry/ScalaZIO.scala | 15 +++++ .../src/test/scala/retry/ScalaZIOSpec.scala | 67 +++++++++++++++++++ 6 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 modules/zio/shared/src/main/scala/retry/ScalaZIO.scala create mode 100644 modules/zio/shared/src/test/scala/retry/ScalaZIOSpec.scala diff --git a/README.md b/README.md index 5f29d85c..6b3d2aa4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ A library for retrying actions that can fail. Designed to work with [cats](https://typelevel.org/cats/) and (optionally) -[cats-effect](https://typelevel.org/cats-effect/) or [Monix](https://monix.io/). +[cats-effect](https://typelevel.org/cats-effect/) or [Monix](https://monix.io/) +and even compatible with [ZIO](https://zio.dev/). Inspired by the [retry Haskell package](https://hackage.haskell.org/package/retry). diff --git a/build.sbt b/build.sbt index 76def051..b78e32cb 100644 --- a/build.sbt +++ b/build.sbt @@ -110,9 +110,29 @@ val monix = crossProject(JVMPlatform, JSPlatform) val monixJVM = monix.jvm val monixJS = monix.js +val zio = crossProject(JVMPlatform, JSPlatform) + .in(file("modules/zio")) + .jvmConfigure(_.dependsOn(coreJVM)) + .settings(moduleSettings) + .settings( + resolvers += Resolver.sonatypeRepo("releases"), + libraryDependencies ++= Seq( + "dev.zio" %%% "zio" % "1.0.0-RC11-1", + "org.typelevel" %%% "cats-effect" % "2.0.0-M4", + compilerPlugin( + "org.typelevel" % "kind-projector" % "0.10.1" cross CrossVersion.binary + ), + "org.scalatest" %%% "scalatest" % scalatestVersion % Test, + "org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test, + "dev.zio" %%% "core-tests" % "1.0.0-RC11-1" % Test, + "dev.zio" %%% "zio-interop-cats" % "2.0.0.0-RC2" % Test + ) + ) +val zioJVM = zio.jvm + val docs = project .in(file("modules/docs")) - .dependsOn(coreJVM, catsEffectJVM, monixJVM) + .dependsOn(coreJVM, catsEffectJVM, monixJVM, zioJVM) .enablePlugins(MicrositesPlugin, BuildInfoPlugin) .settings(moduleSettings) .settings( @@ -144,6 +164,7 @@ val root = project catsEffectJS, monixJVM, monixJS, + zioJVM, docs ) .settings(commonSettings) diff --git a/modules/docs/src/main/tut/docs/sleep.md b/modules/docs/src/main/tut/docs/sleep.md index 0cf8e919..e09c7d84 100644 --- a/modules/docs/src/main/tut/docs/sleep.md +++ b/modules/docs/src/main/tut/docs/sleep.md @@ -53,5 +53,7 @@ Sleep[IO].sleep(10.milliseconds) The `monix` module provides an instance that calls `Task.sleep`. +The `zio` module provides an instance that calls `ZIO.sleep`. + Being able to inject your own `Sleep` instance can be handy in tests, as you can mock it out to avoid slowing down your unit tests. diff --git a/modules/docs/src/main/tut/index.md b/modules/docs/src/main/tut/index.md index 66417072..3e4609ad 100644 --- a/modules/docs/src/main/tut/index.md +++ b/modules/docs/src/main/tut/index.md @@ -6,7 +6,8 @@ title: "cats-retry" A library for retrying actions that can fail. Designed to work with [cats](https://typelevel.org/cats/) and (optionally) -[cats-effect](https://typelevel.org/cats-effect/) or [Monix](https://monix.io/). +[cats-effect](https://typelevel.org/cats-effect/) or [Monix](https://monix.io/) +and even compatible with [ZIO](https://zio.dev/). Inspired by the [retry Haskell package](https://hackage.haskell.org/package/retry). diff --git a/modules/zio/shared/src/main/scala/retry/ScalaZIO.scala b/modules/zio/shared/src/main/scala/retry/ScalaZIO.scala new file mode 100644 index 00000000..1dc6cf11 --- /dev/null +++ b/modules/zio/shared/src/main/scala/retry/ScalaZIO.scala @@ -0,0 +1,15 @@ +package retry + +import zio.ZIO +import zio.clock.Clock +import zio.duration.Duration + +import scala.concurrent.duration.FiniteDuration + +object ScalaZIO { + implicit val zioSleep: Sleep[ZIO[Clock, Nothing, ?]] = + new Sleep[ZIO[Clock, Nothing, ?]] { + override def sleep(delay: FiniteDuration): ZIO[Clock, Nothing, Unit] = + ZIO.sleep(Duration.fromScala(delay)) + } +} diff --git a/modules/zio/shared/src/test/scala/retry/ScalaZIOSpec.scala b/modules/zio/shared/src/test/scala/retry/ScalaZIOSpec.scala new file mode 100644 index 00000000..b22a3564 --- /dev/null +++ b/modules/zio/shared/src/test/scala/retry/ScalaZIOSpec.scala @@ -0,0 +1,67 @@ +package retry + +import retry.ScalaZIO._ + +import org.scalatest.flatspec.AnyFlatSpec +import zio.clock.Clock +import zio.duration.{Duration => ZDuration} +import zio.interop.catz._ +import zio.test.mock.mockEnvironmentManaged +import zio.{DefaultRuntime, UIO, ZIO} + +import scala.collection.mutable.ArrayBuffer +import scala.concurrent.duration._ + +class ScalaZIOSpec extends AnyFlatSpec { + + behavior of "retryingM" + + it should "retry until the action succeeds" in new TestContext { + val policy = RetryPolicies.constantDelay[ZIO[Clock, Nothing, ?]](10.second) + + val finalResult = retryingM[Int][ZIO[Clock, Nothing, ?]]( + policy, + _.toInt > 3, + onError + ) { + ZIO.effectTotal { + attempts = attempts + 1 + attempts + } + } + + val runtime = new DefaultRuntime {} + val mockRuntime = mockEnvironmentManaged + val f = runtime.unsafeRun { + mockRuntime.use { env => + env.clock.sleep(ZDuration.fromScala(30 seconds)) *> + finalResult.provide(env) + } + } + + assert(f == 4) + assert(attempts == 4) + assert(errors.toList == List(1, 2, 3)) + assert(delays.toList == List(10.second, 10.second, 10.second)) + assert(!gaveUp) + + } + + private class TestContext { + + var attempts = 0 + val errors = ArrayBuffer.empty[Int] + val delays = ArrayBuffer.empty[FiniteDuration] + var gaveUp = false + + def onError(error: Int, details: RetryDetails): UIO[Unit] = { + errors.append(error) + details match { + case RetryDetails.WillDelayAndRetry(delay, _, _) => delays.append(delay) + case RetryDetails.GivingUp(_, _) => gaveUp = true + } + ZIO.unit + } + + } +}