{-# 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