@@ -9,12 +9,16 @@ import Prelude.Compat
99
1010import Data.Function
1111 (on )
12+ import Data.List
13+ (nub )
1214import Data.Map
1315 (Map )
1416import qualified Data.Map as M
1517import Data.Text
1618 (Text )
1719import qualified Data.Text as T
20+ import Data.Typeable
21+ (TypeRep )
1822import Network.Wai
1923 (Response , pathInfo )
2024import Servant.Server.Internal.ErrorFormatter
@@ -24,6 +28,12 @@ import Servant.Server.Internal.ServerError
2428
2529type Router env = Router' env RoutingApplication
2630
31+ data CaptureHint = CaptureHint
32+ { captureName :: Text
33+ , capturedType :: TypeRep
34+ }
35+ deriving (Show , Eq )
36+
2737-- | Internal representation of a router.
2838--
2939-- The first argument describes an environment type that is
@@ -36,10 +46,10 @@ data Router' env a =
3646 -- ^ the map contains routers for subpaths (first path component used
3747 -- for lookup and removed afterwards), the list contains handlers
3848 -- for the empty path, to be tried in order
39- | CaptureRouter (Router' (Text , env ) a )
49+ | CaptureRouter [ CaptureHint ] (Router' (Text , env ) a )
4050 -- ^ first path component is passed to the child router in its
4151 -- environment and removed afterwards
42- | CaptureAllRouter (Router' ([Text ], env ) a )
52+ | CaptureAllRouter [ CaptureHint ] (Router' ([Text ], env ) a )
4353 -- ^ all path components are passed to the child router in its
4454 -- environment and are removed afterwards
4555 | RawRouter (env -> a )
@@ -69,8 +79,8 @@ leafRouter l = StaticRouter M.empty [l]
6979choice :: Router' env a -> Router' env a -> Router' env a
7080choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
7181 StaticRouter (M. unionWith choice table1 table2) (ls1 ++ ls2)
72- choice (CaptureRouter router1) (CaptureRouter router2) =
73- CaptureRouter (choice router1 router2)
82+ choice (CaptureRouter hints1 router1) (CaptureRouter hints2 router2) =
83+ CaptureRouter (nub $ hints1 ++ hints2) ( choice router1 router2)
7484choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
7585choice router1 router2 = Choice router1 router2
7686
@@ -84,7 +94,7 @@ choice router1 router2 = Choice router1 router2
8494--
8595data RouterStructure =
8696 StaticRouterStructure (Map Text RouterStructure ) Int
87- | CaptureRouterStructure RouterStructure
97+ | CaptureRouterStructure [ CaptureHint ] RouterStructure
8898 | RawRouterStructure
8999 | ChoiceStructure RouterStructure RouterStructure
90100 deriving (Eq , Show )
@@ -98,11 +108,11 @@ data RouterStructure =
98108routerStructure :: Router' env a -> RouterStructure
99109routerStructure (StaticRouter m ls) =
100110 StaticRouterStructure (fmap routerStructure m) (length ls)
101- routerStructure (CaptureRouter router) =
102- CaptureRouterStructure $
111+ routerStructure (CaptureRouter hints router) =
112+ CaptureRouterStructure hints $
103113 routerStructure router
104- routerStructure (CaptureAllRouter router) =
105- CaptureRouterStructure $
114+ routerStructure (CaptureAllRouter hints router) =
115+ CaptureRouterStructure hints $
106116 routerStructure router
107117routerStructure (RawRouter _) =
108118 RawRouterStructure
@@ -111,11 +121,21 @@ routerStructure (Choice r1 r2) =
111121 (routerStructure r1)
112122 (routerStructure r2)
113123
114- -- | Compare the structure of two routers.
124+ -- | Compare the structure of two routers. Ignores capture hints.
115125--
116126sameStructure :: Router' env a -> Router' env b -> Bool
117- sameStructure r1 r2 =
118- routerStructure r1 == routerStructure r2
127+ sameStructure router1 router2 =
128+ routerStructure router1 `almostEq` routerStructure router2
129+ where
130+ almostEq :: RouterStructure -> RouterStructure -> Bool
131+ almostEq (StaticRouterStructure m1 l1) (StaticRouterStructure m2 l2) =
132+ l1 == l2 && M. isSubmapOfBy almostEq m1 m2 && M. isSubmapOfBy almostEq m2 m1
133+ almostEq (CaptureRouterStructure _ r1) (CaptureRouterStructure _ r2) =
134+ r1 `almostEq` r2
135+ almostEq RawRouterStructure RawRouterStructure = True
136+ almostEq (ChoiceStructure r1 r1') (ChoiceStructure r2 r2') =
137+ r1 `almostEq` r2 && r1' `almostEq` r2'
138+ almostEq _ _ = False
119139
120140-- | Provide a textual representation of the
121141-- structure of a router.
@@ -126,7 +146,8 @@ routerLayout router =
126146 where
127147 mkRouterLayout :: Bool -> RouterStructure -> [Text ]
128148 mkRouterLayout c (StaticRouterStructure m n) = mkSubTrees c (M. toList m) n
129- mkRouterLayout c (CaptureRouterStructure r) = mkSubTree c " <capture>" (mkRouterLayout False r)
149+ mkRouterLayout c (CaptureRouterStructure hints r) =
150+ mkSubTree c (toCaptureTags hints) (mkRouterLayout False r)
130151 mkRouterLayout c RawRouterStructure =
131152 if c then [" ├─ <raw>" ] else [" └─ <raw>" ]
132153 mkRouterLayout c (ChoiceStructure r1 r2) =
@@ -149,6 +170,12 @@ routerLayout router =
149170 mkSubTree True path children = (" ├─ " <> path <> " /" ) : map (" │ " <> ) children
150171 mkSubTree False path children = (" └─ " <> path <> " /" ) : map (" " <> ) children
151172
173+ toCaptureTag :: CaptureHint -> Text
174+ toCaptureTag hint = captureName hint <> " ::" <> (T. pack . show ) (capturedType hint)
175+
176+ toCaptureTags :: [CaptureHint ] -> Text
177+ toCaptureTags hints = " <capture " <> T. intercalate " |" (map toCaptureTag hints) <> " >"
178+
152179-- | Apply a transformation to the response of a `Router`.
153180tweakResponse :: (RouteResult Response -> RouteResult Response ) -> Router env -> Router env
154181tweakResponse f = fmap (\ a -> \ req cont -> a req (cont . f))
@@ -169,15 +196,15 @@ runRouterEnv fmt router env request respond =
169196 -> let request' = request { pathInfo = rest }
170197 in runRouterEnv fmt router' env request' respond
171198 _ -> respond $ Fail $ fmt request
172- CaptureRouter router' ->
199+ CaptureRouter _ router' ->
173200 case pathInfo request of
174201 [] -> respond $ Fail $ fmt request
175202 -- This case is to handle trailing slashes.
176203 [" " ] -> respond $ Fail $ fmt request
177204 first : rest
178205 -> let request' = request { pathInfo = rest }
179206 in runRouterEnv fmt router' (first, env) request' respond
180- CaptureAllRouter router' ->
207+ CaptureAllRouter _ router' ->
181208 let segments = pathInfo request
182209 request' = request { pathInfo = [] }
183210 in runRouterEnv fmt router' (segments, env) request' respond
0 commit comments