on master help sign in
Add CaptureAll to the API definitions
a616a8dJonathan Lange
servant/src/Servant/API.hs+2−2
@@ -8,7 +8,7 @@ module Servant.API (
8
8
9 -- * Accessing information from the request
9 -- * Accessing information from the request
10 module Servant.API.Capture,
10 module Servant.API.Capture,
11− -- | Capturing parts of the url path as parsed values: @'Capture'@
11+ -- | Capturing parts of the url path as parsed values: @'Capture'@ and @'CaptureAll'@
12 module Servant.API.Header,
12 module Servant.API.Header,
13 -- | Retrieving specific headers from the request
13 -- | Retrieving specific headers from the request
14 module Servant.API.HttpVersion,
14 module Servant.API.HttpVersion,
@@ -60,7 +60,7 @@ module Servant.API (
60
60
61import Servant.API.Alternative ((:<|>) (..))
61import Servant.API.Alternative ((:<|>) (..))
62import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..))
62import Servant.API.BasicAuth (BasicAuth,BasicAuthData(..))
63−import Servant.API.Capture (Capture)
63+import Servant.API.Capture (Capture, CaptureAll)
64import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
64import Servant.API.ContentTypes (Accept (..), FormUrlEncoded,
65 FromFormUrlEncoded (..), JSON,
65 FromFormUrlEncoded (..), JSON,
66 MimeRender (..), NoContent (NoContent),
66 MimeRender (..), NoContent (NoContent),
servant/src/Servant/API/Capture.hs+14−1
@@ -2,7 +2,7 @@
2{-# LANGUAGE DeriveDataTypeable #-}
2{-# LANGUAGE DeriveDataTypeable #-}
3{-# LANGUAGE PolyKinds #-}
3{-# LANGUAGE PolyKinds #-}
4{-# OPTIONS_HADDOCK not-home #-}
4{-# OPTIONS_HADDOCK not-home #-}
5−module Servant.API.Capture (Capture) where
5+module Servant.API.Capture (Capture, CaptureAll) where
6
6
7import Data.Typeable (Typeable)
7import Data.Typeable (Typeable)
8import GHC.TypeLits (Symbol)
8import GHC.TypeLits (Symbol)
@@ -15,9 +15,22 @@ import GHC.TypeLits (Symbol)
15data Capture (sym :: Symbol) a
15data Capture (sym :: Symbol) a
16 deriving (Typeable)
16 deriving (Typeable)
17
17
18+
19+-- | Capture all remaining values from the request path under a certain type
20+-- @a@.
21+--
22+-- Example:
23+--
24+-- >>> -- GET /src/*
25+-- >>> type MyAPI = "src" :> CaptureAll "segments" Text :> Get '[JSON] SourceFile
26+data CaptureAll (sym :: Symbol) a
27+ deriving (Typeable)
28+
18-- $setup
29-- $setup
19-- >>> import Servant.API
30-- >>> import Servant.API
20-- >>> import Data.Aeson
31-- >>> import Data.Aeson
21-- >>> import Data.Text
32-- >>> import Data.Text
22-- >>> data Book
33-- >>> data Book
23-- >>> instance ToJSON Book where { toJSON = undefined }
34-- >>> instance ToJSON Book where { toJSON = undefined }
35+-- >>> data SourceFile
36+-- >>> instance ToJSON SourceFile where { toJSON = undefined }
servant/src/Servant/Utils/Links.hs+10−1
@@ -107,7 +107,7 @@ import Prelude.Compat
107
107
108import Web.HttpApiData
108import Web.HttpApiData
109import Servant.API.BasicAuth ( BasicAuth )
109import Servant.API.BasicAuth ( BasicAuth )
110−import Servant.API.Capture ( Capture )
110+import Servant.API.Capture ( Capture, CaptureAll )
111import Servant.API.ReqBody ( ReqBody )
111import Servant.API.ReqBody ( ReqBody )
112import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
112import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
113import Servant.API.Header ( Header )
113import Servant.API.Header ( Header )
@@ -163,6 +163,8 @@ type family IsElem endpoint api :: Constraint where
163 IsElem sa (ReqBody y x :> sb) = IsElem sa sb
163 IsElem sa (ReqBody y x :> sb) = IsElem sa sb
164 IsElem (Capture z y :> sa) (Capture x y :> sb)
164 IsElem (Capture z y :> sa) (Capture x y :> sb)
165 = IsElem sa sb
165 = IsElem sa sb
166+ IsElem (CaptureAll z y :> sa) (CaptureAll x y :> sb)
167+ = IsElem sa sb
166 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
168 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
167 IsElem sa (QueryParams x y :> sb) = IsElem sa sb
169 IsElem sa (QueryParams x y :> sb) = IsElem sa sb
168 IsElem sa (QueryFlag x :> sb) = IsElem sa sb
170 IsElem sa (QueryFlag x :> sb) = IsElem sa sb
@@ -284,6 +286,13 @@ instance (ToHttpApiData v, HasLink sub)
284 toLink (Proxy :: Proxy sub) $
286 toLink (Proxy :: Proxy sub) $
285 addSegment (escape . Text.unpack $ toUrlPiece v) l
287 addSegment (escape . Text.unpack $ toUrlPiece v) l
286
288
289+instance (ToHttpApiData v, HasLink sub)
290+ => HasLink (CaptureAll sym v :> sub) where
291+ type MkLink (CaptureAll sym v :> sub) = [v] -> MkLink sub
292+ toLink _ l vs =
293+ toLink (Proxy :: Proxy sub) $
294+ foldl' (flip $ addSegment . escape . Text.unpack . toUrlPiece) l vs
295+
287instance HasLink sub => HasLink (Header sym a :> sub) where
296instance HasLink sub => HasLink (Header sym a :> sub) where
288 type MkLink (Header sym a :> sub) = MkLink sub
297 type MkLink (Header sym a :> sub) = MkLink sub
289 toLink _ = toLink (Proxy :: Proxy sub)
298 toLink _ = toLink (Proxy :: Proxy sub)
servant/test/Servant/Utils/LinksSpec.hs+5
@@ -13,6 +13,7 @@ import Servant.API
13type TestApi =
13type TestApi =
14 -- Capture and query params
14 -- Capture and query params
15 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Delete '[JSON] NoContent
15 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Delete '[JSON] NoContent
16+ :<|> "all" :> CaptureAll "names" String :> Get '[JSON] NoContent
16
17
17 -- Flags
18 -- Flags
18 :<|> "balls" :> QueryFlag "bouncy" :> QueryFlag "fast" :> Delete '[JSON] NoContent
19 :<|> "balls" :> QueryFlag "bouncy" :> QueryFlag "fast" :> Delete '[JSON] NoContent
@@ -46,6 +47,10 @@ spec = describe "Servant.Utils.Links" $ do
46 :> Delete '[JSON] NoContent)
47 :> Delete '[JSON] NoContent)
47 apiLink l2 "bye" (Just True) `shouldBeURI` "hello/bye?capital=true"
48 apiLink l2 "bye" (Just True) `shouldBeURI` "hello/bye?capital=true"
48
49
50+ it "generates correct links for CaptureAll" $ do
51+ apiLink (Proxy :: Proxy ("all" :> CaptureAll "names" String :> Get '[JSON] NoContent))
52+ ["roads", "lead", "to", "rome"]
53+ `shouldBeURI` "all/roads/lead/to/rome"
49
54
50 it "generates correct links for query flags" $ do
55 it "generates correct links for query flags" $ do
51 let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"
56 let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"