{-# LANGUAGE CPP #-}
-- | Helpers to bind 'digestive-functors' onto a 'wai' request
module Network.Wai.Digestive (queryFormEnv, bodyFormEnv, bodyFormEnv_, requestFormEnv, requestFormEnv_) where
import Control.Arrow (second)
#if MIN_VERSION_wai(2,0,0)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Resource (ResourceT, getInternalState)
#else
import Control.Monad.Trans.Resource (ResourceT)
#endif
import Text.Digestive (Env, FormInput(..))
import Network.HTTP.Types.QueryLike (QueryLike, toQuery, toQueryValue)
import Network.Wai (Request, queryString)
import Network.Wai.Util (queryLookupAll)
import Network.Wai.Parse (parseRequestBody, BackEnd, File, fileContent, tempFileBackEnd)
import Data.Text (Text)
import qualified Data.Text as T
newtype FileQuery = FileQuery [File FilePath]
instance QueryLike FileQuery where
toQuery (FileQuery files) =
map (second (toQueryValue . fileContent)) files
-- Manual currying for performance
-- | Build an 'Text.Digestive.Types.Env' from a query
queryFormEnv :: (QueryLike q, Monad m) => q -> Env m
queryFormEnv qs = \pth ->
return $ map TextInput $ queryLookupAll (pathToText pth) qs'
where
qs' = toQuery qs
-- | Build an 'Text.Digestive.Types.Env' from a request body
#if MIN_VERSION_wai(2,0,0)
bodyFormEnv :: (Monad m, MonadIO io) => BackEnd FilePath -> Request -> io (Env m)
bodyFormEnv backend req = liftIO $ do
#else
bodyFormEnv :: (Monad m) => BackEnd FilePath -> Request -> ResourceT IO (Env m)
bodyFormEnv backend req = do
#endif
(query, files) <- parseRequestBody backend req
return $ queryFormEnv (toQuery query ++ toQuery (FileQuery files))
-- | Build an 'Text.Digestive.Types.Env' from a request body
--
-- Uses a default temporary file 'Network.Wai.Parse.BackEnd'
#if MIN_VERSION_wai(2,0,0)
bodyFormEnv_ :: (Monad m, MonadIO io) => Request -> ResourceT io (Env m)
bodyFormEnv_ req = do
st <- getInternalState
bodyFormEnv (tempFileBackEnd st) req
#else
bodyFormEnv_ :: (Monad m) => Request -> ResourceT IO (Env m)
bodyFormEnv_ = bodyFormEnv tempFileBackEnd
#endif
-- | Build an 'Text.Digestive.Types.Env' from request body and query string
#if MIN_VERSION_wai(2,0,0)
requestFormEnv :: (Monad m, MonadIO io) => BackEnd FilePath -> Request -> io (Env m)
requestFormEnv backend req = liftIO $ do
#else
requestFormEnv :: (Monad m) => BackEnd FilePath -> Request -> ResourceT IO (Env m)
requestFormEnv backend req = do
#endif
(query, files) <- parseRequestBody backend req
return $ queryFormEnv (toQuery query ++ toQuery (FileQuery files) ++ queryString req)
-- | Build an 'Text.Digestive.Types.Env' from request body and query string
--
-- Uses a default temporary file 'Network.Wai.Parse.BackEnd'
#if MIN_VERSION_wai(2,0,0)
requestFormEnv_ :: (Monad m, MonadIO io) => Request -> ResourceT io (Env m)
requestFormEnv_ req = do
st <- getInternalState
requestFormEnv (tempFileBackEnd st) req
#else
requestFormEnv_ :: (Monad m) => Request -> ResourceT IO (Env m)
requestFormEnv_ = requestFormEnv tempFileBackEnd
#endif
-- | Format form paths just like PHP/Rails
pathToText :: [Text] -> Text
pathToText [] = T.empty
pathToText [p] = p
pathToText (p:ps)
| T.null p = pathToText ps -- Eat empties off the front
| otherwise = T.concat (p : concatMap fragment ps)
where
fragment n = [
T.singleton '[',
n,
T.singleton ']'
]