Support for files in forms

This commit is contained in:
Michael Snoyman 2010-07-01 08:41:14 +03:00
parent 53f7837cff
commit f49c16c3ba
3 changed files with 35 additions and 25 deletions

View File

@ -61,7 +61,8 @@ import Data.Char (isLower, isUpper)
import Data.Serialize import Data.Serialize
import qualified Data.Serialize as Ser import qualified Data.Serialize as Ser
import Network.Wai.Parse import Network.Wai.Parse hiding (FileInfo)
import qualified Network.Wai.Parse as NWP
#if TEST #if TEST
import Test.Framework (testGroup, Test) import Test.Framework (testGroup, Test)
@ -122,6 +123,7 @@ typeHelper =
go s@(x:_) go s@(x:_)
| isLower x = VarT $ mkName s | isLower x = VarT $ mkName s
| otherwise = ConT $ mkName s | otherwise = ConT $ mkName s
go [] = error "typeHelper: empty string to go"
mkYesodGeneral :: String -- ^ argument name mkYesodGeneral :: String -- ^ argument name
-> [String] -- ^ parameters for site argument -> [String] -- ^ parameters for site argument
@ -336,8 +338,8 @@ parseWaiRequest env session' = do
rbHelper :: W.Request -> IO RequestBodyContents rbHelper :: W.Request -> IO RequestBodyContents
rbHelper = fmap (fix1 *** map fix2) . parseRequestBody lbsSink where rbHelper = fmap (fix1 *** map fix2) . parseRequestBody lbsSink where
fix1 = map (S.toString *** S.toString) fix1 = map (S.toString *** S.toString)
fix2 (x, FileInfo a b c) = fix2 (x, NWP.FileInfo a b c) =
(S.toString x, FileInfo a b c) (S.toString x, FileInfo (S.toString a) (S.toString b) c)
-- | Produces a \"compute on demand\" value. The computation will be run once -- | Produces a \"compute on demand\" value. The computation will be run once
-- it is requested, and then the result will be stored. This will happen only -- it is requested, and then the result will be stored. This will happen only

View File

@ -71,34 +71,36 @@ instance Applicative FormResult where
_ <*> _ = FormMissing _ <*> _ = FormMissing
newtype Form sub y a = Form newtype Form sub y a = Form
{ deform :: Env -> Incr (GHandler sub y) (FormResult a, Hamlet (Routes y)) { deform :: Env -> FileEnv -> Incr (GHandler sub y) (FormResult a, Hamlet (Routes y))
} }
type Formlet sub y a = Maybe a -> Form sub y a type Formlet sub y a = Maybe a -> Form sub y a
type Env = [(String, String)] type Env = [(String, String)]
type FileEnv = [(String, FileInfo)]
instance Functor (Form sub url) where instance Functor (Form sub url) where
fmap f (Form g) = Form $ \env -> liftM (first $ fmap f) (g env) fmap f (Form g) = Form $ \env fe -> liftM (first $ fmap f) (g env fe)
instance Applicative (Form sub url) where instance Applicative (Form sub url) where
pure a = Form $ const $ return (pure a, mempty) pure a = Form $ const $ const $ return (pure a, mempty)
(Form f) <*> (Form g) = Form $ \env -> do (Form f) <*> (Form g) = Form $ \env fe -> do
(f1, f2) <- f env (f1, f2) <- f env fe
(g1, g2) <- g env (g1, g2) <- g env fe
return (f1 <*> g1, f2 `mappend` g2) return (f1 <*> g1, f2 `mappend` g2)
runFormGeneric :: Env runFormGeneric :: Env
-> FileEnv
-> Form sub y a -> Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y)) -> GHandler sub y (FormResult a, Hamlet (Routes y))
runFormGeneric env f = evalStateT (deform f env) 1 runFormGeneric env fe f = evalStateT (deform f env fe) 1
-- | Run a form against POST parameters. -- | Run a form against POST parameters.
runFormPost :: Form sub y a runFormPost :: Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y)) -> GHandler sub y (FormResult a, Hamlet (Routes y))
runFormPost f = do runFormPost f = do
rr <- getRequest rr <- getRequest
(pp, _) <- liftIO $ reqRequestBody rr (pp, files) <- liftIO $ reqRequestBody rr
runFormGeneric pp f runFormGeneric pp files f
-- | Run a form against POST parameters, disregarding the resulting HTML and -- | Run a form against POST parameters, disregarding the resulting HTML and
-- returning an error response on invalid input. -- returning an error response on invalid input.
@ -120,7 +122,7 @@ runFormGet :: Form sub y a
-> GHandler sub y (FormResult a, Hamlet (Routes y)) -> GHandler sub y (FormResult a, Hamlet (Routes y))
runFormGet f = do runFormGet f = do
gs <- reqGetParams `fmap` getRequest gs <- reqGetParams `fmap` getRequest
runFormGeneric gs f runFormGeneric gs [] f
type Incr = StateT Int type Incr = StateT Int
@ -134,7 +136,7 @@ incr = do
input :: (String -> String -> Hamlet (Routes y)) input :: (String -> String -> Hamlet (Routes y))
-> Maybe String -> Maybe String
-> Form sub y String -> Form sub y String
input mkXml val = Form $ \env -> do input mkXml val = Form $ \env _ -> do
i <- incr i <- incr
let i' = show i let i' = show i
let param = lookup i' env let param = lookup i' env
@ -142,7 +144,7 @@ input mkXml val = Form $ \env -> do
return (maybe FormMissing FormSuccess param, xml) return (maybe FormMissing FormSuccess param, xml)
check :: Form sub url a -> (a -> Either [String] b) -> Form sub url b check :: Form sub url a -> (a -> Either [String] b) -> Form sub url b
check (Form form) f = Form $ \env -> liftM (first go) (form env) check (Form form) f = Form $ \env fe -> liftM (first go) (form env fe)
where where
go FormMissing = FormMissing go FormMissing = FormMissing
go (FormFailure x) = FormFailure x go (FormFailure x) = FormFailure x
@ -169,7 +171,7 @@ sealRow label getVal val =
sealForm :: ([String] -> Hamlet (Routes y) -> Hamlet (Routes y)) sealForm :: ([String] -> Hamlet (Routes y) -> Hamlet (Routes y))
-> Form sub y a -> Form sub y a -> Form sub y a -> Form sub y a
sealForm wrapper (Form form) = Form $ \env -> liftM go (form env) sealForm wrapper (Form form) = Form $ \env fe -> liftM go (form env fe)
where where
go (res, xml) = (res, wrapper (toList res) xml) go (res, xml) = (res, wrapper (toList res) xml)
toList (FormFailure errs) = errs toList (FormFailure errs) = errs
@ -181,22 +183,22 @@ sealFormlet wrapper formlet initVal = sealForm wrapper $ formlet initVal
-------- Prebuilt -------- Prebuilt
optionalField :: String -> Form sub master (Maybe String) optionalField :: String -> Form sub master (Maybe String)
optionalField n = Form $ \env -> optionalField n = Form $ \env _ ->
return (FormSuccess $ lookup n env, mempty) -- FIXME return (FormSuccess $ lookup n env, mempty) -- FIXME
requiredField :: String -> Form sub master String requiredField :: String -> Form sub master String
requiredField n = Form $ \env -> requiredField n = Form $ \env _ ->
return (maybe FormMissing FormSuccess $ lookup n env, mempty) -- FIXME return (maybe FormMissing FormSuccess $ lookup n env, mempty) -- FIXME
notEmptyField :: String -> Form sub master String notEmptyField :: String -> Form sub master String
notEmptyField n = Form $ \env -> return notEmptyField n = Form $ \env _ -> return
(case lookup n env of (case lookup n env of
Nothing -> FormMissing Nothing -> FormMissing
Just "" -> FormFailure [n ++ ": You must provide a non-empty string"] Just "" -> FormFailure [n ++ ": You must provide a non-empty string"]
Just x -> FormSuccess x, mempty) -- FIXME Just x -> FormSuccess x, mempty) -- FIXME
boolField :: String -> Form sub master Bool boolField :: String -> Form sub master Bool
boolField n = Form $ \env -> return boolField n = Form $ \env _ -> return
(FormSuccess $ isJust $ lookup n env, mempty) -- FIXME (FormSuccess $ isJust $ lookup n env, mempty) -- FIXME
class Formable a where class Formable a where
@ -299,7 +301,7 @@ instance Formable (Maybe Int64) where
[] -> Left ["Invalid integer"] [] -> Left ["Invalid integer"]
instance Formable Bool where instance Formable Bool where
formable x = Form $ \env -> do formable x = Form $ \env _ -> do
i <- incr i <- incr
let i' = show i let i' = show i
let param = lookup i' env let param = lookup i' env

