-- | -- Copyright : (c) Raghu Kaippully, 2020 -- License : MPL-2.0 -- Maintainer : rkaippully@gmail.com -- -- Types and functions to route HTTP requests. module WebGear.Route ( RouterT , MonadRouter (..) , runRoute ) where import Control.Applicative (Alternative) import Control.Arrow (Kleisli (..)) import Control.Monad (MonadPlus (..)) import Control.Monad.Except (ExceptT, MonadError (..), runExceptT) import Data.ByteString.Lazy (ByteString) import Data.HashMap.Strict (fromList) import Data.Semigroup (First (..)) import Data.String (fromString) import Data.Version (showVersion) import Network.HTTP.Types (Header, hServer, notFound404) import Paths_webgear_server (version) import WebGear.Trait (linkzero, unlink) import WebGear.Types (Handler, Response (..), waiResponse) import qualified Network.Wai as Wai -- | The monad transformer stack for routing. -- -- * The 'ExceptT' provides short-circuiting behaviour for -- 'rejectRoute' and 'failHandler'. -- -- * In case of 'rejectRoute', a 'Nothing' value is returned and in -- case of 'failHandler', a @Response ByteString@ is returned. -- -- * The 'First' wrapper is provided to get instances of 'Alternative' -- and 'MonadPlus' for 'RouterT'. -- type RouterT m = ExceptT (Maybe (First (Response ByteString))) m -- | HTTP request routing with short circuiting behavior. class (Alternative m, MonadPlus m) => MonadRouter m where -- | Mark the current route as rejected, alternatives can be tried rejectRoute :: m a -- | Short-circuit the current handler and return a response failHandler :: Response ByteString -> m a instance Monad m => MonadRouter (RouterT m) where rejectRoute :: RouterT m a rejectRoute = mzero failHandler :: Response ByteString -> RouterT m a failHandler = throwError . Just . First -- | Convert a routable handler into a plain function. -- -- This function is typically used to convert WebGear routes to a -- 'Wai.Application'. runRoute :: Monad m => Handler (RouterT m) '[] res ByteString -> (Wai.Request -> m Wai.Response) runRoute route req = waiResponse . addServerHeader . either (maybe notFoundResponse getFirst) id <$> runExceptT f where f = unlink <$> runKleisli route (linkzero req) notFoundResponse :: Response ByteString notFoundResponse = Response { respStatus = notFound404 , respHeaders = fromList [] , respBody = Just "Not Found" } addServerHeader :: Response ByteString -> Response ByteString addServerHeader r = r { respHeaders = respHeaders r <> fromList [serverHeader] } serverHeader :: Header serverHeader = (hServer, fromString $ "WebGear/" ++ showVersion version)