Allow catching of form errors

This commit is contained in:
Snoyman 2010-03-05 07:57:52 -08:00
parent 412402cdd4
commit 04fe54ef72
6 changed files with 47 additions and 29 deletions

View File

@ -1,3 +1,5 @@
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-}
-- | Parse forms (and query strings). -- | Parse forms (and query strings).
module Yesod.Form module Yesod.Form
( Form (..) ( Form (..)
@ -11,15 +13,21 @@ module Yesod.Form
, notEmpty , notEmpty
, checkDay , checkDay
, checkBool , checkBool
, checkInteger
-- * Utility
, catchFormError
) where ) where
import Yesod.Request import Yesod.Request
import Yesod.Response (ErrorResponse)
import Yesod.Handler import Yesod.Handler
import Control.Applicative import Control.Applicative
import Data.Time (Day) import Data.Time (Day)
import Data.Convertible.Text import Data.Convertible.Text
import Data.Attempt import Data.Attempt
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import "transformers" Control.Monad.Trans (MonadIO)
import qualified Safe.Failure
noParamNameError :: String noParamNameError :: String
noParamNameError = "No param name (miscalling of Yesod.Form library)" noParamNameError = "No param name (miscalling of Yesod.Form library)"
@ -43,21 +51,24 @@ instance Applicative Form where
type FormError = String type FormError = String
runFormGeneric :: (ParamName -> [ParamValue]) -> Form x -> Handler y x runFormGeneric :: MonadFailure ErrorResponse m
=> (ParamName -> [ParamValue]) -> Form x -> m x
runFormGeneric params (Form f) = runFormGeneric params (Form f) =
case f params of case f params of
Left es -> invalidArgs es Left es -> invalidArgs es
Right (_, x) -> return x Right (_, x) -> return x
-- | Run a form against POST parameters. -- | Run a form against POST parameters.
runFormPost :: Form x -> Handler y x runFormPost :: (RequestReader m, MonadFailure ErrorResponse m, MonadIO m)
=> Form x -> m x
runFormPost f = do runFormPost f = do
rr <- getRequest rr <- getRequest
pp <- postParams rr pp <- postParams rr
runFormGeneric pp f runFormGeneric pp f
-- | Run a form against GET parameters. -- | Run a form against GET parameters.
runFormGet :: Form x -> Handler y x runFormGet :: (RequestReader m, MonadFailure ErrorResponse m)
=> Form x -> m x
runFormGet f = do runFormGet f = do
rr <- getRequest rr <- getRequest
runFormGeneric (getParams rr) f runFormGeneric (getParams rr) f
@ -96,3 +107,17 @@ checkBool = applyForm $ \pv -> Right $ case pv of
[""] -> False [""] -> False
["false"] -> False ["false"] -> False
_ -> True _ -> True
checkInteger :: Form ParamValue -> Form Integer
checkInteger = applyForm $ \pv ->
case Safe.Failure.read pv of
Nothing -> Left "Invalid integer"
Just i -> Right i
-- | 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)

View File

@ -69,7 +69,7 @@ instance Applicative (Handler yesod) where
pure = return pure = return
(<*>) = ap (<*>) = ap
instance Monad (Handler yesod) where instance Monad (Handler yesod) where
fail = failureString -- We want to catch all exceptions anyway fail = failure . InternalError -- We want to catch all exceptions anyway
return x = Handler $ \_ -> return ([], HCContent x) return x = Handler $ \_ -> return ([], HCContent x)
(Handler handler) >>= f = Handler $ \rr -> do (Handler handler) >>= f = Handler $ \rr -> do
(headers, c) <- handler rr (headers, c) <- handler rr
@ -81,8 +81,8 @@ instance Monad (Handler yesod) where
return (headers ++ headers', c') return (headers ++ headers', c')
instance MonadIO (Handler yesod) where instance MonadIO (Handler yesod) where
liftIO i = Handler $ \_ -> i >>= \i' -> return ([], HCContent i') liftIO i = Handler $ \_ -> i >>= \i' -> return ([], HCContent i')
instance Exception e => Failure e (Handler yesod) where instance Failure ErrorResponse (Handler yesod) where
failure e = Handler $ \_ -> return ([], HCError $ InternalError $ show e) failure e = Handler $ \_ -> return ([], HCError e)
instance RequestReader (Handler yesod) where instance RequestReader (Handler yesod) where
getRequest = Handler $ \(HandlerData rr _) getRequest = Handler $ \(HandlerData rr _)
-> return ([], HCContent rr) -> return ([], HCContent rr)
@ -134,9 +134,6 @@ safeEh er = do
specialResponse :: SpecialResponse -> Handler yesod a specialResponse :: SpecialResponse -> Handler yesod a
specialResponse er = Handler $ \_ -> return ([], HCSpecial er) specialResponse er = Handler $ \_ -> return ([], HCSpecial er)
errorResponse :: ErrorResponse -> Handler yesod a
errorResponse er = Handler $ \_ -> return ([], HCError er)
-- | Redirect to the given URL. -- | Redirect to the given URL.
redirect :: RedirectType -> String -> Handler yesod a redirect :: RedirectType -> String -> Handler yesod a
redirect rt = specialResponse . Redirect rt redirect rt = specialResponse . Redirect rt
@ -145,14 +142,14 @@ sendFile :: ContentType -> FilePath -> Handler yesod a
sendFile ct = specialResponse . SendFile ct sendFile ct = specialResponse . SendFile ct
-- | Return a 404 not found page. Also denotes no handler available. -- | Return a 404 not found page. Also denotes no handler available.
notFound :: Handler yesod a notFound :: Failure ErrorResponse m => m a
notFound = errorResponse NotFound notFound = failure NotFound
permissionDenied :: Handler yesod a permissionDenied :: Failure ErrorResponse m => m a
permissionDenied = errorResponse PermissionDenied permissionDenied = failure PermissionDenied
invalidArgs :: [(ParamName, String)] -> Handler yesod a invalidArgs :: Failure ErrorResponse m => [(ParamName, String)] -> m a
invalidArgs = errorResponse . InvalidArgs invalidArgs = failure . InvalidArgs
------- Headers ------- Headers
-- | Set the cookie on the client. -- | Set the cookie on the client.

