{-# LANGUAGE DeriveFunctor , ConstraintKinds , TypeFamilies , FlexibleContexts , GeneralizedNewtypeDeriving #-} {- | Module : Web.Routes.Nested.Types Copyright : (c) 2015 Athan Clark License : BSD-style Maintainer : athan.clark@gmail.com Stability : experimental Portability : GHC -} module Web.Routes.Nested.Types ( -- * Internal Structure Tries (..) , -- * Builder RouterT (..) , execRouterT , ActionT , action , -- * Book Keeping ExtrudeSoundly ) where import Web.Routes.Nested.Match (UrlChunks) import Network.Wai.Middleware.Verbs (VerbListenerT, execVerbListenerT, getVerbFromRequest) import Network.Wai.Middleware.ContentType (FileExtListenerT, fileExtsToMiddleware) import Network.Wai.Trans (MiddlewareT) import Network.Wai (strictRequestBody) import Data.Trie.Pred.Base (RootedPredTrie (..)) import Data.Trie.Pred.Interface.Types (Extrude (..), CatMaybes) import Data.Monoid ((<>)) import qualified Data.Text as T import Data.Function.Poly (ArityTypeListIso) import Data.Singleton.Class (Extractable) import qualified Data.HashMap.Lazy as HM import Control.Monad.Trans (MonadTrans) import Control.Monad.IO.Class (MonadIO) import qualified Control.Monad.State as S import Control.Monad.Trans.Control.Aligned (MonadBaseControl (liftBaseWith)) -- | The internal data structure built during route declaration. data Tries x s = Tries { trieContent :: !(RootedPredTrie T.Text x) , trieCatchAll :: !(RootedPredTrie T.Text x) , trieSecurity :: !(RootedPredTrie T.Text s) } instance Monoid (Tries x s) where mempty = Tries mempty mempty mempty mappend (Tries x1 x2 x3) (Tries y1 y2 y3) = Tries (x1 <> y1) (x2 <> y2) (x3 <> y3) -- | The (syntactic) monad for building a router with functions like -- "Web.Routes.Nested.match". -- it should have a shape of @RouterT (MiddlewareT m) (SecurityToken s) m a@ -- when used with "Web.Routes.Nested.route". newtype RouterT x sec m a = RouterT { runRouterT :: S.StateT (Tries x sec) m a } deriving ( Functor, Applicative, Monad, MonadIO, MonadTrans , S.MonadState (Tries x sec)) -- | Run the monad, only getting the built state and throwing away @a@. execRouterT :: Monad m => RouterT x sec m a -> m (Tries x sec) execRouterT hs = S.execStateT (runRouterT hs) mempty {-# INLINEABLE execRouterT #-} -- | Soundness constraint showing that a function's arity -- can be represented as a type-level list. type ExtrudeSoundly xs' xs c r = ( xs' ~ CatMaybes xs , ArityTypeListIso c xs' r , Extrude (UrlChunks xs) (RootedPredTrie T.Text c) (RootedPredTrie T.Text r) ) -- | The type of "content" builders; using the -- -- and -- packages. type ActionT m a = VerbListenerT (FileExtListenerT m a) m a -- | Run the content builder into a middleware that responds when the content -- is satisfiable (i.e. @Accept@ headers are O.K., etc.) action :: MonadBaseControl IO m stM => Extractable stM => ActionT m () -> MiddlewareT m action xs app req respond = do vmap <- execVerbListenerT xs case HM.lookup (getVerbFromRequest req) vmap of Nothing -> app req respond Just eR -> do c <- case eR of Left c' -> pure c' Right f -> f <$> liftBaseWith (\_ -> strictRequestBody req) fileExtsToMiddleware c app req respond