ditto-0.4.1: src/Ditto/Types.hs
{-# LANGUAGE
BangPatterns
, DeriveFoldable
, DeriveFunctor
, DeriveTraversable
, GeneralizedNewtypeDeriving
, MultiParamTypeClasses
, OverloadedStrings
, PatternSynonyms
, ExplicitForAll
#-}
-- | Types relevant to forms and their validation.
module Ditto.Types (
-- * FormId
FormId(..)
, FormRange(..)
, encodeFormId
, formIdentifier
-- * Form result types
, Value(..)
, View(..)
, Proved(..)
, Result(.., Error, Ok)
) where
import Control.Applicative (Alternative(..))
import Data.List.NonEmpty (NonEmpty(..))
import Data.String (IsString(..))
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.List.NonEmpty as NE
------------------------------------------------------------------------------
-- FormId
------------------------------------------------------------------------------
-- | An ID used to identify forms
data FormId
= FormId
{-# UNPACK #-} !Text -- ^ Global prefix for the form
{-# UNPACK #-} !(NonEmpty Int) -- ^ Stack indicating field. Head is most specific to this item
| FormIdName
{-# UNPACK #-} !Text -- ^ Local name of the input
{-# UNPACK #-} !Int -- ^ Index of the input
deriving (Eq, Ord, Show)
instance IsString FormId where
fromString x = FormIdName (T.pack x) 0
-- | Encoding a @FormId@: use this instead of @show@ for
-- the name of the input / query string parameter
encodeFormId :: FormId -> Text
encodeFormId (FormId p xs) =
let ids = fmap (T.pack . show) (NE.toList xs)
in p <> "-val-" <> T.intercalate "." ids
encodeFormId (FormIdName x _) = x
-- | get the head 'Int' from a 'FormId'
formIdentifier :: FormId -> Int
formIdentifier (FormId _ (x :| _)) = x
formIdentifier (FormIdName _ x) = x
-- | A range of ID's to specify a group of forms
data FormRange
= FormRange FormId FormId
deriving (Eq, Show)
------------------------------------------------------------------------------
-- Form result types
------------------------------------------------------------------------------
-- | views, values as a result of the environment, etc.
-- | Function which creates the form view
newtype View err v = View { unView :: [(FormRange, err)] -> v }
deriving (Semigroup, Monoid, Functor)
-- | used to represent whether a value was found in the form
-- submission data, missing from the form submission data, or expected
-- that the default value should be used
data Value a
= Default
| Missing
| Found a
deriving (Eq, Show, Functor, Traversable, Foldable)
instance Applicative Value where
pure = Found
(Found f) <*> (Found x) = Found (f x)
Default <*> _ = Default
Missing <*> _ = Missing
Found{} <*> Default = Default
Found{} <*> Missing = Default
instance Alternative Value where
empty = Missing
x@Found{} <|> _ = x
Default <|> _ = Default
Missing <|> x = x
instance Semigroup a => Semigroup (Value a) where
Missing <> Missing = Missing
Missing <> Default = Default
Default <> Missing = Default
Default <> Default = Default
Found x <> Found y = Found (x <> y)
Found x <> _ = Found x
_ <> Found y = Found y
-- | Type for failing computations
-- Similar to @Either@ but with an accumilating @Applicative@ instance
newtype Result e ok = Result { getResult :: Either [(FormRange, e)] ok }
deriving (Show, Eq, Functor, Foldable, Traversable, Monad)
pattern Error :: forall e ok. [(FormRange, e)] -> Result e ok
pattern Error e = Result (Left e)
pattern Ok :: forall e ok. ok -> Result e ok
pattern Ok ok = Result (Right ok)
{-# COMPLETE Error, Ok #-}
instance Applicative (Result e) where
pure = Ok
Error x <*> Error y = Error $ x ++ y
Error x <*> Ok _ = Error x
Ok _ <*> Error y = Error y
Ok x <*> Ok y = Ok $ x y
-- | Proved records a value, the location that value came from, and something that was proved about the value.
data Proved a = Proved
{ pos :: FormRange
, unProved :: a
} deriving (Show, Functor, Foldable, Traversable)