Skip to content

Commit edc0183

Browse files
committed
Sample code for EitherT usage
1 parent e8998e1 commit edc0183

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.baeldung.scala.cats.eithert
2+
3+
import cats.data.EitherT
4+
5+
import scala.concurrent.Future
6+
7+
object SimpleErrorHandling {
8+
9+
case class User(userId: String, name: String, isActive: Boolean)
10+
def getUserProfile(userId: String): Either[String, User] = ???
11+
def calculateDiscount(user: User): Either[String, Double] = ???
12+
def placeOrder(
13+
itemId: String,
14+
discount: Double,
15+
user: User
16+
): Either[String, String] = ???
17+
18+
def performAction(userId: String, itemId: String): Either[String, String] =
19+
for {
20+
user <- getUserProfile(userId)
21+
discount <- calculateDiscount(user)
22+
orderId <- placeOrder(itemId, discount, user)
23+
} yield orderId
24+
25+
}
26+
27+
object FutureErrorHandling {
28+
29+
case class User(userId: String, name: String, isActive: Boolean)
30+
def getUserProfile(userId: String): Future[Either[String, User]] = ???
31+
def calculateDiscount(user: User): Future[Either[String, Double]] = ???
32+
def placeOrder(
33+
itemId: String,
34+
discount: Double,
35+
user: User
36+
): Future[Either[String, String]] = ???
37+
38+
import scala.concurrent.Future
39+
import scala.concurrent.ExecutionContext.Implicits.global
40+
41+
def performAction(
42+
userId: String,
43+
itemId: String
44+
): Future[Either[String, String]] = {
45+
for {
46+
userEither <- getUserProfile(userId)
47+
result <- userEither match {
48+
case Left(error) => Future.successful(Left(error))
49+
case Right(user) =>
50+
for {
51+
discountEither <- calculateDiscount(user)
52+
orderResult <- discountEither match {
53+
case Left(error) => Future.successful(Left(error))
54+
case Right(discount) => placeOrder(itemId, discount, user)
55+
}
56+
} yield orderResult
57+
}
58+
} yield result
59+
}
60+
61+
}
62+
63+
object EitherTErrorHandling {
64+
65+
import scala.concurrent.ExecutionContext.Implicits.global
66+
case class User(userId: String, name: String, isActive: Boolean)
67+
def getUserProfile(userId: String): Future[Either[String, User]] = ???
68+
def calculateDiscount(user: User): Future[Either[String, Double]] = ???
69+
def placeOrder(
70+
itemId: String,
71+
discount: Double,
72+
user: User
73+
): Future[Either[String, String]] = ???
74+
75+
def performAction(userId: String, itemId: String): Future[Either[String, String]] = {
76+
(for {
77+
user <- EitherT(getUserProfile(userId))
78+
discount <- EitherT(calculateDiscount(user))
79+
orderId <- EitherT(placeOrder(itemId, discount, user))
80+
} yield orderId).value
81+
}
82+
83+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.baeldung.scala.cats.eithert
2+
3+
import cats.data.EitherT
4+
import org.scalatest.flatspec.AsyncFlatSpec
5+
import org.scalatest.matchers.should.Matchers
6+
7+
import scala.concurrent.Future
8+
import scala.util.{Failure, Try}
9+
10+
class EitherTUnitTest extends AsyncFlatSpec with Matchers {
11+
12+
it should "map over EitherT instance with Future" in {
13+
val response: EitherT[Future, String, Int] =
14+
EitherT(Future.successful(Right(100)))
15+
response.map(_ * 5).value.map(_ shouldBe Right(500))
16+
}
17+
18+
it should "map over EitherT instance with Try" in {
19+
val opValue: Try[Either[String, Int]] = Try(Right(100))
20+
val response: EitherT[Try, String, Int] = EitherT(opValue)
21+
val mappedValue: EitherT[Try, String, Int] = response.map(_ * 5)
22+
val underlying: Try[Either[String, Int]] = mappedValue.value
23+
underlying shouldBe Try(Right(500))
24+
}
25+
26+
it should "map over error" in {
27+
val response: EitherT[Try, String, Int] =
28+
EitherT(Try(Left("invalid number!")))
29+
response.leftMap(_.toUpperCase).value shouldBe Try(Left("INVALID NUMBER!"))
30+
}
31+
32+
it should "be able to map on both side of either using bimap" in {
33+
val success: EitherT[Try, String, Int] = EitherT(Try(Right(100)))
34+
val biMappedSuccess = success.bimap(e => e.toUpperCase, s => s * 5)
35+
biMappedSuccess.value shouldBe Try(Right(500))
36+
val error: EitherT[Try, String, Int] = EitherT(Try(Left("error")))
37+
val biMappedError = error.bimap(e => e.toUpperCase, s => s * 5)
38+
biMappedError.value shouldBe Try(Left("ERROR"))
39+
}
40+
41+
it should "compose multiple EitherT instances together" in {
42+
val num1: EitherT[Try, String, Int] = EitherT.right(Try(100))
43+
val num2: EitherT[Try, String, Int] = EitherT.liftF(Try(2))
44+
val divRes: EitherT[Try, String, Int] = for {
45+
n1 <- num1
46+
n2 <- num2
47+
div = n1 / n2
48+
} yield div
49+
divRes.value shouldBe Try(Right(50))
50+
}
51+
52+
it should "compose multiple EitherT instances together when there is an error" in {
53+
val num1: EitherT[Try, String, Int] = EitherT(Try(Right(100)))
54+
val num2: EitherT[Try, String, Int] = EitherT.left(Try("zero"))
55+
val divRes: EitherT[Try, String, Int] = for {
56+
n1 <- num1
57+
n2 <- num2
58+
div = n1 / n2
59+
} yield div
60+
divRes.value shouldBe Try(Left("zero"))
61+
}
62+
63+
it should "handle Try failure" in {
64+
val failedOp: Try[Either[String, Int]] =
65+
Try(throw new Exception("Operation failed!"))
66+
val response: EitherT[Try, String, Int] = EitherT(failedOp)
67+
response.value.isFailure shouldBe true
68+
}
69+
70+
it should "short circuit if one of the Try fails" in {
71+
val num1: EitherT[Try, String, Int] = EitherT(Try(Right(100)))
72+
val num2: EitherT[Try, String, Int] =
73+
EitherT(Try(throw new Exception("Operation failed!")))
74+
val divRes: EitherT[Try, String, Int] = for {
75+
n1 <- num1
76+
n2 <- num2
77+
div = n1 / n2
78+
} yield div
79+
divRes.value.isFailure shouldBe true
80+
}
81+
82+
it should "lift an either into eitherT" in {
83+
val either: Either[String, Int] = Right(100)
84+
val eitherT: EitherT[Try, String, Int] = EitherT.fromEither(either)
85+
eitherT.value.get shouldBe Right(100)
86+
}
87+
88+
it should "lift an option into eitherT" in {
89+
val opt: Option[Int] = Some(100)
90+
val eitherT: EitherT[Try, String, Int] = EitherT.fromOption(opt, "EMPTY")
91+
eitherT.value.get shouldBe Right(100)
92+
}
93+
94+
}

0 commit comments

Comments
 (0)