snap-accept-0.2.0: src/Snap/Accept.hs
------------------------------------------------------------------------------
-- | HTTP media type functionality as a complement to Snap's 'method' and
-- 'methods' functions. Branches based on the value of the Accept or
-- Accept-Language header of the current request, automatically setting the
-- Content-Type or Content-Language header of the response as appropriate.
module Snap.Accept
(
-- * Branching
accept
, acceptMedia
, acceptLanguage
, accepts
, acceptsMedia
, acceptsLanguage
-- * Accept types
, MediaType
, Language
-- * Header names
, FromHeader (..)
) where
import Control.Monad (join)
import Data.ByteString (ByteString)
import Data.CaseInsensitive (CI)
import Data.Maybe (fromMaybe)
import Data.Proxy (Proxy (..))
import Network.HTTP.Media
import Network.HTTP.Media.RenderHeader (renderHeader)
import Snap.Core
------------------------------------------------------------------------------
-- | The class of values that represent some Accept* header in a request and
-- corresponding Content-* header in a response, such that the name of the
-- header can be retrieved from the type.
class (Accept a, RenderHeader a) => FromHeader a where
-- | The name of the corresponding Accept* header for this type.
requestName :: Proxy a -> CI ByteString
-- | The name of the corresponding Content-* header for this type.
responseName :: Proxy a -> CI ByteString
-- | The default header value to use if the header is absent.
defaultValue :: Proxy a -> ByteString
instance FromHeader MediaType where
requestName _ = "Accept"
responseName _ = "Content-Type"
defaultValue _ = "*/*"
instance FromHeader Language where
requestName _ = "Accept-Language"
responseName _ = "Content-Language"
defaultValue _ = "*"
------------------------------------------------------------------------------
-- | Runs a Snap monad only if the request's Accept* header allows for the
-- given value. If accepted, the response's Content-* header is automatically
-- filled in.
accept :: (FromHeader a, MonadSnap m) => a -> m b -> m b
accept a s =
withAccept (proxyFor a) (matchAccept [a]) >>= maybe pass (`withHeader` s)
where
proxyFor :: a -> Proxy a
proxyFor _ = Proxy
-- | The 'accept' function but specialised to 'MediaType'.
acceptMedia :: (MonadSnap m) => MediaType -> m a -> m a
acceptMedia = accept
-- | The 'accept' function but specialised to 'Language'.
acceptLanguage :: (MonadSnap m) => Language -> m a -> m a
acceptLanguage = accept
------------------------------------------------------------------------------
-- | Matches the Accept* header of the request to each of the values in the
-- pairs of the given list, running the corresponding Snap monad in the pair
-- that is most desired by the client. If two or more results arise with the
-- same quality level and specificity, then the pair that appears earliest in
-- the list is matched. On any match, the response's Content-* header is
-- automatically filled in.
accepts :: (FromHeader a, MonadSnap m) => [(a, m b)] -> m b
accepts d = withAccept (proxyFor d) (mapAccept d') >>= fromMaybe pass
where
d' = map (join $ fmap . withHeader . fst) d
proxyFor :: [(a, m b)] -> Proxy a
proxyFor _ = Proxy
-- | The 'accepts' function but specialised to 'MediaType'.
acceptsMedia :: (MonadSnap m) => [(MediaType, m a)] -> m a
acceptsMedia = accepts
-- | The 'accepts' function but specialised to 'Language'.
acceptsLanguage :: (MonadSnap m) => [(Language, m a)] -> m a
acceptsLanguage = accepts
------------------------------------------------------------------------------
-- | Parses the Accept* header from the request and, if successful, passes it
-- to the given function.
withAccept :: (FromHeader a, MonadSnap m)
=> Proxy a
-> (ByteString -> Maybe b) -> m (Maybe b)
withAccept p f = getsRequest $ f . fromMaybe (defaultValue p) . getHeader name
where
name = requestName p
------------------------------------------------------------------------------
-- | Runs a Snap monad with the rendered value of an Content-* form set in the
-- appropriate header of the response.
withHeader :: (FromHeader a, MonadSnap m) => a -> m b -> m b
withHeader a m = modifyResponse (setHeader name (renderHeader a)) >> m
where
name = responseName (proxyFor a)
proxyFor :: a -> Proxy a
proxyFor _ = Proxy