Shpadoinkle-widgets-0.2.0.1: Shpadoinkle/Widgets/Types/Form.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PartialTypeSignatures #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Shpadoinkle.Widgets.Types.Form
( module Shpadoinkle.Widgets.Types.Form
) where
import Control.Applicative (Alternative (empty),
Applicative (liftA2),
Const (Const, getConst))
import Control.Monad.Except (MonadError (..))
import Data.Aeson (FromJSON, ToJSON)
import Data.Kind (Type)
import Data.String (IsString)
import Data.Text (Text)
import GHC.Generics
#ifdef TESTING
import Test.QuickCheck (Arbitrary (..), elements)
#endif
import Shpadoinkle (NFData)
import Shpadoinkle.Widgets.Types.Core (Hygiene)
data Input a = Input
{ _hygiene :: Hygiene
, _value :: a
} deriving (Eq, Ord, Show, Read, Functor, Traversable, Foldable, Generic, ToJSON, FromJSON, NFData)
#ifdef TESTING
instance Arbitrary a => Arbitrary (Input a) where arbitrary = Input <$> arbitrary <*> arbitrary
#endif
class Control g where
type Val g a :: Type
type Val g a = a
hygiene :: Applicative f => (Hygiene -> f Hygiene) -> g a -> f (g a)
value :: (Applicative f, Ord a) => (Val g a -> f (Val g a)) -> g a -> f (g a)
getValue :: (Ord a, Monoid (Val g a), Control g) => g a -> Val g a
getValue = getConst . value Const
getHygiene :: Control g => g a -> Hygiene
getHygiene = getConst . hygiene Const
instance Control Input where
hygiene f i = (\h -> i { _hygiene = h }) <$> f (_hygiene i)
value f i = (\a -> i { _value = a }) <$> f (_value i)
instance Applicative Input where
Input h fa <*> Input h' a = Input (h <> h') (fa a)
pure = Input mempty
instance Monad Input where
Input h a >>= f = let Input h' a' = f a in Input (h <> h') a'
instance Semigroup a => Semigroup (Input a) where
Input h a <> Input h' a' = Input (h <> h') (a <> a')
instance Monoid a => Monoid (Input a) where
mempty = Input mempty mempty
newtype Placeholder = Placeholder { unPlaceholder :: Text }
deriving newtype (Eq, Ord, Show, Read, IsString, Semigroup, Monoid, ToJSON, FromJSON)
deriving stock Generic
deriving anyclass NFData
data Validated e a = Validated a | Invalid e [e]
deriving (Eq, Ord, Show, Read, Generic, ToJSON, FromJSON, Functor, Foldable, Traversable, NFData)
instance Semigroup (Validated e a) where
Validated a <> Validated _ = Validated a
Validated _ <> x = x
x <> Validated _ = x
Invalid x xs <> Invalid y ys = Invalid x (xs <> (y:ys))
instance Applicative (Validated e) where
Validated f <*> Validated a = Validated (f a)
Invalid x xs <*> _ = Invalid x xs
_ <*> Invalid x xs = Invalid x xs
pure = Validated
instance Monad (Validated e) where
Validated a >>= f = f a
Invalid x xs >>= _ = Invalid x xs
instance MonadError e (Validated e) where
throwError e = Invalid e []
catchError (Invalid x _) f = f x
catchError v _ = v
data Status = Edit | Rules | Valid | Errors
type family Field (s :: Status) (e :: Type) (f :: Type -> Type) (x :: Type) :: Type where
Field 'Valid _ _ a = a
Field 'Errors e _ a = Validated e a
Field 'Edit _ f a = f a
Field 'Rules e f a = Val f a -> Validated e a
class ValidateG rules edit errs where
validateg :: rules a -> edit a -> errs a
instance ValidateG U1 U1 U1 where
validateg _ _ = U1
instance (ValidateG a b c, ValidateG d e f)
=> ValidateG (a :*: d) (b :*: e) (c :*: f) where
validateg (a :*: b) (c :*: d) = validateg a c :*: validateg b d
instance (ValidateG a b c, ValidateG d e f, Alternative (c :+: f))
=> ValidateG (a :+: d) (b :+: e) (c :+: f) where
validateg (L1 a) (L1 b) = L1 $ validateg a b
validateg (R1 a) (R1 b) = R1 $ validateg a b
validateg _ _ = empty
instance ValidateG a b c
=> ValidateG (M1 i x a) (M1 i' x' b) (M1 i'' x'' c) where
validateg (M1 a) (M1 b) = M1 $ validateg a b
instance (Control c, Monoid v, Val c a ~ v, Ord a)
=> ValidateG (K1 i (v -> b)) (K1 i' (c a)) (K1 i'' b) where
validateg (K1 f) (K1 x) = K1 (f $ getValue x)
class ValidG err valid where
validg :: err a -> Maybe (valid a)
instance ValidG U1 U1 where
validg _ = Just U1
instance (ValidG a c, ValidG b d) => ValidG (a :*: b) (c :*: d) where
validg (a :*: b) = liftA2 (:*:) (validg a) (validg b)
instance (ValidG a c, ValidG b d) => ValidG (a :+: b) (c :+: d) where
validg (L1 a) = L1 <$> validg a
validg (R1 a) = R1 <$> validg a
instance (ValidG a b) => ValidG (M1 i c a) (M1 i' c' b) where
validg (M1 a) = M1 <$> validg a
instance ValidG (K1 i (Validated t a)) (K1 i' a) where
validg (K1 (Validated a)) = Just $ K1 a
validg _ = Nothing
class Validate (f :: Status -> Type) where
validate :: f 'Edit -> f 'Errors
default validate
:: Generic (f 'Edit)
=> Generic (f 'Rules)
=> Generic (f 'Errors)
=> ValidateG (Rep (f 'Rules)) (Rep (f 'Edit)) (Rep (f 'Errors))
=> f 'Edit -> f 'Errors
validate edit = to $ validateg (from (rules @ f)) (from edit)
getValid :: f 'Errors -> Maybe (f 'Valid)
default getValid
:: Generic (f 'Errors)
=> Generic (f 'Valid)
=> ValidG (Rep (f 'Errors)) (Rep (f 'Valid))
=> f 'Errors -> Maybe (f 'Valid)
getValid = fmap to . validg . from
rules :: f 'Rules
#ifdef TESTING
instance (Arbitrary a, Arbitrary b) => Arbitrary (Validated a b) where
arbitrary = do
(e, es, a) <- (,,) <$> arbitrary <*> arbitrary <*> arbitrary
elements [ Validated a, Invalid e es ]
#endif