# F#: Bank Account Kata (Edge cases)

In the previous Bank Account kata, I attempted to implement happy path logic using a function-driven approach to later discover my types that would complement them.

Note that my initial implementation did not cover edge case scenarios.

For example:
Can one withdraw an amount that exceeds their balance?

Can one deposit absolutely nothing into their account?

My types were updated to the following:

```type Account =
| Checking of decimal
| Savings  of decimal

type Commands =
| Withdraw of Account * decimal
| Deposit  of Account * decimal
| Transfer of Account * Account * decimal

type Response =
| Withdrawal       of State * State
| WithdrawalFailed of Account * decimal

| Deposited        of State * State
| DepositFailed    of Account * decimal

| Transferred      of TransferSummary   * decimal
| TransferFailed   of Account * Account * decimal

and State =
| BeforeDeposit of Account * decimal
| AfterDeposit  of Account

| BeforeWithdrawal of Account * decimal
| AfterWithdrawal  of Account

and TransferSummary = {
FromBalanceBefore: Account ; ToBalanceBefore: Account
FromBalanceAfter:  Account ; ToBalanceAfter:  Account }
```

My functions were updated to the following:

```(*Functions*)
let balanceOf = function
| Checking v
| Savings  v

let update account amount operator = account |>  function
| Checking v -> Checking (v + (operator * amount))
| Savings  v -> Savings  (v + (operator * amount))

let debit       account amount =  update account amount (- 1m)
let credit      account amount =  update account amount (+ 1m)
let canWithdraw account amount = balanceOf account >= amount

let handleWithdraw =  function
| Withdraw (account , amount) ->
if canWithdraw account amount then
Some (Withdrawal(BeforeWithdrawal (account ,     amount),
AfterWithdrawal  (debit account amount)))
else Some (WithdrawalFailed (account, amount) )
| _ -> None

let handleDeposit =   function
| Deposit   (account , amount) ->
if amount > 0m then
Some (Deposited((BeforeDeposit (account , amount),
AfterDeposit  (credit account amount) )))
else Some (DepositFailed (account , amount))
| _ -> None

let handleTransfer =  function
| Transfer (fromAccount, toAccount, amount) ->
if canWithdraw fromAccount amount then
Some (Transferred ({ FromBalanceBefore=fromAccount
ToBalanceBefore=  toAccount

FromBalanceAfter= debit  fromAccount amount
ToBalanceAfter=   credit toAccount   amount } ,

amount))
else Some (TransferFailed (fromAccount, toAccount, amount))
| _ -> None
```

Here are some client calls for the updated code:

```(*Client*)
let deposit =   handleDeposit  (Deposit  (Checking 100m , 10m))
let withdrawl = handleWithdraw (Withdraw (Savings  10m , 10m))
let transfer =  handleTransfer (Transfer (Checking 100m , Savings 150m , 10m))
```

Here’s the output from the client calls:

```val deposit : Response option =
Some
(Deposited
(BeforeDeposit (Checking 100M,10M),AfterDeposit (Checking 110M)))
val withdrawl : Response option =
Some
(Withdrawal
(BeforeWithdrawal (Savings 10M,10M),AfterWithdrawal (Savings 0M)))
val transfer : Response option =
Some (Transferred ({FromBalanceBefore = Checking 100M;
ToBalanceBefore = Savings 150M;
FromBalanceAfter = Checking 90M;
ToBalanceAfter = Savings 160M;},10M))
```

The entire domain is below:

```module BankAccountImpl2

type Account =
| Checking of decimal
| Savings  of decimal

type Commands =
| Withdraw of Account * decimal
| Deposit  of Account * decimal
| Transfer of Account * Account * decimal

type Response =
| Withdrawal       of State * State
| WithdrawalFailed of Account * decimal

| Deposited        of State * State
| DepositFailed    of Account * decimal

| Transferred      of TransferSummary   * decimal
| TransferFailed   of Account * Account * decimal

and State =
| BeforeDeposit of Account * decimal
| AfterDeposit  of Account

| BeforeWithdrawal of Account * decimal
| AfterWithdrawal  of Account

and TransferSummary = {
FromBalanceBefore: Account ; ToBalanceBefore: Account
FromBalanceAfter:  Account ; ToBalanceAfter:  Account }

(*Functions*)
let balanceOf = function
| Checking v
| Savings  v

let update account amount operator = account |>  function
| Checking v -> Checking (v + (operator * amount))
| Savings  v -> Savings  (v + (operator * amount))

let debit       account amount =  update account amount (- 1m)
let credit      account amount =  update account amount (+ 1m)
let canWithdraw account amount = balanceOf account >= amount

let handleWithdraw =  function
| Withdraw (account , amount) ->
if canWithdraw account amount then
Some (Withdrawal(BeforeWithdrawal (account ,     amount),
AfterWithdrawal  (debit account amount)))
else Some (WithdrawalFailed (account, amount) )
| _ -> None

let handleDeposit =   function
| Deposit   (account , amount) ->
if amount > 0m then
Some (Deposited((BeforeDeposit (account , amount),
AfterDeposit  (credit account amount) )))
else Some (DepositFailed (account , amount))
| _ -> None

let handleTransfer =  function
| Transfer (fromAccount, toAccount, amount) ->
if canWithdraw fromAccount amount then
Some (Transferred ({ FromBalanceBefore=fromAccount
ToBalanceBefore=  toAccount

FromBalanceAfter= debit  fromAccount amount
ToBalanceAfter=   credit toAccount   amount } ,

amount))
else Some (TransferFailed (fromAccount, toAccount, amount))
| _ -> None

(*Client*)
let deposit =   handleDeposit  (Deposit  (Checking 100m , 10m))
let withdrawl = handleWithdraw (Withdraw (Savings  10m , 10m))
let transfer =  handleTransfer (Transfer (Checking 100m , Savings 150m , 10m))
```