------------------------------------------------------------------------------
-- | Provides a simple interface for routing based on the value of the Accept
-- header in the client's request.  The functions 'accept' and 'accepts'
-- mirror Snap's standard 'method' and 'methods' functions.
--
-- The most convenient way of building 'MediaType' values is to use the
-- 'IsString' instance with OverloadedStrings.
--
-- > accept "application/json" serveJson
--
-- Simple constructor operators are also supplied if you prefer.
module Snap.Accept
    (
    -- * Accept routing
      accept
    , accepts

    -- * MediaType
    , MediaType
    , (//)
    , (/:)
    ) where

------------------------------------------------------------------------------
import Control.Monad                (join, (>=>))
import Data.Maybe                   (fromMaybe)
import Network.HTTP.Media
import Network.HTTP.Media.MediaType (toByteString)
import Snap.Core


------------------------------------------------------------------------------
-- | Runs a Snap monad only if the request's Accept header allows for the
-- given media type.  If accepted, the response's Content-Type header is
-- automatically filled in.
accept :: MonadSnap m => MediaType -> m a -> m a
accept mtype action =
    withAccept (matchAccept [mtype]) >>= maybe (run mtype) run
  where
    run = flip runWithType action


------------------------------------------------------------------------------
-- | Runs a Snap monad only if the request's Accept header allows for one of
-- the given media types.  If accepted, the expected type is passed to the
-- given function and the response's Content-Type header is automatically
-- filled in.
accepts :: MonadSnap m => [(MediaType, m a)] -> m a
accepts []   = pass
accepts dict = withAccept (mapAccept dict') >>= fromMaybe (snd $ head dict')
  where
    dict' = map (join $ fmap . runWithType . fst) dict


------------------------------------------------------------------------------
-- | Parses the Accept header from the request and, if successful, passes
-- it to the given function.
withAccept :: MonadSnap m => ([Quality MediaType] -> Maybe a) -> m (Maybe a)
withAccept f = getsRequest $ getHeader "Accept" >=> parseAccept >=> f


------------------------------------------------------------------------------
-- | Runs the given Snap monad with the given media type set in the
-- response's ContentType header.
runWithType :: MonadSnap m => MediaType -> m a -> m a
runWithType mtype action =
    modifyResponse (setContentType $ toByteString mtype) >> action