{-# LANGUAGE DeriveFunctor , FlexibleInstances , FunctionalDependencies , GeneralizedNewtypeDeriving , NamedFieldPuns , OverloadedStrings , RankNTypes , ScopedTypeVariables , StandaloneDeriving , TypeFamilies , LiberalTypeSynonyms , TypeSynonymInstances , UndecidableInstances , DataKinds , KindSignatures #-} -- | The core module for @ditto@. -- -- This module provides the @Form@ type and helper functions -- for constructing typesafe forms inside arbitrary "views" / web frameworks. -- @ditto@ is meant to be a generalized formlet library used to write -- formlet libraries specific to a web / gui framework module Ditto.Core ( -- * Form types -- | The representation of formlets FormState , Form(..) -- * Environment -- | The interface to a given web framework , Environment(..) , NoEnvironment(..) , WithEnvironment(..) , noEnvironment -- * Utility functions , (@$) , catchFormError , catchFormErrorM , eitherForm , getFormId , getFormInput , getFormInput' , getFormRange , getNamedFormId , incrementFormId , isInRange , mapFormMonad , mapResult , mapView , mkOk , retainChildErrors , retainErrors , runForm , runForm_ , successDecode , unitRange , view , viewForm , pureRes , liftForm ) where import Control.Applicative import Control.Monad.Reader import Control.Monad.State.Lazy import Data.Bifunctor import Data.Text (Text) import Ditto.Types import Ditto.Backend import Torsor ------------------------------------------------------------------------------ -- Form types ------------------------------------------------------------------------------ -- | The Form's state is just the range of identifiers so far type FormState m = StateT FormRange m -- | @ditto@'s representation of a formlet data Form m input err view a = Form { formDecodeInput :: input -> m (Either err a) -- ^ Decode the value from the input , formInitialValue :: m a -- ^ The initial value , formFormlet :: FormState m (View err view, Result err (Proved a)) -- ^ A @FormState@ which produced a @View@ and a @Result@ } deriving (Functor) instance (Monad m, Monoid view) => Applicative (Form m input err view) where pure x = Form (successDecode x) (pure x) $ do i <- getFormId pure ( mempty , Ok $ Proved { pos = FormRange i i , unProved = x } ) (Form df ivF frmF) <*> (Form da ivA frmA) = Form ( \inp -> do f <- df inp x <- da inp pure (f <*> x) ) (ivF <*> ivA) ( do ((view1, fok), (view2, aok)) <- bracketState $ do res1 <- frmF incrementFormRange res2 <- frmA pure (res1, res2) case (fok, aok) of (Error errs1, Error errs2) -> pure (view1 <> view2, Error $ errs1 ++ errs2) (Error errs1, _) -> pure (view1 <> view2, Error errs1) (_, Error errs2) -> pure (view1 <> view2, Error errs2) (Ok (Proved (FormRange x _) f), Ok (Proved (FormRange _ y) a)) -> pure ( view1 <> view2 , Ok $ Proved { pos = FormRange x y , unProved = f a } ) ) f1 *> f2 = Form (formDecodeInput f2) (formInitialValue f2) $ do -- Evaluate the form that matters first, so we have a correct range set (v2, r) <- formFormlet f2 (v1, _) <- formFormlet f1 pure (v1 <> v2, r) f1 <* f2 = Form (formDecodeInput f1) (formInitialValue f1) $ do -- Evaluate the form that matters first, so we have a correct range set (v1, r) <- formFormlet f1 (v2, _) <- formFormlet f2 pure (v1 <> v2, r) instance (Environment m input, Monoid view, FormError input err) => Monad (Form m input err view) where form >>= f = let mres = fmap snd $ runForm "" form in Form (\input -> do res <- mres case res of Error {} -> do iv <- formInitialValue form formDecodeInput (f iv) input Ok (Proved _ x) -> formDecodeInput (f x) input ) (do res <- mres case res of Error {} -> do iv <- formInitialValue form formInitialValue $ f iv Ok (Proved _ x) -> formInitialValue (f x) ) (do (View viewF0, res0) <- formFormlet form case res0 of Error errs0 -> do iv <- lift $ formInitialValue form (View viewF, res) <- formFormlet $ f iv let errs = case res of Error es -> es Ok {} -> [] pure (View $ const $ viewF0 errs0 <> viewF errs, Error (errs0 <> errs)) Ok (Proved _ x) -> fmap (first (\(View v) -> View $ \e -> viewF0 [] <> v e)) $ formFormlet (f x) ) return = pure (>>) = (*>) -- way more efficient than the default instance (Monad m, Monoid view, Semigroup a) => Semigroup (Form m input err view a) where (<>) = liftA2 (<>) instance (Monad m, Monoid view, Monoid a) => Monoid (Form m input err view a) where mempty = pure mempty instance Functor m => Bifunctor (Form m input err) where first = mapView second = fmap errorInitialValue :: String errorInitialValue = "ditto: Ditto.Core.errorInitialValue was evaluated" instance (Monad m, Monoid view, FormError input err, Environment m input) => Alternative (Form m input err view) where empty = Form failDecodeMDF (error errorInitialValue) (pure (mempty, Error [])) formA <|> formB = do efA <- formEither formA case efA of Right{} -> formA Left{} -> formB ------------------------------------------------------------------------------ -- Environment ------------------------------------------------------------------------------ -- | The environment typeclass: the interface between ditto and a given framework class Monad m => Environment m input | m -> input where environment :: FormId -> m (Value input) -- | Run the form, but always return the initial value newtype NoEnvironment input m a = NoEnvironment { getNoEnvironment :: m a } deriving (Monad, Functor, Applicative) instance Monad m => Environment (NoEnvironment input m) input where environment = noEnvironment -- | @environment@ which will always return the initial value noEnvironment :: Applicative m => FormId -> m (Value input) noEnvironment = const (pure Default) -- | Run the form, but with a given @environment@ function newtype WithEnvironment input m a = WithEnvironment { getWithEnvironment :: ReaderT (FormId -> m (Value input)) m a } deriving (Monad, Functor, Applicative) deriving instance Monad m => MonadReader (FormId -> m (Value input)) (WithEnvironment input m) instance MonadTrans (WithEnvironment input) where lift = WithEnvironment . lift instance Monad m => Environment (WithEnvironment input m) input where environment fid = do f <- ask lift $ f fid ------------------------------------------------------------------------------ -- Utility functions ------------------------------------------------------------------------------ failDecodeMDF :: forall m input err a. (Applicative m, FormError input err) => input -> m (Either err a) failDecodeMDF = const $ pure $ Left err where mdf :: CommonFormError input mdf = MissingDefaultValue err :: err err = commonFormError mdf -- | Always succeed decoding successDecode :: Applicative m => a -> (input -> m (Either err a)) successDecode = const . pure . Right -- | Common operations on @Form@s -- | Change the view of a form using a simple function -- -- This is useful for wrapping a form inside of a \ or other markup element. mapView :: (Functor m) => (view -> view') -- ^ Manipulator -> Form m input err view a -- ^ Initial form -> Form m input err view' a -- ^ Resulting form mapView f Form{formDecodeInput, formInitialValue, formFormlet} = Form formDecodeInput formInitialValue (fmap (first (fmap f)) formFormlet) -- | Increment a form ID incrementFormId :: FormId -> FormId incrementFormId fid = add 1 fid -- | Check if a 'FormId' is contained in a 'FormRange' isInRange :: FormId -- ^ Id to check for -> FormRange -- ^ Range -> Bool -- ^ If the range contains the id isInRange a (FormRange b c) = formIdentifier a >= formIdentifier b && formIdentifier a < formIdentifier c -- | Check if a 'FormRange' is contained in another 'FormRange' isSubRange :: FormRange -- ^ Sub-range -> FormRange -- ^ Larger range -> Bool -- ^ If the sub-range is contained in the larger range isSubRange (FormRange a b) (FormRange c d) = formIdentifier a >= formIdentifier c && formIdentifier b <= formIdentifier d -- | Get a @FormId@ from the FormState getFormId :: Monad m => FormState m FormId getFormId = do FormRange x _ <- get pure x -- | Utility function: Get the current range getFormRange :: Monad m => FormState m FormRange getFormRange = get -- | Get a @FormIdName@ from the FormState getNamedFormId :: Monad m => Text -> FormState m FormId getNamedFormId name = do FormRange x _ <- get pure $ FormIdName name $ formIdentifier x -- | Turns a @FormId@ into a @FormRange@ by incrementing the base for the end Id unitRange :: FormId -> FormRange unitRange i = FormRange i $ add 1 i bracketState :: Monad m => FormState m a -> FormState m a bracketState k = do FormRange startF1 _ <- get res <- k FormRange _ endF2 <- get put $ FormRange startF1 endF2 pure res -- | Utility function: increment the current 'FormId'. incrementFormRange :: Monad m => FormState m () incrementFormRange = do FormRange _ endF1 <- get put $ unitRange endF1 -- | Run a form runForm :: Monad m => Text -> Form m input err view a -> m (View err view, Result err (Proved a)) runForm prefix Form{formFormlet} = evalStateT formFormlet (unitRange (FormId prefix (pure 0))) -- | Run a form, and unwrap the result runForm_ :: (Monad m) => Text -> Form m input err view a -> m (view , Maybe a) runForm_ prefix form = do (view', result) <- runForm prefix form pure $ case result of Error e -> (unView view' e , Nothing) Ok x -> (unView view' [], Just (unProved x)) -- | Evaluate a form -- -- Returns: -- -- [@Left view@] on failure. The @view@ will have already been applied to the errors. -- -- [@Right a@] on success. -- eitherForm :: (Monad m) => Text -- ^ Identifier for the form -> Form m input err view a -- ^ Form to run -> m (Either view a) -- ^ Result eitherForm id' form = do (view', result) <- runForm id' form return $ case result of Error e -> Left $ unView view' e Ok x -> Right (unProved x) -- | infix mapView: succinctly mix the @view@ dsl and the formlets dsl @foo \@$ do ..@ infixr 0 @$ (@$) :: Monad m => (view -> view') -> Form m input err view a -> Form m input err view' a (@$) = mapView -- | Utility Function: turn a view and pure value into a successful 'FormState' mkOk :: (Monad m) => FormId -> view -> a -> FormState m (View err view, Result err (Proved a)) mkOk i view' val = pure ( View $ const $ view' , Ok ( Proved { pos = unitRange i , unProved = val } ) ) -- | Lift the errors into the result type. This will cause the form to always 'succeed' formEither :: Monad m => Form m input err view a -> Form m input err view (Either [err] a) formEither Form{formDecodeInput, formInitialValue, formFormlet} = Form (\input -> do res <- formDecodeInput input case res of Left err -> pure $ Right $ Left [err] Right x -> pure $ Right $ Right x ) (fmap Right formInitialValue) ( do range <- get (view', res') <- formFormlet let res = case res' of Error err -> Left (map snd err) Ok (Proved _ x) -> Right x pure ( view' , Ok $ Proved { pos = range , unProved = res } ) ) -- | Utility function: Get the current input getFormInput :: Environment m input => FormState m (Value input) getFormInput = getFormId >>= getFormInput' -- | Utility function: Gets the input of an arbitrary 'FormId'. getFormInput' :: Environment m input => FormId -> FormState m (Value input) getFormInput' fid = lift $ environment fid -- | Select the errors for a certain range retainErrors :: FormRange -> [(FormRange, e)] -> [e] retainErrors range = map snd . filter ((== range) . fst) -- | Select the errors originating from this form or from any of the children of -- this form retainChildErrors :: FormRange -> [(FormRange, e)] -> [e] retainChildErrors range = map snd . filter ((`isSubRange` range) . fst) -- | Turn a @view@ into a @Form@ view :: Monad m => view -> Form m input err view () view html = Form (successDecode ()) (pure ()) $ do i <- getFormId pure ( View (const html) , Ok $ Proved { pos = FormRange i i , unProved = () } ) -- | Change the underlying Monad of the form, usually a @lift@ or newtype mapFormMonad :: (Monad f) => (forall x. m x -> f x) -> Form m input err view a -> Form f input err view a mapFormMonad f Form{formDecodeInput, formInitialValue, formFormlet} = Form { formDecodeInput = f . formDecodeInput , formInitialValue = f formInitialValue , formFormlet = do (view', res) <- fstate formFormlet pure $ (view', res) } where fstate st = StateT $ f . runStateT st -- | Catch errors purely catchFormError :: (Monad m) => ([err] -> a) -> Form m input err view a -> Form m input err view a catchFormError ferr Form{formDecodeInput, formInitialValue, formFormlet} = Form formDecodeInput formInitialValue $ do i <- getFormId (View viewf, res) <- formFormlet case res of Ok _ -> formFormlet Error err -> mkOk i (viewf []) (ferr $ fmap snd err) -- | Catch errors inside @Form@ / @m@ catchFormErrorM :: (Monad m) => Form m input err view a -> ([err] -> Form m input err view a) -> Form m input err view a catchFormErrorM form@(Form{formDecodeInput, formInitialValue}) e = Form formDecodeInput formInitialValue $ do (_, res0) <- formFormlet form case res0 of Ok _ -> formFormlet form Error err -> formFormlet $ e $ map snd err -- | Map over the @Result@ and @View@ of a form mapResult :: (Monad m) => (Result err (Proved a) -> Result err (Proved a)) -> (View err view -> View err view) -> Form m input err view a -> Form m input err view a mapResult fres fview Form{formDecodeInput, formInitialValue, formFormlet} = Form formDecodeInput formInitialValue $ do (view', res) <- formFormlet pure (fview view', fres res) -- | Run the form with no environment, return only the html. -- This means that the values will always be their defaults viewForm :: (Monad m) => Text -- ^ form prefix -> Form m input err view a -- ^ form to view -> m view viewForm prefix form = do (v, _) <- getNoEnvironment $ runForm prefix $ mapFormMonad NoEnvironment form pure (unView v []) -- | lift the result of a decoding to a @Form@ pureRes :: (Monad m, Monoid view, FormError input err) => a -> Either err a -> Form m input err view a pureRes def x' = case x' of Right x -> Form (successDecode x) (pure x) $ do i <- getFormId pure ( mempty , Ok $ Proved { pos = FormRange i i , unProved = x } ) Left e -> Form (successDecode def) (pure def) $ do i <- getFormId pure ( mempty , Error [(FormRange i i, e)] ) -- | @Form@ is a @MonadTrans@, but we can't have an instance of it because of the order and kind of its type variables liftForm :: (Monad m, Monoid view) => m a -> Form m input err view a liftForm x = Form (const (fmap Right x)) x $ do res <- lift x i <- getFormId pure (mempty, Ok $ Proved (FormRange i i) res)