Straightforward validation monad. Convenient for validating web forms and APIs.
web
Go to file
2024-01-08 12:32:38 +03:00
src/Control/Monad adjust to mtl-2.3+ 2024-01-08 12:32:38 +03:00
test Removed cabal from travis 2020-02-04 16:23:03 +03:00
.travis.yml Add ghc 8.10.2 nightly 2020-11-03 18:52:12 +03:00
ChangeLog.md bumped version and added explicit default-language to tests. 2020-02-04 15:03:39 +03:00
LICENSE Initial release 2017-05-17 13:08:45 +03:00
README.md Update description and synopsis [ci skip] 2020-11-03 18:51:29 +03:00
Setup.hs Initial release 2017-05-17 13:08:45 +03:00
stack.yaml use 9.6.3 2024-01-03 18:55:49 +03:00
validationt.cabal update cabal description 2020-11-03 19:05:17 +03:00

Travis CI Badge

ValidationT

A simple data validation library. The main idea is to provide an easy way to validate web form data by aggregating errors for each field.

Usage

Suppose you want to validate some data from the user. Say, that the password adheres to some rules.

You might do something like this:

validatePassword :: Monad m => String -> ValidationT [String] m ()
validatePassword password = do
  vLength password
  vAlpha password
  vNum password
  where
    vLength p = when (length p < 8)
      $ vWarning ["The password should be at least 8 characters long."]
    vAlpha p = unless (any isAlpha p)
      $ vWarning ["The password should contain at least one alphabetic character."]
    vNum p = unless (any isDigit p)
      $ vWarning ["The password should contain at least one numeric character."]

ValidationT e m a essentially just gathers the errors thrown by vWarning.

vWarning :: (Monad m, Monoid e) => e -> ValidationT e m ()

vWarning mappends the given e to the already collected errors. This is why the warnings are contained inside a list.

There is also vError. The only difference between vWarning and vError is that vError stops further execution (and further collection of errors and warnings).

You would use the validation like this:

main :: IO ()
main = do
  password <- getLine
  result <- runValidationTEither . validatePassword $ password
  putStrLn $ case result of
    Left errs -> unlines err
    Right () -> "You are fine."

You could, of course, do more complicated things like use vWarningL and vErrorL to add an error to a mempty structure, which gets mappended with other errors.

The library comes with a MonoidMap newtype wrapper around Map, which mappends the values themselves on conflict. This can be useful if you have a multiple points of failure and you want to distinguich between them -- validating a username and a password for example:

data Piece
  = Password
  | UserName
  deriving (Eq, Show, Ord)

validatePassword :: Monad m => String -> ValidationT (MonoidMap Piece [String]) m ()
validatePassword password = do
  vLength password
  vAlpha password
  vNum password
  where
    warning = mmSingleton UserName
    vLength p = when (length p < 8)
      $ warning ["The password should be at least 8 characters long."]
    vAlpha p = unless (any isAlpha p)
      $ warning ["The password should contain at least one alphabetic character."]
    vNum p = unless (any isDigit p)
      $ warning ["The password should contain at least one numeric character."]

(mmSingleton is a covenience initializer for MonoidMap.)