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

module Valida.Validator
    ( Validator (..)
    ) where

import Data.List.NonEmpty (NonEmpty)
import Data.Profunctor    (Profunctor (lmap, rmap))
import Data.Typeable      (Typeable)

import GHC.Generics (Generic)

import Valida.Validation (Validation (..))

{- | An applicative validator. Validates a predicate on an input when run and returns the 'Validation' result.

The type can be understood as-

@
Validator e inp a
          ^   ^ ^------ The output type on successful validation
          |   |
  Error type  |-- The input on which validation is run
@

Validators are run using the __runValidator__ function.
The result is of type __Validation e a__, corresponding to the type params of the same name on 'Validator'.

__Note__: All the primitive combinators (and derivative combinators) use the same type for @inp@ and @a@.
In those cases - upon successful validation, the input itself, wrapped in 'Success', is returned.
-}
newtype Validator e inp a = Validator { Validator e inp a -> inp -> Validation e a
runValidator :: inp -> Validation e a }
  deriving (Typeable, (forall x. Validator e inp a -> Rep (Validator e inp a) x)
-> (forall x. Rep (Validator e inp a) x -> Validator e inp a)
-> Generic (Validator e inp a)
forall x. Rep (Validator e inp a) x -> Validator e inp a
forall x. Validator e inp a -> Rep (Validator e inp a) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall e inp a x. Rep (Validator e inp a) x -> Validator e inp a
forall e inp a x. Validator e inp a -> Rep (Validator e inp a) x
$cto :: forall e inp a x. Rep (Validator e inp a) x -> Validator e inp a
$cfrom :: forall e inp a x. Validator e inp a -> Rep (Validator e inp a) x
Generic)

{- |
[@fmap@] 'fmap' maps given function over the 'Validation' result by re-using 'fmap' on it.

__Examples__

>>> runValidator (fmap (+1) (failureIf (==2) "IsTwo")) 3
Success 4
>>> runValidator (fmap (+1) (failureIf (==2) "IsTwo")) 2
Failure ("IsTwo" :| [])
-}
instance Functor (Validator e inp) where
    fmap :: (a -> b) -> Validator e inp a -> Validator e inp b
fmap a -> b
f (Validator inp -> Validation e a
v) = (inp -> Validation e b) -> Validator e inp b
forall e inp a. (inp -> Validation e a) -> Validator e inp a
Validator ((inp -> Validation e b) -> Validator e inp b)
-> (inp -> Validation e b) -> Validator e inp b
forall a b. (a -> b) -> a -> b
$ (a -> b) -> Validation e a -> Validation e b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> b
f (Validation e a -> Validation e b)
-> (inp -> Validation e a) -> inp -> Validation e b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. inp -> Validation e a
v

