Skip to content

Commit 740bca7

Browse files
authored
Merge pull request #1516 from guygastineau/captureall
CaptureAll
2 parents 644f3ad + 55ee345 commit 740bca7

File tree

3 files changed

+60
-15
lines changed

3 files changed

+60
-15
lines changed

changelog.d/CaptureAll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
synopsis: Bugfix - CaptureAll produces [""] for empty paths due to trailing slash.
2+
prs: #1516
3+
issues: #1243
4+
5+
description: {
6+
7+
CaptureAll resulted in `[""]` for empty paths due to trailing slash. Similar
8+
oddities occurred around these edge cases like `"/"` resulted in `[]` correctly,
9+
but `"//"` resulted in `["", ""]`. This patch simply eliminates the first `""`
10+
in the pathinfo list as taken from the wai response. This might break user
11+
code that relies on personal shims to solve the problem, however simply removing their
12+
workarounds should fix their code as the behavior is now sane.
13+
14+
}

servant-server/src/Servant/Server/Internal/Router.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,10 @@ runRouterEnv fmt router env request respond =
213213
-> let request' = request { pathInfo = rest }
214214
in runRouterEnv fmt router' (first, env) request' respond
215215
CaptureAllRouter _ router' ->
216-
let segments = pathInfo request
216+
let segments = case pathInfo request of
217+
-- this case is to handle trailing slashes.
218+
("":xs) -> xs
219+
xs -> xs
217220
request' = request { pathInfo = [] }
218221
in runRouterEnv fmt router' (segments, env) request' respond
219222
RawRouter app ->

servant-server/test/Servant/ServerSpec.hs

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -282,45 +282,73 @@ captureSpec = do
282282
-- * captureAllSpec {{{
283283
------------------------------------------------------------------------------
284284

285-
type CaptureAllApi = CaptureAll "legs" Integer :> Get '[JSON] Animal
285+
type CaptureAllApi = "legs" :> CaptureAll "legs" Integer :> Get '[JSON] Animal
286+
:<|> "arms" :> CaptureAll "arms" String :> Get '[JSON] [String]
286287
captureAllApi :: Proxy CaptureAllApi
287288
captureAllApi = Proxy
288-
captureAllServer :: [Integer] -> Handler Animal
289-
captureAllServer legs = case sum legs of
290-
4 -> return jerry
291-
2 -> return tweety
292-
0 -> return beholder
293-
_ -> throwError err404
289+
captureAllServer :: Server CaptureAllApi
290+
captureAllServer = handleLegs :<|> return
291+
where
292+
handleLegs [] = return beholder
293+
handleLegs legs = case sum legs of
294+
4 -> return jerry
295+
2 -> return tweety
296+
_ -> throwError err404
297+
298+
type RootedCaptureAllApi = CaptureAll "xs" String :> Get '[JSON] [String]
294299

295300
captureAllSpec :: Spec
296301
captureAllSpec = do
302+
let getStringList = decode' @[String] . simpleBody
303+
297304
describe "Servant.API.CaptureAll" $ do
298305
with (return (serve captureAllApi captureAllServer)) $ do
299306

300307
it "can capture a single element of the 'pathInfo'" $ do
301-
response <- get "/2"
308+
response <- get "/legs/2"
302309
liftIO $ decode' (simpleBody response) `shouldBe` Just tweety
303310

304311
it "can capture multiple elements of the 'pathInfo'" $ do
305-
response <- get "/2/2"
312+
response <- get "/legs/2/2"
306313
liftIO $ decode' (simpleBody response) `shouldBe` Just jerry
307314

308315
it "can capture arbitrarily many elements of the 'pathInfo'" $ do
309-
response <- get "/1/1/0/1/0/1"
316+
response <- get "/legs/1/1/0/1/0/1"
310317
liftIO $ decode' (simpleBody response) `shouldBe` Just jerry
311318

312319
it "can capture when there are no elements in 'pathInfo'" $ do
313-
response <- get "/"
320+
response <- get "/legs/"
314321
liftIO $ decode' (simpleBody response) `shouldBe` Just beholder
315322

316323
it "returns 400 if the decoding fails" $ do
317-
get "/notAnInt" `shouldRespondWith` 400
324+
get "/legs/notAnInt" `shouldRespondWith` 400
318325

319326
it "returns 400 if the decoding fails, regardless of which element" $ do
320-
get "/1/0/0/notAnInt/3/" `shouldRespondWith` 400
327+
get "/legs/1/0/0/notAnInt/3/" `shouldRespondWith` 400
321328

322329
it "returns 400 if the decoding fails, even when it's multiple elements" $ do
323-
get "/1/0/0/notAnInt/3/orange/" `shouldRespondWith` 400
330+
get "/legs/1/0/0/notAnInt/3/orange/" `shouldRespondWith` 400
331+
332+
it "can capture single String" $ do
333+
response <- get "/arms/jerry"
334+
liftIO $ getStringList response `shouldBe` Just ["jerry"]
335+
336+
it "can capture when there are no elements in 'pathinfo'" $ do
337+
response <- get "/arms/"
338+
liftIO $ getStringList response `shouldBe` Just []
339+
340+
it "can capture empty string from captureall" $ do
341+
response <- get "/arms//"
342+
liftIO $ getStringList response `shouldBe` Just [""]
343+
344+
with (return (serve (Proxy :: Proxy RootedCaptureAllApi) return)) $ do
345+
it "can capture empty rooted capture all" $ do
346+
response <- get "/"
347+
liftIO $ getStringList response `shouldBe` Just []
348+
349+
it "can capture empty string from rooted capture all" $ do
350+
response <- get "//"
351+
liftIO $ getStringList response `shouldBe` Just [""]
324352

325353
with (return (serve
326354
(Proxy :: Proxy (CaptureAll "segments" String :> Raw))

0 commit comments

Comments
 (0)