diff options
Diffstat (limited to 'src/main/scala/xyz/driver/common/utils/Computation.scala')
-rw-r--r-- | src/main/scala/xyz/driver/common/utils/Computation.scala | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/src/main/scala/xyz/driver/common/utils/Computation.scala b/src/main/scala/xyz/driver/common/utils/Computation.scala new file mode 100644 index 0000000..a435afe --- /dev/null +++ b/src/main/scala/xyz/driver/common/utils/Computation.scala @@ -0,0 +1,110 @@ +package xyz.driver.common.utils + +import scala.concurrent.{ExecutionContext, Future} + +/** + * Takes care of computations + * + * Success(either) - the computation will be continued. + * Failure(error) - the computation was failed with unhandled error. + * + * Either[Result, T]: + * Left(result) is a final and handled result, another computations (map, flatMap) will be ignored. + * Right(T) is a current result. Functions in map/flatMap will continue the computation. + * + * Example: + * {{{ + * import scala.concurrent.ExecutionContext.Implicits.global + * import scala.concurrent.{ExecutionContext, Future} + * import com.drivergrp.server.com.drivergrp.server.common.utils.Computation + * + * def successful = for { + * x <- Computation.continue(1) + * y <- Computation.continue(2) + * } yield s"\$x + \$y" + * + * // Prints "Success(1 + 2)" + * successful.join.onComplete(print) + * + * def failed = for { + * x <- Computation.abort("Failed on x") + * _ = print("Second step") + * y <- Computation.continue(2) + * } yield s"\$x + \$y" + * + * // Prints "Success(Failed on x)" + * failed.join.onComplete(print) + * }}} + * + * TODO: Make future private + * + * @param future The final flow in a future. + * @tparam R Type of result for aborted computation. + * @tparam T Type of result for continued computation. + */ +final case class Computation[+R, +T](future: Future[Either[R, T]]) { + + def flatMap[R2, T2](f: T => Computation[R2, T2])(implicit ec: ExecutionContext, ev: R <:< R2): Computation[R2, T2] = { + Computation(future.flatMap { + case Left(x) => Future.successful(Left(x)) + case Right(x) => f(x).future + }) + } + + def processExceptions[R2](f: PartialFunction[Throwable, R2]) + (implicit ev1: R <:< R2, + ec: ExecutionContext): Computation[R2, T] = { + val strategy = f.andThen(x => Left(x): Either[R2, T]) + val castedFuture: Future[Either[R2, T]] = future.map { + case Left(x) => Left(x) + case Right(x) => Right(x) + } + Computation(castedFuture.recover(strategy)) + } + + def map[T2](f: T => T2)(implicit ec: ExecutionContext): Computation[R, T2] = flatMap { a => + Computation.continue(f(a)) + } + + def andThen(f: T => Any)(implicit ec: ExecutionContext): Computation[R, T] = map { a => + f(a) + a + } + + def filter(f: T => Boolean)(implicit ec: ExecutionContext): Computation[R, T] = map { a => + if (f(a)) a + else throw new NoSuchElementException("When filtering") + } + + def withFilter(f: T => Boolean)(implicit ec: ExecutionContext): Computation[R, T] = filter(f) + + def foreach[T2](f: T => T2)(implicit ec: ExecutionContext): Unit = future.foreach { + case Right(x) => f(x) + case _ => + } + + def toFuture[R2](resultFormatter: T => R2)(implicit ec: ExecutionContext, ev: R <:< R2): Future[R2] = future.map { + case Left(x) => x + case Right(x) => resultFormatter(x) + } + + def toFuture[R2](implicit ec: ExecutionContext, ev1: R <:< R2, ev2: T <:< R2): Future[R2] = future.map { + case Left(x) => x + case Right(x) => x + } + +} + +object Computation { + + def continue[T](x: T): Computation[Nothing, T] = Computation(Future.successful(Right(x))) + + def abort[R](result: R): Computation[R, Nothing] = Computation(Future.successful(Left(result))) + + def fail(exception: Throwable): Computation[Nothing, Nothing] = Computation(Future.failed(exception)) + + def fromFuture[T](input: Future[T])(implicit ec: ExecutionContext): Computation[Nothing, T] = Computation { + input.map { x => Right(x) } + } + +} |