{- |
[@pure@] 'pure' creates a 'Validator' that always yields given value wrapped in 'Success', ignoring its input.
[@(\<*\>)@] '(<*>)' runs 2 validators to obtain the 2 'Validation' results and combines them with '(<*>)'.
This can be understood as-

    @
    (Validator ff) \<*\> (Validator v) = Validator (\\inp -> ff inp \<*\> v inp)
    @

    i.e Run __ff__ and __v__ on the input, and compose the 'Validation' results with '(<*>)'.

__Examples__

>>> runValidator (pure 5) 42
Success 5
>>> runValidator (const <$> failureIf (==2) "IsTwo" <*> failureIf even "IsEven") 5
Success 5
>>> runValidator (const <$> failureIf (==2) "IsTwo" <*> failureIf even "IsEven") 4
Failure ("IsEven" :| [])
>>> runValidator (const <$> failureIf (==2) "IsTwo" <*> failureIf even "IsEven") 2
Failure ("IsTwo" :| ["IsEven"])
-}
instance Semigroup e => Applicative (Validator e inp) where
    {-# SPECIALIZE instance Applicative (Validator (NonEmpty err) inp) #-}
    {-# SPECIALIZE instance Applicative (Validator () inp) #-}
    {-# SPECIALIZE instance Applicative (Validator [err] inp) #-}
    pure :: a -> Validator e inp a
pure = (inp -> Validation e a) -> Validator e inp a
forall e inp a. (inp -> Validation e a) -> Validator e inp a
Validator ((inp -> Validation e a) -> Validator e inp a)
-> (a -> inp -> Validation e a) -> a -> Validator e inp a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Validation e a -> inp -> Validation e a
forall a b. a -> b -> a
const (Validation e a -> inp -> Validation e a)
-> (a -> Validation e a) -> a -> inp -> Validation e a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Validation e a
forall e a. a -> Validation e a
Success
    {-# INLINEABLE pure #-}
    Validator inp -> Validation e (a -> b)
ff <*> :: Validator e inp (a -> b) -> Validator e inp a -> Validator e inp b
<*> Validator inp -> Validation e a
v = (inp -> Validation e b) -> Validator e inp b
forall e inp a. (inp -> Validation e a) -> Validator e inp a
Validator ((inp -> Validation e b) -> Validator e inp b)
-> (inp -> Validation e b) -> Validator e inp b
forall a b. (a -> b) -> a -> b
$ \inp
x -> inp -> Validation e (a -> b)
ff inp
x Validation e (a -> b) -> Validation e a -> Validation e b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> inp -> Validation e a
v inp
x
    {-# INLINEABLE (<*>) #-}

{- |
[@(<>)@] '(<>)' builds a validator that /succeeds/ only if both of the given validators succeed.
__Left-most__ failure is returned, other validator /is not used/ if one fails. If all validators succeed,
__right-most__ success is returned.

__Examples__

>>> let v1 = failureIf (==2) "IsTwo"
>>> let v2 = failureIf even "IsEven"
>>> runValidator (v1 <> v2) 5
Success 5
>>> runValidator (v1 <> v2) 4
Failure ("IsEven" :| [])
>>> runValidator (v1 <> v2) 2
Failure ("IsTwo" :| [])
-}
instance Semigroup (Validator e inp a) where
    Validator inp -> Validation e a
v1 <> :: Validator e inp a -> Validator e inp a -> Validator e inp a
<> Validator inp -> Validation e a
v2 = (inp -> Validation e a) -> Validator e inp a
forall e inp a. (inp -> Validation e a) -> Validator e inp a
Validator ((inp -> Validation e a) -> Validator e inp a)
-> (inp -> Validation e a) -> Validator e inp a
forall a b. (a -> b) -> a -> b
$ \inp
x -> case (inp -> Validation e a
v1 inp
x, inp -> Validation e a
v2 inp
x) of
        (f :: Validation e a
f@(Failure e
_), Validation e a
_) -> Validation e a
f
        (Validation e a
_, Validation e a
b)             -> Validation e a
b
{- |

[@mempty@] 'mempty' is a validator that always succeeds and uses /unit/ as output type.

__Examples__

>>> runValidator (mempty :: Validator String Int ()) 42
Success ()
-}
instance Monoid (Validator e inp ()) where
    mempty :: Validator e inp ()
mempty = (inp -> Validation e ()) -> Validator e inp ()
forall e inp a. (inp -> Validation e a) -> Validator e inp a
Validator ((inp -> Validation e ()) -> Validator e inp ())
-> (inp -> Validation e ()) -> Validator e inp ()
forall a b. (a -> b) -> a -> b
$ Validation e () -> inp -> Validation e ()
forall a b. a -> b -> a
const (Validation e () -> inp -> Validation e ())
-> Validation e () -> inp -> Validation e ()
forall a b. (a -> b) -> a -> b
$ () -> Validation e ()
forall e a. a -> Validation e a
Success ()

{- |

[@lmap@] 'lmap' runs given function on 'Validator' input before applying it to the validator function.
This is similar to the 'Data.Functor.Contravariant.Predicate' type.

[@rmap@] 'rmap' is the same as 'fmap'.

__Examples__

>>> runValidator (lmap fst (failureIf (==2) "IsTwo")) (3, 2)
Success 3
>>> runValidator (lmap snd (failureIf (==2) "IsTwo")) (3, 2)
Failure ("IsTwo" :| [])
>>> runValidator (rmap (+1) (failureIf (==2) "IsTwo")) 3
Failure ("IsTwo" :| [])
-}
instance Profunctor (Validator e) where
    lmap :: (a -> b) -> Validator e b c -> Validator e a c
lmap a -> b
selector (Validator b -> Validation e c
v) = (a -> Validation e c) -> Validator e a c
forall e inp a. (inp -> Validation e a) -> Validator e inp a
Validator ((a -> Validation e c) -> Validator e a c)
-> (a -> Validation e c) -> Validator e a c
forall a b. (a -> b) -> a -> b
$ b -> Validation e c
v (b -> Validation e c) -> (a -> b) -> a -> Validation e c
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> b
selector
    rmap :: (b -> c) -> Validator e a b -> Validator e a c
rmap = (b -> c) -> Validator e a b -> Validator e a c
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap