Skip to content

Commit

Permalink
Implement noneOrFail zio#4841
Browse files Browse the repository at this point in the history
  • Loading branch information
Michel Daviot committed Mar 29, 2021
1 parent 31234be commit 57f6290
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 17 deletions.
88 changes: 71 additions & 17 deletions core-tests/shared/src/test/scala/zio/ZIOSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ object ZIOSpec extends ZIOBaseSpec {
suite("cached")(
testM("returns new instances after duration") {
def incrementAndGet(ref: Ref[Int]): UIO[Int] = ref.updateAndGet(_ + 1)

for {
ref <- Ref.make(0)
cache <- incrementAndGet(ref).cached(60.minutes)
Expand All @@ -162,6 +163,7 @@ object ZIOSpec extends ZIOBaseSpec {
suite("cachedInvalidate")(
testM("returns new instances after duration") {
def incrementAndGet(ref: Ref[Int]): UIO[Int] = ref.updateAndGet(_ + 1)

for {
ref <- Ref.make(0)
tuple <- incrementAndGet(ref).cachedInvalidate(60.minutes)
Expand Down Expand Up @@ -754,6 +756,7 @@ object ZIOSpec extends ZIOBaseSpec {
} @@ zioTag(interruption),
testM("runs a task that throws an unsuspended exception") {
def f(i: Int): Task[Int] = throw new Exception(i.toString)

for {
_ <- IO.foreachPar(1 to 1)(f).run
} yield assertCompletes
Expand Down Expand Up @@ -916,9 +919,11 @@ object ZIOSpec extends ZIOBaseSpec {
} yield assert(result)(equalTo(boom))
},
testM("propagates defects") {
val boom = new Exception("boom")
val die: UIO[Unit] = ZIO.die(boom)
val boom = new Exception("boom")
val die: UIO[Unit] = ZIO.die(boom)

def joinDefect(fiber: Fiber[Nothing, _]) = fiber.join.sandbox.flip

for {
fiber1 <- ZIO.forkAll(List(die))
fiber2 <- ZIO.forkAll(List(die, ZIO.succeed(42)))
Expand Down Expand Up @@ -996,6 +1001,7 @@ object ZIOSpec extends ZIOBaseSpec {
import scala.concurrent.{ExecutionContext, Future}
def infiniteFuture(ref: AtomicInteger)(implicit ec: ExecutionContext): Future[Nothing] =
Future(ref.getAndIncrement()).flatMap(_ => infiniteFuture(ref))

for {
ref <- ZIO.effectTotal(new AtomicInteger(0))
fiber <- ZIO.fromFutureInterrupt(ec => infiniteFuture(ref)(ec)).fork
Expand Down Expand Up @@ -1154,23 +1160,26 @@ object ZIOSpec extends ZIOBaseSpec {
testM("with Tuple2") {
checkM(Gen.anyInt, Gen.alphaNumericString) { (int: Int, str: String) =>
def f(i: Int, s: String): String = i.toString + s
val actual = ZIO.mapN(ZIO.succeed(int), ZIO.succeed(str))(f)
val expected = f(int, str)

val actual = ZIO.mapN(ZIO.succeed(int), ZIO.succeed(str))(f)
val expected = f(int, str)
assertM(actual)(equalTo(expected))
}
},
testM("with Tuple3") {
checkM(Gen.anyInt, Gen.alphaNumericString, Gen.alphaNumericString) { (int: Int, str1: String, str2: String) =>
def f(i: Int, s1: String, s2: String): String = i.toString + s1 + s2
val actual = ZIO.mapN(ZIO.succeed(int), ZIO.succeed(str1), ZIO.succeed(str2))(f)
val expected = f(int, str1, str2)

val actual = ZIO.mapN(ZIO.succeed(int), ZIO.succeed(str1), ZIO.succeed(str2))(f)
val expected = f(int, str1, str2)
assertM(actual)(equalTo(expected))
}
},
testM("with Tuple4") {
checkM(Gen.anyInt, Gen.alphaNumericString, Gen.alphaNumericString, Gen.alphaNumericString) {
(int: Int, str1: String, str2: String, str3: String) =>
def f(i: Int, s1: String, s2: String, s3: String): String = i.toString + s1 + s2 + s3

val actual =
ZIO.mapN(ZIO.succeed(int), ZIO.succeed(str1), ZIO.succeed(str2), ZIO.succeed(str3))(f)
val expected = f(int, str1, str2, str3)
Expand All @@ -1182,23 +1191,26 @@ object ZIOSpec extends ZIOBaseSpec {
testM("with Tuple2") {
checkM(Gen.anyInt, Gen.alphaNumericString) { (int: Int, str: String) =>
def f(i: Int, s: String): String = i.toString + s
val actual = ZIO.mapParN(ZIO.succeed(int), ZIO.succeed(str))(f)
val expected = f(int, str)

val actual = ZIO.mapParN(ZIO.succeed(int), ZIO.succeed(str))(f)
val expected = f(int, str)
assertM(actual)(equalTo(expected))
}
},
testM("with Tuple3") {
checkM(Gen.anyInt, Gen.alphaNumericString, Gen.alphaNumericString) { (int: Int, str1: String, str2: String) =>
def f(i: Int, s1: String, s2: String): String = i.toString + s1 + s2
val actual = ZIO.mapParN(ZIO.succeed(int), ZIO.succeed(str1), ZIO.succeed(str2))(f)
val expected = f(int, str1, str2)

val actual = ZIO.mapParN(ZIO.succeed(int), ZIO.succeed(str1), ZIO.succeed(str2))(f)
val expected = f(int, str1, str2)
assertM(actual)(equalTo(expected))
}
},
testM("with Tuple4") {
checkM(Gen.anyInt, Gen.alphaNumericString, Gen.alphaNumericString, Gen.alphaNumericString) {
(int: Int, str1: String, str2: String, str3: String) =>
def f(i: Int, s1: String, s2: String, s3: String): String = i.toString + s1 + s2 + s3

val actual =
ZIO.mapParN(ZIO.succeed(int), ZIO.succeed(str1), ZIO.succeed(str2), ZIO.succeed(str3))(f)
val expected = f(int, str1, str2, str3)
Expand Down Expand Up @@ -1303,6 +1315,24 @@ object ZIOSpec extends ZIOBaseSpec {
assertM(ZIO.succeed(false).negate)(equalTo(true))
}
),
suite("noneOrFail")(
testM("on None succeeds with Unit") {
val option: Option[String] = None
val adaptError: String => String = identity
for {
value <- ZIO.noneOrFail(option, adaptError)
} yield {
assert(value)(equalTo(()))
}
},
testM("on Some fails") {
for {
value <- ZIO.noneOrFail(Some("value"), (v: String) => v + v).catchAll(e => ZIO.succeed(e))
} yield {
assert(value)(equalTo("valuevalue"))
}
} @@ zioTag(errors)
),
suite("once")(
testM("returns an effect that will only be executed once") {
for {
Expand Down Expand Up @@ -2080,7 +2110,12 @@ object ZIOSpec extends ZIOBaseSpec {
testM("fail ensuring") {
var finalized = false

val io = Task.fail(ExampleError).ensuring(IO.effectTotal { finalized = true; () })
val io = Task
.fail(ExampleError)
.ensuring(IO.effectTotal {
finalized = true;
()
})

for {
a1 <- assertM(io.run)(fails(equalTo(ExampleError)))
Expand All @@ -2091,7 +2126,11 @@ object ZIOSpec extends ZIOBaseSpec {
@volatile var finalized = false

val cleanup: Cause[Throwable] => UIO[Unit] =
_ => IO.effectTotal[Unit] { finalized = true; () }
_ =>
IO.effectTotal[Unit] {
finalized = true;
()
}

val io = Task.fail(ExampleError).onError(cleanup)

Expand All @@ -2118,7 +2157,13 @@ object ZIOSpec extends ZIOBaseSpec {
.succeed[Int](42)
.ensuring(IO.die(ExampleError))
.fork
.flatMap(_.await.flatMap[Any, Nothing, Any](e => UIO.effectTotal { reported = e }))
.flatMap(
_.await.flatMap[Any, Nothing, Any](e =>
UIO.effectTotal {
reported = e
}
)
)

for {
a1 <- assertM(io)(anything)
Expand Down Expand Up @@ -3255,8 +3300,10 @@ object ZIOSpec extends ZIOBaseSpec {
},
testM("no information is lost during composition") {
val causes = Gen.causes(Gen.anyString, Gen.throwable)

def cause[R, E](zio: ZIO[R, E, Nothing]): ZIO[R, Nothing, Cause[E]] =
zio.foldCauseM(ZIO.succeed(_), ZIO.fail)

checkM(causes) { c =>
for {
result <- cause(ZIO.halt(c).sandbox.mapErrorCause(e => e.untraced).unsandbox)
Expand All @@ -3276,9 +3323,11 @@ object ZIOSpec extends ZIOBaseSpec {
),
suite("validate")(
testM("returns all errors if never valid") {
val in = List.fill(10)(0)
val in = List.fill(10)(0)

def fail[A](a: A): IO[A, A] = IO.fail(a)
val res = IO.validate(in)(fail).flip

val res = IO.validate(in)(fail).flip
assertM(res)(equalTo(in))
} @@ zioTag(errors),
testM("accumulate errors and ignore successes") {
Expand All @@ -3296,9 +3345,11 @@ object ZIOSpec extends ZIOBaseSpec {
),
suite("validatePar")(
testM("returns all errors if never valid") {
val in = List.fill(1000)(0)
val in = List.fill(1000)(0)

def fail[A](a: A): IO[A, A] = IO.fail(a)
val res = IO.validatePar(in)(fail).flip

val res = IO.validatePar(in)(fail).flip
assertM(res)(equalTo(in))
} @@ zioTag(errors),
testM("accumulate errors and ignore successes") {
Expand Down Expand Up @@ -3696,7 +3747,10 @@ object ZIOSpec extends ZIOBaseSpec {
type Logging = Has[Logging.Service]

object Logging {

trait Service

val live: ZLayer[Any, Nothing, Logging] = ZLayer.succeed(new Logging.Service {})
}

}
6 changes: 6 additions & 0 deletions core/shared/src/main/scala/zio/IO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,12 @@ object IO {
*/
val none: UIO[Option[Nothing]] = ZIO.none

/**
* @see See [[zio.ZIO.noneOrFail()]]
*/
def noneOrFail[E, O](o: Option[O], f: O => E): IO[E, Unit] =
ZIO.noneOrFail(o, f)

/**
* @see See [[zio.ZIO.not]]
*/
Expand Down
8 changes: 8 additions & 0 deletions core/shared/src/main/scala/zio/ZIO.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3649,6 +3649,14 @@ object ZIO extends ZIOCompanionPlatformSpecific {
*/
val none: UIO[Option[Nothing]] = succeedNow(None)

/**
* Lifts an Option into a IO.
* If the option is empty it succeeds with Unit.
* If the option is defined it fails with an error adapted with f.
*/
def noneOrFail[E, O](o: Option[O], f: O => E): IO[E, Unit] =
getOrFailUnit(o).flip.mapError(f)

/**
* Feeds elements of type `A` to a function `f` that returns an effect.
* Collects all successes and failures in a tupled fashion.
Expand Down

0 comments on commit 57f6290

Please sign in to comment.