View File

@ -165,7 +165,7 @@ rpxnowLogin = do
(('#':rest):_) -> rest (('#':rest):_) -> rest
(s:_) -> s (s:_) -> s
(d:_) -> d (d:_) -> d
ident <- Rpxnow.authenticate apiKey token ident <- liftIO $ Rpxnow.authenticate apiKey token
onRpxnowLogin ident onRpxnowLogin ident
header authCookieName $ Rpxnow.identifier ident header authCookieName $ Rpxnow.identifier ident
header authDisplayName $ getDisplayName ident header authDisplayName $ getDisplayName ident

View File

@ -54,7 +54,7 @@ templateHtml tn f = do
tg <- getTemplateGroup' tg <- getTemplateGroup'
y <- getYesod y <- getYesod
t <- case getStringTemplate tn tg of t <- case getStringTemplate tn tg of
Nothing -> failure $ NoSuchTemplate tn Nothing -> failure $ InternalError $ show $ NoSuchTemplate tn
Just x -> return x Just x -> return x
rr <- getRequest rr <- getRequest
return $ RepHtml $ ioTextToContent return $ RepHtml $ ioTextToContent
@ -81,7 +81,7 @@ templateHtmlJson tn ho f = do
y <- getYesod y <- getYesod
rr <- getRequest rr <- getRequest
t <- case getStringTemplate tn tg of t <- case getStringTemplate tn tg of
Nothing -> failure $ NoSuchTemplate tn Nothing -> failure $ InternalError $ show $ NoSuchTemplate tn
Just x -> return x Just x -> return x
return $ RepHtmlJson return $ RepHtmlJson
( ioTextToContent ( ioTextToContent

View File

@ -89,16 +89,12 @@ one piece of data.
> factRedirect :: Handler y () > factRedirect :: Handler y ()
> factRedirect = do > factRedirect = do
> rr <- getRequest > res <- runFormPost $ catchFormError
> let i = case getParams rr "num" of -- FIXME > $ checkInteger
> [] -> "1" > $ required
> (x:_) -> x > $ input "num"
> _ <- redirect RedirectPermanent $ "../" ++ i ++ "/" > let i = either (const "1") show res
> redirect RedirectPermanent $ "../" ++ i ++ "/"
The following line would be unnecesary if we had a type signature on
factRedirect.
> return ()
You could replace this main to use any WAI handler you want. For production, You could replace this main to use any WAI handler you want. For production,
you could use CGI, FastCGI or a more powerful server. Just check out Hackage you could use CGI, FastCGI or a more powerful server. Just check out Hackage

View File

@ -27,7 +27,7 @@ showYamlH = do
fi <- case lookup "yaml" files of fi <- case lookup "yaml" files of
Nothing -> invalidArgs [("yaml", "Missing input")] Nothing -> invalidArgs [("yaml", "Missing input")]
Just x -> return x Just x -> return x
to <- decode $ B.concat $ L.toChunks $ fileContent fi to <- liftIO $ decode $ B.concat $ L.toChunks $ fileContent fi
let ho' = fmap Text to let ho' = fmap Text to
templateHtmlJson "pretty-yaml" ho' $ \ho -> templateHtmlJson "pretty-yaml" ho' $ \ho ->
return . setHtmlAttrib "yaml" (Scalar $ cs ho :: HtmlObject) return . setHtmlAttrib "yaml" (Scalar $ cs ho :: HtmlObject)