Skip to content

Instantly share code, notes, and snippets.

@harrylaou
Last active May 3, 2018 13:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save harrylaou/06821788a42954b168e13064708514d1 to your computer and use it in GitHub Desktop.
Save harrylaou/06821788a42954b168e13064708514d1 to your computer and use it in GitHub Desktop.
Monad Transformers package - just import utils.fee._ , to start using it
package utils.fee
import application.logger.MyLogger.RichLogger
import application.logger.MyLoggerT
import cats.syntax.EitherSyntax
import play.api.mvc.Results
import scala.util.Try
trait EitherConstructors extends EitherSyntax {
def fromBoolean[M](b: Boolean): Boolean = b
implicit class EOption[B](ob: Option[B]) {
def toEither[A](ifNone: A): Either[A, B] = Either.fromOption(ob, ifNone)
}
implicit class ETry[B](b: => Try[B]) {
def toEitherErr(errStatus: Results.Status = Results.UnprocessableEntity): Either[Erratum, B] =
b.toEither
.leftMap((th: Throwable) => Erratum.fromTh(errStatus)(th))
}
implicit class EitherThrowable[B](b: => Either[scala.Throwable, B]) {
def toEitherErr: Either[Erratum, B] = b.leftMap(Erratum.fromTh)
}
implicit class EitherError[B](b: => Either[Erratum, B]) {
@SuppressWarnings(Array("org.wartremover.warts.Throw"))
def unsafeGet(implicit log: MyLoggerT.Logger): B = b match {
case Right(ld) => ld
case Left(err) =>
log.error(err)
throw err.throwable
}
}
}
package utils.fee
import application.logger.MyLoggerT
import cats.Applicative
import cats.data.EitherT
import io.circe.DecodingFailure
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import application.logger.MyLogger._
/**
*
*/
trait EitherTConstructors extends EitherConstructors {
implicit class ETFromBase[B](b: B) {
def pureET(implicit ec: Applicative[Future]): FEET[B] =
EitherT.pure[Future, Erratum](b)
}
implicit class ETFApply[B](feb: FEE[B]) {
def wrapET: FEET[B] = EitherT(feb)
}
implicit class ETLift[B](eb: Either[Erratum, B]) {
def liftET(implicit ec: Applicative[Future]): FEET[B] =
EitherT.fromEither[Future](eb)
}
implicit class ETTry[B](eb: Try[B]) {
def liftET(implicit ec: Applicative[Future]): FEET[B] =
eb.toEither.liftET
}
implicit class ETOption[B](ob: Option[B]) {
def liftET(ifNone: Erratum)(implicit ec: Applicative[Future]): FEET[B] =
EitherT.fromOption[Future](ob, ifNone)
}
implicit class ETFutOption[B](fob: Future[Option[B]]) {
def wrapET(ifNone: Erratum)(implicit ec: ExecutionContext): FEET[B] =
fob.map(ob => ob.toEither(ifNone)).wrapET
}
implicit class ETFutDecodingFailure[B](fob: Future[Either[DecodingFailure, B]]) {
def wrapET(implicit ec: ExecutionContext): FEET[B] =
fob.map(ob => ob.leftMap(Erratum.fromDF)).wrapET
}
implicit class ETFromFuture[B](fb: Future[B]) {
def liftET(implicit ec: ExecutionContext): FEET[B] =
fb.map[Either[Erratum, B]](Right(_)).recover { case th: Throwable => Left(Erratum.fromTh(th)) }.wrapET
}
implicit class ETFromFutureSeq[B](fSeq: Future[Seq[B]]) {
def liftET(implicit ec: ExecutionContext): FEET[Seq[B]] =
fSeq
.map[Either[Erratum, Seq[B]]](seq => Right(seq))
.recover { case th: Throwable => Left(Erratum.fromTh(th)) }
.wrapET
}
//FIXME
implicit class ETFromErratum(erratum: Erratum) {
def pureET[B](implicit ec: Applicative[Future]): FEET[B] =
Left[Erratum, B](erratum).liftET
}
implicit class ETFromEitherDecodingFailure[B](eb: Either[DecodingFailure, B]) {
def liftET(implicit ec: Applicative[Future]): FEET[B] =
eb.leftMap(Erratum.fromDF).liftET
}
implicit class ETFromEitherThrowable[B](eb: Either[Throwable, B]) {
def liftET(implicit ec: Applicative[Future]): FEET[B] =
eb.leftMap(Erratum.fromTh).liftET
}
def catchNonFatal[A, B](f: A => B)(a: A)(implicit ec: Applicative[Future]): FEET[B] =
EitherT.fromEither[Future](Either.catchNonFatal(f(a)).leftMap(Erratum.fromTh))
def cleanErratums[M](seq: Seq[Either[Erratum, M]])(implicit log: MyLoggerT.Logger): Seq[M] = {
val tup: (Seq[Either[Erratum, M]], Seq[Either[Erratum, M]]) = seq.partition(_.isLeft)
val errors: Seq[Either[Erratum, M]] = tup._1
val models: Seq[Either[Erratum, M]] = tup._2
errors.foreach(
e =>
log.error(e.left.toOption.map(_.message).getOrElse("This should not be printed. It should be left"))
)
models.flatMap(_.right.toOption.toList)
}
implicit class ETMuUtils[B](feeb: FEE[B]) {
def rightMap[C](f: B => C)(implicit ec: ExecutionContext): FEE[C] = feeb.map(_.map(f))
def rightFlatMap[C](f: B => FEE[C])(implicit ec: ExecutionContext): FEE[C] = feeb.flatMap {
case Left(erratum) => Future.successful(Left(erratum))
case Right(b) => f(b)
}
def leftMap(f: Erratum => Erratum)(implicit ec: ExecutionContext): FEE[B] = feeb.map(_.leftMap(f))
def leftMap(f: PartialFunction[Erratum, Erratum])(implicit ec: ExecutionContext): FEE[B] =
feeb.map(_.leftMap(f))
@SuppressWarnings(Array("org.wartremover.warts.EitherProjectionPartial"))
def toFOption(customMessage: String = "")(implicit ec: ExecutionContext,
log: MyLoggerT.Logger): Future[Option[B]] =
feeb.map(ee => {
if (ee.isLeft) {
log.error(ee.left.get, customMessage)
}
ee.toOption
})
}
}
package utils.fee
import cats.data.NonEmptyList
import io.circe.{DecodingFailure, Json}
import play.api.mvc.{Result, Results}
/**
* Represents the system error.
*
*/
trait Erratum {
def status: Results.Status
def message: String
protected def maybeThrowable: Option[Throwable]
def toResult: Result =
status(this.toString)
val throwable: Throwable = maybeThrowable.getOrElse(new RuntimeException(message))
def combine(other: Erratum): Erratum = Erratum.combine(this, other)
def withStatus(_status: Results.Status): Erratum =
new Erratum.ErratumImpl(status = _status, message = message, maybeThrowable = maybeThrowable)
val asJson: Json = maybeThrowable match {
case None =>
Json.obj(
"status" -> Json.fromString(status.toString),
"message" -> Json.fromString(message)
)
case Some(th) =>
Json.obj(
"status" -> Json.fromString(status.toString),
"message" -> Json.fromString(message),
"stacktrace" -> Json.fromString(th.getStackTrace.mkString("\n"))
)
}
override def toString: String = asJson.spaces2
}
object Erratum {
private final class ErratumImpl(val status: Results.Status,
val message: String,
protected val maybeThrowable: Option[Throwable])
extends Erratum
def fromTh(status: Results.Status)(throwable: Throwable): Erratum =
new ErratumImpl(status, throwable.getMessage, maybeThrowable = Some(throwable))
def fromTh(throwable: Throwable): Erratum =
fromTh(status = Results.InternalServerError)(throwable = throwable)
def fromS(status: Results.Status)(message: String): Erratum =
new ErratumImpl(status, message, None)
def fromS(message: String): Erratum =
new ErratumImpl(Results.InternalServerError, message, None)
def fromDF(df: DecodingFailure): Erratum =
new ErratumImpl(Results.UnprocessableEntity, df.getMessage(), Some(df))
def toResult(errors: NonEmptyList[Erratum]): Result = {
val newMessage = errors.toList
.map(e => s"Status:${e.status.toString()} - message: ${e.message}")
.mkString("\n")
errors.head.status(newMessage)
}
def combine(_this: Erratum, _that: Erratum): Erratum =
new Erratum.ErratumImpl(
status = _this.status,
message = _this.message + " - \n" + _that.message,
maybeThrowable = _this.maybeThrowable.orElse(_that.maybeThrowable)
)
def toDecodingFailure(err: Erratum): DecodingFailure = DecodingFailure(err.message, List())
}
package utils.fee
import scala.concurrent.duration.{Duration, _}
import scala.concurrent.{Await, ExecutionContext, Future}
//FIXME I don't like this but Async in ScalaTest 3 seems a bit off
//FIXME at least I cannot make it work with custom application
trait FutureUtils {
implicit class RichFuture[T](future: Future[T]) {
def await(implicit duration: Duration = 60.seconds): T = Await.result(future, duration)
}
implicit class ETMuUtilsSeq[B](fsb: Future[Seq[B]]) {
def mapSeq[C](f: B => C)(implicit ec: ExecutionContext): Future[Seq[C]] = fsb.map(_.map(f))
}
}
object FutureUtils extends FutureUtils
gistpackage utils
import application.logger.MyLoggerT
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try
import application.logger.MyLogger._
import cats.data.EitherT
/**
*
*/
package object fee extends EitherTConstructors with FutureUtils {
type FEE[M] = Future[Either[Erratum, M]]
type FEET[M] = EitherT[Future, Erratum, M]
object FEE {
def apply[M](m: M): FEE[M] = Future.successful(Right(m))
}
implicit class FEECombine[A](fea: FEE[A]) {
def combineFEE[B](feb: FEE[B])(implicit ec: ExecutionContext): FEE[(A, B)] =
fea.zip(feb).map {
case (Right(a), Right(b)) => Right((a, b))
case (Left(err), Right(_)) => Left(err)
case (Right(_), Left(err)) => Left(err)
case (Left(errA), Left(errB)) => Left(errA.combine(errB))
}
}
implicit class FEEMap[A](fee: FEE[A]) {
def mapFEE[B](f: A => B)(implicit ec: ExecutionContext): FEE[B] =
fee.map(ee => ee.flatMap(a => Try(f(a)).toEitherErr()))
}
implicit class FEEFlatMappable[A](fee: FEE[A]) {
def flatMapFEE[B](f: A => FEE[B])(implicit ec: ExecutionContext): FEE[B] = fee.flatMap {
case Left(err) => Future.successful(Left(err))
case Right(a) => f(a)
}
def andThenFEE[B](feeB: => FEE[B])(implicit ec: ExecutionContext): FEE[B] = fee.flatMap {
case Left(_) => feeB
case Right(_) => feeB
}
def orElseFEE[B >: A](feeB: => FEE[B])(implicit ec: ExecutionContext): FEE[B] = fee.flatMapFEE {
case Left(_) => feeB
case Right(_) => fee
}
}
implicit class FEEFoldable[A](fee: FEE[A]) {
def foldFEE[B](default: FEE[B], f: A => FEE[B])(implicit ec: ExecutionContext): FEE[B] =
fee.flatMap {
case Left(_) => default
case Right(a) => f(a)
}
def fold[B](default: B, f: A => B)(implicit ec: ExecutionContext): FEE[B] = fee.map { ee =>
ee.flatMap(a => Try(f(a)).toEitherErr()).orElse(Right(default))
}
}
implicit class FEESequence[A](fees: Seq[FEE[A]]) {
/**
*
* @param ec ExecutionContext
* @param log MyLoggerT.Logger
* @return
*/
@SuppressWarnings(Array("org.wartremover.warts.EitherProjectionPartial"))
def sequence(implicit ec: ExecutionContext, log: MyLoggerT.Logger): FEE[Seq[A]] = {
val fseq: Future[Seq[A]] = Future.sequence(fees).map { lsFees =>
lsFees.filter(eit => eit.isLeft).map(eit => eit.left.get).foreach(e => log.error(e))
lsFees
.filter(eit => eit.isRight)
.map(eit => eit.unsafeGet)
}
fseq.liftET.value
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment