Guest User

Untitled

a guest
Dec 17th, 2017
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.47 KB | None | 0 0
  1. package utils.attempt
  2.  
  3. import cats.data.EitherT
  4.  
  5. import scala.concurrent.{ExecutionContext, Future}
  6. import scala.util.control.NonFatal
  7.  
  8. /**
  9. * Represents a value that will need to be calculated using an asynchronous
  10. * computation that may fail.
  11. */
  12. case class Attempt[A] private (underlying: Future[Either[Failure, A]]) {
  13. def map[B](f: A => B)(implicit ec: ExecutionContext): Attempt[B] =
  14. flatMap(a => Attempt.Right(f(a)))
  15.  
  16. def flatMap[B](f: A => Attempt[B])(implicit ec: ExecutionContext): Attempt[B] = Attempt {
  17. asFuture.flatMap {
  18. case Right(a) => f(a).asFuture
  19. case Left(e) => Future.successful(Left(e))
  20. }
  21. }
  22.  
  23. def fold[B](failure: Failure => B, success: A => B)(implicit ec: ExecutionContext): Future[B] = {
  24. asFuture.map(_.fold(failure, success))
  25. }
  26.  
  27. def map2[B, C](bAttempt: Attempt[B])(f: (A, B) => C)(implicit ec: ExecutionContext): Attempt[C] = {
  28. for {
  29. a <- this
  30. b <- bAttempt
  31. } yield f(a, b)
  32. }
  33.  
  34. def recoverWith(pf: PartialFunction[Failure, Attempt[A]])(implicit ec: ExecutionContext) = Attempt {
  35. asFuture.flatMap {
  36. case Right(a) =>
  37. Attempt.Right(a).asFuture
  38.  
  39. case Left(err) =>
  40. val ret = pf.lift(err) match {
  41. case Some(attempt) => attempt
  42. case None => Attempt.Left[A](err)
  43. }
  44.  
  45. ret.asFuture
  46. }
  47. }
  48.  
  49. def transform[B](sf: A => Attempt[B], ef: Failure => Attempt[B])(implicit ec: ExecutionContext) = Attempt {
  50. asFuture.flatMap {
  51. case Right(a) => sf(a).asFuture
  52. case Left(err) => ef(err).asFuture
  53. }
  54. }
  55.  
  56. /**
  57. * If there is an error in the Future itself (e.g. a timeout) we convert it to a
  58. * Left so we have a consistent error representation. Unfortunately, this means
  59. * the error isn't being handled properly so we're left with just the information
  60. * provided by the exception.
  61. *
  62. * Try to avoid hitting this method's failure case by always handling Future errors
  63. * and creating a suitable failure instance for the problem.
  64. */
  65. def asFuture(implicit ec: ExecutionContext): Future[Either[Failure, A]] = {
  66. underlying recover { case err =>
  67. scala.Left(UnknownFailure(err))
  68. }
  69. }
  70.  
  71. def toEitherT: EitherT[Future, Failure, A] = EitherT(underlying)
  72.  
  73. def isSuccess(implicit ec: ExecutionContext): Future[Boolean] = fold(_ => false, _ => true)
  74. def isFailure(implicit ec: ExecutionContext): Future[Boolean] = fold(_ => true, _ => false)
  75. }
  76.  
  77. object Attempt {
  78. /**
  79. * Changes generated `List[Attempt[A]]` to `Attempt[List[A]]` via provided
  80. * traversal function (like `Future.traverse`).
  81. *
  82. * This implementation returns the first failure in the resulting list,
  83. * or the successful result.
  84. */
  85. def traverse[A, B](as: List[A])(f: A => Attempt[B])(implicit ec: ExecutionContext): Attempt[List[B]] = {
  86. as.foldRight[Attempt[List[B]]](Right(Nil))(f(_).map2(_)(_ :: _))
  87. }
  88.  
  89. /**
  90. * Using the provided traversal function, sequence the resulting attempts
  91. * into a list that preserves failures.
  92. *
  93. * This is useful if failure is acceptable in part of the application.
  94. */
  95. def traverseWithFailures[A, B](as: List[A])(f: A => Attempt[B])(implicit ec: ExecutionContext): Attempt[List[Either[Failure, B]]] = {
  96. sequenceWithFailures(as.map(f))
  97. }
  98.  
  99. /**
  100. * As with `Future.sequence`, changes `List[Attempt[A]]` to `Attempt[List[A]]`.
  101. *
  102. * This implementation returns the first failure in the list, or the successful result.
  103. */
  104. def sequence[A](responses: List[Attempt[A]])(implicit ec: ExecutionContext): Attempt[List[A]] = {
  105. traverse(responses)(identity)
  106. }
  107.  
  108. /**
  109. * Sequence these attempts into a list that preserves failures.
  110. *
  111. * This is useful if failure is acceptable in part of the application.
  112. */
  113. def sequenceWithFailures[A](attempts: List[Attempt[A]])(implicit ec: ExecutionContext): Attempt[List[Either[Failure, A]]] = {
  114. async.Right(Future.traverse(attempts)(_.asFuture))
  115. }
  116.  
  117. def fromEither[A](e: Either[Failure, A]): Attempt[A] =
  118. Attempt(Future.successful(e))
  119.  
  120. def fromOption[A](optA: Option[A], ifNone: => Failure): Attempt[A] =
  121. fromEither(optA.toRight(ifNone))
  122.  
  123. /**
  124. * Run f and catch any non-fatal exceptions from the execution. This is typically used to wrap IO SDK calls.
  125. * @param f function to run
  126. * @param recovery partial function to convert thrown exceptions to Failure types
  127. */
  128. def catchNonFatal[A](f: => A)(recovery: PartialFunction[Throwable, Failure]): Attempt[A] = {
  129. try {
  130. Attempt.Right(f)
  131. } catch {
  132. case NonFatal(t) => Attempt.Left(recovery.lift(t).getOrElse(UnknownFailure(t)))
  133. }
  134. }
  135.  
  136. /**
  137. * Run f and convert all non fatal exceptions to UnknownFailures. Best practice is to use catchNonFatal
  138. * instead but this can be used when you don't know what kind of exceptions will be thrown or you have a blasé attitude.
  139. */
  140. def catchNonFatalBlasé[A](f: => A): Attempt[A] = catchNonFatal(f)(Map.empty)
  141.  
  142. /**
  143. * Convert a plain `Future` value to an attempt by providing a recovery handler.
  144. */
  145. def fromFuture[A](future: Future[A])(recovery: PartialFunction[Throwable, Failure])(implicit ec: ExecutionContext): Attempt[A] = {
  146. Attempt {
  147. future
  148. .map(scala.Right(_))
  149. .recover { case t =>
  150. scala.Left(recovery(t))
  151. }
  152. }
  153. }
  154.  
  155. /**
  156. * Discard failures from a list of attempts.
  157. *
  158. * **Use with caution**.
  159. */
  160. def successfulAttempts[A](attempts: List[Attempt[A]])(implicit ec: ExecutionContext): Attempt[List[A]] = {
  161. Attempt.async.Right {
  162. Future.traverse(attempts)(_.asFuture).map(_.collect { case Right(a) => a })
  163. }
  164. }
  165.  
  166. /**
  167. * Create an Attempt instance from a "good" value.
  168. */
  169. def Right[A](a: A): Attempt[A] =
  170. Attempt(Future.successful(scala.Right(a)))
  171.  
  172. /**
  173. * Syntax sugar to create an Attempt failure if there's only a single error.
  174. */
  175. def Left[A](err: Failure): Attempt[A] =
  176. Attempt(Future.successful(scala.Left(err)))
  177.  
  178. /**
  179. * Asynchronous versions of the Attempt Right/Left helpers for when you have
  180. * a Future that returns a good/bad value directly.
  181. */
  182. object async {
  183. /**
  184. * Run f asynchronously and catch any non-fatal exceptions from the execution. This is typically used to wrap IO
  185. * SDK calls.
  186. * @param f function to run asynchronously
  187. * @param recovery partial function to convert thrown exceptions to Failure types
  188. */
  189. def catchNonFatal[A](f: => A)(recovery: PartialFunction[Throwable, Failure])(implicit ec: ExecutionContext): Attempt[A] =
  190. Attempt(Future(scala.Right(f)).recover {
  191. case NonFatal(t) => scala.Left(recovery.lift(t).getOrElse(UnknownFailure(t)))
  192. })
  193.  
  194. /**
  195. * Run f asynchronously and convert all non fatal exceptions to UnknownFailures. Best practice is to use
  196. * catchNonFatal instead but this can be used when you don't know what kind of exceptions will be thrown or you
  197. * have a blasé attitude to exceptions.
  198. */
  199. def catchNonFatalBlasé[A](f: => A)(implicit ec: ExecutionContext): Attempt[A] = catchNonFatal(f)(Map.empty)
  200.  
  201. /**
  202. * Create an Attempt from a Future of a good value.
  203. */
  204. def Right[A](fa: Future[A])(implicit ec: ExecutionContext): Attempt[A] =
  205. Attempt(fa.map(scala.Right(_)))
  206.  
  207. /**
  208. * Create an Attempt from a known failure in the future. For example,
  209. * if a piece of logic fails but you need to make a Database/API call to
  210. * get the failure information.
  211. */
  212. def Left[A](ferr: Future[Failure])(implicit ec: ExecutionContext): Attempt[A] =
  213. Attempt(ferr.map(scala.Left(_)))
  214. }
  215. }
Add Comment
Please, Sign In to add comment