{-# LANGUAGE OverloadedStrings #-}

module Snap.Extras.MethodOverride
    ( handleMethodOverride
    , handleMethodOverride'
    ) where

-------------------------------------------------------------------------------
import           Control.Applicative  as A
import           Data.ByteString      (ByteString)
import           Data.CaseInsensitive (mk, original)
import           Data.Maybe           (fromMaybe)
import           Safe                 (headMay)
import           Snap.Core
-------------------------------------------------------------------------------



-------------------------------------------------------------------------------
-- | Wrap a handler with method override support. This means that if
-- (and only if) the request is a POST, _method param is passed, and
-- it is a parsable method name, it will change the request method to
-- the supplied one. This works around some browser limitations with
-- forms. If you use a different parameter name than _method, use
-- handleMethodOverride'
handleMethodOverride :: MonadSnap m
                     => m a
                     -- ^ Internal handler to call
                     -> m a
handleMethodOverride = handleMethodOverride' "_method"


-------------------------------------------------------------------------------
handleMethodOverride' :: MonadSnap m
                      => ByteString
                      -- ^ parameter name for method
                      -> m a
                      -- ^ Internal handler to call
                      -> m a
handleMethodOverride' pn = (modifyRequest (methodOverride pn) >>)


-------------------------------------------------------------------------------
methodOverride :: ByteString -> Request -> Request
methodOverride param r
  | rqMethod r == POST = r { rqMethod = overridden }
  | otherwise          = r
  where
    overridden = fromMaybe POST $ do
      meth <- mk A.<$> (headMay =<< rqParam param r)
      case meth of
       "HEAD"    -> Just HEAD
       "POST"    -> Just POST
       "PUT"     -> Just PUT
       "DELETE"  -> Just DELETE
       "TRACE"   -> Just TRACE
       "OPTIONS" -> Just OPTIONS
       "CONNECT" -> Just CONNECT
       "PATCH"   -> Just PATCH
       ""        -> Nothing
       s         -> Just $ Method $ original s