View File

@ -46,7 +46,6 @@ import qualified Network.Wai as W
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import "transformers" Control.Monad.IO.Class import "transformers" Control.Monad.IO.Class
import Control.Monad (liftM) import Control.Monad (liftM)
import Network.Wai.Parse
import Control.Monad.Instances () -- I'm missing the instance Monad ((->) r import Control.Monad.Instances () -- I'm missing the instance Monad ((->) r
import Data.Maybe (listToMaybe) import Data.Maybe (listToMaybe)
@ -85,9 +84,16 @@ waiRequest = reqWaiRequest `liftM` getRequest
-- | A tuple containing both the POST parameters and submitted files. -- | A tuple containing both the POST parameters and submitted files.
type RequestBodyContents = type RequestBodyContents =
( [(ParamName, ParamValue)] ( [(ParamName, ParamValue)]
, [(ParamName, FileInfo BL.ByteString)] , [(ParamName, FileInfo)]
) )
data FileInfo = FileInfo
{ fileName :: String
, fileContentType :: String
, fileContent :: BL.ByteString
}
deriving (Eq, Show)
-- | The parsed request information. -- | The parsed request information.
data Request = Request data Request = Request
{ reqGetParams :: [(ParamName, ParamValue)] { reqGetParams :: [(ParamName, ParamValue)]
@ -134,13 +140,13 @@ lookupPostParam = liftM listToMaybe . lookupPostParams
-- | Lookup for POSTed files. -- | Lookup for POSTed files.
lookupFile :: (MonadIO m, RequestReader m) lookupFile :: (MonadIO m, RequestReader m)
=> ParamName => ParamName
-> m (Maybe (FileInfo BL.ByteString)) -> m (Maybe FileInfo)
lookupFile = liftM listToMaybe . lookupFiles lookupFile = liftM listToMaybe . lookupFiles
-- | Lookup for POSTed files. -- | Lookup for POSTed files.
lookupFiles :: (MonadIO m, RequestReader m) lookupFiles :: (MonadIO m, RequestReader m)
=> ParamName => ParamName
-> m [FileInfo BL.ByteString] -> m [FileInfo]
lookupFiles pn = do lookupFiles pn = do
rr <- getRequest rr <- getRequest
(_, files) <- liftIO $ reqRequestBody rr (_, files) <- liftIO $ reqRequestBody rr