Merged Yesod.Form and yesod.Formable

This commit is contained in:
Michael Snoyman 2010-06-30 22:41:47 +03:00
parent 95047029f8
commit 1a375e8fb4
9 changed files with 353 additions and 438 deletions

View File

@ -10,7 +10,6 @@ module Yesod
, module Yesod.Form
, module Yesod.Hamlet
, module Yesod.Json
, module Yesod.Formable
, Application
, liftIO
, mempty
@ -27,8 +26,7 @@ import Yesod.Dispatch
#endif
import Yesod.Request
import Yesod.Form hiding (Form)
import Yesod.Formable
import Yesod.Form
import Yesod.Yesod
import Yesod.Handler hiding (runHandler)
import Network.Wai (Application)

View File

@ -1,25 +1,38 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Parse forms (and query strings).
module Yesod.Form
( Form (..)
, runFormGeneric
, runFormPost
( -- * Data types
Form (..)
, Formlet
, FormResult (..)
-- * Unwrapping functions
, runFormGet
, runFormPost
, runFormGet'
, runFormPost'
-- * Create your own formlets
, incr
, input
, applyForm
-- * Specific checks
, required
, optional
, notEmpty
, checkDay
, checkBool
, checkInteger
-- * Utility
, catchFormError
, check
-- * Error display
, wrapperRow
, sealFormlet
, sealForm
, sealRow
-- * Formable
, Formable (..)
, deriveFormable
, share2
-- * Pre-built formlets
) where
import Text.Hamlet
import Yesod.Request
import Yesod.Handler
import Control.Applicative hiding (optional)
@ -28,110 +41,323 @@ import Data.Maybe (fromMaybe)
import "transformers" Control.Monad.IO.Class
import Yesod.Internal
import Control.Monad.Attempt
import Control.Monad ((<=<), liftM, join)
import Data.Monoid (mempty, mappend)
import Control.Monad.Trans.State
import Control.Arrow (first)
import Language.Haskell.TH.Syntax
import Database.Persist.Base (PersistField, EntityDef (..))
import Data.Char (isAlphaNum, toUpper, isUpper)
import Data.Maybe (fromMaybe, isJust)
import Web.Routes.Quasi (SinglePiece)
import Data.Int (Int64)
import qualified Data.ByteString.Lazy.UTF8
noParamNameError :: String
noParamNameError = "No param name (miscalling of Yesod.Form library)"
data Form x = Form (
(ParamName -> [ParamValue])
-> Either [(ParamName, FormError)] (Maybe ParamName, x)
)
data FormResult a = FormMissing
| FormFailure [String]
| FormSuccess a
instance Functor FormResult where
fmap _ FormMissing = FormMissing
fmap _ (FormFailure errs) = FormFailure errs
fmap f (FormSuccess a) = FormSuccess $ f a
instance Applicative FormResult where
pure = FormSuccess
(FormSuccess f) <*> (FormSuccess g) = FormSuccess $ f g
(FormFailure x) <*> (FormFailure y) = FormFailure $ x ++ y
(FormFailure x) <*> _ = FormFailure x
_ <*> (FormFailure y) = FormFailure y
_ <*> _ = FormMissing
instance Functor Form where
fmap f (Form x) = Form $ \l -> case x l of
Left errors -> Left errors
Right (pn, x') -> Right (pn, f x')
instance Applicative Form where
pure x = Form $ \_ -> Right (Nothing, x)
(Form f') <*> (Form x') = Form $ \l -> case (f' l, x' l) of
(Right (_, f), Right (_, x)) -> Right (Nothing, f x)
(Left e1, Left e2) -> Left $ e1 ++ e2
(Left e, _) -> Left e
(_, Left e) -> Left e
newtype Form sub y a = Form
{ deform :: Env -> Incr (GHandler sub y) (FormResult a, Hamlet (Routes y))
}
type Formlet sub y a = Maybe a -> Form sub y a
type FormError = String
type Env = [(String, String)]
runFormGeneric :: Failure ErrorResponse m
=> (ParamName -> [ParamValue]) -> Form x -> m x
runFormGeneric params (Form f) =
case f params of
Left es -> invalidArgs es
Right (_, x) -> return x
instance Functor (Form sub url) where
fmap f (Form g) = Form $ \env -> liftM (first $ fmap f) (g env)
instance Applicative (Form sub url) where
pure a = Form $ const $ return (pure a, mempty)
(Form f) <*> (Form g) = Form $ \env -> do
(f1, f2) <- f env
(g1, g2) <- g env
return (f1 <*> g1, f2 `mappend` g2)
runFormGeneric :: Env
-> Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y))
runFormGeneric env f = evalStateT (deform f env) 1
-- | Run a form against POST parameters.
runFormPost :: (RequestReader m, Failure ErrorResponse m, MonadIO m)
=> Form x -> m x
runFormPost :: Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y))
runFormPost f = do
rr <- getRequest
(pp, _) <- liftIO $ reqRequestBody rr
runFormGeneric (flip lookup' pp) f
runFormGeneric pp f
lookup' :: Eq a => a -> [(a, b)] -> [b]
lookup' a = map snd . filter (\x -> a == fst x)
-- | Run a form against POST parameters, disregarding the resulting HTML and
-- returning an error response on invalid input.
runFormPost' :: Form sub y a -> GHandler sub y a
runFormPost' = helper <=< runFormPost
-- | Run a form against GET parameters, disregarding the resulting HTML and
-- returning an error response on invalid input.
runFormGet' :: Form sub y a -> GHandler sub y a
runFormGet' = helper <=< runFormGet
helper :: (FormResult a, Hamlet (Routes y)) -> GHandler sub y a
helper (FormSuccess a, _) = return a
helper (FormFailure e, _) = invalidArgs e
helper (FormMissing, _) = invalidArgs ["No input found"]
-- | Run a form against GET parameters.
runFormGet :: (RequestReader m, Failure ErrorResponse m)
=> Form x -> m x
runFormGet :: Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y))
runFormGet f = do
rr <- getRequest
runFormGeneric (flip lookupGetParams rr) f
gets <- reqGetParams `fmap` getRequest
runFormGeneric gets f
input :: ParamName -> Form [ParamValue]
input pn = Form $ \l -> Right (Just pn, l pn)
type Incr = StateT Int
applyForm :: (x -> Either FormError y) -> Form x -> Form y
applyForm f (Form x') =
Form $ \l ->
case x' l of
Left e -> Left e
Right (pn, x) ->
case f x of
Left e -> Left [(fromMaybe noParamNameError pn, e)]
Right y -> Right (pn, y)
incr :: Monad m => Incr m Int
incr = do
i <- get
let i' = i + 1
put i'
return i'
required :: Form [ParamValue] -> Form ParamValue
required = applyForm $ \pvs -> case pvs of
[x] -> Right x
[] -> Left "No value for required field"
_ -> Left "Multiple values for required field"
input :: (String -> String -> Hamlet (Routes y))
-> Maybe String
-> Form sub y String
input mkXml val = Form $ \env -> do
i <- incr
let i' = show i
let param = lookup i' env
let xml = mkXml i' $ fromMaybe (fromMaybe "" val) param
return (maybe FormMissing FormSuccess param, xml)
optional :: Form [ParamValue] -> Form (Maybe ParamValue)
optional = applyForm $ \pvs -> case pvs of
[""] -> Right Nothing
[x] -> Right $ Just x
[] -> Right Nothing
_ -> Left "Multiple values for optional field"
notEmpty :: Form ParamValue -> Form ParamValue
notEmpty = applyForm $ \pv ->
if null pv
then Left "Value required"
else Right pv
checkDay :: Form ParamValue -> Form Day
checkDay = applyForm $ maybe (Left "Invalid day") Right . readMay
check :: Form sub url a -> (a -> Either [String] b) -> Form sub url b
check (Form form) f = Form $ \env -> liftM (first go) (form env)
where
readMay s = case reads s of
(x, _):_ -> Just x
[] -> Nothing
go FormMissing = FormMissing
go (FormFailure x) = FormFailure x
go (FormSuccess a) =
case f a of
Left errs -> FormFailure errs
Right b -> FormSuccess b
checkBool :: Form [ParamValue] -> Form Bool
checkBool = applyForm $ \pv -> Right $ case pv of
[] -> False
[""] -> False
["false"] -> False
_ -> True
wrapperRow :: String -> [String] -> Hamlet url -> Hamlet url
wrapperRow label errs control = [$hamlet|
%tr
%th $string.label$
%td ^control^
$if not.null.errs
%td.errors
%ul
$forall errs err
%li $string.err$
|]
checkInteger :: Form ParamValue -> Form Integer
checkInteger = applyForm $ \pv ->
case reads pv of
[] -> Left "Invalid integer"
((i, _):_) -> Right i
sealRow :: Formable b => String -> (a -> b) -> Maybe a -> Form sub master b
sealRow label getVal val =
sealForm (wrapperRow label) $ formable $ fmap getVal val
-- | Instead of calling 'failure' with an 'InvalidArgs', return the error
-- messages.
catchFormError :: Form x -> Form (Either [(ParamName, FormError)] x)
catchFormError (Form x) = Form $ \l ->
case x l of
Left e -> Right (Nothing, Left e)
Right (_, v) -> Right (Nothing, Right v)
sealForm :: ([String] -> Hamlet (Routes y) -> Hamlet (Routes y))
-> Form sub y a -> Form sub y a
sealForm wrapper (Form form) = Form $ \env -> liftM go (form env)
where
go (res, xml) = (res, wrapper (toList res) xml)
toList (FormFailure errs) = errs
toList _ = []
sealFormlet :: ([String] -> Hamlet (Routes y) -> Hamlet (Routes y))
-> Formlet sub y a -> Formlet sub y a
sealFormlet wrapper formlet initVal = sealForm wrapper $ formlet initVal
class Formable a where
formable :: Formlet sub master a
--------------- Formable instances
instance Formable String where
formable x = input go x `check` notEmpty
where
go name val = [$hamlet|
%input!type=text!name=$string.name$!value=$string.val$
|]
notEmpty s
| null s = Left ["Value required"]
| otherwise = Right s
instance Formable (Maybe String) where
formable x = input go (join x) `check` isEmpty
where
go name val = [$hamlet|
%input!type=text!name=$string.name$!value=$string.val$
|]
isEmpty s
| null s = Right Nothing
| otherwise = Right $ Just s
instance Formable (Html ()) where
formable = fmap preEscapedString
. input go
. fmap (Data.ByteString.Lazy.UTF8.toString . renderHtml)
where
go name val = [$hamlet|%textarea!name=$string.name$ $string.val$|]
instance Formable Day where
formable x = input go (fmap show x) `check` asDay
where
go name val = [$hamlet|
%input!type=date!name=$string.name$!value=$string.val$
|]
asDay s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid day"]
instance Formable Int64 where
formable x = input go (fmap show x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid integer"]
instance Formable Double where
formable x = input go (fmap numstring x) `check` asDouble
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asDouble s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid double"]
numstring d =
let s = show d
in case reverse s of
'0':'.':y -> reverse y
_ -> s
instance Formable (Maybe Day) where
formable x = input go (fmap show $ join x) `check` asDay
where
go name val = [$hamlet|
%input!type=date!name=$string.name$!value=$string.val$
|]
asDay "" = Right Nothing
asDay s = case reads s of
(y, _):_ -> Right $ Just y
[] -> Left ["Invalid day"]
instance Formable (Maybe Int) where
formable x = input go (fmap show $ join x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt "" = Right Nothing
asInt s = case reads s of
(y, _):_ -> Right $ Just y
[] -> Left ["Invalid integer"]
instance Formable (Maybe Int64) where
formable x = input go (fmap show $ join x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt "" = Right Nothing
asInt s = case reads s of
(y, _):_ -> Right $ Just y
[] -> Left ["Invalid integer"]
instance Formable Bool where
formable x = Form $ \env -> do
i <- incr
let i' = show i
let param = lookup i' env
let def = if null env then fromMaybe False x else isJust param
return (FormSuccess $ isJust param, go i' def)
where
go name val = [$hamlet|
%input!type=checkbox!name=$string.name$!:val:checked
|]
instance Formable Int where
formable x = input go (fmap show x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid integer"]
newtype Slug = Slug { unSlug :: String }
deriving (Read, Eq, Show, SinglePiece, PersistField)
instance Formable Slug where
formable x = input go (fmap unSlug x) `check` asSlug
where
go name val = [$hamlet|
%input!type=text!name=$string.name$!value=$string.val$
|]
asSlug [] = Left ["Slug must be non-empty"]
asSlug x'
| all (\c -> c `elem` "-_" || isAlphaNum c) x' =
Right $ Slug x'
| otherwise = Left ["Slug must be alphanumeric, - and _"]
share2 :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]
share2 f g a = do
f' <- f a
g' <- g a
return $ f' ++ g'
deriveFormable :: [EntityDef] -> Q [Dec]
deriveFormable = mapM derive
where
derive :: EntityDef -> Q Dec
derive t = do
let fst3 (x, _, _) = x
let cols = map (toLabel . fst3) $ entityColumns t
ap <- [|(<*>)|]
just <- [|pure|]
nothing <- [|Nothing|]
let just' = just `AppE` ConE (mkName $ entityName t)
let c1 = Clause [ ConP (mkName "Nothing") []
]
(NormalB $ go ap just' $ zip cols $ map (const nothing) cols)
[]
xs <- mapM (const $ newName "x") cols
let xs' = map (AppE just . VarE) xs
let c2 = Clause [ ConP (mkName "Just") [ConP (mkName $ entityName t)
$ map VarP xs]]
(NormalB $ go ap just' $ zip cols xs')
[]
return $ InstanceD [] (ConT ''Formable
`AppT` ConT (mkName $ entityName t))
[FunD (mkName "formable") [c1, c2]]
go ap just' = foldl (ap' ap) just' . map go'
go' (label, ex) =
VarE (mkName "sealForm") `AppE`
(VarE (mkName "wrapperRow") `AppE` LitE (StringL label)) `AppE`
(VarE (mkName "formable") `AppE` ex)
ap' ap x y = InfixE (Just x) ap (Just y)
toLabel :: String -> String
toLabel "" = ""
toLabel (x:rest) = toUpper x : go rest
where
go "" = ""
go (c:cs)
| isUpper c = ' ' : c : go cs
| otherwise = c : go cs

View File

@ -1,319 +0,0 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Yesod.Formable
( Form (..)
, Formlet
, FormResult (..)
, runForm
, incr
, Formable (..)
, deriveFormable
, share2
, wrapperRow
, sealFormlet
, sealForm
, Slug (..)
, sealRow
, check
) where
import Text.Hamlet
import Data.Time (Day)
import Control.Applicative
import Database.Persist (PersistField)
import Database.Persist.Base (EntityDef (..))
import Data.Char (isAlphaNum, toUpper, isUpper)
import Language.Haskell.TH.Syntax
import Control.Monad (liftM, join)
import Control.Arrow (first)
import Data.Maybe (fromMaybe, isJust)
import Data.Monoid (mempty, mappend)
import qualified Data.ByteString.Lazy.UTF8
import Yesod.Request
import Yesod.Handler
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State
import Web.Routes.Quasi (SinglePiece)
import Data.Int (Int64)
sealRow :: Formable b => String -> (a -> b) -> Maybe a -> Form sub master b
sealRow label getVal val =
sealForm (wrapperRow label) $ formable $ fmap getVal val
runForm :: Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y))
runForm f = do
req <- getRequest
(pp, _) <- liftIO $ reqRequestBody req
evalStateT (deform f pp) 1
type Env = [(String, String)]
type Incr = StateT Int
incr :: Monad m => Incr m Int
incr = do
i <- get
let i' = i + 1
put i'
return i'
data FormResult a = FormMissing
| FormFailure [String]
| FormSuccess a
instance Functor FormResult where
fmap _ FormMissing = FormMissing
fmap _ (FormFailure errs) = FormFailure errs
fmap f (FormSuccess a) = FormSuccess $ f a
instance Applicative FormResult where
pure = FormSuccess
(FormSuccess f) <*> (FormSuccess g) = FormSuccess $ f g
(FormFailure x) <*> (FormFailure y) = FormFailure $ x ++ y
(FormFailure x) <*> _ = FormFailure x
_ <*> (FormFailure y) = FormFailure y
_ <*> _ = FormMissing
newtype Form sub y a = Form
{ deform :: Env -> Incr (GHandler sub y) (FormResult a, Hamlet (Routes y))
}
type Formlet sub y a = Maybe a -> Form sub y a
instance Functor (Form sub url) where
fmap f (Form g) = Form $ \env -> liftM (first $ fmap f) (g env)
instance Applicative (Form sub url) where
pure a = Form $ const $ return (pure a, mempty)
(Form f) <*> (Form g) = Form $ \env -> do
(f1, f2) <- f env
(g1, g2) <- g env
return (f1 <*> g1, f2 `mappend` g2)
sealForm :: ([String] -> Hamlet (Routes y) -> Hamlet (Routes y))
-> Form sub y a -> Form sub y a
sealForm wrapper (Form form) = Form $ \env -> liftM go (form env)
where
go (res, xml) = (res, wrapper (toList res) xml)
toList (FormFailure errs) = errs
toList _ = []
sealFormlet :: ([String] -> Hamlet (Routes y) -> Hamlet (Routes y))
-> Formlet sub y a -> Formlet sub y a
sealFormlet wrapper formlet initVal = sealForm wrapper $ formlet initVal
input' :: (String -> String -> Hamlet (Routes y))
-> Maybe String
-> Form sub y String
input' mkXml val = Form $ \env -> do
i <- incr
let i' = show i
let param = lookup i' env
let xml = mkXml i' $ fromMaybe (fromMaybe "" val) param
return (maybe FormMissing FormSuccess param, xml)
check :: Form sub url a -> (a -> Either [String] b) -> Form sub url b
check (Form form) f = Form $ \env -> liftM (first go) (form env)
where
go FormMissing = FormMissing
go (FormFailure x) = FormFailure x
go (FormSuccess a) =
case f a of
Left errs -> FormFailure errs
Right b -> FormSuccess b
class Formable a where
formable :: Formlet sub master a
wrapperRow :: String -> [String] -> Hamlet url -> Hamlet url
wrapperRow label errs control = [$hamlet|
%tr
%th $string.label$
%td ^control^
$if not.null.errs
%td.errors
%ul
$forall errs err
%li $string.err$
|]
instance Formable String where
formable x = input' go x `check` notEmpty
where
go name val = [$hamlet|
%input!type=text!name=$string.name$!value=$string.val$
|]
notEmpty s
| null s = Left ["Value required"]
| otherwise = Right s
instance Formable (Maybe String) where
formable x = input' go (join x) `check` isEmpty
where
go name val = [$hamlet|
%input!type=text!name=$string.name$!value=$string.val$
|]
isEmpty s
| null s = Right Nothing
| otherwise = Right $ Just s
instance Formable (Html ()) where
formable = fmap preEscapedString
. input' go
. fmap (Data.ByteString.Lazy.UTF8.toString . renderHtml)
where
go name val = [$hamlet|%textarea!name=$string.name$ $string.val$|]
instance Formable Day where
formable x = input' go (fmap show x) `check` asDay
where
go name val = [$hamlet|
%input!type=date!name=$string.name$!value=$string.val$
|]
asDay s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid day"]
instance Formable Int64 where
formable x = input' go (fmap show x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid integer"]
instance Formable Double where
formable x = input' go (fmap numstring x) `check` asDouble
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asDouble s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid double"]
numstring d =
let s = show d
in case reverse s of
'0':'.':y -> reverse y
_ -> s
instance Formable (Maybe Day) where
formable x = input' go (fmap show $ join x) `check` asDay
where
go name val = [$hamlet|
%input!type=date!name=$string.name$!value=$string.val$
|]
asDay "" = Right Nothing
asDay s = case reads s of
(y, _):_ -> Right $ Just y
[] -> Left ["Invalid day"]
instance Formable (Maybe Int) where
formable x = input' go (fmap show $ join x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt "" = Right Nothing
asInt s = case reads s of
(y, _):_ -> Right $ Just y
[] -> Left ["Invalid integer"]
instance Formable (Maybe Int64) where
formable x = input' go (fmap show $ join x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt "" = Right Nothing
asInt s = case reads s of
(y, _):_ -> Right $ Just y
[] -> Left ["Invalid integer"]
instance Formable Bool where
formable x = Form $ \env -> do
i <- incr
let i' = show i
let param = lookup i' env
let def = if null env then fromMaybe False x else isJust param
return (FormSuccess $ isJust param, go i' def)
where
go name val = [$hamlet|
%input!type=checkbox!name=$string.name$!:val:checked
|]
instance Formable Int where
formable x = input' go (fmap show x) `check` asInt
where
go name val = [$hamlet|
%input!type=number!name=$string.name$!value=$string.val$
|]
asInt s = case reads s of
(y, _):_ -> Right y
[] -> Left ["Invalid integer"]
newtype Slug = Slug { unSlug :: String }
deriving (Read, Eq, Show, SinglePiece, PersistField)
instance Formable Slug where
formable x = input' go (fmap unSlug x) `check` asSlug
where
go name val = [$hamlet|
%input!type=text!name=$string.name$!value=$string.val$
|]
asSlug [] = Left ["Slug must be non-empty"]
asSlug x'
| all (\c -> c `elem` "-_" || isAlphaNum c) x' =
Right $ Slug x'
| otherwise = Left ["Slug must be alphanumeric, - and _"]
share2 :: Monad m => (a -> m [b]) -> (a -> m [b]) -> a -> m [b]
share2 f g a = do
f' <- f a
g' <- g a
return $ f' ++ g'
deriveFormable :: [EntityDef] -> Q [Dec]
deriveFormable = mapM derive
where
derive :: EntityDef -> Q Dec
derive t = do
let fst3 (x, _, _) = x
let cols = map (toLabel . fst3) $ entityColumns t
ap <- [|(<*>)|]
just <- [|pure|]
nothing <- [|Nothing|]
let just' = just `AppE` ConE (mkName $ entityName t)
let c1 = Clause [ ConP (mkName "Nothing") []
]
(NormalB $ go ap just' $ zip cols $ map (const nothing) cols)
[]
xs <- mapM (const $ newName "x") cols
let xs' = map (AppE just . VarE) xs
let c2 = Clause [ ConP (mkName "Just") [ConP (mkName $ entityName t)
$ map VarP xs]]
(NormalB $ go ap just' $ zip cols xs')
[]
return $ InstanceD [] (ConT ''Formable
`AppT` ConT (mkName $ entityName t))
[FunD (mkName "formable") [c1, c2]]
go ap just' = foldl (ap' ap) just' . map go'
go' (label, ex) =
VarE (mkName "sealForm") `AppE`
(VarE (mkName "wrapperRow") `AppE` LitE (StringL label)) `AppE`
(VarE (mkName "formable") `AppE` ex)
ap' ap x y = InfixE (Just x) ap (Just y)
toLabel :: String -> String
toLabel "" = ""
toLabel (x:rest) = toUpper x : go rest
where
go "" = ""
go (c:cs)
| isUpper c = ' ' : c : go cs
| otherwise = c : go cs

View File

@ -377,7 +377,7 @@ permissionDenied :: Failure ErrorResponse m => String -> m a
permissionDenied = failure . PermissionDenied
-- | Return a 400 invalid arguments page.
invalidArgs :: Failure ErrorResponse m => [(ParamName, String)] -> m a
invalidArgs :: Failure ErrorResponse m => [String] -> m a
invalidArgs = failure . InvalidArgs
------- Headers

View File

@ -179,10 +179,22 @@ $maybe message msg
%input!type=submit!value=Login
|]
-- FIXME next two functions should show up in Yesod.Form properly
requiredField :: String -> Form sub master String
requiredField n = Form $ \env ->
return (maybe FormMissing FormSuccess $ lookup n env, mempty)
notEmptyField :: String -> Form sub master String
notEmptyField n = Form $ \env -> return
(case lookup n env of
Nothing -> FormMissing
Just "" -> FormFailure [n ++ ": You must provide a non-empty string"]
Just x -> FormSuccess x, mempty)
getOpenIdForward :: GHandler Auth master ()
getOpenIdForward = do
testOpenId
oid <- runFormGet $ required $ input "openid"
oid <- runFormGet' $ requiredField "openid"
render <- getUrlRender
toMaster <- getRouteToMaster
let complete = render $ toMaster OpenIdComplete
@ -220,7 +232,7 @@ handleRpxnowR = do
token1 <- lookupGetParam "token"
token2 <- lookupPostParam "token"
let token = case token1 `mplus` token2 of
Nothing -> invalidArgs [("token", "Value not supplied")]
Nothing -> invalidArgs ["token: Value not supplied"]
Just x -> x
Rpxnow.Identifier ident extra <- liftIO $ Rpxnow.authenticate apiKey token
let creds = Creds
@ -302,7 +314,7 @@ getEmailRegisterR = do
postEmailRegisterR :: YesodAuth master => GHandler Auth master RepHtml
postEmailRegisterR = do
ae <- getAuthEmailSettings
email <- runFormPost $ notEmpty $ required $ input "email" -- FIXME checkEmail
email <- runFormPost' $ notEmptyField "email" -- FIXME checkEmail
y <- getYesod
mecreds <- liftIO $ getEmailCreds ae email
(lid, verKey) <-
@ -366,9 +378,9 @@ $maybe msg ms
postEmailLoginR :: YesodAuth master => GHandler Auth master ()
postEmailLoginR = do
ae <- getAuthEmailSettings
(email, pass) <- runFormPost $ (,)
<$> notEmpty (required $ input "email") -- FIXME valid e-mail?
<*> required (input "password")
(email, pass) <- runFormPost' $ (,)
<$> notEmptyField "email" -- FIXME valid e-mail?
<*> requiredField "password"
y <- getYesod
mecreds <- liftIO $ getEmailCreds ae email
let mlid =
@ -419,9 +431,9 @@ $maybe msg ms
postEmailPasswordR :: YesodAuth master => GHandler Auth master ()
postEmailPasswordR = do
ae <- getAuthEmailSettings
(new, confirm) <- runFormPost $ (,)
<$> notEmpty (required $ input "new")
<*> notEmpty (required $ input "confirm")
(new, confirm) <- runFormPost' $ (,)
<$> notEmptyField "new"
<*> notEmptyField "confirm"
toMaster <- getRouteToMaster
when (new /= confirm) $ do
setMessage $ string "Passwords did not match, please try again"
@ -495,7 +507,7 @@ getFacebookR = do
render <- getUrlRender
tm <- getRouteToMaster
let fb = Facebook.Facebook cid secret $ render $ tm FacebookR
code <- runFormGet $ required $ input "code"
code <- runFormGet' $ requiredField "code"
at <- liftIO $ Facebook.getAccessToken fb code
so <- liftIO $ Facebook.getGraphData at "me"
let c = fromMaybe (error "Invalid response from Facebook") $ do

View File

@ -15,7 +15,7 @@ import Yesod.Dispatch
import Yesod.Content
import Yesod.Handler
import Text.Hamlet
import Yesod.Formable
import Yesod.Form
import Data.Monoid (mempty)
class Formable a => Item a where
@ -127,7 +127,7 @@ crudHelper
-> GHandler (Crud master a) master RepHtml
crudHelper title me isPost = do
crud <- getYesodSub
(errs, form) <- runForm $ formable $ fmap snd me
(errs, form) <- runFormPost $ formable $ fmap snd me
toMaster <- getRouteToMaster
case (isPost, errs) of
(True, FormSuccess a) -> do

View File

@ -13,7 +13,7 @@ module Yesod.Internal
data ErrorResponse =
NotFound
| InternalError String
| InvalidArgs [(String, String)]
| InvalidArgs [String]
| PermissionDenied String
| BadMethod String
deriving (Show, Eq)

View File

@ -183,10 +183,9 @@ defaultErrorHandler (PermissionDenied msg) =
defaultErrorHandler (InvalidArgs ia) =
applyLayout' "Invalid Arguments" $ [$hamlet|
%h1 Invalid Arguments
%dl
$forall ia pair
%dt $string.fst.pair$
%dd $string.snd.pair$
%ul
$forall ia msg
%li $string.msg$
|]
defaultErrorHandler (InternalError e) =
applyLayout' "Internal Server Error" $ [$hamlet|

View File

@ -48,7 +48,6 @@ library
Yesod.Content
Yesod.Dispatch
Yesod.Form
Yesod.Formable
Yesod.Hamlet
Yesod.Handler
Yesod.Internal