-- | Validators that can be attached to forms
--
module Text.Digestive.Validate
( Validator
, validate
, validateMany
, check
, checkM
) where
import Prelude hiding (id)
import Control.Monad (liftM2)
import Data.Monoid (Monoid (..))
import Control.Category (id)
import Text.Digestive.Types
import Text.Digestive.Transform
-- | A validator. Invariant: the validator should not modify the result value,
-- only check it.
--
newtype Validator m e a = Validator {unValidator :: Transformer m e a a}
instance Monad m => Monoid (Validator m e a) where
mempty = Validator id
v1 `mappend` v2 = Validator $ Transformer $ \inp ->
liftM2 eitherPlus (unTransformer (unValidator v1) inp)
(unTransformer (unValidator v2) inp)
where
eitherPlus (Left e) (Left i) = Left $ e ++ i
eitherPlus (Left e) (Right _) = Left e
eitherPlus (Right _) (Left e) = Left e
eitherPlus (Right a) (Right _) = Right a
-- | Attach a validator to a form.
--
validate :: Monad m => Form m i e v a -> Validator m e a -> Form m i e v a
validate form = transform form . unValidator
-- | Attach multiple validators to a form.
--
validateMany :: Monad m => Form m i e v a -> [Validator m e a] -> Form m i e v a
validateMany form = validate form . mconcat
-- | Easy way to create a pure validator
--
check :: Monad m
=> e -- ^ Error message
-> (a -> Bool) -- ^ Actual validation
-> Validator m e a -- ^ Resulting validator
check error' = checkM error' . (return .)
-- | Easy way to create a monadic validator
--
checkM :: Monad m
=> e -- ^ Error message
-> (a -> m Bool) -- ^ Actual validation
-> Validator m e a -- ^ Resulting validator
checkM error' f = Validator $ Transformer $ \x -> do
valid <- f x
return $ if valid then Right x else Left [error']