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