snap-extras-0.6: src/Snap/Extras/CoreUtils.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
module Snap.Extras.CoreUtils
( finishEarly
, badReq
, notFound
, serverError
, plainResponse
, jsonResponse
, jsResponse
, easyLog
, getParam'
, reqParam
, readParam
, readMayParam
, redirectReferer
, redirectRefererFunc
, dirify
, undirify
, maybeBadReq
, fromMaybeM
, (-/-)
) where
-------------------------------------------------------------------------------
import Control.Monad
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Maybe
import Safe
import Snap.Core
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- | Discard anything after this and return given status code to HTTP
-- client immediately.
finishEarly :: MonadSnap m => Int -> ByteString -> m b
finishEarly code str = do
modifyResponse $ setResponseStatus code str
modifyResponse $ addHeader "Content-Type" "text/plain"
writeBS str
getResponse >>= finishWith
-------------------------------------------------------------------------------
-- | Finish early with error code 400
badReq :: MonadSnap m => ByteString -> m b
badReq = finishEarly 400
-------------------------------------------------------------------------------
-- | Finish early with error code 404
notFound :: MonadSnap m => ByteString -> m b
notFound = finishEarly 404
-------------------------------------------------------------------------------
-- | Finish early with error code 500
serverError :: MonadSnap m => ByteString -> m b
serverError = finishEarly 500
-------------------------------------------------------------------------------
-- | Mark response as 'text/plain'
plainResponse :: MonadSnap m => m ()
plainResponse = modifyResponse $ setHeader "Content-Type" "text/plain"
-------------------------------------------------------------------------------
-- | Mark response as 'application/json'
jsonResponse :: MonadSnap m => m ()
jsonResponse = modifyResponse $ setHeader "Content-Type" "application/json"
-------------------------------------------------------------------------------
-- | Mark response as 'application/javascript'
jsResponse :: MonadSnap m => m ()
jsResponse = modifyResponse $ setHeader "Content-Type" "application/javascript"
------------------------------------------------------------------------------
-- | Easier debug logging into error log. First argument is a
-- category/namespace and the second argument is anything that has a
-- 'Show' instance.
easyLog :: (Show t, MonadSnap m) => String -> t -> m ()
easyLog k v = logError . B.pack $ ("[Debug] " ++ k ++ ": " ++ show v)
-------------------------------------------------------------------------------
-- | Alternate version of getParam that considers empty string Nothing
getParam' :: MonadSnap m => ByteString -> m (Maybe ByteString)
getParam' = return . maybe Nothing f <=< getParam
where f "" = Nothing
f x = Just x
-------------------------------------------------------------------------------
-- | Require that a parameter is present or terminate early.
reqParam :: (MonadSnap m) => ByteString -> m ByteString
reqParam s = do
p <- getParam s
maybe (badReq $ B.concat ["Required parameter ", s, " is missing."]) return p
-------------------------------------------------------------------------------
-- | Read a parameter from request. Be sure it is readable if it's
-- there, or else this will raise an error.
readParam :: (MonadSnap m, Read a) => ByteString -> m (Maybe a)
readParam k = fmap (readNote "readParam failed" . B.unpack) `fmap` getParam k
-------------------------------------------------------------------------------
-- | Try to read a parameter from request. Computation may fail
-- because the param is not there, or because it can't be read.
readMayParam :: (MonadSnap m, Read a) => ByteString -> m (Maybe a)
readMayParam k = do
p <- getParam k
return $ readMay . B.unpack =<< p
------------------------------------------------------------------------------
-- | Redirects back to the refering page. If there is no Referer header, then
-- redirect to /.
redirectReferer :: MonadSnap m => m b
redirectReferer = redirectRefererFunc (fromMaybe "/")
------------------------------------------------------------------------------
-- | Redirects back to the refering page. If there is no Referer header, then
-- redirect to /.
redirectRefererFunc :: MonadSnap m => (Maybe ByteString -> ByteString) -> m b
redirectRefererFunc f = do
req <- getRequest
let referer = getHeader "Referer" req
redirect $ f referer
------------------------------------------------------------------------------
-- | If the current rqURI does not have a trailing slash, then redirect to the
-- same page with a slash added.
dirify :: MonadSnap m => m ()
dirify = do
uri <- withRequest (return . rqURI)
if B.length uri > 1 && B.last uri /= '/'
then redirect (uri `B.append` "/")
else return ()
------------------------------------------------------------------------------
-- | If the current rqURI has a trailing slash, then redirect to the same page
-- with no trailing slash.
undirify :: MonadSnap m => m ()
undirify = do
uri <- withRequest (return . rqURI)
if B.length uri > 1 && B.last uri == '/'
then redirect (B.init uri)
else return ()
-------------------------------------------------------------------------------
maybeBadReq :: MonadSnap m => ByteString -> m (Maybe a) -> m a
maybeBadReq e f = fromMaybeM (badReq e) f
-------------------------------------------------------------------------------
-- | Evaluates an action that returns a Maybe and
fromMaybeM :: Monad m => m a -> m (Maybe a) -> m a
fromMaybeM e f = maybe e return =<< f
------------------------------------------------------------------------------
-- | Concatenates two URL segments with a '/' between them. To prevent double
-- slashes, all trailing slashes are removed from the first path and all
-- leading slashes are removed from the second path.
(-/-) :: ByteString -> ByteString -> ByteString
(-/-) a b = B.concat [revDrop a, "/", dropSlash b]
where
dropSlash = B.dropWhile (=='/')
revDrop = B.reverse . dropSlash . B.reverse