Skip to content

Ch 7 Exercise Groups #1 and #2 are complete #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a36fec7
Keep only chapter 7 exercises
oldfartdeveloper May 1, 2020
af34c99
Added unit test for regex validator
oldfartdeveloper May 2, 2020
574a669
Dropped unnecessary parenthesis
oldfartdeveloper May 2, 2020
3f41044
Example Group 2 Unit Testing Complete
oldfartdeveloper May 2, 2020
28de3c4
Updated chapter 7 text to include PR #127
oldfartdeveloper May 2, 2020
bbb793f
At this point this works; saving it for reference
oldfartdeveloper May 4, 2020
c2b1a6a
Can't understand how to convert a solution to a unit test
oldfartdeveloper May 4, 2020
ce307bc
Final setup to describe issue
oldfartdeveloper May 4, 2020
c031220
Finished exercise 2 in exercise group 3
oldfartdeveloper May 5, 2020
2cee1cf
Merging master
oldfartdeveloper May 12, 2020
f9c6ffe
Progress
oldfartdeveloper May 15, 2020
0527901
Progress
oldfartdeveloper May 15, 2020
668d9bf
Added type instance eqPhoneType
oldfartdeveloper May 17, 2020
cc927ae
Tests are running again
oldfartdeveloper May 17, 2020
f8c64ff
Set up external examplePerson
oldfartdeveloper May 17, 2020
b8ed8a0
Refactoring and renaming
oldfartdeveloper May 17, 2020
261eaa5
Testing 'Just' condition now auto-tested
oldfartdeveloper May 17, 2020
a5ff4b1
Test 'Just Address' when city is empty' test passes
oldfartdeveloper May 18, 2020
287d55b
Refactoring
oldfartdeveloper May 18, 2020
1d1c30c
Refactor
oldfartdeveloper May 18, 2020
19c1331
Refactored again
oldfartdeveloper May 18, 2020
dc1993c
Refactored
oldfartdeveloper May 18, 2020
3185c7f
Removed redundant 'Test' from suite description
oldfartdeveloper May 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 4 additions & 7 deletions exercises/chapter7/spago.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@
Welcome to a Spago project!
You can edit this file as you like.
-}
{ name =
"my-project"
{ name = "my-project"
, dependencies =
[ "console", "effect", "psci-support", "strings", "validation" ]
, packages =
./packages.dhall
, sources =
[ "src/**/*.purs", "test/**/*.purs" ]
[ "console", "effect", "psci-support", "strings", "test-unit", "validation" ]
, packages = ./packages.dhall
, sources = [ "src/**/*.purs", "test/**/*.purs" ]
}
73 changes: 35 additions & 38 deletions exercises/chapter7/src/Data/AddressBook.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,59 @@ module Data.AddressBook where

import Prelude

newtype Address = Address
{ street :: String
, city :: String
, state :: String
}
type Address
= { street :: String
, city :: String
, state :: String
}

address :: String -> String -> String -> Address
address street city state = Address { street, city, state }
address street city state = { street, city, state }

data PhoneType
= HomePhone
| WorkPhone
| CellPhone
| OtherPhone

newtype PhoneNumber = PhoneNumber
{ "type" :: PhoneType
, number :: String
}
instance eqPhoneType :: Eq PhoneType where
eq HomePhone HomePhone = true
eq WorkPhone WorkPhone = true
eq CellPhone CellPhone = true
eq OtherPhone OtherPhone = true
eq _ _ = false

instance showPhoneType :: Show PhoneType where
show HomePhone = "HomePhone"
show WorkPhone = "WorkPhone"
show CellPhone = "CellPhone"
show OtherPhone = "OtherPhone"

type PhoneNumber
= { "type" :: PhoneType
, number :: String
}

phoneNumber :: PhoneType -> String -> PhoneNumber
phoneNumber ty number = PhoneNumber
phoneNumber ty number =
{ "type": ty
, number: number
}

newtype Person = Person
{ firstName :: String
, lastName :: String
, homeAddress :: Address
, phones :: Array PhoneNumber
}
type Person
= { firstName :: String
, lastName :: String
, homeAddress :: Address
, phones :: Array PhoneNumber
}

person :: String -> String -> Address -> Array PhoneNumber -> Person
person firstName lastName homeAddress phones =
Person { firstName, lastName, homeAddress, phones }
person firstName lastName homeAddress phones = { firstName, lastName, homeAddress, phones }

examplePerson :: Person
examplePerson =
person "John" "Smith"
(address "123 Fake St." "FakeTown" "CA")
[ phoneNumber HomePhone "555-555-5555"
, phoneNumber CellPhone "555-555-0000"
]

instance showAddress :: Show Address where
show (Address o) = "Address " <> show o

instance showPhoneType :: Show PhoneType where
show HomePhone = "HomePhone"
show WorkPhone = "WorkPhone"
show CellPhone = "CellPhone"
show OtherPhone = "OtherPhone"

instance showPhoneNumber :: Show PhoneNumber where
show (PhoneNumber o) = "PhoneNumber " <> show o

instance showPerson :: Show Person where
show (Person o) = "Person " <> show o
(address "123 Fake St." "FakeTown" "CA")
[ phoneNumber HomePhone "555-555-5555"
, phoneNumber CellPhone "555-555-0000"
]
83 changes: 43 additions & 40 deletions exercises/chapter7/src/Data/AddressBook/Validation.purs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Data.AddressBook.Validation where

import Prelude
import Data.AddressBook (Address(..), Person(..), PhoneNumber(..), address, person, phoneNumber)
import Data.AddressBook (Address, Person, PhoneNumber, address, person, phoneNumber)
import Data.Either (Either(..))
import Data.String (length)
import Data.String.Regex (Regex, test, regex)
Expand All @@ -10,71 +10,74 @@ import Data.Traversable (traverse)
import Data.Validation.Semigroup (V, unV, invalid)
import Partial.Unsafe (unsafePartial)

type Errors = Array String
type Errors
= Array String

nonEmpty :: String -> String -> V Errors Unit
nonEmpty field "" = invalid ["Field '" <> field <> "' cannot be empty"]
nonEmpty _ _ = pure unit
nonEmpty field "" = invalid [ "Field '" <> field <> "' cannot be empty" ]

nonEmpty _ _ = pure unit

arrayNonEmpty :: forall a. String -> Array a -> V Errors Unit
arrayNonEmpty field [] = invalid ["Field '" <> field <> "' must contain at least one value"]
arrayNonEmpty _ _ = pure unit
arrayNonEmpty field [] = invalid [ "Field '" <> field <> "' must contain at least one value" ]

arrayNonEmpty _ _ = pure unit

lengthIs :: String -> Int -> String -> V Errors Unit
lengthIs field len value | length value /= len = invalid ["Field '" <> field <> "' must have length " <> show len]
lengthIs _ _ _ = pure unit
lengthIs field len value
| length value /= len = invalid [ "Field '" <> field <> "' must have length " <> show len ]

lengthIs _ _ _ = pure unit

phoneNumberRegex :: Regex
phoneNumberRegex =
unsafePartial
case regex "^\\d{3}-\\d{3}-\\d{4}$" noFlags of
Right r -> r
unsafePartial case regex "^\\d{3}-\\d{3}-\\d{4}$" noFlags of
Right r -> r

matches :: String -> Regex -> String -> V Errors Unit
matches _ regex value | test regex value = pure unit
matches field _ _ = invalid ["Field '" <> field <> "' did not match the required format"]
matches _ regex value
| test regex value = pure unit

matches field _ _ = invalid [ "Field '" <> field <> "' did not match the required format" ]

validateAddress :: Address -> V Errors Address
validateAddress (Address o) =
address <$> (nonEmpty "Street" o.street *> pure o.street)
<*> (nonEmpty "City" o.city *> pure o.city)
<*> (lengthIs "State" 2 o.state *> pure o.state)
validateAddress a =
address <$> (nonEmpty "Street" a.street *> pure a.street)
<*> (nonEmpty "City" a.city *> pure a.city)
<*> (lengthIs "State" 2 a.state *> pure a.state)

validateAddressAdo :: Address -> V Errors Address
validateAddressAdo (Address o) = ado
street <- (nonEmpty "Street" o.street *> pure o.street)
city <- (nonEmpty "City" o.city *> pure o.city)
state <- (lengthIs "State" 2 o.state *> pure o.state)
validateAddressAdo a = ado
street <- (nonEmpty "Street" a.street *> pure a.street)
city <- (nonEmpty "City" a.city *> pure a.city)
state <- (lengthIs "State" 2 a.state *> pure a.state)
in address street city state


validatePhoneNumber :: PhoneNumber -> V Errors PhoneNumber
validatePhoneNumber (PhoneNumber o) =
phoneNumber <$> pure o."type"
<*> (matches "Number" phoneNumberRegex o.number *> pure o.number)
validatePhoneNumber pn =
phoneNumber <$> pure pn."type"
<*> (matches "Number" phoneNumberRegex pn.number *> pure pn.number)

validatePhoneNumberAdo :: PhoneNumber -> V Errors PhoneNumber
validatePhoneNumberAdo (PhoneNumber o) = ado
tpe <- pure o."type"
number <- (matches "Number" phoneNumberRegex o.number *> pure o.number)
validatePhoneNumberAdo pn = ado
tpe <- pure pn."type"
number <- (matches "Number" phoneNumberRegex pn.number *> pure pn.number)
in phoneNumber tpe number


validatePerson :: Person -> V Errors Person
validatePerson (Person o) =
person <$> (nonEmpty "First Name" o.firstName *> pure o.firstName)
<*> (nonEmpty "Last Name" o.lastName *> pure o.lastName)
<*> validateAddress o.homeAddress
<*> (arrayNonEmpty "Phone Numbers" o.phones *> traverse validatePhoneNumber o.phones)
validatePerson p =
person <$> (nonEmpty "First Name" p.firstName *> pure p.firstName)
<*> (nonEmpty "Last Name" p.lastName *> pure p.lastName)
<*> validateAddress p.homeAddress
<*> (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones)

validatePersonAdo :: Person -> V Errors Person
validatePersonAdo (Person o) = ado
firstName <- (nonEmpty "First Name" o.firstName *> pure o.firstName)
lastName <- (nonEmpty "Last Name" o.lastName *> pure o.lastName)
address <- validateAddress o.homeAddress
numbers <- (arrayNonEmpty "Phone Numbers" o.phones *> traverse validatePhoneNumber o.phones)
validatePersonAdo p = ado
firstName <- (nonEmpty "First Name" p.firstName *> pure p.firstName)
lastName <- (nonEmpty "Last Name" p.lastName *> pure p.lastName)
address <- validateAddress p.homeAddress
numbers <- (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones)
in person firstName lastName address numbers


validatePerson' :: Person -> Either Errors Person
validatePerson' p = unV Left Right $ validatePerson p
Loading