Skip to content

Instantly share code, notes, and snippets.

@JoolsF
Last active February 4, 2019 11:01
Show Gist options
  • Select an option

  • Save JoolsF/8301295d68b9346b0f8124470cf51beb to your computer and use it in GitHub Desktop.

Select an option

Save JoolsF/8301295d68b9346b0f8124470cf51beb to your computer and use it in GitHub Desktop.
EitherT - FutureEither example 1
import FutureEither.FutureEither
import cats.data.EitherT
import scala.concurrent.{ExecutionContext, Future}
//ServiceError
sealed trait ServiceError {
val msg: String
}
case class NoSuchUserError(msg: String) extends ServiceError
case class DataValidationError(msg: String) extends ServiceError
case class ResourceAccessError(msg: String) extends ServiceError
case class InsufficientFundsError(msg: String) extends ServiceError
// FutureEither helper Singleton
object FutureEither {
//FutureEither types
type ServiceResult[+A] = Either[ServiceError, A]
type FutureEither[A] = EitherT[Future, ServiceError, A]
def apply[A](f: => ServiceResult[A])(implicit ec: ExecutionContext): FutureEither[A] =
apply(Future(f))
def apply[A](f: Future[ServiceResult[A]]): FutureEither[A] =
EitherT(f)
def apply[A](o: Option[A], error: ServiceError)(implicit ec: ExecutionContext): FutureEither[A] = o match {
case Some(r) => apply(Right(r))
case None => apply(Left(error))
}
def errored[A](error: ServiceError): FutureEither[A] =
apply(Future.successful(Left(error)))
def failed[A](ex: Throwable): FutureEither[A] =
apply(Future.failed(ex))
def successful[A](result: A): FutureEither[A] =
apply(Future.successful(Right(result)))
def apply[A](o: Future[Option[A]], error: ServiceError)(implicit ec: ExecutionContext): FutureEither[A] =
EitherT(
o.map(_ match {
case Some(v) => Right(v)
case None => Left(error)
})
)
}
/*
* Example use
*/
case class User(id: Long, firstName: String, lastName: String, balance: Long, isActive: Boolean)
case class HttpResponse(code: Int, message: String)
case class WithdrawlRequest(userId: Long, amount: Long)
object UserDao {
private implicit val executionContext = scala.concurrent.ExecutionContext.global
val repo: Seq[User] = Seq(
User(1L, "Julian", "Fenner", 1000L, true),
User(2L, "John", "Doe", 0L, false)
)
def getUser(id: Long)(implicit executionContext: ExecutionContext): Future[Option[User]] =
Future(repo.find(_.id == id))
}
object Service {
import UserDao._
private def debitAccount(user: User, amount: Long)(implicit executionContext: ExecutionContext): FutureEither[User] = {
if (amount > user.balance) {
FutureEither.errored(InsufficientFundsError(s"User ${user.id} does not have enough money to fulfil this request"))
} else {
//TODO update repo
FutureEither.successful(user.copy(balance = user.balance - amount))
}
}
def debitAccount(id: Long, amount: Long)(implicit executionContext: ExecutionContext): FutureEither[User] =
for {
user <- FutureEither(getUser(id), NoSuchUserError(s"user $id does not exist"))
res <- debitAccount(user, amount)
} yield res
}
object TestRouter1 {
def withdrawMoney(id: Long, amount: Long)(implicit executionContext: ExecutionContext): Future[HttpResponse] = {
Service.debitAccount(id.toLong, amount.toLong).value.flatMap(_ match {
case Left(ex: InsufficientFundsError) => Future.successful(HttpResponse(409, ex.msg))
case Left(ex: ResourceAccessError) => Future.successful(HttpResponse(400, ex.msg))
case Left(ex: NoSuchUserError) => Future.successful(HttpResponse(404, ex.msg))
case Right(user) => Future.successful(HttpResponse(200, s"User ${user.id} has successfully withdrawn $amount - remaining balance is ${user.balance}"))
}
).recover { case ex => HttpResponse(500, "Oh no! Something went wrong on our side") }
}
}
object FutureEitherTest extends App {
import TestRouter1._
private implicit val executionContext = scala.concurrent.ExecutionContext.global
withdrawMoney(3, 10).map(println)
withdrawMoney(2, 10).map(println)
withdrawMoney(1, 2000).map(println)
withdrawMoney(1, 650).map(println)
// HttpResponse(409,User 1 does not have enough money to fulfil this request)
// HttpResponse(200,User 1 has successfully withdrawn 650 - remaining balance is 350)
// HttpResponse(400,User 2 is inactive)
// HttpResponse(404,User 3 does not exist)
Thread.sleep(2000)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment