Last active
January 28, 2016 13:24
-
-
Save SimonRichardson/7c212acdd4068f2bc134 to your computer and use it in GitHub Desktop.
Validation.purs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Data.AddressBook where | |
| import Data.Maybe | |
| newtype Address = Address | |
| { street :: String | |
| , city :: String | |
| , state :: String | |
| } | |
| address :: String -> String -> String -> Address | |
| address street city state = Address | |
| { street: street | |
| , city: city | |
| , state: state | |
| } | |
| instance showAddress :: Show Address where | |
| show (Address a) = "Address " ++ | |
| "{ street: " ++ show a.street ++ | |
| ", city: " ++ show a.city ++ | |
| ", state: " ++ show a.state ++ | |
| " }" | |
| data PhoneType | |
| = HomePhone | |
| | WorkPhone | |
| | CellPhone | |
| | OtherPhone | |
| instance showPhoneType :: Show PhoneType where | |
| show HomePhone = "HomePhone" | |
| show WorkPhone = "WorkPhone" | |
| show CellPhone = "CellPhone" | |
| show OtherPhone = "OtherPhone" | |
| newtype PhoneNumber = PhoneNumber | |
| { "type" :: PhoneType | |
| , number :: String | |
| } | |
| phoneNumber :: PhoneType -> String -> PhoneNumber | |
| phoneNumber t number = PhoneNumber | |
| { "type": t | |
| , number: number | |
| } | |
| instance showPhoneNumber :: Show PhoneNumber where | |
| show (PhoneNumber p) = "PhoneNumber " ++ | |
| "{ type: " ++ show p."type" ++ | |
| ", number: " ++ show p.number ++ | |
| " }" | |
| newtype Person = Person | |
| { firstName :: String | |
| , lastName :: String | |
| , address :: Maybe Address | |
| , phones :: [PhoneNumber] | |
| } | |
| person :: String -> String -> Maybe Address -> [PhoneNumber] -> Person | |
| person firstName lastName address phones = Person | |
| { firstName: firstName | |
| , lastName: lastName | |
| , address: address | |
| , phones: phones | |
| } | |
| examplePerson :: Person | |
| examplePerson = | |
| person "John" "Smith" | |
| (Just $ address "123 Fake St." "Fake Town" "CA") | |
| [ phoneNumber HomePhone "555-555-5555" | |
| , phoneNumber CellPhone "555-555-0000" | |
| ] | |
| exampleEmptyPersonAddress :: Person | |
| exampleEmptyPersonAddress = | |
| person "Authur" "Guinness" | |
| Nothing | |
| [ phoneNumber HomePhone "555-555-0001" | |
| ] | |
| instance showPerson :: Show Person where | |
| show (Person p) = "Person " ++ | |
| "{ firstName: " ++ show p.firstName ++ | |
| ", lastName: " ++ show p.lastName ++ | |
| ", address: " ++ show p.address ++ | |
| ", phones: " ++ show p.phones ++ | |
| " }" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module Data.AddressBook.Validation where | |
| import Data.Array | |
| import Data.Either | |
| import Data.Validation | |
| import Data.AddressBook | |
| import Data.Traversable | |
| import qualified Data.String as S | |
| import qualified Data.String.Regex as R | |
| import Control.Apply | |
| type Errors = [String] | |
| nonEmpty :: String -> String -> V Errors Unit | |
| nonEmpty field "" = invalid ["Field '" ++ field ++ "' cannot be empty"] | |
| nonEmpty _ _ = pure unit | |
| nonEmpty' :: String -> String -> V Errors Unit | |
| nonEmpty' field value = matches field whiteSpaceRegex value | |
| arrayNonEmpty :: forall a. String -> [a] -> V Errors Unit | |
| arrayNonEmpty field [] = invalid ["Field '" ++ field ++ "' must contain at least one value"] | |
| arrayNonEmpty _ _ = pure unit | |
| lengthIs :: String -> Number -> String -> V Errors Unit | |
| lengthIs field len value | S.length value /= len = invalid ["Field '" ++ field ++ "' must have length " ++ show len] | |
| lengthIs _ _ _ = pure unit | |
| regexpFlags :: R.RegexFlags | |
| regexpFlags = { unicode: false | |
| , sticky: false | |
| , multiline: false | |
| , ignoreCase: false | |
| , global: false | |
| } | |
| phoneNumberRegex :: R.Regex | |
| phoneNumberRegex = | |
| R.regex | |
| "^\\d{3}-\\d{3}-\\d{4}$" | |
| regexpFlags | |
| stateRegex :: R.Regex | |
| stateRegex = | |
| R.regex | |
| "^[A-Z]{2}$" | |
| regexpFlags | |
| whiteSpaceRegex :: R.Regex | |
| whiteSpaceRegex = | |
| R.regex | |
| "^\\S+.*(\\S+)$" | |
| regexpFlags | |
| matches :: String -> R.Regex -> String -> V Errors Unit | |
| matches _ regex value | R.test regex value = pure unit | |
| matches field _ _ = invalid ["Field '" ++ field ++ "' did not match the required format"] | |
| validateState :: String -> V Errors String | |
| validateState s = (matches "State" stateRegex s *> pure s) | |
| validateAddress :: Address -> V Errors Address | |
| validateAddress (Address a) = | |
| address <$> (nonEmpty' "Street" a.street *> pure a.street) | |
| <*> (nonEmpty' "City" a.city *> pure a.city) | |
| <*> validateState a.state | |
| validatePhoneNumber :: PhoneNumber -> V Errors PhoneNumber | |
| validatePhoneNumber (PhoneNumber p) = | |
| phoneNumber <$> pure p."type" | |
| <*> (matches "Number" phoneNumberRegex p.number *> pure p.number) | |
| validatePerson :: Person -> V Errors Person | |
| validatePerson (Person p) = | |
| person <$> (nonEmpty' "First Name" p.firstName *> pure p.firstName) | |
| <*> (nonEmpty' "Last Name" p.lastName *> pure p.lastName) | |
| <*> traverse validateAddress p.address | |
| <*> (arrayNonEmpty "Phone Numbers" p.phones *> traverse validatePhoneNumber p.phones) | |
| validatePerson' :: Person -> Either Errors Person | |
| validatePerson' p = runV Left Right $ validatePerson p |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment