Как составлять ошибки с помощью data.validation

Я использую замечательный пакет Data.Validation для проверки. Однако я не могу правильно составить их для сложных типов.

Я создал типы для электронной почты и пароля Email и Password соответственно. Я создал проверки и ошибки, которые могут накапливаться с помощью пакета Data.Validation. Я создал новый тип под названием data Creds = Email Password. Теперь я не могу накапливать ошибки как из электронной почты, так и из пароля

`

``
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveGeneric       #-}

module ErrorComposition
  ()
where


import           Data.Text                     as T
import           Data.Validation
import           Control.Lens


------------------------------------
-- validations and errors for Email
------------------------------------
data Email =  Email
  {
    email :: Text
  } deriving (Show)

data EmailError = EmailNoDotError | EmailNoPeriodError
  deriving (Show)


noDot :: Email -> Validation [EmailError] ()
noDot x = if "." `isInfixOf` (email x)
  then _Success # ()
  else _Failure # [EmailNoDotError]

noPeriod :: Email -> Validation [EmailError] ()
noPeriod x = if "@" `isInfixOf` (email x)
  then _Success # ()
  else _Failure # [EmailNoPeriodError]

validateEmail :: Email -> Validation [EmailError] ()
validateEmail e = noDot e <* noPeriod e

mkEmail :: Text -> Validation [EmailError] Email
mkEmail e = pure (Email e) <* validateEmail (Email e)



-----------------------------------------------------------
-- validations for password and errors
-- --------------------------------------------------------

data Password = Password
  {
    password :: Text
  } deriving Show

data PasswordError = MinLengthPasswordError | NotStrongPasswordError
 deriving (Show)

minLength :: Int -> Password -> Validation [PasswordError] ()
minLength n x | T.length (password x) >= n = _Success # ()
              | otherwise                  = _Failure # [MinLengthPasswordError]

notStrong :: Password -> Validation [PasswordError] ()
notStrong x = if "strong" `isInfixOf` (password x)
  then _Success # ()
  else _Failure # [NotStrongPasswordError]

validatePassword :: Password -> Validation [PasswordError] ()
validatePassword p = minLength 7 p <* notStrong p

mkPassword :: Text -> Validation [PasswordError] Password
mkPassword p = pure (Password p) <* validatePassword (Password p)



--------------------------------------------------------------------
-- Domain Modelling for an Creds type
-- The expectation is errors from email and password should be combined
-- ----------------------------------------------------------------

data Creds = Creds
  {
    credsEmail :: Email
  , credsPassword:: Password
  } deriving (Show)

data CredsError = EmailError | PasswordError
 deriving (Show)

validateCreds :: Creds -> Validation [CredsError] ()
--validateCreds (Creds e p) = validateEmail e <* validatePassword p
validateCreds = undefined

mkCreds :: Email -> Password -> Validation [CredsError] Creds
mkCreds e p = pure (Creds e p) <* validateCreds (Creds e p)

ErrorComposition> let e1= mkEmail "bob"
*ErrorComposition> show e1
"Failure [EmailNoDotError,EmailNoPeriodError]"
*ErrorComposition> let p1 = mkPassword "he"
*ErrorComposition> show p1
"Failure [MinLengthPasswordError,NotStrongPasswordError]"
*ErrorComposition> let c1 = mkCreds (Email "bob") (Password "he")



What Am I expecting? 
Failure [EmailNoDotError, EmailNoPeriodError, MinLengthPasswordError,NotStrongPasswordError] 

1. Is it possible to get the above result?
2. Even if the above result is not possible because the error types differ, atleast, I'm looking for a way to get the following:
  Failure ["EmailNoDotError", "EmailNoPeriodError", 
           "MinLengthPasswordError,"NotStrongPasswordError"] 
3. Essentially could you please help me in fixing the validateCreds function above in the code?


person Twig    schedule 04.04.2019    source источник


Ответы (1)


Я буду использовать Either EmailError PasswordError в качестве комбинированного типа. Вместо этого вы можете создать собственный тип суммы.

mkCreds :: Text -> Text -> Validation [Either EmailError PasswordError] Creds
mkCreds e p = Creds <$> first Left (mkEmail e) <*> first Right (mkPassword p)

Это делает три вещи:

  • используйте mkEmail и mkPassword для проверки каждой части
  • поднимите ошибки в тип ошибки Either с first Left и first Right
  • объединить либо все ошибки, либо все части Creds с <$> и <*>
person bergey    schedule 04.04.2019