Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding MsgPack convenience module #8

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
dist
.DS*
cabal-dev
.cabal-sandbox
cabal.sandbox.config
dist
*.swp
TAGS
1 change: 1 addition & 0 deletions snap-extras.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Library
hs-source-dirs: src
Build-depends:
aeson >= 0.6 && < 0.8
, msgpack >= 0.7.2 && < 0.7.3
, base >= 4 && < 5
, blaze-builder >= 0.3 && < 0.4
, blaze-html >= 0.6 && < 0.8
Expand Down
36 changes: 21 additions & 15 deletions src/Snap/Extras/CoreUtils.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}

module Snap.Extras.CoreUtils
( finishEarly
Expand All @@ -8,6 +8,7 @@ module Snap.Extras.CoreUtils
, serverError
, plainResponse
, jsonResponse
, mpResponse
, jsResponse
, easyLog
, getParam'
Expand All @@ -24,20 +25,20 @@ module Snap.Extras.CoreUtils
) where

-------------------------------------------------------------------------------
import Control.Monad
import Data.ByteString.Char8 (ByteString)
import Control.Monad
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Maybe
import Safe
import Snap.Core
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 :: MonadSnap m => Int -> ByteString -> m b
finishEarly code str = do
modifyResponse $ setResponseStatus code str
modifyResponse $ addHeader "Content-Type" "text/plain"
Expand All @@ -47,19 +48,19 @@ finishEarly code str = do

-------------------------------------------------------------------------------
-- | Finish early with error code 400
badReq :: MonadSnap m => ByteString -> m b
badReq = finishEarly 400
badReq :: MonadSnap m => ByteString -> m b
badReq = finishEarly 400


-------------------------------------------------------------------------------
-- | Finish early with error code 404
notFound :: MonadSnap m => ByteString -> m b
notFound :: MonadSnap m => ByteString -> m b
notFound = finishEarly 404


-------------------------------------------------------------------------------
-- | Finish early with error code 500
serverError :: MonadSnap m => ByteString -> m b
serverError :: MonadSnap m => ByteString -> m b
serverError = finishEarly 500


Expand All @@ -74,6 +75,11 @@ plainResponse = modifyResponse $ setHeader "Content-Type" "text/plain"
jsonResponse :: MonadSnap m => m ()
jsonResponse = modifyResponse $ setHeader "Content-Type" "application/json"

-------------------------------------------------------------------------------
-- | Mark response as 'application/x-msgpack'
mpResponse :: MonadSnap m => m ()
mpResponse = modifyResponse $ setHeader "Content-Type" "application/x-msgpack"


-------------------------------------------------------------------------------
-- | Mark response as 'application/javascript'
Expand Down Expand Up @@ -103,7 +109,7 @@ 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
Expand All @@ -116,7 +122,7 @@ 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
readMayParam k = do
p <- getParam k
return $ readMay . B.unpack =<< p

Expand All @@ -134,7 +140,7 @@ redirectReferer = redirectRefererFunc (fromMaybe "/")
redirectRefererFunc :: MonadSnap m => (Maybe ByteString -> ByteString) -> m b
redirectRefererFunc f = do
req <- getRequest
let referer = getHeader "Referer" req
let referer = getHeader "Referer" req
redirect $ f referer


Expand Down Expand Up @@ -166,7 +172,7 @@ maybeBadReq e f = fromMaybeM (badReq e) f


-------------------------------------------------------------------------------
-- | Evaluates an action that returns a Maybe and
-- | 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

Expand Down
93 changes: 93 additions & 0 deletions src/Snap/Extras/MsgPack.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}

module Snap.Extras.MsgPack
(
-- * Parsing MP from Request Body
getBoundedMP
, getMP
, reqBoundedMP
, reqMP
, getMPField
, reqMPField
-- * Sending MP Data
, writeMP
) where


-------------------------------------------------------------------------------
import qualified Data.ByteString.Char8 as B
import Data.Int
import Data.MessagePack as MP
import Snap.Core
-------------------------------------------------------------------------------
import Snap.Extras.CoreUtils
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-- | Demand the presence of MP in the body assuming it is not larger
-- than 50000 bytes.
reqMP :: (MonadSnap m, Unpackable b) => m b
reqMP = reqBoundedMP 50000

-------------------------------------------------------------------------------
-- | Demand the presence of MP in the body with a size up to N
-- bytes. If parsing fails for any reson, request is terminated early
-- and a server error is returned.
reqBoundedMP
:: (MonadSnap m, Unpackable a)
=> Int64
-- ^ Maximum size in bytes
-> m a
reqBoundedMP n = do
res <- getBoundedMP n
case res of
Left e -> badReq $ B.pack e
Right a -> return a

-------------------------------------------------------------------------------
-- | Try to parse request body as MP with a default max size of
-- 50000.
getMP :: (MonadSnap m, Unpackable a) => m (Either String a)
getMP = getBoundedMP 50000

-------------------------------------------------------------------------------
-- | Parse request body into MP or return an error string.
getBoundedMP
:: (MonadSnap m, Unpackable a)
=> Int64
-- ^ Maximum size in bytes
-> m (Either String a)
getBoundedMP n = tryUnpack `fmap` readRequestBody n

-------------------------------------------------------------------------------
-- | Get MP data from the given Param field
getMPField
:: (MonadSnap m, Unpackable a)
=> B.ByteString
-> m (Either String a)
getMPField fld = do
val <- getParam fld
return $ case val of
Nothing -> Left $ "Cant find field " ++ B.unpack fld
Just val' -> tryUnpack val'

-------------------------------------------------------------------------------
-- | Force the MP value from field. Similar to 'getMPField'
reqMPField
:: (MonadSnap m, Unpackable a)
=> B.ByteString
-> m a
reqMPField fld = do
res <- getMPField fld
case res of
Left e -> badReq $ B.pack e
Right a -> return a

-------------------------------------------------------------------------------
-- | Set MIME to 'application/x-msgpack' and write given object into
-- 'Response' body.
writeMP :: (MonadSnap m, Packable a) => a -> m ()
writeMP a = do
mpResponse
writeLBS . MP.pack $ a