module Web.Routes.Nested
(
match
, matchHere
, matchAny
, matchGroup
, auth
,
route
, routeAuth
, extractMatch
, extractMatchAny
, extractAuthSym
, extractAuth
, extractNearestVia
,
SecurityToken (..)
, AuthScope (..)
,
module Web.Routes.Nested.Match
, module Web.Routes.Nested.Types
, module Network.Wai.Middleware.Verbs
, module Network.Wai.Middleware.ContentType
) where
import Web.Routes.Nested.Match
import Web.Routes.Nested.Types
import Network.Wai.Trans
import Network.Wai.Middleware.Verbs
import Network.Wai.Middleware.ContentType hiding (responseStatus, responseHeaders, responseData)
import qualified Data.Trie.Pred.Base as PT
import Data.Trie.Pred.Base (RootedPredTrie (..), PredTrie (..))
import Data.Trie.Pred.Base.Step (PredStep (..), PredSteps (..))
import Data.Trie.Pred.Interface.Types (Singleton (..), Extrude (..), CatMaybes)
import qualified Data.Trie.Class as TC
import Data.Trie.HashMap (HashMapStep (..), HashMapChildren (..))
import qualified Data.HashMap.Lazy as HM
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
import Data.Hashable
import Data.Monoid
import Data.Functor.Syntax
import Data.Function.Poly
import Control.Monad
import qualified Control.Monad.State as S
import Control.Monad.Catch
import Control.Monad.Trans
match :: ( Monad m
, Singleton (UrlChunks xs)
childContent
(RootedPredTrie T.Text resultContent)
, cleanxs ~ CatMaybes xs
, ArityTypeListIso childContent cleanxs resultContent
) => UrlChunks xs
-> childContent
-> HandlerT resultContent sec m ()
match !ts !vl =
tell' $ Tries (singleton ts vl)
mempty
mempty
matchHere :: ( Monad m
) => content
-> HandlerT content sec m ()
matchHere = match origin_
matchAny :: ( Monad m
) => content
-> HandlerT content sec m ()
matchAny !vl =
tell' $ Tries mempty
(singleton origin_ vl)
mempty
matchGroup :: ( Monad m
, cleanxs ~ CatMaybes xs
, ExtrudeSoundly cleanxs xs childContent resultContent
, ExtrudeSoundly cleanxs xs childSec resultSec
) => UrlChunks xs
-> HandlerT childContent childSec m ()
-> HandlerT resultContent resultSec m ()
matchGroup !ts cs = do
(Tries trieContent trieNotFound trieSec) <- lift $ execHandlerT cs
tell' $ Tries (extrude ts trieContent)
(extrude ts trieNotFound)
(extrude ts trieSec)
data SecurityToken s = SecurityToken
{ securityToken :: !s
, securityScope :: !AuthScope
} deriving (Show)
data AuthScope
= ProtectHere
| DontProtectHere
deriving (Show, Eq)
auth :: ( Monad m
) => sec
-> AuthScope
-> HandlerT content (SecurityToken sec) m ()
auth !token !scope =
tell' $ Tries mempty
mempty
(singleton origin_ $ SecurityToken token scope)
route :: ( Monad m
) => HandlerT (MiddlewareT m) sec m a
-> MiddlewareT m
route hs app req resp = do
let path = pathInfo req
mMatch <- extractMatch path hs
case mMatch of
Nothing -> do
mMatch <- extractMatchAny path hs
maybe
(app req resp)
(\mid -> mid app req resp)
mMatch
Just mid -> mid app req resp
routeAuth :: ( Monad m
, MonadThrow m
) => (Request -> [sec] -> m ())
-> HandlerT (MiddlewareT m) (SecurityToken sec) m a
-> MiddlewareT m
routeAuth authorize hs app req resp = do
extractAuth authorize req hs
route hs app req resp
extractMatch :: ( Monad m
) => [T.Text]
-> HandlerT r sec m a
-> m (Maybe r)
extractMatch path !hs = do
trie <- trieContent <$> execHandlerT hs
case matchWithLRPT trimFileExt path trie of
Nothing -> return $ do
guard $ not (null path)
guard $ trimFileExt (last path) == "index"
TC.lookup (init path) trie
Just (_,r) -> return (Just r)
extractMatchAny :: ( Monad m
) => [T.Text]
-> HandlerT r sec m a
-> m (Maybe r)
extractMatchAny path = extractNearestVia path (\x -> trieCatchAll <$> execHandlerT x)
extractAuthSym :: ( Monad m
) => [T.Text]
-> HandlerT x (SecurityToken sec) m a
-> m [sec]
extractAuthSym path hs = do
trie <- trieSecurity <$> execHandlerT hs
return $! foldr go [] $ PT.matchesRPT path trie
where
go (_,SecurityToken _ DontProtectHere,[]) ys = ys
go (_,SecurityToken x _ ,_ ) ys = x:ys
extractAuth :: ( Monad m
, MonadThrow m
) => (Request -> [sec] -> m ())
-> Request
-> HandlerT x (SecurityToken sec) m a
-> m ()
extractAuth authorize req hs = do
ss <- extractAuthSym (pathInfo req) hs
authorize req ss
extractNearestVia :: ( Monad m
) => [T.Text]
-> (HandlerT r sec m a -> m (RootedPredTrie T.Text r))
-> HandlerT r sec m a
-> m (Maybe r)
extractNearestVia path extr hs = do
trie <- extr hs
pure (mid <$> PT.matchRPT path trie)
where
mid (_,r,_) = r
trimFileExt :: T.Text -> T.Text
trimFileExt !s = fst $! T.breakOn "." s
matchWithLPT :: ( Hashable s
, Eq s
) => (s -> s) -> NonEmpty s -> PredTrie s a -> Maybe ([s], a)
matchWithLPT f (t:|ts) (PredTrie (HashMapStep ls) (PredSteps ps))
| null ts = getFirst $ First ((goLit $! f t) ls) <> foldMap (First . goPred) ps
| otherwise = getFirst $ First (goLit t ls) <> foldMap (First . goPred) ps
where
goLit t' xs = do
(HashMapChildren mx mxs) <- HM.lookup t' xs
if null ts
then ([t],) <$> mx
else fmap (\(ts',x) -> (t:ts',x)) $! matchWithLPT f (NE.fromList ts) =<< mxs
goPred (PredStep _ predicate mx xs) = do
d <- predicate t
if null ts
then ([t],) <$> (mx <$~> d)
else fmap (\(ts',x) -> (t:ts',x d)) $! matchWithLPT f (NE.fromList ts) xs
matchWithLRPT :: ( Hashable s
, Eq s
) => (s -> s) -> [s] -> RootedPredTrie s a -> Maybe ([s], a)
matchWithLRPT _ [] (RootedPredTrie mx _) = ([],) <$> mx
matchWithLRPT f ts (RootedPredTrie _ xs) = matchWithLPT f (NE.fromList ts) xs
tell' :: (Monoid w, S.MonadState w m) => w -> m ()
tell' x = S.modify' (<> x)