{-# LANGUAGE
LambdaCase, DerivingStrategies, DerivingVia, StandaloneDeriving, KindSignatures, GeneralizedNewtypeDeriving,
PolyKinds, TypeOperators,
DefaultSignatures, InstanceSigs, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, UndecidableInstances
#-}
module Control.Validation.Class(
validate, validate',
CheckChain(..), overChain, (+?+), singleChain,
Validatable(..),
TrivialCheck(..),
) where
import Data.Foldable(fold)
import Data.Kind(Type)
import Data.Void(Void)
import Data.Int(Int8, Int16, Int32, Int64)
import Data.ByteString(ByteString)
import Data.Text(Text)
import Control.Validation.Check
import Data.Functor.Identity(Identity(..))
import Data.Sequence(Seq)
import GHC.Generics
import Data.Functor.Contravariant.Compose(ComposeFC(..))
import Data.Functor.Contravariant(Contravariant(..))
import Data.Functor.Contravariant.Divisible(Divisible(..), Decidable(..))
import Control.Monad.Morph(MFunctor(..))
newtype CheckChain (e :: Type) (m :: Type -> Type) (a :: Type) =
CheckChain { runCheckChain :: [ Check e m a ] }
deriving newtype ( Monoid, Semigroup )
deriving (Contravariant, Divisible, Decidable) via (ComposeFC [] (Check e m))
instance MFunctor (CheckChain e) where
hoist f = overChain (hoist f)
overChain :: (Check e m a -> Check e' n b) -> CheckChain e m a -> CheckChain e' n b
overChain f = CheckChain . fmap f . runCheckChain
{-# INLINE (+?+) #-}
(+?+) :: CheckChain e m a -> CheckChain e m a -> CheckChain e m a
(+?+) = (<>)
infixr 5 +?+
{-# INLINE emptyChain #-}
emptyChain :: CheckChain e m a
emptyChain = mempty
{-# INLINE singleChain #-}
singleChain :: Check e m a -> CheckChain e m a
singleChain x = CheckChain [ x ]
{-# INLINABLE validate' #-}
validate' :: Validatable e Identity a => Unvalidated a -> Either (Seq e) a
validate' u@(Unvalidated x) =
checkResultToEither x
. runIdentity
. runCheck defaultCheck
$ u
{-# INLINABLE validate #-}
validate :: (Validatable e m a, Functor m) => Unvalidated a -> m (Either (Seq e) a)
validate u@(Unvalidated x) =
fmap (checkResultToEither x)
. runCheck defaultCheck
$ u
class Validatable (e :: Type) (m :: Type -> Type) (a :: Type) | a -> m, a -> e where
checkChain :: CheckChain e m a
default checkChain :: (Generic a, GValidatable e m (Rep a)) => CheckChain e m a
checkChain = contramap from gCheckChain
defaultCheck :: Check e m a
default defaultCheck :: Applicative m => Check e m a
defaultCheck = fold . runCheckChain $ checkChain
isValid :: Unvalidated a -> m Bool
default isValid :: Applicative m => Unvalidated a -> m Bool
isValid u = fmap (all passed) $ traverse (($ u) . runCheck) $ runCheckChain checkChain
deriving via TrivialCheck () instance Validatable Void Identity ()
deriving via TrivialCheck Bool instance Validatable Void Identity Bool
deriving via TrivialCheck Char instance Validatable Void Identity Char
deriving via TrivialCheck Double instance Validatable Void Identity Double
deriving via TrivialCheck Float instance Validatable Void Identity Float
deriving via TrivialCheck Int instance Validatable Void Identity Int
deriving via TrivialCheck Int8 instance Validatable Void Identity Int8
deriving via TrivialCheck Int16 instance Validatable Void Identity Int16
deriving via TrivialCheck Int32 instance Validatable Void Identity Int32
deriving via TrivialCheck Int64 instance Validatable Void Identity Int64
deriving via TrivialCheck Integer instance Validatable Void Identity Integer
deriving via TrivialCheck ByteString instance Validatable Void Identity ByteString
deriving via TrivialCheck Text instance Validatable Void Identity Text
instance (Validatable e m a, Applicative m) => (Validatable e m (Maybe a)) where
checkChain = traverseWithCheck `overChain` checkChain
instance (Validatable e m b, Validatable e m a, Applicative m) => Validatable e m (Either a b) where
checkChain = traverseWithCheck `overChain` checkChain
instance (Validatable e m a, Applicative m) => (Validatable e m [a]) where
checkChain = traverseWithCheck `overChain` checkChain
newtype TrivialCheck a = TrivialCheck { unTrivialCheck :: a }
instance Validatable Void Identity (TrivialCheck a) where
{-# INLINE checkChain #-}
checkChain = emptyChain
{-# INLINE defaultCheck #-}
defaultCheck = mempty
{-# INLINE isValid #-}
isValid = const (Identity True)
class GValidatable (e :: Type) (m :: Type -> Type) (rep :: k -> Type) | rep -> m, rep -> e where
gCheckChain :: CheckChain e m (rep x)
instance GValidatable Void Identity V1 where
gCheckChain = mempty
instance GValidatable Void Identity U1 where
gCheckChain = mempty
instance Validatable e m a => GValidatable e m (K1 i a) where
gCheckChain :: CheckChain e m (K1 i a x)
gCheckChain = contramap unK1 checkChain
instance (Applicative m, GValidatable e m f, GValidatable e m g) => GValidatable e m (f :*: g) where
gCheckChain = divide id_tup gCheckChain gCheckChain
where id_tup (x :*: y) = (x, y)
instance (GValidatable e m f, GValidatable e m g, Applicative m) => GValidatable e m (f :+: g) where
gCheckChain = choose id_sum gCheckChain gCheckChain
where id_sum = \case
L1 l -> Left l
R1 r -> Right r
instance (GValidatable e m rep) => GValidatable e m (M1 i c rep) where
gCheckChain = contramap unM1 gCheckChain