{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE Rank2Types #-}
module Snap.Internal.Routing
( Route(..)
, pRoute
, route
, routeEarliestNC
, routeHeight
, routeLocal
, splitPath
) where
import Control.Applicative ((<|>))
import Data.ByteString (ByteString)
import qualified Data.ByteString as B (head, intercalate, length, null, pack, splitWith, tail)
import Data.ByteString.Internal (c2w)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H (elems, empty, fromList, lookup, unionWith)
import qualified Data.Map as Map (empty, insertWith, unionWith)
import Snap.Internal.Core (MonadSnap, getRequest, getsRequest, localRequest, modifyRequest, pass, updateContextPath)
import Snap.Internal.Http.Types (Params, Request (rqContextPath, rqParams, rqPathInfo))
import Snap.Internal.Parsing (urlDecode)
#if !MIN_VERSION_base(4,8,0)
import Data.Monoid (Monoid (..))
#endif
import Data.Semigroup (Semigroup (..))
data Route a m = Action ((MonadSnap m) => m a)
| Capture ByteString (Route a m) (Route a m)
| Dir (HashMap ByteString (Route a m)) (Route a m)
| NoRoute
instance Semigroup (Route a m) where
Route a m
NoRoute <> :: Route a m -> Route a m -> Route a m
<> Route a m
r = Route a m
r
l :: Route a m
l@(Action MonadSnap m => m a
a) <> Route a m
r = case Route a m
r of
(Action MonadSnap m => m a
a') -> (MonadSnap m => m a) -> Route a m
forall a (m :: * -> *). (MonadSnap m => m a) -> Route a m
Action (m a
MonadSnap m => m a
a m a -> m a -> m a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> m a
MonadSnap m => m a
a')
(Capture ByteString
p Route a m
r' Route a m
fb) -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p Route a m
r' (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
l)
(Dir HashMap ByteString (Route a m)
_ Route a m
_) -> HashMap ByteString (Route a m) -> Route a m -> Route a m
forall a (m :: * -> *).
HashMap ByteString (Route a m) -> Route a m -> Route a m
Dir HashMap ByteString (Route a m)
forall k v. HashMap k v
H.empty Route a m
l Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r
Route a m
NoRoute -> Route a m
l
l :: Route a m
l@(Capture ByteString
p Route a m
r' Route a m
fb) <> Route a m
r = case Route a m
r of
(Action MonadSnap m => m a
_) -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p Route a m
r' (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r)
(Capture ByteString
p' Route a m
r'' Route a m
fb')
| ByteString
p ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
p' -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p (Route a m
r' Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r'') (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
fb')
| Int
rh' Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
rh'' -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p Route a m
r' (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r)
| Int
rh' Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
rh'' -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p' Route a m
r'' (Route a m
fb' Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
l)
| Int
en' Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
en'' -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p Route a m
r' (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r)
| Bool
otherwise -> ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture ByteString
p' Route a m
r'' (Route a m
fb' Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
l)
where
rh' :: Int
rh' = Route a m -> Int
forall a (m :: * -> *). Route a m -> Int
routeHeight Route a m
r'
rh'' :: Int
rh'' = Route a m -> Int
forall a (m :: * -> *). Route a m -> Int
routeHeight Route a m
r''
en' :: Int
en' = Route a m -> Int -> Int
forall a (m :: * -> *). Route a m -> Int -> Int
routeEarliestNC Route a m
r' Int
1
en'' :: Int
en'' = Route a m -> Int -> Int
forall a (m :: * -> *). Route a m -> Int -> Int
routeEarliestNC Route a m
r'' Int
1
(Dir HashMap ByteString (Route a m)
rm Route a m
fb') -> HashMap ByteString (Route a m) -> Route a m -> Route a m
forall a (m :: * -> *).
HashMap ByteString (Route a m) -> Route a m -> Route a m
Dir HashMap ByteString (Route a m)
rm (Route a m
fb' Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
l)
Route a m
NoRoute -> Route a m
l
(<>) l :: Route a m
l@(Dir HashMap ByteString (Route a m)
rm Route a m
fb) Route a m
r = case Route a m
r of
(Action MonadSnap m => m a
_) -> HashMap ByteString (Route a m) -> Route a m -> Route a m
forall a (m :: * -> *).
HashMap ByteString (Route a m) -> Route a m -> Route a m
Dir HashMap ByteString (Route a m)
rm (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r)
(Capture ByteString
_ Route a m
_ Route a m
_) -> HashMap ByteString (Route a m) -> Route a m -> Route a m
forall a (m :: * -> *).
HashMap ByteString (Route a m) -> Route a m -> Route a m
Dir HashMap ByteString (Route a m)
rm (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
r)
(Dir HashMap ByteString (Route a m)
rm' Route a m
fb') -> HashMap ByteString (Route a m) -> Route a m -> Route a m
forall a (m :: * -> *).
HashMap ByteString (Route a m) -> Route a m -> Route a m
Dir ((Route a m -> Route a m -> Route a m)
-> HashMap ByteString (Route a m)
-> HashMap ByteString (Route a m)
-> HashMap ByteString (Route a m)
forall k v.
(Eq k, Hashable k) =>
(v -> v -> v) -> HashMap k v -> HashMap k v -> HashMap k v
H.unionWith Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
(<>) HashMap ByteString (Route a m)
rm HashMap ByteString (Route a m)
rm') (Route a m
fb Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
<> Route a m
fb')
Route a m
NoRoute -> Route a m
l
instance Monoid (Route a m) where
mempty :: Route a m
mempty = Route a m
forall a (m :: * -> *). Route a m
NoRoute
mappend :: Route a m -> Route a m -> Route a m
mappend = Route a m -> Route a m -> Route a m
forall a. Semigroup a => a -> a -> a
(<>)
routeHeight :: Route a m -> Int
routeHeight :: Route a m -> Int
routeHeight Route a m
r = case Route a m
r of
Route a m
NoRoute -> Int
1
(Action MonadSnap m => m a
_) -> Int
1
(Capture ByteString
_ Route a m
r' Route a m
_) -> Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Route a m -> Int
forall a (m :: * -> *). Route a m -> Int
routeHeight Route a m
r'
(Dir HashMap ByteString (Route a m)
rm Route a m
_) -> Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int -> Int -> Int) -> Int -> [Int] -> Int
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
1 ((Route a m -> Int) -> [Route a m] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map Route a m -> Int
forall a (m :: * -> *). Route a m -> Int
routeHeight ([Route a m] -> [Int]) -> [Route a m] -> [Int]
forall a b. (a -> b) -> a -> b
$ HashMap ByteString (Route a m) -> [Route a m]
forall k v. HashMap k v -> [v]
H.elems HashMap ByteString (Route a m)
rm)
{-# INLINE routeHeight #-}
routeEarliestNC :: Route a m -> Int -> Int
routeEarliestNC :: Route a m -> Int -> Int
routeEarliestNC Route a m
r Int
n = case Route a m
r of
Route a m
NoRoute -> Int
n
(Action MonadSnap m => m a
_) -> Int
n
(Capture ByteString
_ Route a m
r' Route a m
_) -> Route a m -> Int -> Int
forall a (m :: * -> *). Route a m -> Int -> Int
routeEarliestNC Route a m
r' Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1
(Dir HashMap ByteString (Route a m)
_ Route a m
_) -> Int
n
{-# INLINE routeEarliestNC #-}
route :: MonadSnap m => [(ByteString, m a)] -> m a
route :: [(ByteString, m a)] -> m a
route [(ByteString, m a)]
rts = do
ByteString
p <- (Request -> ByteString) -> m ByteString
forall (m :: * -> *) a. MonadSnap m => (Request -> a) -> m a
getsRequest Request -> ByteString
rqPathInfo
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' (() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return (() -> m ()) -> () -> m ()
forall a b. (a -> b) -> a -> b
$! ()) [] (ByteString -> [ByteString]
splitPath ByteString
p) Params
forall k a. Map k a
Map.empty Route a m
rts'
where
rts' :: Route a m
rts' = [Route a m] -> Route a m
forall a. Monoid a => [a] -> a
mconcat (((ByteString, m a) -> Route a m)
-> [(ByteString, m a)] -> [Route a m]
forall a b. (a -> b) -> [a] -> [b]
map (ByteString, m a) -> Route a m
forall (m :: * -> *) a.
MonadSnap m =>
(ByteString, m a) -> Route a m
pRoute [(ByteString, m a)]
rts)
{-# INLINE route #-}
routeLocal :: MonadSnap m => [(ByteString, m a)] -> m a
routeLocal :: [(ByteString, m a)] -> m a
routeLocal [(ByteString, m a)]
rts = do
Request
req <- m Request
forall (m :: * -> *). MonadSnap m => m Request
getRequest
let ctx :: ByteString
ctx = Request -> ByteString
rqContextPath Request
req
let p :: ByteString
p = Request -> ByteString
rqPathInfo Request
req
let md :: m ()
md = (Request -> Request) -> m ()
forall (m :: * -> *). MonadSnap m => (Request -> Request) -> m ()
modifyRequest ((Request -> Request) -> m ()) -> (Request -> Request) -> m ()
forall a b. (a -> b) -> a -> b
$ \Request
r -> Request
r {rqContextPath :: ByteString
rqContextPath=ByteString
ctx, rqPathInfo :: ByteString
rqPathInfo=ByteString
p}
(m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
md [] (ByteString -> [ByteString]
splitPath ByteString
p) Params
forall k a. Map k a
Map.empty Route a m
rts') m a -> m a -> m a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> (m ()
md m () -> m a -> m a
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> m a
forall (m :: * -> *) a. MonadSnap m => m a
pass)
where
rts' :: Route a m
rts' = [Route a m] -> Route a m
forall a. Monoid a => [a] -> a
mconcat (((ByteString, m a) -> Route a m)
-> [(ByteString, m a)] -> [Route a m]
forall a b. (a -> b) -> [a] -> [b]
map (ByteString, m a) -> Route a m
forall (m :: * -> *) a.
MonadSnap m =>
(ByteString, m a) -> Route a m
pRoute [(ByteString, m a)]
rts)
{-# INLINE routeLocal #-}
splitPath :: ByteString -> [ByteString]
splitPath :: ByteString -> [ByteString]
splitPath = (Word8 -> Bool) -> ByteString -> [ByteString]
B.splitWith (Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== (Char -> Word8
c2w Char
'/'))
{-# INLINE splitPath #-}
pRoute :: MonadSnap m => (ByteString, m a) -> Route a m
pRoute :: (ByteString, m a) -> Route a m
pRoute (ByteString
r, m a
a) = (ByteString -> Route a m -> Route a m)
-> Route a m -> [ByteString] -> Route a m
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr ByteString -> Route a m -> Route a m
forall a (m :: * -> *). ByteString -> Route a m -> Route a m
f ((MonadSnap m => m a) -> Route a m
forall a (m :: * -> *). (MonadSnap m => m a) -> Route a m
Action m a
MonadSnap m => m a
a) [ByteString]
hier
where
hier :: [ByteString]
hier = (ByteString -> Bool) -> [ByteString] -> [ByteString]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (ByteString -> Bool) -> ByteString -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Bool
B.null) ([ByteString] -> [ByteString]) -> [ByteString] -> [ByteString]
forall a b. (a -> b) -> a -> b
$ (Word8 -> Bool) -> ByteString -> [ByteString]
B.splitWith (Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== (Char -> Word8
c2w Char
'/')) ByteString
r
f :: ByteString -> Route a m -> Route a m
f ByteString
s Route a m
rt = if ByteString -> Word8
B.head ByteString
s Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Char -> Word8
c2w Char
':'
then ByteString -> Route a m -> Route a m -> Route a m
forall a (m :: * -> *).
ByteString -> Route a m -> Route a m -> Route a m
Capture (ByteString -> ByteString
B.tail ByteString
s) Route a m
rt Route a m
forall a (m :: * -> *). Route a m
NoRoute
else HashMap ByteString (Route a m) -> Route a m -> Route a m
forall a (m :: * -> *).
HashMap ByteString (Route a m) -> Route a m -> Route a m
Dir ([(ByteString, Route a m)] -> HashMap ByteString (Route a m)
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
H.fromList [(ByteString
s, Route a m
rt)]) Route a m
forall a (m :: * -> *). Route a m
NoRoute
{-# INLINE pRoute #-}
route' :: MonadSnap m
=> m ()
-> [ByteString]
-> [ByteString]
-> Params
-> Route a m
-> m a
route' :: m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre ![ByteString]
ctx [ByteString]
_ !Params
params (Action MonadSnap m => m a
action) =
(Request -> Request) -> m a -> m a
forall (m :: * -> *) a.
MonadSnap m =>
(Request -> Request) -> m a -> m a
localRequest (Int -> Request -> Request
updateContextPath (ByteString -> Int
B.length ByteString
ctx') (Request -> Request) -> (Request -> Request) -> Request -> Request
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> Request
updateParams)
(m ()
pre m () -> m a -> m a
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> m a
MonadSnap m => m a
action)
where
ctx' :: ByteString
ctx' = ByteString -> [ByteString] -> ByteString
B.intercalate ([Word8] -> ByteString
B.pack [Char -> Word8
c2w Char
'/']) ([ByteString] -> [ByteString]
forall a. [a] -> [a]
reverse [ByteString]
ctx)
updateParams :: Request -> Request
updateParams Request
req = Request
req
{ rqParams :: Params
rqParams = ([ByteString] -> [ByteString] -> [ByteString])
-> Params -> Params -> Params
forall k a. Ord k => (a -> a -> a) -> Map k a -> Map k a -> Map k a
Map.unionWith (([ByteString] -> [ByteString] -> [ByteString])
-> [ByteString] -> [ByteString] -> [ByteString]
forall a b c. (a -> b -> c) -> b -> a -> c
flip [ByteString] -> [ByteString] -> [ByteString]
forall a. [a] -> [a] -> [a]
(++)) Params
params (Request -> Params
rqParams Request
req) }
route' m ()
pre ![ByteString]
ctx [] !Params
params (Capture ByteString
_ Route a m
_ Route a m
fb) =
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre [ByteString]
ctx [] Params
params Route a m
fb
route' m ()
pre ![ByteString]
ctx paths :: [ByteString]
paths@(ByteString
cwd:[ByteString]
rest) !Params
params (Capture ByteString
p Route a m
rt Route a m
fb)
| ByteString -> Bool
B.null ByteString
cwd = m a
fallback
| Bool
otherwise = m a
m m a -> m a -> m a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> m a
fallback
where
fallback :: m a
fallback = m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre [ByteString]
ctx [ByteString]
paths Params
params Route a m
fb
m :: m a
m = m a -> (ByteString -> m a) -> Maybe ByteString -> m a
forall b a. b -> (a -> b) -> Maybe a -> b
maybe m a
forall (m :: * -> *) a. MonadSnap m => m a
pass
(\ByteString
cwd' -> let params' :: Params
params' = ([ByteString] -> [ByteString] -> [ByteString])
-> ByteString -> [ByteString] -> Params -> Params
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith (([ByteString] -> [ByteString] -> [ByteString])
-> [ByteString] -> [ByteString] -> [ByteString]
forall a b c. (a -> b -> c) -> b -> a -> c
flip [ByteString] -> [ByteString] -> [ByteString]
forall a. [a] -> [a] -> [a]
(++)) ByteString
p [ByteString
cwd'] Params
params
in m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre (ByteString
cwdByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[ByteString]
ctx) [ByteString]
rest Params
params' Route a m
rt)
(ByteString -> Maybe ByteString
urlDecode ByteString
cwd)
route' m ()
pre ![ByteString]
ctx [] !Params
params (Dir HashMap ByteString (Route a m)
_ Route a m
fb) =
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre [ByteString]
ctx [] Params
params Route a m
fb
route' m ()
pre ![ByteString]
ctx paths :: [ByteString]
paths@(ByteString
cwd:[ByteString]
rest) !Params
params (Dir HashMap ByteString (Route a m)
rtm Route a m
fb) = do
ByteString
cwd' <- m ByteString
-> (ByteString -> m ByteString) -> Maybe ByteString -> m ByteString
forall b a. b -> (a -> b) -> Maybe a -> b
maybe m ByteString
forall (m :: * -> *) a. MonadSnap m => m a
pass ByteString -> m ByteString
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe ByteString -> m ByteString)
-> Maybe ByteString -> m ByteString
forall a b. (a -> b) -> a -> b
$ ByteString -> Maybe ByteString
urlDecode ByteString
cwd
case ByteString -> HashMap ByteString (Route a m) -> Maybe (Route a m)
forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
H.lookup ByteString
cwd' HashMap ByteString (Route a m)
rtm of
Just Route a m
rt -> (m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre (ByteString
cwdByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[ByteString]
ctx) [ByteString]
rest Params
params Route a m
rt) m a -> m a -> m a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|>
(m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre [ByteString]
ctx [ByteString]
paths Params
params Route a m
fb)
Maybe (Route a m)
Nothing -> m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
forall (m :: * -> *) a.
MonadSnap m =>
m () -> [ByteString] -> [ByteString] -> Params -> Route a m -> m a
route' m ()
pre [ByteString]
ctx [ByteString]
paths Params
params Route a m
fb
route' m ()
_ [ByteString]
_ [ByteString]
_ Params
_ Route a m
NoRoute = m a
forall (m :: * -> *) a. MonadSnap m => m a
pass