Skip to content

Commit 6dec1a7

Browse files
chore: add error managment kata
1 parent a983470 commit 6dec1a7

File tree

8 files changed

+357
-0
lines changed

8 files changed

+357
-0
lines changed

errormanagment/.scalafmt.conf

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
version=3.5.8
2+
3+
# Only format files tracked by git.
4+
project.git = true
5+
6+
maxColumn = 120
7+
8+
runner.dialect = scala213
9+
10+
trailingCommas = preserve
11+
12+
literals.long = Upper
13+
literals.float = Upper
14+
literals.double = Upper
15+
16+
continuationIndent {
17+
callSite = 2
18+
defnSite = 2
19+
}
20+
21+
danglingParentheses.preset = true
22+
23+
newlines {
24+
avoidAfterYield = false
25+
sometimesBeforeColonInMethodReturnType = false
26+
}
27+
28+
spaces {
29+
inImportCurlyBraces = true
30+
}
31+
32+
align.preset = none
33+
34+
docstrings {
35+
style = SpaceAsterisk
36+
wrap = no
37+
}
38+
39+
rewrite {
40+
rules = [SortImports, RedundantParens, SortModifiers]
41+
}

errormanagment/build.sbt

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
// The simplest possible sbt build file is just one line:
3+
4+
scalaVersion := "2.13.8"
5+
// That is, to create a valid sbt build, all you've got to do is define the
6+
// version of Scala you'd like your project to use.
7+
8+
// ============================================================================
9+
10+
// Lines like the above defining `scalaVersion` are called "settings". Settings
11+
// are key/value pairs. In the case of `scalaVersion`, the key is "scalaVersion"
12+
// and the value is "2.13.8"
13+
14+
// It's possible to define many kinds of settings, such as:
15+
16+
name := "errorManagement"
17+
organization := "ch.epfl.scala"
18+
version := "1.0"
19+
20+
// Note, it's not required for you to define these three settings. These are
21+
// mostly only necessary if you intend to publish your library's binaries on a
22+
// place like Sonatype.
23+
24+
25+
// Want to use a published library in your project?
26+
// You can define other libraries as dependencies in your build like this:
27+
28+
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1"
29+
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % Test
30+
libraryDependencies +="org.typelevel" %% "cats-core" % "2.10.0"
31+
32+
33+
// Here, `libraryDependencies` is a set of dependencies, and by using `+=`,
34+
// we're adding the scala-parser-combinators dependency to the set of dependencies
35+
// that sbt will go and fetch when it starts up.
36+
// Now, in any Scala file, you can import classes, objects, etc., from
37+
// scala-parser-combinators with a regular import.
38+
39+
// TIP: To find the "dependency" that you need to add to the
40+
// `libraryDependencies` set, which in the above example looks like this:
41+
42+
// "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1"
43+
44+
// You can use Scaladex, an index of all known published Scala libraries. There,
45+
// after you find the library you want, you can just copy/paste the dependency
46+
// information that you need into your build file. For example, on the
47+
// scala/scala-parser-combinators Scaladex page,
48+
// https://index.scala-lang.org/scala/scala-parser-combinators, you can copy/paste
49+
// the sbt dependency from the sbt box on the right-hand side of the screen.
50+
51+
// IMPORTANT NOTE: while build files look _kind of_ like regular Scala, it's
52+
// important to note that syntax in *.sbt files doesn't always behave like
53+
// regular Scala. For example, notice in this build file that it's not required
54+
// to put our settings into an enclosing object or class. Always remember that
55+
// sbt is a bit different, semantically, than vanilla Scala.
56+
57+
// ============================================================================
58+
59+
// Most moderately interesting Scala projects don't make use of the very simple
60+
// build file style (called "bare style") used in this build.sbt file. Most
61+
// intermediate Scala projects make use of so-called "multi-project" builds. A
62+
// multi-project build makes it possible to have different folders which sbt can
63+
// be configured differently for. That is, you may wish to have different
64+
// dependencies or different testing frameworks defined for different parts of
65+
// your codebase. Multi-project builds make this possible.
66+
67+
// Here's a quick glimpse of what a multi-project build looks like for this
68+
// build, with only one "subproject" defined, called `root`:
69+
70+
// lazy val root = (project in file(".")).
71+
// settings(
72+
// inThisBuild(List(
73+
// organization := "ch.epfl.scala",
74+
// scalaVersion := "2.13.8"
75+
// )),
76+
// name := "hello-world"
77+
// )
78+
79+
// To learn more about multi-project builds, head over to the official sbt
80+
// documentation at http://www.scala-sbt.org/documentation.html
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version=1.9.7
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
object Main extends App {
2+
println("Hello, World!")
3+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import org.scalatest.flatspec.AnyFlatSpec
2+
import org.scalatest.matchers.should.Matchers
3+
4+
class ErrorManagementWithBooleanSpec extends AnyFlatSpec with Matchers {
5+
6+
trait AccountStatus
7+
case object Open extends AccountStatus
8+
case object Closed extends AccountStatus
9+
10+
case class BankAccount(availableMoney : Double, status : AccountStatus)
11+
def withDraw(amount: Double, bankAccount: BankAccount): Boolean = {
12+
if (amount < 0) {
13+
false
14+
} else if (amount > bankAccount.availableMoney) {
15+
false
16+
}else if (bankAccount.status == Closed){
17+
false
18+
} else {
19+
true
20+
}
21+
}
22+
23+
"Withdrawing more money than the bank account holds" should " return false" in {
24+
val emptyBankAccount = BankAccount(0, Open)
25+
withDraw(amount = 20_000, bankAccount = emptyBankAccount) shouldBe false
26+
}
27+
28+
"Withdrawing negative money" should " return false" in {
29+
val bankAccountWithMoney = BankAccount(10_000, Open)
30+
withDraw(-100, bankAccountWithMoney) shouldBe false
31+
}
32+
"Withdrawing on closed money" should "return false" in {
33+
val bankAccountWithMoney = BankAccount(0, Closed)
34+
withDraw(100, bankAccountWithMoney) shouldBe false
35+
}
36+
37+
"withdrawing money from an adequately funded account" should "return true" in {
38+
val bankAccountWithMoney = BankAccount(10_000, Open)
39+
withDraw(100, bankAccountWithMoney) shouldBe true
40+
}
41+
42+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import org.scalatest.flatspec.AnyFlatSpec
2+
import org.scalatest.matchers.should.Matchers
3+
4+
class ErrorManagementWithEitherStringSpec extends AnyFlatSpec with Matchers {
5+
6+
trait AccountStatus
7+
case object Open extends AccountStatus
8+
case object Closed extends AccountStatus
9+
10+
case class BankAccount(availableMoney : Double, status : AccountStatus)
11+
def withDraw(amount: Double, bankAccount: BankAccount): Either[String, BankAccount] = {
12+
if (amount < 0) {
13+
Left("amount should be greater that 0 ")
14+
} else if (amount > bankAccount.availableMoney) {
15+
Left("amount should be lower that available money")
16+
} else if (bankAccount.status == Closed){
17+
Left("Account should be open")
18+
} else {
19+
Right(bankAccount.copy(availableMoney = bankAccount.availableMoney - amount))
20+
}
21+
}
22+
23+
"Withdrawing more money than the bank account holds" should "return Left with error message " in {
24+
val emptyBankAccount = BankAccount(0, Open)
25+
withDraw(amount = 20_000, bankAccount = emptyBankAccount) shouldBe Left("amount should be lower that available money")
26+
}
27+
28+
"Withdrawing negative money" should " return false" in {
29+
val bankAccountWithMoney = BankAccount(10_000, Open)
30+
withDraw(-100, bankAccountWithMoney) shouldBe Left("amount should be greater that 0 ")
31+
}
32+
"Withdrawing on closed money" should "return false" in {
33+
val bankAccountWithMoney = BankAccount(10_000, Closed)
34+
withDraw(100, bankAccountWithMoney) shouldBe Left("Account should be open")
35+
}
36+
37+
"withdrawing money from an adequately funded account" should "return true" in {
38+
val bankAccountWithMoney = BankAccount(10_000, Open)
39+
withDraw(100, bankAccountWithMoney) shouldBe Right(BankAccount(9_900, Open))
40+
}
41+
42+
"(using flatMap) multiple cash withdrawals must work with an adequately funded account" should "return true" in {
43+
val bankAccountWithMoney = BankAccount(10_000, Open)
44+
45+
val bankAccountOneWithdrawOrError: Either[String, BankAccount] = withDraw(100, bankAccountWithMoney)
46+
bankAccountOneWithdrawOrError shouldBe Right(BankAccount(9_900, Open))
47+
48+
val bankAccountWithTwoWithdraw: Either[String, BankAccount] = bankAccountOneWithdrawOrError.flatMap(bankAccount => withDraw(100, bankAccount))
49+
bankAccountWithTwoWithdraw shouldBe Right(BankAccount(9_800, Open))
50+
51+
val bankAccountWithThreeWithdraw: Either[String, BankAccount] = bankAccountWithTwoWithdraw.flatMap(bankAccount => withDraw(100, bankAccount))
52+
bankAccountWithThreeWithdraw shouldBe Right(BankAccount(9_700, Open))
53+
}
54+
55+
"(using for comprehension) multiple cash withdrawals must work with an adequately funded account" should "return true" in {
56+
val bankAccountWithMoney = BankAccount(10_000, Open)
57+
58+
val bankAccountOrError = for {
59+
bankAccountOneWithdraw <- withDraw(100, bankAccountWithMoney)
60+
bankAccountTwoWithdraw <- withDraw(100,bankAccountOneWithdraw)
61+
bankAccountThreeWithdraw <- withDraw(100,bankAccountTwoWithdraw)
62+
} yield bankAccountThreeWithdraw
63+
64+
bankAccountOrError shouldBe Right(BankAccount(9_700, Open))
65+
}
66+
67+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import org.scalatest.flatspec.AnyFlatSpec
2+
import org.scalatest.matchers.should.Matchers
3+
4+
class ErrorManagementWithEitherTraitSpec extends AnyFlatSpec with Matchers {
5+
6+
trait AccountStatus
7+
8+
case object Open extends AccountStatus
9+
10+
case object Closed extends AccountStatus
11+
12+
case class BankAccount(availableMoney: Double, status: AccountStatus)
13+
trait OperationError
14+
case object AmountShouldPositive extends OperationError
15+
case object AmountShouldLowerThanAvailableMoney extends OperationError
16+
case object AccountShouldBeOpen extends OperationError
17+
18+
def withDraw(amount: Double, bankAccount: BankAccount): Either[OperationError, BankAccount] = {
19+
if (amount < 0) {
20+
Left(AmountShouldPositive)
21+
} else if (amount > bankAccount.availableMoney) {
22+
Left(AmountShouldLowerThanAvailableMoney)
23+
} else if (bankAccount.status == Closed) {
24+
Left(AccountShouldBeOpen)
25+
} else {
26+
Right(bankAccount.copy(availableMoney = bankAccount.availableMoney - amount))
27+
}
28+
}
29+
30+
"Withdrawing more money than the bank account holds" should "return Left with error message " in {
31+
val emptyBankAccount = BankAccount(0, Open)
32+
withDraw(amount = 20_000, bankAccount = emptyBankAccount) shouldBe Left(AmountShouldLowerThanAvailableMoney)
33+
}
34+
35+
"Withdrawing negative money" should " return false" in {
36+
val bankAccountWithMoney = BankAccount(10_000, Open)
37+
withDraw(-100, bankAccountWithMoney) shouldBe Left(AmountShouldPositive)
38+
}
39+
"Withdrawing on closed money" should "return false" in {
40+
val bankAccountWithMoney = BankAccount(10_000, Closed)
41+
withDraw(100, bankAccountWithMoney) shouldBe Left(AccountShouldBeOpen)
42+
}
43+
44+
"withdrawing money from an adequately funded account" should "return true" in {
45+
val bankAccountWithMoney = BankAccount(10_000, Open)
46+
withDraw(100, bankAccountWithMoney) shouldBe Right(BankAccount(9_900, Open))
47+
}
48+
49+
"(using flatMap) multiple cash withdrawals must work with an adequately funded account" should "return true" in {
50+
val bankAccountWithMoney = BankAccount(10_000, Open)
51+
52+
val bankAccountOneWithdrawOrError= withDraw(100, bankAccountWithMoney)
53+
bankAccountOneWithdrawOrError shouldBe Right(BankAccount(9_900, Open))
54+
55+
val bankAccountWithTwoWithdraw= bankAccountOneWithdrawOrError.flatMap(bankAccount => withDraw(100, bankAccount))
56+
bankAccountWithTwoWithdraw shouldBe Right(BankAccount(9_800, Open))
57+
58+
val bankAccountWithThreeWithdraw= bankAccountWithTwoWithdraw.flatMap(bankAccount => withDraw(100, bankAccount))
59+
bankAccountWithThreeWithdraw shouldBe Right(BankAccount(9_700, Open))
60+
}
61+
62+
"(using for comprehension) multiple cash withdrawals must work with an adequately funded account" should "return true" in {
63+
val bankAccountWithMoney = BankAccount(10_000, Open)
64+
65+
val bankAccountOrError = for {
66+
bankAccountOneWithdraw <- withDraw(100, bankAccountWithMoney)
67+
bankAccountTwoWithdraw <- withDraw(100, bankAccountOneWithdraw)
68+
bankAccountThreeWithdraw <- withDraw(100, bankAccountTwoWithdraw)
69+
} yield bankAccountThreeWithdraw
70+
71+
bankAccountOrError shouldBe Right(BankAccount(9_700, Open))
72+
}
73+
74+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import cats.data.Validated.Invalid
2+
import cats.data.{Chain, ValidatedNec}
3+
import cats.implicits._
4+
import org.scalatest.flatspec.AnyFlatSpec
5+
import org.scalatest.matchers.should.Matchers
6+
class ErrorManagementWithValidSpec extends AnyFlatSpec with Matchers {
7+
8+
trait AccountStatus
9+
case object Open extends AccountStatus
10+
case object Closed extends AccountStatus
11+
case class BankAccount(availableMoney: Double, status: AccountStatus)
12+
trait OperationError
13+
case object AmountShouldPositive extends OperationError
14+
case object AmountShouldLowerThanAvailableMoney extends OperationError
15+
case object AccountShouldBeOpen extends OperationError
16+
17+
def amountShouldBePositive(amount : Double): ValidatedNec[OperationError, Double] = {
18+
if (amount < 0) {
19+
AmountShouldPositive.invalidNec
20+
}else amount.validNec
21+
}
22+
23+
def amountShouldLowerThanAvailableMoney(amount: Double, bankAccount: BankAccount): ValidatedNec[OperationError, Double] = {
24+
if (amount > bankAccount.availableMoney) {
25+
AmountShouldLowerThanAvailableMoney.invalidNec
26+
} else amount.validNec
27+
}
28+
def accountShouldBeOpen(bankAccount: BankAccount): ValidatedNec[OperationError, BankAccount] = {
29+
if (bankAccount.status == Closed){
30+
AccountShouldBeOpen.invalidNec
31+
} else bankAccount.validNec
32+
}
33+
34+
def withDraw(amount: Double, bankAccount: BankAccount): ValidatedNec[OperationError, BankAccount] = {
35+
val amountIsPositiveOrError: ValidatedNec[OperationError, Double] = amountShouldBePositive(amount)
36+
val amountIsLowerOrError: ValidatedNec[OperationError, Double] = amountShouldLowerThanAvailableMoney(amount, bankAccount)
37+
val accountIsOpenOrError: ValidatedNec[OperationError, BankAccount] = accountShouldBeOpen(bankAccount)
38+
((amountIsPositiveOrError combine amountIsLowerOrError), accountIsOpenOrError).mapN(
39+
(validAmount, bankAccount) => bankAccount.copy(availableMoney = bankAccount.availableMoney- validAmount) )
40+
}
41+
42+
"Withdrawing more money than the bank account holds on a close account" should "return all errors " in {
43+
val emptyAndClosedBankAccount = BankAccount(0, Closed)
44+
val bankAccountOrError = withDraw(amount = 20_000, bankAccount = emptyAndClosedBankAccount)
45+
46+
bankAccountOrError shouldBe Invalid(Chain(AmountShouldLowerThanAvailableMoney, AccountShouldBeOpen))
47+
}
48+
49+
}

0 commit comments

Comments
 (0)