packages feed

valida-base-0.1.0: src/Valida/ValidationRule.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE Safe          #-}

module Valida.ValidationRule
    ( ValidationRule (..)
    , vrule
    ) where

import Data.Typeable (Typeable)

import GHC.Generics (Generic)

import Valida.Validation (Validation (..))

{- | The rule a Validator uses to run validation.

Contains a function that accepts the target type and returns a `Validation` result.

The type- __ValidationRule (NonEmpty String) Int__, designates a rule that verifies the validity of an __Int__, and
uses a value of type __NonEmpty String__ to represent error, in case of failure.
-}
newtype ValidationRule e a
  -- | Builds a 'ValidationRule' from a function to generate error and a validation predicate.
  = ValidationRule
  -- ^ The validation predicate.
    (a -> Validation e ())
  deriving (Typeable, Generic)

{- |

[@(<>)@] '(<>)' creates a new `ValidationRule` that only succeeds when both given rule succeed.
Otherwise left-most failure is returned.

__Examples__

>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 5
Success 5
>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 4
Failure ("IsEven" :| [])
>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 15
Failure ("GreaterThan9" :| [])
>>> runValidator (validate (failureIf even "IsEven" <> failureIf (>9) "GreaterThan9")) 12
Failure ("IsEven" :| [])
-}
instance Semigroup (ValidationRule e a) where
    ValidationRule rl1 <> ValidationRule rl2 = ValidationRule
        $ \x -> case (rl1 x, rl2 x) of
            (f@(Failure _), _) -> f
            (_, b)             -> b

{- |

[@mempty@] 'mempty' is a 'ValidationRule' that always succeeds.

__Examples__

>>> runValidator (validate mempty) 'a'
Success 'a'
-}
instance Monoid (ValidationRule e a) where
    mempty = ValidationRule $ const $ Success ()

{- | Low level function to manually build a `ValidationRule`. You should use the combinators instead.

==== __Examples__

>>> runValidator (validate (vrule (\x -> if isDigit x then Success () else Failure "NotDigit"))) 'a'
Failure "NotDigit"
>>> runValidator (validate (vrule (\x -> if isDigit x then Success () else Failure "NotDigit"))) '5'
Success '5'
-}
vrule :: (a -> Validation e ()) -> ValidationRule e a
vrule = ValidationRule