{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-}
module Happstack.Server.RqData
(
look
, looks
, lookText
, lookText'
, lookTexts
, lookTexts'
, lookBS
, lookBSs
, lookRead
, lookReads
, lookFile
, lookPairs
, lookPairsBS
, lookCookie
, lookCookieValue
, readCookieValue
, lookInput
, lookInputs
, body
, queryString
, bytestring
, checkRq
, checkRqM
, readRq
, unsafeReadRq
, decodeBody
, BodyPolicy(..)
, defaultBodyPolicy
, RqData
, mapRqData
, Errors(..)
, getDataFn
, withDataFn
, FromData(..)
, getData
, withData
, RqEnv
, HasRqData(askRqEnv, localRqEnv,rqDataError)
) where
import Control.Applicative (Applicative((<*>), pure), Alternative((<|>), empty), WrappedMonad(WrapMonad, unwrapMonad), (<$>))
import Control.Monad (MonadPlus(mzero))
import Control.Monad.Reader (ReaderT(ReaderT, runReaderT), MonadReader(ask, local), mapReaderT)
import qualified Control.Monad.State.Lazy as Lazy (StateT, mapStateT)
import qualified Control.Monad.State.Strict as Strict (StateT, mapStateT)
import qualified Control.Monad.Writer.Lazy as Lazy (WriterT, mapWriterT)
import qualified Control.Monad.Writer.Strict as Strict (WriterT, mapWriterT)
import qualified Control.Monad.RWS.Lazy as Lazy (RWST, mapRWST)
import qualified Control.Monad.RWS.Strict as Strict (RWST, mapRWST)
import Control.Monad.Error (Error(noMsg, strMsg), ErrorT, mapErrorT)
import Control.Monad.Trans (MonadIO(..), lift)
import Control.Monad.Trans.Except (ExceptT, mapExceptT)
import qualified Data.ByteString.Char8 as P
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.ByteString.Lazy.UTF8 as LU
import Data.Char (toLower)
import Data.Either (partitionEithers)
import Data.Generics (Data, Typeable)
import Data.Maybe (fromJust)
import Data.Monoid (Monoid(mempty, mappend, mconcat))
import qualified Data.Semigroup as SG
import Data.Text (Text)
import qualified Data.Text.Lazy as LazyText
import qualified Data.Text.Lazy.Encoding as LazyText
import Happstack.Server.Cookie (Cookie (cookieValue))
import Happstack.Server.Internal.Monads
import Happstack.Server.Types
import Happstack.Server.Internal.MessageWrap (BodyPolicy(..), bodyInput, defaultBodyPolicy)
import Happstack.Server.Response (requestEntityTooLarge, toResponse)
newtype ReaderError r e a = ReaderError { unReaderError :: ReaderT r (Either e) a }
deriving (Functor, Monad, MonadPlus)
instance (Error e, Monoid e) => MonadReader r (ReaderError r e) where
ask = ReaderError ask
local f m = ReaderError $ local f (unReaderError m)
instance (Monoid e, Error e) => Applicative (ReaderError r e) where
pure = return
(ReaderError (ReaderT f)) <*> (ReaderError (ReaderT a))
= ReaderError $ ReaderT $ \env -> (f env) `apEither` (a env)
instance (Monoid e, Error e) => Alternative (ReaderError r e) where
empty = unwrapMonad empty
f <|> g = unwrapMonad $ (WrapMonad f) <|> (WrapMonad g)
apEither :: (Monoid e) => Either e (a -> b) -> Either e a -> Either e b
apEither (Left errs1) (Left errs2) = Left (errs1 `mappend` errs2)
apEither (Left errs) _ = Left errs
apEither _ (Left errs) = Left errs
apEither (Right f) (Right a) = Right (f a)
newtype Errors a = Errors { unErrors :: [a] }
deriving (Eq, Ord, Show, Read, Data, Typeable)
instance SG.Semigroup (Errors a) where
(Errors x) <> (Errors y) = Errors (x ++ y)
instance Monoid (Errors a) where
mempty = Errors []
mappend = (SG.<>)
mconcat errs = Errors $ concatMap unErrors errs
instance Error (Errors String) where
noMsg = Errors []
strMsg str = Errors [str]
runReaderError :: ReaderError r e a -> r -> Either e a
runReaderError = runReaderT . unReaderError
type RqEnv = ([(String, Input)], Maybe [(String, Input)], [(String, Cookie)])
newtype RqData a = RqData { unRqData :: ReaderError RqEnv (Errors String) a }
deriving (Functor, Monad, MonadPlus, Applicative, Alternative, MonadReader RqEnv )
class HasRqData m where
askRqEnv :: m RqEnv
localRqEnv :: (RqEnv -> RqEnv) -> m a -> m a
rqDataError :: Errors String -> m a
instance HasRqData RqData where
askRqEnv = RqData ask
localRqEnv f (RqData re) = RqData $ local f re
rqDataError e = mapRqData ((Left e) `apEither`) (return ())
instance (MonadIO m, MonadPlus m) => HasRqData (ServerPartT m) where
askRqEnv = smAskRqEnv
rqDataError _e = mzero
localRqEnv = smLocalRqEnv
instance (Monad m, HasRqData m) => HasRqData (ReaderT s m) where
askRqEnv = lift askRqEnv
localRqEnv f = mapReaderT (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m) => HasRqData (Lazy.StateT s m) where
askRqEnv = lift askRqEnv
localRqEnv f = Lazy.mapStateT (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m) => HasRqData (Strict.StateT s m) where
askRqEnv = lift askRqEnv
localRqEnv f = Strict.mapStateT (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Lazy.WriterT w m) where
askRqEnv = lift askRqEnv
localRqEnv f = Lazy.mapWriterT (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Strict.WriterT w m) where
askRqEnv = lift askRqEnv
localRqEnv f = Strict.mapWriterT (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Lazy.RWST r w s m) where
askRqEnv = lift askRqEnv
localRqEnv f = Lazy.mapRWST (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m, Monoid w) => HasRqData (Strict.RWST r w s m) where
askRqEnv = lift askRqEnv
localRqEnv f = Strict.mapRWST (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, Error e, HasRqData m) => HasRqData (ErrorT e m) where
askRqEnv = lift askRqEnv
localRqEnv f = mapErrorT (localRqEnv f)
rqDataError e = lift (rqDataError e)
instance (Monad m, HasRqData m) => HasRqData (ExceptT e m) where
askRqEnv = lift askRqEnv
localRqEnv f = mapExceptT (localRqEnv f)
rqDataError e = lift (rqDataError e)
runRqData :: RqData a -> RqEnv -> Either [String] a
runRqData rqData rqEnv =
either (Left . unErrors) Right $ runReaderError (unRqData rqData) rqEnv
mapRqData :: (Either (Errors String) a -> Either (Errors String) b) -> RqData a -> RqData b
mapRqData f m = RqData $ ReaderError $ mapReaderT f (unReaderError (unRqData m))
unsafeReadRq :: (Read a) =>
String
-> String
-> Either String a
unsafeReadRq key val =
case reads val of
[(a,[])] -> Right a
_ -> Left $ "readRq failed while parsing key: " ++ key ++ " which has the value: " ++ val
readRq :: (FromReqURI a) =>
String
-> String
-> Either String a
readRq key val =
case fromReqURI val of
(Just a) -> Right a
_ -> Left $ "readRq failed while parsing key: " ++ key ++ " which has the value: " ++ val
checkRq :: (Monad m, HasRqData m) => m a -> (a -> Either String b) -> m b
checkRq rq f =
do a <- rq
case f a of
(Left e) -> rqDataError (strMsg e)
(Right b) -> return b
checkRqM :: (Monad m, HasRqData m) => m a -> (a -> m (Either String b)) -> m b
checkRqM rq f =
do a <- rq
eb <- f a
case eb of
(Left e) -> rqDataError (strMsg e)
(Right b) -> return b
class FromData a where
fromData :: RqData a
instance (FromData a, FromData b) => FromData (a,b) where
fromData = (,) <$> fromData <*> fromData
instance (FromData a, FromData b, FromData c) => FromData (a,b,c) where
fromData = (,,) <$> fromData <*> fromData <*> fromData
instance (FromData a, FromData b, FromData c, FromData d) => FromData (a,b,c,d) where
fromData = (,,,) <$> fromData <*> fromData <*> fromData <*> fromData
instance FromData a => FromData (Maybe a) where
fromData = (Just <$> fromData) <|> (pure Nothing)
lookups :: (Eq a) => a -> [(a, b)] -> [b]
lookups a = map snd . filter ((a ==) . fst)
fromMaybeBody :: String -> String -> Maybe [(String, Input)] -> [(String, Input)]
fromMaybeBody funName fieldName mBody =
case mBody of
Nothing -> error $ funName ++ " " ++ fieldName ++ " failed because the request body has not been decoded yet. Try using 'decodeBody' to decode the body. Or the 'queryString' filter to ignore the body."
(Just bdy) -> bdy
lookInput :: (Monad m, HasRqData m) => String -> m Input
lookInput name
= do (query, mBody, _cookies) <- askRqEnv
let bdy = fromMaybeBody "lookInput" name mBody
case lookup name (query ++ bdy) of
Just i -> return $ i
Nothing -> rqDataError (strMsg $ "Parameter not found: " ++ name)
lookInputs :: (Monad m, HasRqData m) => String -> m [Input]
lookInputs name
= do (query, mBody, _cookies) <- askRqEnv
let bdy = fromMaybeBody "lookInputs" name mBody
return $ lookups name (query ++ bdy)
lookBS :: (Functor m, Monad m, HasRqData m) => String -> m L.ByteString
lookBS n =
do i <- fmap inputValue (lookInput n)
case i of
(Left _fp) -> rqDataError $ (strMsg $ "lookBS: " ++ n ++ " is a file.")
(Right bs) -> return bs
lookBSs :: (Functor m, Monad m, HasRqData m) => String -> m [L.ByteString]
lookBSs n =
do is <- fmap (map inputValue) (lookInputs n)
case partitionEithers is of
([], bs) -> return bs
(_fp, _) -> rqDataError (strMsg $ "lookBSs: " ++ n ++ " is a file.")
look :: (Functor m, Monad m, HasRqData m) => String -> m String
look = fmap LU.toString . lookBS
looks :: (Functor m, Monad m, HasRqData m) => String -> m [String]
looks = fmap (map LU.toString) . lookBSs
lookText :: (Functor m, Monad m, HasRqData m) => String -> m LazyText.Text
lookText = fmap LazyText.decodeUtf8 . lookBS
lookText' :: (Functor m, Monad m, HasRqData m) => String -> m Text
lookText' = fmap LazyText.toStrict . lookText
lookTexts :: (Functor m, Monad m, HasRqData m) => String -> m [LazyText.Text]
lookTexts = fmap (map LazyText.decodeUtf8) . lookBSs
lookTexts' :: (Functor m, Monad m, HasRqData m) => String -> m [Text]
lookTexts' = fmap (map LazyText.toStrict) . lookTexts
lookCookie :: (Monad m, HasRqData m) => String -> m Cookie
lookCookie name
= do (_query,_body, cookies) <- askRqEnv
case lookup (map toLower name) cookies of
Nothing -> rqDataError $ strMsg $ "lookCookie: cookie not found: " ++ name
Just c -> return c
lookCookieValue :: (Functor m, Monad m, HasRqData m) => String -> m String
lookCookieValue = fmap cookieValue . lookCookie
readCookieValue :: (Functor m, Monad m, HasRqData m, FromReqURI a) => String -> m a
readCookieValue name = fmap cookieValue (lookCookie name) `checkRq` (readRq name)
lookRead :: (Functor m, Monad m, HasRqData m, FromReqURI a) => String -> m a
lookRead name = look name `checkRq` (readRq name)
lookReads :: (Functor m, Monad m, HasRqData m, FromReqURI a) => String -> m [a]
lookReads name =
do vals <- looks name
mapM (\v -> (return v) `checkRq` (readRq name)) vals
lookFile :: (Monad m, HasRqData m) =>
String
-> m (FilePath, FilePath, ContentType)
lookFile n =
do i <- lookInput n
case inputValue i of
(Right _) -> rqDataError $ (strMsg $ "lookFile: " ++ n ++ " was found but is not a file.")
(Left fp) -> return (fp, fromJust $ inputFilename i, inputContentType i)
lookPairs :: (Monad m, HasRqData m) => m [(String, Either FilePath String)]
lookPairs =
do (query, mBody, _cookies) <- askRqEnv
let bdy = fromMaybeBody "lookPairs" "" mBody
return $ map (\(n,vbs)->(n, (\e -> case e of Left fp -> Left fp ; Right bs -> Right (LU.toString bs)) $ inputValue vbs)) (query ++ bdy)
lookPairsBS :: (Monad m, HasRqData m) => m [(String, Either FilePath L.ByteString)]
lookPairsBS =
do (query, mBody, _cookies) <- askRqEnv
let bdy = fromMaybeBody "lookPairsBS" "" mBody
return $ map (\(n,vbs) -> (n, inputValue vbs)) (query ++ bdy)
decodeBody :: (ServerMonad m, MonadPlus m, MonadIO m, FilterMonad Response m, WebMonad Response m) => BodyPolicy -> m ()
decodeBody bp =
do rq <- askRq
(_, me) <- bodyInput bp rq
case me of
Nothing -> return ()
Just e -> escape $ requestEntityTooLarge (toResponse e)
getDataFn :: (HasRqData m, ServerMonad m) =>
RqData a
-> m (Either [String] a)
getDataFn rqData =
do rqEnv <- askRqEnv
return (runRqData rqData rqEnv)
withDataFn :: (HasRqData m, MonadPlus m, ServerMonad m) => RqData a -> (a -> m r) -> m r
withDataFn fn handle = getDataFn fn >>= either (const mzero) handle
getData :: (HasRqData m, ServerMonad m, FromData a) => m (Either [String] a)
getData = getDataFn fromData
withData :: (HasRqData m, FromData a, MonadPlus m, ServerMonad m) => (a -> m r) -> m r
withData = withDataFn fromData
body :: (HasRqData m) => m a -> m a
body rqData = localRqEnv f rqData
where
f (_query, bdy, _cookies) = ([], bdy, [])
queryString :: (HasRqData m) => m a -> m a
queryString rqData = localRqEnv f rqData
where
f (query, _body, _cookies) = (query, Just [], [])
bytestring :: (HasRqData m) => m a -> m a
bytestring rqData = localRqEnv f rqData
where
f (query, bdy, cookies) = (filter bsf query, filter bsf <$> bdy, cookies)
bsf (_, i) =
case inputValue i of
(Left _fp) -> False
(Right _bs) -> True