{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
module Servant.Server.Internal.Router where
import Prelude ()
import Prelude.Compat
import Data.Function
(on)
import Data.Map
(Map)
import qualified Data.Map as M
import Data.Text
(Text)
import qualified Data.Text as T
import Network.Wai
(Response, pathInfo)
import Servant.Server.Internal.RoutingApplication
import Servant.Server.Internal.RouteResult
import Servant.Server.Internal.ServerError
type Router env = Router' env RoutingApplication
data Router' env a =
StaticRouter (Map Text (Router' env a)) [env -> a]
| CaptureRouter (Router' (Text, env) a)
| CaptureAllRouter (Router' ([Text], env) a)
| RawRouter (env -> a)
| Choice (Router' env a) (Router' env a)
deriving Functor
pathRouter :: Text -> Router' env a -> Router' env a
pathRouter t r = StaticRouter (M.singleton t r) []
leafRouter :: (env -> a) -> Router' env a
leafRouter l = StaticRouter M.empty [l]
choice :: Router' env a -> Router' env a -> Router' env a
choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
StaticRouter (M.unionWith choice table1 table2) (ls1 ++ ls2)
choice (CaptureRouter router1) (CaptureRouter router2) =
CaptureRouter (choice router1 router2)
choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
choice router1 router2 = Choice router1 router2
data RouterStructure =
StaticRouterStructure (Map Text RouterStructure) Int
| CaptureRouterStructure RouterStructure
| RawRouterStructure
| ChoiceStructure RouterStructure RouterStructure
deriving (Eq, Show)
routerStructure :: Router' env a -> RouterStructure
routerStructure (StaticRouter m ls) =
StaticRouterStructure (fmap routerStructure m) (length ls)
routerStructure (CaptureRouter router) =
CaptureRouterStructure $
routerStructure router
routerStructure (CaptureAllRouter router) =
CaptureRouterStructure $
routerStructure router
routerStructure (RawRouter _) =
RawRouterStructure
routerStructure (Choice r1 r2) =
ChoiceStructure
(routerStructure r1)
(routerStructure r2)
sameStructure :: Router' env a -> Router' env b -> Bool
sameStructure r1 r2 =
routerStructure r1 == routerStructure r2
routerLayout :: Router' env a -> Text
routerLayout router =
T.unlines (["/"] ++ mkRouterLayout False (routerStructure router))
where
mkRouterLayout :: Bool -> RouterStructure -> [Text]
mkRouterLayout c (StaticRouterStructure m n) = mkSubTrees c (M.toList m) n
mkRouterLayout c (CaptureRouterStructure r) = mkSubTree c "<capture>" (mkRouterLayout False r)
mkRouterLayout c RawRouterStructure =
if c then ["├─ <raw>"] else ["└─ <raw>"]
mkRouterLayout c (ChoiceStructure r1 r2) =
mkRouterLayout True r1 ++ ["┆"] ++ mkRouterLayout c r2
mkSubTrees :: Bool -> [(Text, RouterStructure)] -> Int -> [Text]
mkSubTrees _ [] 0 = []
mkSubTrees c [] n =
concat (replicate (n - 1) (mkLeaf True) ++ [mkLeaf c])
mkSubTrees c [(t, r)] 0 =
mkSubTree c t (mkRouterLayout False r)
mkSubTrees c ((t, r) : trs) n =
mkSubTree True t (mkRouterLayout False r) ++ mkSubTrees c trs n
mkLeaf :: Bool -> [Text]
mkLeaf True = ["├─•","┆"]
mkLeaf False = ["└─•"]
mkSubTree :: Bool -> Text -> [Text] -> [Text]
mkSubTree True path children = ("├─ " <> path <> "/") : map ("│ " <>) children
mkSubTree False path children = ("└─ " <> path <> "/") : map (" " <>) children
tweakResponse :: (RouteResult Response -> RouteResult Response) -> Router env -> Router env
tweakResponse f = fmap (\a -> \req cont -> a req (cont . f))
runRouter :: Router () -> RoutingApplication
runRouter r = runRouterEnv r ()
runRouterEnv :: Router env -> env -> RoutingApplication
runRouterEnv router env request respond =
case router of
StaticRouter table ls ->
case pathInfo request of
[] -> runChoice ls env request respond
[""] -> runChoice ls env request respond
first : rest | Just router' <- M.lookup first table
-> let request' = request { pathInfo = rest }
in runRouterEnv router' env request' respond
_ -> respond $ Fail err404
CaptureRouter router' ->
case pathInfo request of
[] -> respond $ Fail err404
[""] -> respond $ Fail err404
first : rest
-> let request' = request { pathInfo = rest }
in runRouterEnv router' (first, env) request' respond
CaptureAllRouter router' ->
let segments = pathInfo request
request' = request { pathInfo = [] }
in runRouterEnv router' (segments, env) request' respond
RawRouter app ->
app env request respond
Choice r1 r2 ->
runChoice [runRouterEnv r1, runRouterEnv r2] env request respond
runChoice :: [env -> RoutingApplication] -> env -> RoutingApplication
runChoice ls =
case ls of
[] -> \ _ _ respond -> respond (Fail err404)
[r] -> r
(r : rs) ->
\ env request respond ->
r env request $ \ response1 ->
case response1 of
Fail _ -> runChoice rs env request $ \ response2 ->
respond $ highestPri response1 response2
_ -> respond response1
where
highestPri (Fail e1) (Fail e2) =
if worseHTTPCode (errHTTPCode e1) (errHTTPCode e2)
then Fail e2
else Fail e1
highestPri (Fail _) y = y
highestPri x _ = x
worseHTTPCode :: Int -> Int -> Bool
worseHTTPCode = on (<) toPriority
where
toPriority :: Int -> Int
toPriority 404 = 0
toPriority 405 = 1
toPriority 401 = 2
toPriority 415 = 3
toPriority 406 = 4
toPriority 400 = 6
toPriority _ = 5