git.iko.soy/typeable/servant
on master help sign in
Merge pull request #252 from haskell-servant/jkarni/removeMatrix
c970b2bJulian Arni
servant-client/CHANGELOG.md+1
@@ -7,6 +7,7 @@ HEAD
7* `client` now takes an explicit `Manager` argument.
7* `client` now takes an explicit `Manager` argument.
8* Use `http-api-data` instead of `Servant.Common.Text`
8* Use `http-api-data` instead of `Servant.Common.Text`
9* Client functions now consider any 2xx succesful.
9* Client functions now consider any 2xx succesful.
10+* Remove matrix params.
10
11
110.4.1
120.4.1
12-----
13-----
servant-client/src/Servant/Client.hs−128
@@ -477,134 +477,6 @@ instance (KnownSymbol sym, HasClient sublayout)
477
477
478 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
478 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
479
479
480−-- | If you use a 'MatrixParam' in one of your endpoints in your API,
481−-- the corresponding querying function will automatically take
482−-- an additional argument of the type specified by your 'MatrixParam',
483−-- enclosed in Maybe.
484−--
485−-- If you give Nothing, nothing will be added to the query string.
486−--
487−-- If you give a non-'Nothing' value, this function will take care
488−-- of inserting a textual representation of this value in the query string.
489−--
490−-- You can control how values for your type are turned into
491−-- text by specifying a 'ToHttpApiData' instance for your type.
492−--
493−-- Example:
494−--
495−-- > type MyApi = "books" :> MatrixParam "author" Text :> Get '[JSON] [Book]
496−-- >
497−-- > myApi :: Proxy MyApi
498−-- > myApi = Proxy
499−-- >
500−-- > getBooksBy :: Maybe Text -> ExceptT String IO [Book]
501−-- > getBooksBy = client myApi host
502−-- > where host = BaseUrl Http "localhost" 8080
503−-- > -- then you can just use "getBooksBy" to query that endpoint.
504−-- > -- 'getBooksBy Nothing' for all books
505−-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
506−instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
507− => HasClient (MatrixParam sym a :> sublayout) where
508−
509− type Client (MatrixParam sym a :> sublayout) =
510− Maybe a -> Client sublayout
511−
512− -- if mparam = Nothing, we don't add it to the query string
513− clientWithRoute Proxy req baseurl manager mparam =
514− clientWithRoute (Proxy :: Proxy sublayout)
515− (maybe req
516− (flip (appendToMatrixParams pname . Just) req)
517− mparamText
518− )
519− baseurl manager
520−
521− where pname = symbolVal (Proxy :: Proxy sym)
522− mparamText = fmap (cs . toQueryParam) mparam
523−
524−-- | If you use a 'MatrixParams' in one of your endpoints in your API,
525−-- the corresponding querying function will automatically take an
526−-- additional argument, a list of values of the type specified by your
527−-- 'MatrixParams'.
528−--
529−-- If you give an empty list, nothing will be added to the query string.
530−--
531−-- Otherwise, this function will take care of inserting a textual
532−-- representation of your values in the path segment string, under the
533−-- same matrix string parameter name.
534−--
535−-- You can control how values for your type are turned into text by
536−-- specifying a 'ToHttpApiData' instance for your type.
537−--
538−-- Example:
539−--
540−-- > type MyApi = "books" :> MatrixParams "authors" Text :> Get '[JSON] [Book]
541−-- >
542−-- > myApi :: Proxy MyApi
543−-- > myApi = Proxy
544−-- >
545−-- > getBooksBy :: [Text] -> ExceptT String IO [Book]
546−-- > getBooksBy = client myApi host
547−-- > where host = BaseUrl Http "localhost" 8080
548−-- > -- then you can just use "getBooksBy" to query that endpoint.
549−-- > -- 'getBooksBy []' for all books
550−-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
551−-- > -- to get all books by Asimov and Heinlein
552−instance (KnownSymbol sym, ToHttpApiData a, HasClient sublayout)
553− => HasClient (MatrixParams sym a :> sublayout) where
554−
555− type Client (MatrixParams sym a :> sublayout) =
556− [a] -> Client sublayout
557−
558− clientWithRoute Proxy req baseurl manager paramlist =
559− clientWithRoute (Proxy :: Proxy sublayout)
560− (foldl' (\ req' value -> maybe req' (flip (appendToMatrixParams pname) req' . Just . cs) value)
561− req
562− paramlist'
563− )
564− baseurl manager
565−
566− where pname = cs pname'
567− pname' = symbolVal (Proxy :: Proxy sym)
568− paramlist' = map (Just . toQueryParam) paramlist
569−
570−-- | If you use a 'MatrixFlag' in one of your endpoints in your API,
571−-- the corresponding querying function will automatically take an
572−-- additional 'Bool' argument.
573−--
574−-- If you give 'False', nothing will be added to the path segment.
575−--
576−-- Otherwise, this function will insert a value-less matrix parameter
577−-- under the name associated to your 'MatrixFlag'.
578−--
579−-- Example:
580−--
581−-- > type MyApi = "books" :> MatrixFlag "published" :> Get '[JSON] [Book]
582−-- >
583−-- >
584−-- > myApi :: Proxy MyApi
585−-- > myApi = Proxy
586−-- >
587−-- > getBooks :: Bool -> ExceptT String IO [Book]
588−-- > getBooks = client myApi host
589−-- > where host = BaseUrl Http "localhost" 8080
590−-- > -- then you can just use "getBooks" to query that endpoint.
591−-- > -- 'getBooksBy False' for all books
592−-- > -- 'getBooksBy True' to only get _already published_ books
593−instance (KnownSymbol sym, HasClient sublayout)
594− => HasClient (MatrixFlag sym :> sublayout) where
595−
596− type Client (MatrixFlag sym :> sublayout) =
597− Bool -> Client sublayout
598−
599− clientWithRoute Proxy req baseurl manager flag =
600− clientWithRoute (Proxy :: Proxy sublayout)
601− (if flag
602− then appendToMatrixParams paramname Nothing req
603− else req
604− )
605− baseurl manager
606−
607− where paramname = cs $ symbolVal (Proxy :: Proxy sym)
608
480
609-- | Pick a 'Method' and specify where the server you want to query is. You get
481-- | Pick a 'Method' and specify where the server you want to query is. You get
610-- back the full `Response`.
482-- back the full `Response`.
servant-client/src/Servant/Common/Req.hs−7
@@ -72,13 +72,6 @@ appendToPath :: String -> Req -> Req
72appendToPath p req =
72appendToPath p req =
73 req { reqPath = reqPath req ++ "/" ++ p }
73 req { reqPath = reqPath req ++ "/" ++ p }
74
74
75−appendToMatrixParams :: String
76− -> Maybe String
77− -> Req
78− -> Req
79−appendToMatrixParams pname pvalue req =
80− req { reqPath = reqPath req ++ ";" ++ pname ++ maybe "" ("=" ++) pvalue }
81−
82appendToQueryString :: Text -- ^ param name
75appendToQueryString :: Text -- ^ param name
83 -> Maybe Text -- ^ param value
76 -> Maybe Text -- ^ param value
84 -> Req
77 -> Req
servant-client/test/Servant/ClientSpec.hs+14−39
@@ -18,6 +18,7 @@
18{-# LANGUAGE UndecidableInstances #-}
18{-# LANGUAGE UndecidableInstances #-}
19{-# OPTIONS_GHC -fcontext-stack=100 #-}
19{-# OPTIONS_GHC -fcontext-stack=100 #-}
20{-# OPTIONS_GHC -fno-warn-orphans #-}
20{-# OPTIONS_GHC -fno-warn-orphans #-}
21+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
21
22
22module Servant.ClientSpec where
23module Servant.ClientSpec where
23
24
@@ -25,22 +26,23 @@ module Servant.ClientSpec where
25import Control.Applicative ((<$>))
26import Control.Applicative ((<$>))
26#endif
27#endif
27import Control.Arrow (left)
28import Control.Arrow (left)
28−import Control.Concurrent
29+import Control.Concurrent (forkIO, killThread, ThreadId)
29−import Control.Exception
30+import Control.Exception (bracket)
30−import Control.Monad.Trans.Except
31+import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)
31import Data.Aeson
32import Data.Aeson
32−import Data.Char
33+import Data.Char (chr, isPrint)
33import Data.Foldable (forM_)
34import Data.Foldable (forM_)
34import Data.Monoid hiding (getLast)
35import Data.Monoid hiding (getLast)
35import Data.Proxy
36import Data.Proxy
36import qualified Data.Text as T
37import qualified Data.Text as T
37−import GHC.Generics
38+import GHC.Generics (Generic)
38import GHC.TypeLits
39import GHC.TypeLits
39import qualified Network.HTTP.Client as C
40import qualified Network.HTTP.Client as C
40import Network.HTTP.Media
41import Network.HTTP.Media
41−import Network.HTTP.Types hiding (Header)
42+import Network.HTTP.Types (Status (..), badRequest400,
43+ methodGet, ok200, status400)
42import Network.Socket
44import Network.Socket
43−import Network.Wai hiding (Response)
45+import Network.Wai (Application, responseLBS)
44import Network.Wai.Handler.Warp
46import Network.Wai.Handler.Warp
45import System.IO.Unsafe (unsafePerformIO)
47import System.IO.Unsafe (unsafePerformIO)
46import Test.Hspec
48import Test.Hspec
@@ -96,9 +98,6 @@ type Api =
96 :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person
98 :<|> "param" :> QueryParam "name" String :> Get '[FormUrlEncoded,JSON] Person
97 :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
99 :<|> "params" :> QueryParams "names" String :> Get '[JSON] [Person]
98 :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
100 :<|> "flag" :> QueryFlag "flag" :> Get '[JSON] Bool
99− :<|> "matrixparam" :> MatrixParam "name" String :> Get '[JSON] Person
100− :<|> "matrixparams" :> MatrixParams "name" String :> Get '[JSON] [Person]
101− :<|> "matrixflag" :> MatrixFlag "flag" :> Get '[JSON] Bool
102 :<|> "rawSuccess" :> Raw
101 :<|> "rawSuccess" :> Raw
103 :<|> "rawFailure" :> Raw
102 :<|> "rawFailure" :> Raw
104 :<|> "multiple" :>
103 :<|> "multiple" :>
@@ -120,13 +119,7 @@ server = serve api (
120 :<|> return
119 :<|> return
121 :<|> (\ name -> case name of
120 :<|> (\ name -> case name of
122 Just "alice" -> return alice
121 Just "alice" -> return alice
123− Just name -> throwE $ ServantErr 400 (name ++ " not found") "" []
124− Nothing -> throwE $ ServantErr 400 "missing parameter" "" [])
125− :<|> (\ names -> return (zipWith Person names [0..]))
126− :<|> return
127− :<|> (\ name -> case name of
128− Just "alice" -> return alice
129− Just name -> throwE $ ServantErr 400 (name ++ " not found") "" []
122+ Just n -> throwE $ ServantErr 400 (n ++ " not found") "" []
130 Nothing -> throwE $ ServantErr 400 "missing parameter" "" [])
123 Nothing -> throwE $ ServantErr 400 "missing parameter" "" [])
131 :<|> (\ names -> return (zipWith Person names [0..]))
124 :<|> (\ names -> return (zipWith Person names [0..]))
132 :<|> return
125 :<|> return
@@ -198,26 +191,8 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
198 let getQueryFlag = getNth (Proxy :: Proxy 6) $ client api baseUrl manager
191 let getQueryFlag = getNth (Proxy :: Proxy 6) $ client api baseUrl manager
199 (left show <$> runExceptT (getQueryFlag flag)) `shouldReturn` Right flag
192 (left show <$> runExceptT (getQueryFlag flag)) `shouldReturn` Right flag
200
193
201− it "Servant.API.MatrixParam" $ \(_, baseUrl) -> do
202− let getMatrixParam = getNth (Proxy :: Proxy 7) $ client api baseUrl manager
203− left show <$> runExceptT (getMatrixParam (Just "alice")) `shouldReturn` Right alice
204− Left FailureResponse{..} <- runExceptT (getMatrixParam (Just "bob"))
205− responseStatus `shouldBe` Status 400 "bob not found"
206−
207− it "Servant.API.MatrixParam.MatrixParams" $ \(_, baseUrl) -> do
208− let getMatrixParams = getNth (Proxy :: Proxy 8) $ client api baseUrl manager
209− left show <$> runExceptT (getMatrixParams []) `shouldReturn` Right []
210− left show <$> runExceptT (getMatrixParams ["alice", "bob"])
211− `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
212−
213− context "Servant.API.MatrixParam.MatrixFlag" $
214− forM_ [False, True] $ \ flag ->
215− it (show flag) $ \(_, baseUrl) -> do
216− let getMatrixFlag = getNth (Proxy :: Proxy 9) $ client api baseUrl manager
217− left show <$> runExceptT (getMatrixFlag flag) `shouldReturn` Right flag
218−
219 it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
194 it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
220− let getRawSuccess = getNth (Proxy :: Proxy 10) $ client api baseUrl manager
195+ let getRawSuccess = getNth (Proxy :: Proxy 7) $ client api baseUrl manager
221 res <- runExceptT (getRawSuccess methodGet)
196 res <- runExceptT (getRawSuccess methodGet)
222 case res of
197 case res of
223 Left e -> assertFailure $ show e
198 Left e -> assertFailure $ show e
@@ -227,7 +202,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
227 C.responseStatus response `shouldBe` ok200
202 C.responseStatus response `shouldBe` ok200
228
203
229 it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do
204 it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do
230− let getRawFailure = getNth (Proxy :: Proxy 11) $ client api baseUrl manager
205+ let getRawFailure = getNth (Proxy :: Proxy 8) $ client api baseUrl manager
231 res <- runExceptT (getRawFailure methodGet)
206 res <- runExceptT (getRawFailure methodGet)
232 case res of
207 case res of
233 Right _ -> assertFailure "expected Left, but got Right"
208 Right _ -> assertFailure "expected Left, but got Right"
@@ -236,7 +211,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
236 Servant.Client.responseBody e `shouldBe` "rawFailure"
211 Servant.Client.responseBody e `shouldBe` "rawFailure"
237
212
238 it "Returns headers appropriately" $ \(_, baseUrl) -> do
213 it "Returns headers appropriately" $ \(_, baseUrl) -> do
239− let getRespHeaders = getNth (Proxy :: Proxy 13) $ client api baseUrl manager
214+ let getRespHeaders = getNth (Proxy :: Proxy 10) $ client api baseUrl manager
240 res <- runExceptT getRespHeaders
215 res <- runExceptT getRespHeaders
241 case res of
216 case res of
242 Left e -> assertFailure $ show e
217 Left e -> assertFailure $ show e
@@ -244,7 +219,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
244
219
245 modifyMaxSuccess (const 20) $ do
220 modifyMaxSuccess (const 20) $ do
246 it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) ->
221 it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) ->
247− let getMultiple = getNth (Proxy :: Proxy 12) $ client api baseUrl manager
222+ let getMultiple = getNth (Proxy :: Proxy 9) $ client api baseUrl manager
248 in property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
223 in property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
249 ioProperty $ do
224 ioProperty $ do
250 result <- left show <$> runExceptT (getMultiple cap num flag body)
225 result <- left show <$> runExceptT (getMultiple cap num flag body)
servant-docs/CHANGELOG.md+1
@@ -8,6 +8,7 @@ HEAD
8* Add more `ToSamples` instances: `Bool`, `Ordering`, tuples (up to 7), `[]`, `Maybe`, `Either`, `Const`, `ZipList` and some monoids
8* Add more `ToSamples` instances: `Bool`, `Ordering`, tuples (up to 7), `[]`, `Maybe`, `Either`, `Const`, `ZipList` and some monoids
9* Move `toSample` out of `ToSample` class
9* Move `toSample` out of `ToSample` class
10* Add a few helper functions to define `toSamples`
10* Add a few helper functions to define `toSamples`
11+* Remove matrix params.
11
12
120.4
130.4
13---
14---
servant-docs/example/greet.hs+1−8
@@ -46,13 +46,6 @@ instance ToParam (QueryParam "capital" Bool) where
46 \Default is false."
46 \Default is false."
47 Normal
47 Normal
48
48
49−instance ToParam (MatrixParam "lang" String) where
50− toParam _ =
51− DocQueryParam "lang"
52− ["en", "sv", "fr"]
53− "Get the greeting message selected language. Default is en."
54− Normal
55−
56instance ToSample Greet where
49instance ToSample Greet where
57 toSamples _ =
50 toSamples _ =
58 [ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
51 [ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
@@ -81,7 +74,7 @@ intro2 = DocIntro "This title is below the last"
81-- API specification
74-- API specification
82type TestApi =
75type TestApi =
83 -- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText
76 -- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText
84− "hello" :> MatrixParam "lang" String :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet
77+ "hello" :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet
85
78
86 -- POST /greet with a Greet as JSON in the request body,
79 -- POST /greet with a Greet as JSON in the request body,
87 -- returns a Greet as JSON
80 -- returns a Greet as JSON
servant-docs/example/greet.md+34−33
@@ -1,10 +1,10 @@
1−#### On proper introductions.
1+## On proper introductions.
2
2
3Hello there.
3Hello there.
4
4
5As documentation is usually written for humans, it's often useful to introduce concepts with a few words.
5As documentation is usually written for humans, it's often useful to introduce concepts with a few words.
6
6
7−#### This title is below the last
7+## This title is below the last
8
8
9You'll also note that multiple intros are possible.
9You'll also note that multiple intros are possible.
10
10
@@ -19,12 +19,13 @@ You'll also note that multiple intros are possible.
19- Example: `application/json`
19- Example: `application/json`
20
20
21```javascript
21```javascript
22−"Hello, haskeller!"
22+"HELLO, HASKELLER"
23```
23```
24
24
25#### Response:
25#### Response:
26
26
27- Status code 201
27- Status code 201
28+- Headers: [("X-Example","1729")]
28
29
29- Supported content types are:
30- Supported content types are:
30
31
@@ -42,21 +43,43 @@ You'll also note that multiple intros are possible.
42"Hello, haskeller"
43"Hello, haskeller"
43```
44```
44
45
45−## GET /hello;lang=<value>/:name
46+## DELETE /greet/:greetid
47+
48+#### Title
49+
50+This is some text
51+
52+#### Second secton
53+
54+And some more
46
55
47#### Captures:
56#### Captures:
48
57
49−- *name*: name of the person to greet
58+- *greetid*: identifier of the greet msg to remove
50
59
51−#### Matrix Parameters:
52
60
53−**hello**:
61+- This endpoint is sensitive to the value of the **unicorns** HTTP header.
54
62
55−- lang
63+#### Response:
56− - **Values**: *en, sv, fr*
64+
57− - **Description**: Get the greeting message selected language. Default is en.
65+- Status code 200
66+- Headers: []
67+
68+- Supported content types are:
69+
70+ - `application/json`
71+
72+- Response body as below.
73+
74+```javascript
75+[]
76+```
58
77
78+## GET /hello/:name
59
79
80+#### Captures:
81+
82+- *name*: name of the person to greet
60
83
61#### GET Parameters:
84#### GET Parameters:
62
85
@@ -68,6 +91,7 @@ You'll also note that multiple intros are possible.
68#### Response:
91#### Response:
69
92
70- Status code 200
93- Status code 200
94+- Headers: []
71
95
72- Supported content types are:
96- Supported content types are:
73
97
@@ -98,27 +122,4 @@ You'll also note that multiple intros are possible.
98"Hello, haskeller"
122"Hello, haskeller"
99```
123```
100
124
101−## DELETE /greet/:greetid
102−
103−#### Title
104−
105−This is some text
106−
107−#### Second secton
108−
109−And some more
110−
111−#### Captures:
112−
113−- *greetid*: identifier of the greet msg to remove
114−
115−
116−- This endpoint is sensitive to the value of the **unicorns** HTTP header.
117−
118−#### Response:
119−
120−- Status code 200
121−
122−- No response body
123−
124
125
servant-docs/src/Servant/Docs.hs+1−120
@@ -20,126 +20,7 @@
20-- The only thing you'll need to do will be to implement some classes
20-- The only thing you'll need to do will be to implement some classes
21-- for your captures, get parameters and request or response bodies.
21-- for your captures, get parameters and request or response bodies.
22--
22--
23−-- Here is a complete example that you can run to see the markdown pretty
23+-- See example/greet.hs for an example.
24−-- printer in action:
25−--
26−-- > {-# LANGUAGE DataKinds #-}
27−-- > {-# LANGUAGE DeriveGeneric #-}
28−-- > {-# LANGUAGE FlexibleInstances #-}
29−-- > {-# LANGUAGE MultiParamTypeClasses #-}
30−-- > {-# LANGUAGE OverloadedStrings #-}
31−-- > {-# LANGUAGE TypeOperators #-}
32−-- > {-# OPTIONS_GHC -fno-warn-orphans #-}
33−-- > import Control.Lens
34−-- > import Data.Aeson
35−-- > import Data.Proxy
36−-- > import Data.String.Conversions
37−-- > import Data.Text (Text)
38−-- > import GHC.Generics
39−-- > import Servant.API
40−-- > import Servant.Docs
41−-- >
42−-- > -- * Example
43−-- >
44−-- > -- | A greet message data type
45−-- > newtype Greet = Greet Text
46−-- > deriving (Generic, Show)
47−-- >
48−-- > -- | We can get JSON support automatically. This will be used to parse
49−-- > -- and encode a Greeting as 'JSON'.
50−-- > instance FromJSON Greet
51−-- > instance ToJSON Greet
52−-- >
53−-- > -- | We can also implement 'MimeRender' for additional formats like 'PlainText'.
54−-- > instance MimeRender PlainText Greet where
55−-- > mimeRender Proxy (Greet s) = "\"" <> cs s <> "\""
56−-- >
57−-- > -- We add some useful annotations to our captures,
58−-- > -- query parameters and request body to make the docs
59−-- > -- really helpful.
60−-- > instance ToCapture (Capture "name" Text) where
61−-- > toCapture _ = DocCapture "name" "name of the person to greet"
62−-- >
63−-- > instance ToCapture (Capture "greetid" Text) where
64−-- > toCapture _ = DocCapture "greetid" "identifier of the greet msg to remove"
65−-- >
66−-- > instance ToParam (QueryParam "capital" Bool) where
67−-- > toParam _ =
68−-- > DocQueryParam "capital"
69−-- > ["true", "false"]
70−-- > "Get the greeting message in uppercase (true) or not (false).\
71−-- > \Default is false."
72−-- > Normal
73−-- >
74−-- > instance ToParam (MatrixParam "lang" String) where
75−-- > toParam _ =
76−-- > DocQueryParam "lang"
77−-- > ["en", "sv", "fr"]
78−-- > "Get the greeting message selected language. Default is en."
79−-- > Normal
80−-- >
81−-- > instance ToSample Greet where
82−-- > toSample _ = Just $ Greet "Hello, haskeller!"
83−-- >
84−-- > toSamples _ =
85−-- > [ ("If you use ?capital=true", Greet "HELLO, HASKELLER")
86−-- > , ("If you use ?capital=false", Greet "Hello, haskeller")
87−-- > ]
88−-- >
89−-- > -- We define some introductory sections, these will appear at the top of the
90−-- > -- documentation.
91−-- > --
92−-- > -- We pass them in with 'docsWith', below. If you only want to add
93−-- > -- introductions, you may use 'docsWithIntros'
94−-- > intro1 :: DocIntro
95−-- > intro1 = DocIntro "On proper introductions." -- The title
96−-- > [ "Hello there."
97−-- > , "As documentation is usually written for humans, it's often useful \
98−-- > \to introduce concepts with a few words." ] -- Elements are paragraphs
99−-- >
100−-- > intro2 :: DocIntro
101−-- > intro2 = DocIntro "This title is below the last"
102−-- > [ "You'll also note that multiple intros are possible." ]
103−-- >
104−-- >
105−-- > -- API specification
106−-- > type TestApi =
107−-- > -- GET /hello/:name?capital={true, false} returns a Greet as JSON or PlainText
108−-- > "hello" :> MatrixParam "lang" String :> Capture "name" Text :> QueryParam "capital" Bool :> Get '[JSON, PlainText] Greet
109−-- >
110−-- > -- POST /greet with a Greet as JSON in the request body,
111−-- > -- returns a Greet as JSON
112−-- > :<|> "greet" :> ReqBody '[JSON] Greet :> Post '[JSON] Greet
113−-- >
114−-- > -- DELETE /greet/:greetid
115−-- > :<|> "greet" :> Capture "greetid" Text :> Delete '[JSON] ()
116−-- >
117−-- > testApi :: Proxy TestApi
118−-- > testApi = Proxy
119−-- >
120−-- > -- Build some extra information for the DELETE /greet/:greetid endpoint. We
121−-- > -- want to add documentation about a secret unicorn header and some extra
122−-- > -- notes.
123−-- > extra :: ExtraInfo TestApi
124−-- > extra =
125−-- > extraInfo (Proxy :: Proxy ("greet" :> Capture "greetid" Text :> Delete '[JSON] ())) $
126−-- > defAction & headers <>~ ["unicorns"]
127−-- > & notes <>~ [ DocNote "Title" ["This is some text"]
128−-- > , DocNote "Second secton" ["And some more"]
129−-- > ]
130−-- >
131−-- > -- Generate the data that lets us have API docs. This
132−-- > -- is derived from the type as well as from
133−-- > -- the 'ToCapture', 'ToParam' and 'ToSample' instances from above.
134−-- > --
135−-- > -- If you didn't want intros and extra information, you could just call:
136−-- > --
137−-- > -- > docs testAPI :: API
138−-- > docsGreet :: API
139−-- > docsGreet = docsWith [intro1, intro2] extra testApi
140−-- >
141−-- > main :: IO ()
142−-- > main = putStrLn $ markdown docsGreet
143module Servant.Docs
24module Servant.Docs
144 ( -- * 'HasDocs' class and key functions
25 ( -- * 'HasDocs' class and key functions
145 HasDocs(..), docs, markdown
26 HasDocs(..), docs, markdown
servant-docs/src/Servant/Docs/Internal.hs+1−59
@@ -24,8 +24,7 @@ module Servant.Docs.Internal where
24import Control.Applicative
24import Control.Applicative
25import Control.Arrow (second)
25import Control.Arrow (second)
26import Control.Lens (makeLenses, over, traversed, (%~),
26import Control.Lens (makeLenses, over, traversed, (%~),
27− (&), (.~), (<>~), (^.), _1, _2,
27+ (&), (.~), (<>~), (^.), (|>))
28− _last, (|>))
29import qualified Control.Monad.Omega as Omega
28import qualified Control.Monad.Omega as Omega
30import Data.ByteString.Conversion (ToByteString, toByteString)
29import Data.ByteString.Conversion (ToByteString, toByteString)
31import Data.ByteString.Lazy.Char8 (ByteString)
30import Data.ByteString.Lazy.Char8 (ByteString)
@@ -546,7 +545,6 @@ markdown api = unlines $
546 "" :
545 "" :
547 notesStr (action ^. notes) ++
546 notesStr (action ^. notes) ++
548 capturesStr (action ^. captures) ++
547 capturesStr (action ^. captures) ++
549− mxParamsStr (action ^. mxParams) ++
550 headersStr (action ^. headers) ++
548 headersStr (action ^. headers) ++
551 paramsStr (action ^. params) ++
549 paramsStr (action ^. params) ++
552 rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++
550 rqbodyStr (action ^. rqtypes) (action ^. rqbody) ++
@@ -590,20 +588,6 @@ markdown api = unlines $
590 captureStr cap =
588 captureStr cap =
591 "- *" ++ (cap ^. capSymbol) ++ "*: " ++ (cap ^. capDesc)
589 "- *" ++ (cap ^. capSymbol) ++ "*: " ++ (cap ^. capDesc)
592
590
593− mxParamsStr :: [(String, [DocQueryParam])] -> [String]
594− mxParamsStr [] = []
595− mxParamsStr l =
596− "#### Matrix Parameters:" :
597− "" :
598− map segmentStr l
599− segmentStr :: (String, [DocQueryParam]) -> String
600− segmentStr (segment, l) = unlines $
601− ("**" ++ segment ++ "**:") :
602− "" :
603− map paramStr l ++
604− "" :
605− []
606−
607 headersStr :: [Text] -> [String]
591 headersStr :: [Text] -> [String]
608 headersStr [] = []
592 headersStr [] = []
609 headersStr l = [""] ++ map headerStr l ++ [""]
593 headersStr l = [""] ++ map headerStr l ++ [""]
@@ -898,48 +882,6 @@ instance (KnownSymbol sym, ToParam (QueryFlag sym), HasDocs sublayout)
898 action' = over params (|> toParam paramP) action
882 action' = over params (|> toParam paramP) action
899
883
900
884
901−instance (KnownSymbol sym, ToParam (MatrixParam sym a), HasDocs sublayout)
902− => HasDocs (MatrixParam sym a :> sublayout) where
903−
904− docsFor Proxy (endpoint, action) =
905− docsFor sublayoutP (endpoint', action')
906−
907− where sublayoutP = Proxy :: Proxy sublayout
908− paramP = Proxy :: Proxy (MatrixParam sym a)
909− segment = endpoint ^. (path._last)
910− segment' = action ^. (mxParams._last._1)
911− endpoint' = over (path._last) (\p -> p ++ ";" ++ symbolVal symP ++ "=<value>") endpoint
912−
913− action' = if segment' /= segment
914− -- This is the first matrix parameter for this segment, insert a new entry into the mxParams list
915− then over mxParams (|> (segment, [toParam paramP])) action
916− -- We've already inserted a matrix parameter for this segment, append to the existing list
917− else action & mxParams._last._2 <>~ [toParam paramP]
918− symP = Proxy :: Proxy sym
919−
920−
921−instance (KnownSymbol sym, {- ToParam (MatrixParams sym a), -} HasDocs sublayout)
922− => HasDocs (MatrixParams sym a :> sublayout) where
923−
924− docsFor Proxy (endpoint, action) =
925− docsFor sublayoutP (endpoint', action)
926−
927− where sublayoutP = Proxy :: Proxy sublayout
928− endpoint' = over path (\p -> p ++ [";" ++ symbolVal symP ++ "=<value>"]) endpoint
929− symP = Proxy :: Proxy sym
930−
931−
932−instance (KnownSymbol sym, {- ToParam (MatrixFlag sym), -} HasDocs sublayout)
933− => HasDocs (MatrixFlag sym :> sublayout) where
934−
935− docsFor Proxy (endpoint, action) =
936− docsFor sublayoutP (endpoint', action)
937−
938− where sublayoutP = Proxy :: Proxy sublayout
939−
940− endpoint' = over path (\p -> p ++ [";" ++ symbolVal symP]) endpoint
941− symP = Proxy :: Proxy sym
942−
943instance HasDocs Raw where
885instance HasDocs Raw where
944 docsFor _proxy (endpoint, action) _ =
886 docsFor _proxy (endpoint, action) _ =
945 single endpoint action
887 single endpoint action
servant-foreign/src/Servant/Foreign.hs+14−50
@@ -20,7 +20,6 @@ module Servant.Foreign
20 ( HasForeign(..)
20 ( HasForeign(..)
21 , Segment(..)
21 , Segment(..)
22 , SegmentType(..)
22 , SegmentType(..)
23− , MatrixArg
24 , FunctionName
23 , FunctionName
25 , QueryArg(..)
24 , QueryArg(..)
26 , HeaderArg(..)
25 , HeaderArg(..)
@@ -47,15 +46,13 @@ module Servant.Foreign
47 , module Servant.API
46 , module Servant.API
48 ) where
47 ) where
49
48
50−import Control.Lens (makeLenses, (%~), (&), (.~),
49+import Control.Lens (makeLenses, (%~), (&), (.~), (<>~))
51− (<>~), _last)
52−import Data.Monoid ((<>))
50+import Data.Proxy
53−import Data.Text
51+import Data.Text
54−import Data.Proxy
55−import GHC.Exts (Constraint)
52+import GHC.Exts (Constraint)
56−import GHC.TypeLits
53+import GHC.TypeLits
57−import Servant.API
58−import Prelude hiding (concat)
54+import Prelude hiding (concat)
55+import Servant.API
59
56
60-- | Function name builder that simply concat each part together
57-- | Function name builder that simply concat each part together
61concatCase :: FunctionName -> Text
58concatCase :: FunctionName -> Text
@@ -76,7 +73,7 @@ camelCase (p:ps) = concat $ p : camelCase' ps
76
73
77type Arg = Text
74type Arg = Text
78
75
79−data Segment = Segment { _segment :: SegmentType, _matrix :: [MatrixArg] }
76+newtype Segment = Segment { _segment :: SegmentType }
80 deriving (Eq, Show)
77 deriving (Eq, Show)
81
78
82data SegmentType = Static Text -- ^ a static path segment. like "/foo"
79data SegmentType = Static Text -- ^ a static path segment. like "/foo"
@@ -105,8 +102,6 @@ data HeaderArg = HeaderArg
105 } deriving (Eq, Show)
102 } deriving (Eq, Show)
106
103
107
104
108−type MatrixArg = QueryArg
109−
110data Url = Url
105data Url = Url
111 { _path :: Path
106 { _path :: Path
112 , _queryStr :: [QueryArg]
107 , _queryStr :: [QueryArg]
@@ -132,12 +127,12 @@ makeLenses ''Url
132makeLenses ''Req
127makeLenses ''Req
133
128
134isCapture :: Segment -> Bool
129isCapture :: Segment -> Bool
135−isCapture (Segment (Cap _) _) = True
130+isCapture (Segment (Cap _)) = True
136−isCapture _ = False
131+isCapture _ = False
137
132
138captureArg :: Segment -> Arg
133captureArg :: Segment -> Arg
139−captureArg (Segment (Cap s) _) = s
134+captureArg (Segment (Cap s)) = s
140−captureArg _ = error "captureArg called on non capture"
135+captureArg _ = error "captureArg called on non capture"
141
136
142defReq :: Req
137defReq :: Req
143defReq = Req defUrl "GET" [] False []
138defReq = Req defUrl "GET" [] False []
@@ -169,7 +164,7 @@ instance (KnownSymbol sym, HasForeign sublayout)
169
164
170 foreignFor Proxy req =
165 foreignFor Proxy req =
171 foreignFor (Proxy :: Proxy sublayout) $
166 foreignFor (Proxy :: Proxy sublayout) $
172− req & reqUrl.path <>~ [Segment (Cap str) []]
167+ req & reqUrl.path <>~ [Segment (Cap str)]
173 & funcName %~ (++ ["by", str])
168 & funcName %~ (++ ["by", str])
174
169
175 where str = pack . symbolVal $ (Proxy :: Proxy sym)
170 where str = pack . symbolVal $ (Proxy :: Proxy sym)
@@ -242,37 +237,6 @@ instance (KnownSymbol sym, HasForeign sublayout)
242
237
243 where str = pack . symbolVal $ (Proxy :: Proxy sym)
238 where str = pack . symbolVal $ (Proxy :: Proxy sym)
244
239
245−instance (KnownSymbol sym, HasForeign sublayout)
246− => HasForeign (MatrixParam sym a :> sublayout) where
247− type Foreign (MatrixParam sym a :> sublayout) = Foreign sublayout
248−
249− foreignFor Proxy req =
250− foreignFor (Proxy :: Proxy sublayout) $
251− req & reqUrl.path._last.matrix <>~ [QueryArg strArg Normal]
252−
253− where str = pack . symbolVal $ (Proxy :: Proxy sym)
254− strArg = str <> "Value"
255−
256−instance (KnownSymbol sym, HasForeign sublayout)
257− => HasForeign (MatrixParams sym a :> sublayout) where
258− type Foreign (MatrixParams sym a :> sublayout) = Foreign sublayout
259−
260− foreignFor Proxy req =
261− foreignFor (Proxy :: Proxy sublayout) $
262− req & reqUrl.path._last.matrix <>~ [QueryArg str List]
263−
264− where str = pack . symbolVal $ (Proxy :: Proxy sym)
265−
266−instance (KnownSymbol sym, HasForeign sublayout)
267− => HasForeign (MatrixFlag sym :> sublayout) where
268− type Foreign (MatrixFlag sym :> sublayout) = Foreign sublayout
269−
270− foreignFor Proxy req =
271− foreignFor (Proxy :: Proxy sublayout) $
272− req & reqUrl.path._last.matrix <>~ [QueryArg str Flag]
273−
274− where str = pack . symbolVal $ (Proxy :: Proxy sym)
275−
276instance HasForeign Raw where
240instance HasForeign Raw where
277 type Foreign Raw = Method -> Req
241 type Foreign Raw = Method -> Req
278
242
@@ -293,7 +257,7 @@ instance (KnownSymbol path, HasForeign sublayout)
293
257
294 foreignFor Proxy req =
258 foreignFor Proxy req =
295 foreignFor (Proxy :: Proxy sublayout) $
259 foreignFor (Proxy :: Proxy sublayout) $
296− req & reqUrl.path <>~ [Segment (Static str) []]
260+ req & reqUrl.path <>~ [Segment (Static str)]
297 & funcName %~ (++ [str])
261 & funcName %~ (++ [str])
298
262
299 where str = Data.Text.map (\c -> if c == '.' then '_' else c) . pack . symbolVal $ (Proxy :: Proxy path)
263 where str = Data.Text.map (\c -> if c == '.' then '_' else c) . pack . symbolVal $ (Proxy :: Proxy path)
servant-js/CHANGELOG.md+1
@@ -4,6 +4,7 @@ HEAD
4* Provide new targets for code generation along with the old jQuery one: vanilla Javascript and Angular.js
4* Provide new targets for code generation along with the old jQuery one: vanilla Javascript and Angular.js
5* Greatly simplify usage of this library by reducing down the API to just 2 functions: `jsForAPI` and `writeJSForAPI` + the choice of a code generator
5* Greatly simplify usage of this library by reducing down the API to just 2 functions: `jsForAPI` and `writeJSForAPI` + the choice of a code generator
6* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
6* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
7+* Remove matrix params.
7
8
80.4
90.4
9---
10---
servant-js/src/Servant/JS/Internal.hs+4−9
@@ -9,7 +9,6 @@ module Servant.JS.Internal
9 , segmentTypeToStr
9 , segmentTypeToStr
10 , jsParams
10 , jsParams
11 , jsGParams
11 , jsGParams
12− , jsMParams
13 , paramToStr
12 , paramToStr
14 , toValidFunctionName
13 , toValidFunctionName
15 , toJSHeader
14 , toJSHeader
@@ -92,7 +91,7 @@ toValidFunctionName :: Text -> Text
92toValidFunctionName t =
91toValidFunctionName t =
93 case T.uncons t of
92 case T.uncons t of
94 Just (x,xs) ->
93 Just (x,xs) ->
95− setFirstChar x `T.cons` T.filter remainder xs
94+ setFirstChar x `T.cons` T.filter remainder xs
96 Nothing -> "_"
95 Nothing -> "_"
97 where
96 where
98 setFirstChar c = if firstChar c then c else '_'
97 setFirstChar c = if firstChar c then c else '_'
@@ -105,7 +104,7 @@ toValidFunctionName t =
105 , Set.titlecaseLetter
104 , Set.titlecaseLetter
106 , Set.modifierLetter
105 , Set.modifierLetter
107 , Set.otherLetter
106 , Set.otherLetter
108− , Set.letterNumber
107+ , Set.letterNumber
109 ]
108 ]
110 remainderOK = firstLetterOK
109 remainderOK = firstLetterOK
111 <> mconcat
110 <> mconcat
@@ -134,8 +133,8 @@ jsSegments [x] = "/" <> segmentToStr x False
134jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
133jsSegments (x:xs) = "/" <> segmentToStr x True <> jsSegments xs
135
134
136segmentToStr :: Segment -> Bool -> Text
135segmentToStr :: Segment -> Bool -> Text
137−segmentToStr (Segment st ms) notTheEnd =
136+segmentToStr (Segment st) notTheEnd =
138− segmentTypeToStr st <> jsMParams ms <> if notTheEnd then "" else "'"
137+ segmentTypeToStr st <> if notTheEnd then "" else "'"
139
138
140segmentTypeToStr :: SegmentType -> Text
139segmentTypeToStr :: SegmentType -> Text
141segmentTypeToStr (Static s) = s
140segmentTypeToStr (Static s) = s
@@ -149,10 +148,6 @@ jsGParams s (x:xs) = paramToStr x True <> s <> jsGParams s xs
149jsParams :: [QueryArg] -> Text
148jsParams :: [QueryArg] -> Text
150jsParams = jsGParams "&"
149jsParams = jsGParams "&"
151
150
152−jsMParams :: [MatrixArg] -> Text
153−jsMParams [] = ""
154−jsMParams xs = ";" <> jsGParams ";" xs
155−
156paramToStr :: QueryArg -> Bool -> Text
151paramToStr :: QueryArg -> Bool -> Text
157paramToStr qarg notTheEnd =
152paramToStr qarg notTheEnd =
158 case qarg ^. argType of
153 case qarg ^. argType of
servant-mock/src/Servant/Mock.hs−11
@@ -136,17 +136,6 @@ instance (KnownSymbol s, FromHttpApiData a, HasMock rest)
136instance (KnownSymbol s, HasMock rest) => HasMock (QueryFlag s :> rest) where
136instance (KnownSymbol s, HasMock rest) => HasMock (QueryFlag s :> rest) where
137 mock _ = \_ -> mock (Proxy :: Proxy rest)
137 mock _ = \_ -> mock (Proxy :: Proxy rest)
138
138
139−instance (KnownSymbol s, FromHttpApiData a, HasMock rest)
140− => HasMock (MatrixParam s a :> rest) where
141− mock _ = \_ -> mock (Proxy :: Proxy rest)
142−
143−instance (KnownSymbol s, FromHttpApiData a, HasMock rest)
144− => HasMock (MatrixParams s a :> rest) where
145− mock _ = \_ -> mock (Proxy :: Proxy rest)
146−
147−instance (KnownSymbol s, HasMock rest) => HasMock (MatrixFlag s :> rest) where
148− mock _ = \_ -> mock (Proxy :: Proxy rest)
149−
150instance (KnownSymbol h, FromHttpApiData a, HasMock rest) => HasMock (Header h a :> rest) where
139instance (KnownSymbol h, FromHttpApiData a, HasMock rest) => HasMock (Header h a :> rest) where
151 mock _ = \_ -> mock (Proxy :: Proxy rest)
140 mock _ = \_ -> mock (Proxy :: Proxy rest)
152
141
servant-server/CHANGELOG.md+1
@@ -4,6 +4,7 @@ HEAD
4* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
4* Support for the `HttpVersion`, `IsSecure`, `RemoteHost` and `Vault` combinators
5* Drop `EitherT` in favor of `ExceptT`
5* Drop `EitherT` in favor of `ExceptT`
6* Use `http-api-data` instead of `Servant.Common.Text`
6* Use `http-api-data` instead of `Servant.Common.Text`
7+* Remove matrix params.
7
8
80.4.1
90.4.1
9-----
10-----
servant-server/servant-server.cabal−1
@@ -37,7 +37,6 @@ library
37 Servant.Server
37 Servant.Server
38 Servant.Server.Internal
38 Servant.Server.Internal
39 Servant.Server.Internal.Enter
39 Servant.Server.Internal.Enter
40− Servant.Server.Internal.PathInfo
41 Servant.Server.Internal.Router
40 Servant.Server.Internal.Router
42 Servant.Server.Internal.RoutingApplication
41 Servant.Server.Internal.RoutingApplication
43 Servant.Server.Internal.ServantErr
42 Servant.Server.Internal.ServantErr
servant-server/src/Servant/Server/Internal.hs+8−124
@@ -14,7 +14,6 @@
14
14
15module Servant.Server.Internal
15module Servant.Server.Internal
16 ( module Servant.Server.Internal
16 ( module Servant.Server.Internal
17− , module Servant.Server.Internal.PathInfo
18 , module Servant.Server.Internal.Router
17 , module Servant.Server.Internal.Router
19 , module Servant.Server.Internal.RoutingApplication
18 , module Servant.Server.Internal.RoutingApplication
20 , module Servant.Server.Internal.ServantErr
19 , module Servant.Server.Internal.ServantErr
@@ -31,8 +30,6 @@ import Data.Maybe (mapMaybe, fromMaybe)
31import Data.String (fromString)
30import Data.String (fromString)
32import Data.String.Conversions (cs, (<>), ConvertibleStrings)
31import Data.String.Conversions (cs, (<>), ConvertibleStrings)
33import Data.Text (Text)
32import Data.Text (Text)
34−import qualified Data.Text as T
35−import Data.Text.Encoding (encodeUtf8)
36import Data.Typeable
33import Data.Typeable
37import GHC.TypeLits (KnownSymbol, symbolVal)
34import GHC.TypeLits (KnownSymbol, symbolVal)
38import Network.HTTP.Types hiding (Header, ResponseHeaders)
35import Network.HTTP.Types hiding (Header, ResponseHeaders)
@@ -41,11 +38,10 @@ import Network.Wai (Application, lazyRequestBody,
41 rawQueryString, requestHeaders,
38 rawQueryString, requestHeaders,
42 requestMethod, responseLBS, remoteHost,
39 requestMethod, responseLBS, remoteHost,
43 isSecure, vault, httpVersion, Response,
40 isSecure, vault, httpVersion, Response,
44− Request)
41+ Request, pathInfo)
45import Servant.API ((:<|>) (..), (:>), Capture,
42import Servant.API ((:<|>) (..), (:>), Capture,
46 Delete, Get, Header,
43 Delete, Get, Header,
47− IsSecure(..), MatrixFlag, MatrixParam,
48− MatrixParams, Patch, Post, Put,
44+ IsSecure(..), Patch, Post, Put,
49 QueryFlag, QueryParam, QueryParams,
45 QueryFlag, QueryParam, QueryParams,
50 Raw, RemoteHost, ReqBody, Vault)
46 Raw, RemoteHost, ReqBody, Vault)
51import Servant.API.ContentTypes (AcceptHeader (..),
47import Servant.API.ContentTypes (AcceptHeader (..),
@@ -54,7 +50,6 @@ import Servant.API.ContentTypes (AcceptHeader (..),
54import Servant.API.ResponseHeaders (Headers, getResponse, GetHeaders,
50import Servant.API.ResponseHeaders (Headers, getResponse, GetHeaders,
55 getHeaders)
51 getHeaders)
56
52
57−import Servant.Server.Internal.PathInfo
58import Servant.Server.Internal.Router
53import Servant.Server.Internal.Router
59import Servant.Server.Internal.RoutingApplication
54import Servant.Server.Internal.RoutingApplication
60import Servant.Server.Internal.ServantErr
55import Servant.Server.Internal.ServantErr
@@ -548,123 +543,6 @@ instance (KnownSymbol sym, HasServer sublayout)
548 examine v | v == "true" || v == "1" || v == "" = True
543 examine v | v == "true" || v == "1" || v == "" = True
549 | otherwise = False
544 | otherwise = False
550
545
551−parseMatrixText :: B.ByteString -> QueryText
552−parseMatrixText = parseQueryText
553−
554−-- | If you use @'MatrixParam' "author" Text@ in one of the endpoints for your API,
555−-- this automatically requires your server-side handler to be a function
556−-- that takes an argument of type @'Maybe' 'Text'@.
557−--
558−-- This lets servant worry about looking it up in the query string
559−-- and turning it into a value of the type you specify, enclosed
560−-- in 'Maybe', because it may not be there and servant would then
561−-- hand you 'Nothing'.
562−--
563−-- You can control how it'll be converted from 'Text' to your type
564−-- by simply providing an instance of 'FromHttpApiData' for your type.
565−--
566−-- Example:
567−--
568−-- > type MyApi = "books" :> MatrixParam "author" Text :> Get [Book]
569−-- >
570−-- > server :: Server MyApi
571−-- > server = getBooksBy
572−-- > where getBooksBy :: Maybe Text -> ExceptT ServantErr IO [Book]
573−-- > getBooksBy Nothing = ...return all books...
574−-- > getBooksBy (Just author) = ...return books by the given author...
575−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
576− => HasServer (MatrixParam sym a :> sublayout) where
577−
578− type ServerT (MatrixParam sym a :> sublayout) m =
579− Maybe a -> ServerT sublayout m
580−
581− route Proxy subserver = WithRequest $ \ request ->
582− case parsePathInfo request of
583− (first : _)
584− -> do let querytext = parseMatrixText . encodeUtf8 $ T.tail first
585− param = case lookup paramname querytext of
586− Nothing -> Nothing -- param absent from the query string
587− Just Nothing -> Nothing -- param present with no value -> Nothing
588− Just (Just v) -> parseQueryParamMaybe v -- if present, we try to convert to
589− -- the right type
590− route (Proxy :: Proxy sublayout) (feedTo subserver param)
591− _ -> route (Proxy :: Proxy sublayout) (feedTo subserver Nothing)
592−
593− where paramname = cs $ symbolVal (Proxy :: Proxy sym)
594−
595−-- | If you use @'MatrixParams' "authors" Text@ in one of the endpoints for your API,
596−-- this automatically requires your server-side handler to be a function
597−-- that takes an argument of type @['Text']@.
598−--
599−-- This lets servant worry about looking up 0 or more values in the query string
600−-- associated to @authors@ and turning each of them into a value of
601−-- the type you specify.
602−--
603−-- You can control how the individual values are converted from 'Text' to your type
604−-- by simply providing an instance of 'FromHttpApiData' for your type.
605−--
606−-- Example:
607−--
608−-- > type MyApi = "books" :> MatrixParams "authors" Text :> Get [Book]
609−-- >
610−-- > server :: Server MyApi
611−-- > server = getBooksBy
612−-- > where getBooksBy :: [Text] -> ExceptT ServantErr IO [Book]
613−-- > getBooksBy authors = ...return all books by these authors...
614−instance (KnownSymbol sym, FromHttpApiData a, HasServer sublayout)
615− => HasServer (MatrixParams sym a :> sublayout) where
616−
617− type ServerT (MatrixParams sym a :> sublayout) m =
618− [a] -> ServerT sublayout m
619−
620− route Proxy subserver = WithRequest $ \ request ->
621− case parsePathInfo request of
622− (first : _)
623− -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
624− -- if sym is "foo", we look for matrix parameters
625− -- named "foo" or "foo[]" and call parseQueryParam on the
626− -- corresponding values
627− parameters = filter looksLikeParam matrixtext
628− values = mapMaybe (convert . snd) parameters
629− route (Proxy :: Proxy sublayout) (feedTo subserver values)
630− _ -> route (Proxy :: Proxy sublayout) (feedTo subserver [])
631− where paramname = cs $ symbolVal (Proxy :: Proxy sym)
632− looksLikeParam (name, _) = name == paramname || name == (paramname <> "[]")
633− convert Nothing = Nothing
634− convert (Just v) = parseQueryParamMaybe v
635−
636−-- | If you use @'MatrixFlag' "published"@ in one of the endpoints for your API,
637−-- this automatically requires your server-side handler to be a function
638−-- that takes an argument of type 'Bool'.
639−--
640−-- Example:
641−--
642−-- > type MyApi = "books" :> MatrixFlag "published" :> Get [Book]
643−-- >
644−-- > server :: Server MyApi
645−-- > server = getBooks
646−-- > where getBooks :: Bool -> ExceptT ServantErr IO [Book]
647−-- > getBooks onlyPublished = ...return all books, or only the ones that are already published, depending on the argument...
648−instance (KnownSymbol sym, HasServer sublayout)
649− => HasServer (MatrixFlag sym :> sublayout) where
650−
651− type ServerT (MatrixFlag sym :> sublayout) m =
652− Bool -> ServerT sublayout m
653−
654− route Proxy subserver = WithRequest $ \ request ->
655− case parsePathInfo request of
656− (first : _)
657− -> do let matrixtext = parseMatrixText . encodeUtf8 $ T.tail first
658− param = case lookup paramname matrixtext of
659− Just Nothing -> True -- param is there, with no value
660− Just (Just v) -> examine v -- param with a value
661− Nothing -> False -- param not in the query string
662− route (Proxy :: Proxy sublayout) (feedTo subserver param)
663− _ -> route (Proxy :: Proxy sublayout) (feedTo subserver False)
664− where paramname = cs $ symbolVal (Proxy :: Proxy sym)
665− examine v | v == "true" || v == "1" || v == "" = True
666− | otherwise = False
667−
668-- | Just pass the request to the underlying application and serve its response.
546-- | Just pass the request to the underlying application and serve its response.
669--
547--
670-- Example:
548-- Example:
@@ -762,5 +640,11 @@ instance HasServer api => HasServer (HttpVersion :> api) where
762 route Proxy subserver = WithRequest $ \req ->
640 route Proxy subserver = WithRequest $ \req ->
763 route (Proxy :: Proxy api) (feedTo subserver $ httpVersion req)
641 route (Proxy :: Proxy api) (feedTo subserver $ httpVersion req)
764
642
643+pathIsEmpty :: Request -> Bool
644+pathIsEmpty = go . pathInfo
645+ where go [] = True
646+ go [""] = True
647+ go _ = False
648+
765ct_wildcard :: B.ByteString
649ct_wildcard :: B.ByteString
766ct_wildcard = "*" <> "/" <> "*" -- Because CPP
650ct_wildcard = "*" <> "/" <> "*" -- Because CPP
servant-server/src/Servant/Server/Internal/PathInfo.hsdeleted−38
@@ -1,38 +0,0 @@
1−{-# LANGUAGE OverloadedStrings #-}
2−module Servant.Server.Internal.PathInfo where
3−
4−import Data.List (unfoldr)
5−import Data.Text (Text)
6−import qualified Data.Text as T
7−import Network.Wai (Request, pathInfo)
8−
9−-- | Like `null . pathInfo`, but works with redundant trailing slashes.
10−pathIsEmpty :: Request -> Bool
11−pathIsEmpty = f . processedPathInfo
12− where
13− f [] = True
14− f [""] = True
15− f _ = False
16−
17−
18−splitMatrixParameters :: Text -> (Text, Text)
19−splitMatrixParameters = T.break (== ';')
20−
21−parsePathInfo :: Request -> [Text]
22−parsePathInfo = filter (/= "") . mergePairs . map splitMatrixParameters . pathInfo
23− where mergePairs = concat . unfoldr pairToList
24− pairToList [] = Nothing
25− pairToList ((a, b):xs) = Just ([a, b], xs)
26−
27−-- | Returns a processed pathInfo from the request.
28−--
29−-- In order to handle matrix parameters in the request correctly, the raw pathInfo needs to be
30−-- processed, so routing works as intended. Therefor this function should be used to access
31−-- the pathInfo for routing purposes.
32−processedPathInfo :: Request -> [Text]
33−processedPathInfo r =
34− case pinfo of
35− (x:xs) | T.head x == ';' -> xs
36− _ -> pinfo
37− where pinfo = parsePathInfo r
38−
servant-server/src/Servant/Server/Internal/Router.hs+2−3
@@ -7,7 +7,6 @@ import qualified Data.Map as M
7import Data.Monoid ((<>))
7import Data.Monoid ((<>))
8import Data.Text (Text)
8import Data.Text (Text)
9import Network.Wai (Request, Response, pathInfo)
9import Network.Wai (Request, Response, pathInfo)
10−import Servant.Server.Internal.PathInfo
11import Servant.Server.Internal.RoutingApplication
10import Servant.Server.Internal.RoutingApplication
12
11
13type Router = Router' RoutingApplication
12type Router = Router' RoutingApplication
@@ -59,14 +58,14 @@ runRouter :: Router -> RoutingApplication
59runRouter (WithRequest router) request respond =
58runRouter (WithRequest router) request respond =
60 runRouter (router request) request respond
59 runRouter (router request) request respond
61runRouter (StaticRouter table) request respond =
60runRouter (StaticRouter table) request respond =
62− case processedPathInfo request of
61+ case pathInfo request of
63 first : rest
62 first : rest
64 | Just router <- M.lookup first table
63 | Just router <- M.lookup first table
65 -> let request' = request { pathInfo = rest }
64 -> let request' = request { pathInfo = rest }
66 in runRouter router request' respond
65 in runRouter router request' respond
67 _ -> respond $ failWith NotFound
66 _ -> respond $ failWith NotFound
68runRouter (DynamicRouter fun) request respond =
67runRouter (DynamicRouter fun) request respond =
69− case processedPathInfo request of
68+ case pathInfo request of
70 first : rest
69 first : rest
71 -> let request' = request { pathInfo = rest }
70 -> let request' = request { pathInfo = rest }
72 in runRouter (fun first) request' respond
71 in runRouter (fun first) request' respond
servant-server/test/Servant/ServerSpec.hs+7−91
@@ -38,13 +38,13 @@ import Test.Hspec (Spec, describe, it, shouldBe)
38import Test.Hspec.Wai (get, liftIO, matchHeaders,
38import Test.Hspec.Wai (get, liftIO, matchHeaders,
39 matchStatus, post, request,
39 matchStatus, post, request,
40 shouldRespondWith, with, (<:>))
40 shouldRespondWith, with, (<:>))
41−import Servant.API ((:<|>) (..), (:>),
41+import Servant.API ((:<|>) (..), (:>), Capture, Delete,
42− addHeader, Capture,
43− Delete, Get, Header (..), Headers,
42+ Get, Header (..), Headers,
44− HttpVersion, IsSecure(..), JSON, MatrixFlag,
43+ HttpVersion, IsSecure (..), JSON,
45− MatrixParam, MatrixParams, Patch, PlainText,
44+ Patch, PlainText, Post, Put,
46− Post, Put, RemoteHost, QueryFlag, QueryParam,
45+ QueryFlag, QueryParam, QueryParams,
47− QueryParams, Raw, ReqBody)
46+ Raw, RemoteHost, ReqBody,
47+ addHeader)
48import Servant.Server (Server, serve, ServantErr(..), err404)
48import Servant.Server (Server, serve, ServantErr(..), err404)
49import Servant.Server.Internal.Router
49import Servant.Server.Internal.Router
50 (tweakResponse, runRouter,
50 (tweakResponse, runRouter,
@@ -95,7 +95,6 @@ spec = do
95 putSpec
95 putSpec
96 patchSpec
96 patchSpec
97 queryParamSpec
97 queryParamSpec
98− matrixParamSpec
99 headerSpec
98 headerSpec
100 rawSpec
99 rawSpec
101 unionSpec
100 unionSpec
@@ -275,89 +274,6 @@ queryParamSpec = do
275 name = "Alice"
274 name = "Alice"
276 }
275 }
277
276
278−type MatrixParamApi = "a" :> MatrixParam "name" String :> Get '[JSON] Person
279− :<|> "b" :> MatrixParams "names" String :> "bsub" :> MatrixParams "names" String :> Get '[JSON] Person
280− :<|> "c" :> MatrixFlag "capitalize" :> Get '[JSON] Person
281− :<|> "d" :> Capture "foo" Integer :> MatrixParam "name" String :> MatrixFlag "capitalize" :> "dsub" :> Get '[JSON] Person
282−
283−matrixParamApi :: Proxy MatrixParamApi
284−matrixParamApi = Proxy
285−
286−mpServer :: Server MatrixParamApi
287−mpServer = matrixParamServer :<|> mpNames :<|> mpCapitalize alice :<|> mpComplex
288− where mpNames (_:name2:_) _ = return alice { name = name2 }
289− mpNames _ _ = return alice
290−
291− mpCapitalize p False = return p
292− mpCapitalize p True = return p { name = map toUpper (name p) }
293−
294− matrixParamServer (Just name) = return alice{name = name}
295− matrixParamServer Nothing = return alice
296−
297− mpAge age p = return p { age = age }
298− mpComplex capture name cap = matrixParamServer name >>= flip mpCapitalize cap >>= mpAge capture
299−
300−matrixParamSpec :: Spec
301−matrixParamSpec = do
302− describe "Servant.API.MatrixParam" $ do
303− it "allows to retrieve simple matrix parameters" $
304− (flip runSession) (serve matrixParamApi mpServer) $ do
305− response1 <- Network.Wai.Test.request defaultRequest{
306− pathInfo = ["a;name=bob"]
307− }
308− liftIO $ do
309− decode' (simpleBody response1) `shouldBe` Just alice{
310− name = "bob"
311− }
312−
313− it "allows to retrieve lists in matrix parameters" $
314− (flip runSession) (serve matrixParamApi mpServer) $ do
315− response2 <- Network.Wai.Test.request defaultRequest{
316− pathInfo = ["b;names=bob;names=john", "bsub;names=anna;names=sarah"]
317− }
318− liftIO $
319− decode' (simpleBody response2) `shouldBe` Just alice{
320− name = "john"
321− }
322−
323− it "allows to retrieve value-less matrix parameters" $
324− (flip runSession) (serve matrixParamApi mpServer) $ do
325− response3 <- Network.Wai.Test.request defaultRequest{
326− pathInfo = ["c;capitalize"]
327− }
328− liftIO $
329− decode' (simpleBody response3) `shouldBe` Just alice{
330− name = "ALICE"
331− }
332−
333− response3' <- Network.Wai.Test.request defaultRequest{
334− pathInfo = ["c;capitalize="]
335− }
336− liftIO $
337− decode' (simpleBody response3') `shouldBe` Just alice{
338− name = "ALICE"
339− }
340−
341− it "allows to retrieve matrix parameters on captured segments" $
342− (flip runSession) (serve matrixParamApi mpServer) $ do
343− response4 <- Network.Wai.Test.request defaultRequest{
344− pathInfo = ["d", "12;name=stephen;capitalize", "dsub"]
345− }
346− liftIO $
347− decode' (simpleBody response4) `shouldBe` Just alice{
348− name = "STEPHEN",
349− age = 12
350− }
351−
352− response4' <- Network.Wai.Test.request defaultRequest{
353− pathInfo = ["d;ignored=1", "5", "dsub"]
354− }
355− liftIO $
356− decode' (simpleBody response4') `shouldBe` Just alice{
357− name = "Alice",
358− age = 5
359− }
360−
361type PostApi =
277type PostApi =
362 ReqBody '[JSON] Person :> Post '[JSON] Integer
278 ReqBody '[JSON] Person :> Post '[JSON] Integer
363 :<|> "bla" :> ReqBody '[JSON] Person :> Post '[JSON] Integer
279 :<|> "bla" :> ReqBody '[JSON] Person :> Post '[JSON] Integer
servant/CHANGELOG.md+1
@@ -5,6 +5,7 @@ HEAD
5* Fix safeLink, so Header is not in fact required.
5* Fix safeLink, so Header is not in fact required.
6* Add more instances for (:<|>)
6* Add more instances for (:<|>)
7* Use `http-api-data` instead of `Servant.Common.Text`
7* Use `http-api-data` instead of `Servant.Common.Text`
8+* Remove matrix params.
8
9
90.4.2
100.4.2
10-----
11-----
servant/servant.cabal−1
@@ -37,7 +37,6 @@ library
37 Servant.API.Post
37 Servant.API.Post
38 Servant.API.Put
38 Servant.API.Put
39 Servant.API.QueryParam
39 Servant.API.QueryParam
40− Servant.API.MatrixParam
41 Servant.API.Raw
40 Servant.API.Raw
42 Servant.API.RemoteHost
41 Servant.API.RemoteHost
43 Servant.API.ReqBody
42 Servant.API.ReqBody
servant/src/Servant/API.hs−4
@@ -17,8 +17,6 @@ module Servant.API (
17 -- | Retrieving parameters from the query string of the 'URI': @'QueryParam'@
17 -- | Retrieving parameters from the query string of the 'URI': @'QueryParam'@
18 module Servant.API.ReqBody,
18 module Servant.API.ReqBody,
19 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
19 -- | Accessing the request body as a JSON-encoded type: @'ReqBody'@
20− module Servant.API.MatrixParam,
21− -- | Retrieving matrix parameters from the 'URI' segment: @'MatrixParam'@
22 module Servant.API.RemoteHost,
20 module Servant.API.RemoteHost,
23 -- | Retrieving the IP of the client
21 -- | Retrieving the IP of the client
24 module Servant.API.IsSecure,
22 module Servant.API.IsSecure,
@@ -71,8 +69,6 @@ import Servant.API.Get (Get)
71import Servant.API.Header (Header (..))
69import Servant.API.Header (Header (..))
72import Servant.API.HttpVersion (HttpVersion (..))
70import Servant.API.HttpVersion (HttpVersion (..))
73import Servant.API.IsSecure (IsSecure (..))
71import Servant.API.IsSecure (IsSecure (..))
74−import Servant.API.MatrixParam (MatrixFlag, MatrixParam,
75− MatrixParams)
76import Servant.API.Patch (Patch)
72import Servant.API.Patch (Patch)
77import Servant.API.Post (Post)
73import Servant.API.Post (Post)
78import Servant.API.Put (Put)
74import Servant.API.Put (Put)
servant/src/Servant/API/MatrixParam.hsdeleted−51
@@ -1,51 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE PolyKinds #-}
4−{-# OPTIONS_HADDOCK not-home #-}
5−module Servant.API.MatrixParam (MatrixFlag, MatrixParam, MatrixParams) where
6−
7−import Data.Typeable (Typeable)
8−import GHC.TypeLits (Symbol)
9−-- | Lookup the value associated to the @sym@ matrix string parameter
10−-- and try to extract it as a value of type @a@.
11−--
12−-- Example:
13−--
14−-- >>> -- /books;author=<author name>
15−-- >>> type MyApi = "books" :> MatrixParam "author" Text :> Get '[JSON] [Book]
16−data MatrixParam (sym :: Symbol) a
17− deriving (Typeable)
18−
19−-- | Lookup the values associated to the @sym@ matrix string parameter
20−-- and try to extract it as a value of type @[a]@. This is typically
21−-- meant to support matrix string parameters of the form
22−-- @param[]=val1;param[]=val2@ and so on. Note that servant doesn't actually
23−-- require the @[]@s and will fetch the values just fine with
24−-- @param=val1;param=val2@, too.
25−--
26−-- Example:
27−--
28−-- >>> -- /books;authors[]=<author1>;authors[]=<author2>;...
29−-- >>> type MyApi = "books" :> MatrixParams "authors" Text :> Get '[JSON] [Book]
30−data MatrixParams (sym :: Symbol) a
31− deriving (Typeable)
32−
33−-- | Lookup a potentially value-less matrix string parameter
34−-- with boolean semantics. If the param @sym@ is there without any value,
35−-- or if it's there with value "true" or "1", it's interpreted as 'True'.
36−-- Otherwise, it's interpreted as 'False'.
37−--
38−-- Example:
39−--
40−-- >>> -- /books;published
41−-- >>> type MyApi = "books" :> MatrixFlag "published" :> Get '[JSON] [Book]
42−data MatrixFlag (sym :: Symbol)
43− deriving (Typeable)
44−
45−
46−-- $setup
47−-- >>> import Servant.API
48−-- >>> import Data.Aeson
49−-- >>> import Data.Text
50−-- >>> data Book
51−-- >>> instance ToJSON Book where { toJSON = undefined }
servant/src/Servant/Utils/Links.hs+1−50
@@ -117,7 +117,6 @@ import Web.HttpApiData
117import Servant.API.Capture ( Capture )
117import Servant.API.Capture ( Capture )
118import Servant.API.ReqBody ( ReqBody )
118import Servant.API.ReqBody ( ReqBody )
119import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
119import Servant.API.QueryParam ( QueryParam, QueryParams, QueryFlag )
120−import Servant.API.MatrixParam ( MatrixParam, MatrixParams, MatrixFlag )
121import Servant.API.Header ( Header )
120import Servant.API.Header ( Header )
122import Servant.API.Get ( Get )
121import Servant.API.Get ( Get )
123import Servant.API.Post ( Post )
122import Servant.API.Post ( Post )
@@ -173,9 +172,6 @@ type family IsElem endpoint api :: Constraint where
173 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
172 IsElem sa (QueryParam x y :> sb) = IsElem sa sb
174 IsElem sa (QueryParams x y :> sb) = IsElem sa sb
173 IsElem sa (QueryParams x y :> sb) = IsElem sa sb
175 IsElem sa (QueryFlag x :> sb) = IsElem sa sb
174 IsElem sa (QueryFlag x :> sb) = IsElem sa sb
176− IsElem sa (MatrixParam x y :> sb) = IsElem sa sb
177− IsElem sa (MatrixParams x y :> sb) = IsElem sa sb
178− IsElem sa (MatrixFlag x :> sb) = IsElem sa sb
179 IsElem (Get ct typ) (Get ct' typ) = IsSubList ct ct'
175 IsElem (Get ct typ) (Get ct' typ) = IsSubList ct ct'
180 IsElem (Post ct typ) (Post ct' typ) = IsSubList ct ct'
176 IsElem (Post ct typ) (Post ct' typ) = IsSubList ct ct'
181 IsElem (Put ct typ) (Put ct' typ) = IsSubList ct ct'
177 IsElem (Put ct typ) (Put ct' typ) = IsSubList ct ct'
@@ -192,10 +188,9 @@ type family IsSubList a b :: Constraint where
192 IsSubList (x ': xs) y = IsSubList '[x] y `And` IsSubList xs y
188 IsSubList (x ': xs) y = IsSubList '[x] y `And` IsSubList xs y
193
189
194-- Phantom types for Param
190-- Phantom types for Param
195−data Matrix
196data Query
191data Query
197
192
198−-- | Query/Matrix param
193+-- | Query param
199data Param a
194data Param a
200 = SingleParam String Text
195 = SingleParam String Text
201 | ArrayElemParam String Text
196 | ArrayElemParam String Text
@@ -209,21 +204,6 @@ addQueryParam :: Param Query -> Link -> Link
209addQueryParam qp l =
204addQueryParam qp l =
210 l { _queryParams = _queryParams l <> [qp] }
205 l { _queryParams = _queryParams l <> [qp] }
211
206
212−-- Not particularly efficient for many updates. Something to optimise if it's
213−-- a problem.
214−addMatrixParam :: Param Matrix -> Link -> Link
215−addMatrixParam param l = l { _segments = f (_segments l) }
216− where
217− f [] = []
218− f xs = init xs <> [g (last xs)]
219− -- Modify the segment at the "top" of the stack
220− g :: String -> String
221− g seg =
222− case param of
223− SingleParam k v -> seg <> ";" <> k <> "=" <> escape (unpack v)
224− ArrayElemParam k v -> seg <> ";" <> k <> "[]=" <> escape (unpack v)
225− FlagParam k -> seg <> ";" <> k
226−
227linkURI :: Link -> URI
207linkURI :: Link -> URI
228linkURI (Link segments q_params) =
208linkURI (Link segments q_params) =
229 URI mempty -- No scheme (relative)
209 URI mempty -- No scheme (relative)
@@ -300,35 +280,6 @@ instance (KnownSymbol sym, HasLink sub)
300 where
280 where
301 k = symbolVal (Proxy :: Proxy sym)
281 k = symbolVal (Proxy :: Proxy sym)
302
282
303−-- MatrixParam instances
304−instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
305− => HasLink (MatrixParam sym v :> sub) where
306− type MkLink (MatrixParam sym v :> sub) = Maybe v -> MkLink sub
307− toLink _ l mv =
308− toLink (Proxy :: Proxy sub) $
309− maybe id (addMatrixParam . SingleParam k . toQueryParam) mv l
310− where
311− k = symbolVal (Proxy :: Proxy sym)
312−
313−instance (KnownSymbol sym, ToHttpApiData v, HasLink sub)
314− => HasLink (MatrixParams sym v :> sub) where
315− type MkLink (MatrixParams sym v :> sub) = [v] -> MkLink sub
316− toLink _ l =
317− toLink (Proxy :: Proxy sub) .
318− foldl' (\l' v -> addMatrixParam (ArrayElemParam k (toQueryParam v)) l') l
319− where
320− k = symbolVal (Proxy :: Proxy sym)
321−
322−instance (KnownSymbol sym, HasLink sub)
323− => HasLink (MatrixFlag sym :> sub) where
324− type MkLink (MatrixFlag sym :> sub) = Bool -> MkLink sub
325− toLink _ l False =
326− toLink (Proxy :: Proxy sub) l
327− toLink _ l True =
328− toLink (Proxy :: Proxy sub) $ addMatrixParam (FlagParam k) l
329− where
330− k = symbolVal (Proxy :: Proxy sym)
331−
332-- Misc instances
283-- Misc instances
333instance HasLink sub => HasLink (ReqBody ct a :> sub) where
284instance HasLink sub => HasLink (ReqBody ct a :> sub) where
334 type MkLink (ReqBody ct a :> sub) = MkLink sub
285 type MkLink (ReqBody ct a :> sub) = MkLink sub
servant/test/Servant/Utils/LinksSpec.hs+3−20
@@ -11,14 +11,10 @@ import Data.Proxy ( Proxy(..) )
11import Servant.API
11import Servant.API
12
12
13type TestApi =
13type TestApi =
14− -- Capture and query/matrix params
14+ -- Capture and query params
15 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Delete '[JSON] ()
15 "hello" :> Capture "name" String :> QueryParam "capital" Bool :> Delete '[JSON] ()
16
16
17− :<|> "parent" :> MatrixParams "name" String :> "child"
18− :> MatrixParam "gender" String :> Get '[JSON] String
19−
20 -- Flags
17 -- Flags
21− :<|> "ducks" :> MatrixFlag "yellow" :> MatrixFlag "loud" :> Delete '[JSON] ()
22 :<|> "balls" :> QueryFlag "bouncy" :> QueryFlag "fast" :> Delete '[JSON] ()
18 :<|> "balls" :> QueryFlag "bouncy" :> QueryFlag "fast" :> Delete '[JSON] ()
23
19
24 -- All of the verbs
20 -- All of the verbs
@@ -34,7 +30,6 @@ type TestLink3 = "parent" :> "child" :> Get '[JSON] String
34
30
35type BadTestLink = "hallo" :> "hi" :> Get '[JSON] Bool
31type BadTestLink = "hallo" :> "hi" :> Get '[JSON] Bool
36type BadTestLink2 = "greet" :> Get '[PlainText] Bool
32type BadTestLink2 = "greet" :> Get '[PlainText] Bool
37−type BadTestLink3 = "parent" :> "child" :> MatrixFlag "male" :> Get '[JSON] String
38
33
39type BadTestLink' = "hello" :> "hi" :> Get '[OctetStream] Bool
34type BadTestLink' = "hello" :> "hi" :> Get '[OctetStream] Bool
40type BadTestLink'2 = "greet" :> Get '[OctetStream] Bool
35type BadTestLink'2 = "greet" :> Get '[OctetStream] Bool
@@ -54,7 +49,7 @@ shouldBeURI link expected =
54
49
55spec :: Spec
50spec :: Spec
56spec = describe "Servant.Utils.Links" $ do
51spec = describe "Servant.Utils.Links" $ do
57− it "Generates correct links for capture query and matrix params" $ do
52+ it "Generates correct links for capture query params" $ do
58 let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete '[JSON] ())
53 let l1 = Proxy :: Proxy ("hello" :> Capture "name" String :> Delete '[JSON] ())
59 apiLink l1 "hi" `shouldBeURI` "hello/hi"
54 apiLink l1 "hi" `shouldBeURI` "hello/hi"
60
55
@@ -63,25 +58,13 @@ spec = describe "Servant.Utils.Links" $ do
63 :> Delete '[JSON] ())
58 :> Delete '[JSON] ())
64 apiLink l2 "bye" (Just True) `shouldBeURI` "hello/bye?capital=true"
59 apiLink l2 "bye" (Just True) `shouldBeURI` "hello/bye?capital=true"
65
60
66− let l3 = Proxy :: Proxy ("parent" :> MatrixParams "name" String
67− :> "child"
68− :> MatrixParam "gender" String
69− :> Get '[JSON] String)
70− apiLink l3 ["Hubert?x=;&", "Cumberdale"] (Just "Edward?")
71− `shouldBeURI` "parent;name[]=Hubert%3Fx%3D%3B%26;\
72− \name[]=Cumberdale/child;gender=Edward%3F"
73
61
74− it "Generates correct links for query and matrix flags" $ do
62+ it "Generates correct links for query flags" $ do
75 let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"
63 let l1 = Proxy :: Proxy ("balls" :> QueryFlag "bouncy"
76 :> QueryFlag "fast" :> Delete '[JSON] ())
64 :> QueryFlag "fast" :> Delete '[JSON] ())
77 apiLink l1 True True `shouldBeURI` "balls?bouncy&fast"
65 apiLink l1 True True `shouldBeURI` "balls?bouncy&fast"
78 apiLink l1 False True `shouldBeURI` "balls?fast"
66 apiLink l1 False True `shouldBeURI` "balls?fast"
79
67
80− let l2 = Proxy :: Proxy ("ducks" :> MatrixFlag "yellow"
81− :> MatrixFlag "loud" :> Delete '[JSON] ())
82− apiLink l2 True True `shouldBeURI` "ducks;yellow;loud"
83− apiLink l2 False True `shouldBeURI` "ducks;loud"
84−
85 it "Generates correct links for all of the verbs" $ do
68 it "Generates correct links for all of the verbs" $ do
86 apiLink (Proxy :: Proxy ("get" :> Get '[JSON] ())) `shouldBeURI` "get"
69 apiLink (Proxy :: Proxy ("get" :> Get '[JSON] ())) `shouldBeURI` "get"
87 apiLink (Proxy :: Proxy ("put" :> Put '[JSON] ())) `shouldBeURI` "put"
70 apiLink (Proxy :: Proxy ("put" :> Put '[JSON] ())) `shouldBeURI` "put"