diff --git a/src/main/scala/io/getclump/Clump.scala b/src/main/scala/io/getclump/Clump.scala index be7e8d5..f6c7942 100644 --- a/src/main/scala/io/getclump/Clump.scala +++ b/src/main/scala/io/getclump/Clump.scala @@ -31,11 +31,31 @@ sealed trait Clump[+T] { */ def handle[B >: T](f: PartialFunction[Throwable, Option[B]]): Clump[B] = new ClumpHandle(this, f) + /** + * Alias for [[handle]] + */ + def recover[B >: T](f: PartialFunction[Throwable, Option[B]]): Clump[B] = handle(f) + /** * Define a fallback clump to use in the case of specified exceptions */ def rescue[B >: T](f: PartialFunction[Throwable, Clump[B]]): Clump[B] = new ClumpRescue(this, f) + /** + * Alias for [[rescue]] + */ + def recoverWith[B >: T](f: PartialFunction[Throwable, Clump[B]]): Clump[B] = rescue(f) + + /** + * On any exception, fallback to a default value + */ + def fallback[B >: T](default: => Option[B]): Clump[B] = handle(PartialFunction(_ => default)) + + /** + * On any exception, fallback to a default clump + */ + def fallbackTo[B >: T](default: => Clump[B]): Clump[B] = rescue(PartialFunction(_ => default)) + /** * Alias for [[filter]] used by for-comprehensions */ @@ -46,6 +66,11 @@ sealed trait Clump[+T] { */ def filter[B >: T](f: B => Boolean): Clump[B] = new ClumpFilter(this, f) + /** + * If this clump does not return a value then use the default instead + */ + def orElse[B >: T: ClassTag](default: => B): Clump[B] = new ClumpOrElse(this, Clump.value(default)) + /** * If this clump does not return a value then use the value from a default clump instead */ @@ -96,6 +121,11 @@ object Clump extends Joins with Sources { */ def empty[T]: Clump[T] = value(scala.None) + /** + * Alias for [[value]] except that it propagates exceptions inside a clump instance + */ + def apply[T](value: => T): Clump[T] = try { this.value(value) } catch { case e: Throwable => this.exception(e) } + /** * The unit method: create a clump whose value has already been resolved to the input */ @@ -106,11 +136,21 @@ object Clump extends Joins with Sources { */ def value[T](value: Option[T]): Clump[T] = future(Future.value(value)) + /** + * Alias for [[value]] + */ + def successful[T](value: T): Clump[T] = this.value(value) + /** * Create a failed clump with the given exception */ def exception[T](exception: Throwable): Clump[T] = future(Future.exception(exception)) + /** + * Alias for [[exception]] + */ + def failed[T](exception: Throwable): Clump[T] = this.exception(exception) + /** * Create a clump whose value will be the result of the inputted future */ diff --git a/src/main/scala/io/getclump/ClumpSource.scala b/src/main/scala/io/getclump/ClumpSource.scala index a86107f..20b5f03 100644 --- a/src/main/scala/io/getclump/ClumpSource.scala +++ b/src/main/scala/io/getclump/ClumpSource.scala @@ -1,7 +1,6 @@ package io.getclump import com.twitter.util.Future -import scala.collection.generic.CanBuildFrom class ClumpSource[T, U] private[getclump] (val functionIdentity: FunctionIdentity, val fetch: Set[T] => Future[Map[T, U]], diff --git a/src/test/scala/io/getclump/ClumpApiSpec.scala b/src/test/scala/io/getclump/ClumpApiSpec.scala index 2cac32d..3fe2ea7 100644 --- a/src/test/scala/io/getclump/ClumpApiSpec.scala +++ b/src/test/scala/io/getclump/ClumpApiSpec.scala @@ -33,10 +33,25 @@ class ClumpApiSpec extends Spec { } } + "from a value (Clump.apply)" >> { + "propogates exceptions" in { + val clump = Clump { throw new IllegalStateException } + clumpResult(clump) must throwA[IllegalStateException] + } + + "no exception" in { + clumpResult(Clump(1)) mustEqual Some(1) + } + } + "from a value (Clump.value)" in { clumpResult(Clump.value(1)) mustEqual Some(1) } + "from a value (Clump.successful)" in { + clumpResult(Clump.successful(1)) mustEqual Some(1) + } + "from an option (Clump.value)" >> { "defined" in { @@ -51,6 +66,10 @@ class ClumpApiSpec extends Spec { "failed (Clump.exception)" in { clumpResult(Clump.exception(new IllegalStateException)) must throwA[IllegalStateException] } + + "failed (Clump.failed)" in { + clumpResult(Clump.failed(new IllegalStateException)) must throwA[IllegalStateException] + } } "allows to create a clump traversing multiple inputs (Clump.traverse)" in { @@ -186,6 +205,30 @@ class ClumpApiSpec extends Spec { } } + "using a function that recovers using a new value (clump.recover)" >> { + "exception happens" in { + val clump = + Clump.exception(new IllegalStateException).recover { + case e: IllegalStateException => Some(2) + } + clumpResult(clump) mustEqual Some(2) + } + "exception doesn't happen" in { + val clump = + Clump.value(1).recover { + case e: IllegalStateException => None + } + clumpResult(clump) mustEqual Some(1) + } + "exception isn't caught" in { + val clump = + Clump.exception(new NullPointerException).recover { + case e: IllegalStateException => Some(1) + } + clumpResult(clump) must throwA[NullPointerException] + } + } + "using a function that recovers the failure using a new clump (clump.rescue)" >> { "exception happens" in { val clump = @@ -209,6 +252,54 @@ class ClumpApiSpec extends Spec { clumpResult(clump) must throwA[NullPointerException] } } + + "using a function that recovers the failure using a new clump (clump.recoverWith)" >> { + "exception happens" in { + val clump = + Clump.exception(new IllegalStateException).recoverWith { + case e: IllegalStateException => Clump.value(2) + } + clumpResult(clump) mustEqual Some(2) + } + "exception doesn't happen" in { + val clump = + Clump.value(1).recoverWith { + case e: IllegalStateException => Clump.value(None) + } + clumpResult(clump) mustEqual Some(1) + } + "exception isn't caught" in { + val clump = + Clump.exception(new NullPointerException).recoverWith { + case e: IllegalStateException => Clump.value(1) + } + clumpResult(clump) must throwA[NullPointerException] + } + } + + "using a function that recovers using a new value (clump.fallback) on any exception" >> { + "exception happens" in { + val clump = Clump.exception(new IllegalStateException).fallback(Some(1)) + clumpResult(clump) mustEqual Some(1) + } + + "exception doesn't happen" in { + val clump = Clump.value(1).fallback(Some(2)) + clumpResult(clump) mustEqual Some(1) + } + } + + "using a function that recovers using a new clump (clump.fallbackTo) on any exception" >> { + "exception happens" in { + val clump = Clump.exception(new IllegalStateException).fallbackTo(Clump.value(1)) + clumpResult(clump) mustEqual Some(1) + } + + "exception doesn't happen" in { + val clump = Clump.value(1).fallbackTo(Clump.value(2)) + clumpResult(clump) mustEqual Some(1) + } + } } "can have its result filtered (clump.filter)" in { @@ -225,6 +316,15 @@ class ClumpApiSpec extends Spec { } "allows to defined a fallback value (clump.orElse)" >> { + "undefined" in { + clumpResult(Clump.empty.orElse(1)) ==== Some(1) + } + "defined" in { + clumpResult(Clump.value(Some(1)).orElse(2)) ==== Some(1) + } + } + + "allows to defined a fallback clump (clump.orElse)" >> { "undefined" in { clumpResult(Clump.empty.orElse(Clump.value(1))) ==== Some(1) }