-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. module Network.Wai.Route.Tree ( -- * Routing Tree Tree , fromList , lookup , foldTree , mapTree , toList , segments -- ** Tree leaf payload , Payload , value , path , captures -- ** Captures , Captures , captured , captureParams , captureValues ) where import Control.Applicative import Data.ByteString (ByteString) import Data.List (foldl') import Data.HashMap.Strict (HashMap) import Data.Maybe (fromMaybe) import Data.Semigroup import Data.Word import Network.HTTP.Types (urlDecode, urlEncode) import Prelude hiding (lookup) import qualified Data.ByteString as B import qualified Data.HashMap.Strict as M data Tree a = Tree { subtree :: HashMap ByteString (Tree a) , capture :: Maybe (Tree a) , payload :: Maybe (Payload a) } deriving (Eq, Show) data Payload a = Payload { path :: !ByteString , value :: !a , captures :: !Captures } deriving (Eq, Show) data Captures = Captures { params :: [ByteString] , values :: [ByteString] } deriving (Eq, Show) instance Semigroup (Tree a) where a <> b = Tree (subtree a <> subtree b) (capture a <> capture b) (payload a <|> payload b) instance Monoid (Tree a) where mempty = Tree mempty Nothing Nothing mappend = (<>) captureParams :: Captures -> [ByteString] captureParams = params captureValues :: Captures -> [ByteString] captureValues = values captured :: Captures -> [(ByteString, ByteString)] captured (Captures a b) = zip a b fromList :: [(ByteString, a)] -> Tree a fromList = foldl' addRoute mempty where addRoute t (p,a) = go t (segments p) [] where go n [] cs = let pa = Payload p a (Captures cs []) in n { payload = Just pa } go n (c:ps) cs | B.head c == colon = let b = fromMaybe mempty $ capture n in n { capture = Just $! go b ps (B.tail c : cs) } go n (d:ps) cs = let d' = urlEncode False d b = fromMaybe mempty $ M.lookup d' (subtree n) in n { subtree = M.insert d' (go b ps cs) (subtree n) } lookup :: Tree a -> [ByteString] -> Maybe (Payload a) lookup t p = go p [] t where go [] cvs n = let f e = e { captures = Captures (params (captures e)) cvs } in f <$> payload n go (s:ss) cvs n = maybe (capture n >>= go ss (urlDecode False s : cvs)) (go ss cvs) (M.lookup s $ subtree n) foldTree :: (Payload a -> b -> b) -> b -> Tree a -> b foldTree f z (Tree sub cap pay) = let a = M.foldl' (foldTree f) z sub b = maybe a (foldTree f a) cap c = maybe b (flip f b) pay in c mapTree :: (Payload a -> Payload b) -> Tree a -> Tree b mapTree f t = foldTree apply mempty t where apply x tr = tr { payload = Just (f x) } toList :: Tree a -> [Payload a] toList = foldTree (:) [] segments :: ByteString -> [ByteString] segments = filter (not . B.null) . B.split slash slash, colon :: Word8 slash = 0x2F colon = 0x3A {-# INLINE slash #-} {-# INLINE colon #-}