Last active
May 3, 2018 13:42
-
-
Save harrylaou/06821788a42954b168e13064708514d1 to your computer and use it in GitHub Desktop.
Monad Transformers package - just import utils.fee._ , to start using it
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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