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 + } + + } +}