@@ -9,12 +9,16 @@ import Prelude.Compat
9
9
10
10
import Data.Function
11
11
(on )
12
+ import Data.List
13
+ (nub )
12
14
import Data.Map
13
15
(Map )
14
16
import qualified Data.Map as M
15
17
import Data.Text
16
18
(Text )
17
19
import qualified Data.Text as T
20
+ import Data.Typeable
21
+ (TypeRep )
18
22
import Network.Wai
19
23
(Response , pathInfo )
20
24
import Servant.Server.Internal.ErrorFormatter
@@ -24,6 +28,12 @@ import Servant.Server.Internal.ServerError
24
28
25
29
type Router env = Router' env RoutingApplication
26
30
31
+ data CaptureHint = CaptureHint
32
+ { captureName :: Text
33
+ , capturedType :: TypeRep
34
+ }
35
+ deriving (Show , Eq )
36
+
27
37
-- | Internal representation of a router.
28
38
--
29
39
-- The first argument describes an environment type that is
@@ -36,10 +46,10 @@ data Router' env a =
36
46
-- ^ the map contains routers for subpaths (first path component used
37
47
-- for lookup and removed afterwards), the list contains handlers
38
48
-- for the empty path, to be tried in order
39
- | CaptureRouter (Router' (Text , env ) a )
49
+ | CaptureRouter [ CaptureHint ] (Router' (Text , env ) a )
40
50
-- ^ first path component is passed to the child router in its
41
51
-- environment and removed afterwards
42
- | CaptureAllRouter (Router' ([Text ], env ) a )
52
+ | CaptureAllRouter [ CaptureHint ] (Router' ([Text ], env ) a )
43
53
-- ^ all path components are passed to the child router in its
44
54
-- environment and are removed afterwards
45
55
| RawRouter (env -> a )
@@ -69,8 +79,8 @@ leafRouter l = StaticRouter M.empty [l]
69
79
choice :: Router' env a -> Router' env a -> Router' env a
70
80
choice (StaticRouter table1 ls1) (StaticRouter table2 ls2) =
71
81
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)
74
84
choice router1 (Choice router2 router3) = Choice (choice router1 router2) router3
75
85
choice router1 router2 = Choice router1 router2
76
86
@@ -84,7 +94,7 @@ choice router1 router2 = Choice router1 router2
84
94
--
85
95
data RouterStructure =
86
96
StaticRouterStructure (Map Text RouterStructure ) Int
87
- | CaptureRouterStructure RouterStructure
97
+ | CaptureRouterStructure [ CaptureHint ] RouterStructure
88
98
| RawRouterStructure
89
99
| ChoiceStructure RouterStructure RouterStructure
90
100
deriving (Eq , Show )
@@ -98,11 +108,11 @@ data RouterStructure =
98
108
routerStructure :: Router' env a -> RouterStructure
99
109
routerStructure (StaticRouter m ls) =
100
110
StaticRouterStructure (fmap routerStructure m) (length ls)
101
- routerStructure (CaptureRouter router) =
102
- CaptureRouterStructure $
111
+ routerStructure (CaptureRouter hints router) =
112
+ CaptureRouterStructure hints $
103
113
routerStructure router
104
- routerStructure (CaptureAllRouter router) =
105
- CaptureRouterStructure $
114
+ routerStructure (CaptureAllRouter hints router) =
115
+ CaptureRouterStructure hints $
106
116
routerStructure router
107
117
routerStructure (RawRouter _) =
108
118
RawRouterStructure
@@ -111,11 +121,21 @@ routerStructure (Choice r1 r2) =
111
121
(routerStructure r1)
112
122
(routerStructure r2)
113
123
114
- -- | Compare the structure of two routers.
124
+ -- | Compare the structure of two routers. Ignores capture hints.
115
125
--
116
126
sameStructure :: 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
119
139
120
140
-- | Provide a textual representation of the
121
141
-- structure of a router.
@@ -126,7 +146,8 @@ routerLayout router =
126
146
where
127
147
mkRouterLayout :: Bool -> RouterStructure -> [Text ]
128
148
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)
130
151
mkRouterLayout c RawRouterStructure =
131
152
if c then [" ├─ <raw>" ] else [" └─ <raw>" ]
132
153
mkRouterLayout c (ChoiceStructure r1 r2) =
@@ -149,6 +170,12 @@ routerLayout router =
149
170
mkSubTree True path children = (" ├─ " <> path <> " /" ) : map (" │ " <> ) children
150
171
mkSubTree False path children = (" └─ " <> path <> " /" ) : map (" " <> ) children
151
172
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
+
152
179
-- | Apply a transformation to the response of a `Router`.
153
180
tweakResponse :: (RouteResult Response -> RouteResult Response ) -> Router env -> Router env
154
181
tweakResponse f = fmap (\ a -> \ req cont -> a req (cont . f))
@@ -169,15 +196,15 @@ runRouterEnv fmt router env request respond =
169
196
-> let request' = request { pathInfo = rest }
170
197
in runRouterEnv fmt router' env request' respond
171
198
_ -> respond $ Fail $ fmt request
172
- CaptureRouter router' ->
199
+ CaptureRouter _ router' ->
173
200
case pathInfo request of
174
201
[] -> respond $ Fail $ fmt request
175
202
-- This case is to handle trailing slashes.
176
203
[" " ] -> respond $ Fail $ fmt request
177
204
first : rest
178
205
-> let request' = request { pathInfo = rest }
179
206
in runRouterEnv fmt router' (first, env) request' respond
180
- CaptureAllRouter router' ->
207
+ CaptureAllRouter _ router' ->
181
208
let segments = pathInfo request
182
209
request' = request { pathInfo = [] }
183
210
in runRouterEnv fmt router' (segments, env) request' respond
0 commit comments