Thanks. I suppose I confused you by showing you a passing test, partially with some behavior I didnāt want.
I wanted to protect the Password
type in my code, by keeping it private to an internal module, and force the callers (from other modules) to initialize it through the mkPassword
smart constructor.
That way, only passwords validated according to business rules could flow through the system because thereās no way to initialize them otherwise.
I thought about it more and I got this which Iām happy with (Iām using the hspec
testing library btw):
describe "mkPassword" $
it "ensures the password length is at minimum 10" $ do
mkPassword "123456789" `shouldBe` Left [PasswordTooShortError]
case mkPassword "123456789a" of
Left _ -> expectationFailure "This password should have been rejected!"
Right _pw -> return ()
If thatās not clear, hereās a bit more context (library code below truncated for brevity):
module Domain.Authentication (mkPassword, PasswordValidationError (..)) where
newtype Password = Password {passwordRaw :: Text} deriving (Show, Eq)
data PasswordValidationError
= PasswordTooShortError
| PasswordMustContainUpperCaseError
| PasswordMustContainLowerCaseError
| PasswordMustContainNumberError
deriving (Show, Eq)
mkPassword :: Text -> Either [PasswordValidationError] Password
mkPassword =
validate
Password
[ lengthBetween 10 500 PasswordTooShortError -- this type name is slightly confusing at the moment
]
module Domain.Validation where
type Validation error a = a -> Maybe error
validate :: (a -> b) -> [Validation error a] -> a -> Either [error] b
validate constructor validations val =
case concatMap (\f -> maybeToList $ f val) validations of
[] -> Right $ constructor val
errs -> Left errs