on master help sign in
Merge pull request #2 from dlarsson/master
83e46f4Julian Arni
.gitignorenew file+17
@@ -0,0 +1,17 @@
1+dist
2+cabal-dev
3+*.o
4+*.hi
5+*.chi
6+*.chs.h
7+*.dyn_o
8+*.dyn_hi
9+.virtualenv
10+.hpc
11+.hsenv
12+.cabal-sandbox/
13+cabal.sandbox.config
14+cabal.config
15+*.prof
16+*.aux
17+*.hp
servant-client.cabal+3−3
@@ -1,5 +1,5 @@
1name: servant-client
1name: servant-client
2−version: 0.2.1
2+version: 0.2.2
3synopsis: automatical derivation of querying functions for servant webservices
3synopsis: automatical derivation of querying functions for servant webservices
4description:
4description:
5 This library lets you derive automatically Haskell functions that
5 This library lets you derive automatically Haskell functions that
@@ -47,7 +47,7 @@ library
47 , http-types
47 , http-types
48 , network-uri >= 2.6
48 , network-uri >= 2.6
49 , safe
49 , safe
50− , servant >= 0.2.1
50+ , servant >= 0.2.2
51 , string-conversions
51 , string-conversions
52 , text
52 , text
53 , transformers
53 , transformers
@@ -71,7 +71,7 @@ test-suite spec
71 , hspec == 2.*
71 , hspec == 2.*
72 , http-types
72 , http-types
73 , network >= 2.6
73 , network >= 2.6
74− , QuickCheck
74+ , QuickCheck >= 2.7
75 , servant >= 0.2.1
75 , servant >= 0.2.1
76 , servant-client
76 , servant-client
77 , servant-server >= 0.2.1
77 , servant-server >= 0.2.1
src/Servant/Client.hs+117−2
@@ -210,7 +210,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout)
210 -- if mparam = Nothing, we don't add it to the query string
210 -- if mparam = Nothing, we don't add it to the query string
211 clientWithRoute Proxy req mparam =
211 clientWithRoute Proxy req mparam =
212 clientWithRoute (Proxy :: Proxy sublayout) $
212 clientWithRoute (Proxy :: Proxy sublayout) $
213− appendToQueryString pname mparamText req
213+ maybe req (flip (appendToQueryString pname) req . Just) mparamText
214
214
215 where pname = cs pname'
215 where pname = cs pname'
216 pname' = symbolVal (Proxy :: Proxy sym)
216 pname' = symbolVal (Proxy :: Proxy sym)
@@ -251,7 +251,7 @@ instance (KnownSymbol sym, ToText a, HasClient sublayout)
251
251
252 clientWithRoute Proxy req paramlist =
252 clientWithRoute Proxy req paramlist =
253 clientWithRoute (Proxy :: Proxy sublayout) $
253 clientWithRoute (Proxy :: Proxy sublayout) $
254− foldl' (\ value req' -> appendToQueryString pname req' value) req paramlist'
254+ foldl' (\ req' -> maybe req' (flip (appendToQueryString pname) req' . Just)) req paramlist'
255
255
256 where pname = cs pname'
256 where pname = cs pname'
257 pname' = symbolVal (Proxy :: Proxy sym)
257 pname' = symbolVal (Proxy :: Proxy sym)
@@ -292,6 +292,121 @@ instance (KnownSymbol sym, HasClient sublayout)
292
292
293 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
293 where paramname = cs $ symbolVal (Proxy :: Proxy sym)
294
294
295+-- | If you use a 'MatrixParam' in one of your endpoints in your API,
296+-- the corresponding querying function will automatically take
297+-- an additional argument of the type specified by your 'MatrixParam',
298+-- enclosed in Maybe.
299+--
300+-- If you give Nothing, nothing will be added to the query string.
301+--
302+-- If you give a non-'Nothing' value, this function will take care
303+-- of inserting a textual representation of this value in the query string.
304+--
305+-- You can control how values for your type are turned into
306+-- text by specifying a 'ToText' instance for your type.
307+--
308+-- Example:
309+--
310+-- > type MyApi = "books" :> MatrixParam "author" Text :> Get [Book]
311+-- >
312+-- > myApi :: Proxy MyApi
313+-- > myApi = Proxy
314+-- >
315+-- > getBooksBy :: Maybe Text -> BaseUrl -> EitherT String IO [Book]
316+-- > getBooksBy = client myApi
317+-- > -- then you can just use "getBooksBy" to query that endpoint.
318+-- > -- 'getBooksBy Nothing' for all books
319+-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
320+instance (KnownSymbol sym, ToText a, HasClient sublayout)
321+ => HasClient (MatrixParam sym a :> sublayout) where
322+
323+ type Client (MatrixParam sym a :> sublayout) =
324+ Maybe a -> Client sublayout
325+
326+ -- if mparam = Nothing, we don't add it to the query string
327+ clientWithRoute Proxy req mparam =
328+ clientWithRoute (Proxy :: Proxy sublayout) $
329+ maybe req (flip (appendToMatrixParams pname . Just) req) mparamText
330+
331+ where pname = symbolVal (Proxy :: Proxy sym)
332+ mparamText = fmap (cs . toText) mparam
333+
334+-- | If you use a 'MatrixParams' in one of your endpoints in your API,
335+-- the corresponding querying function will automatically take an
336+-- additional argument, a list of values of the type specified by your
337+-- 'MatrixParams'.
338+--
339+-- If you give an empty list, nothing will be added to the query string.
340+--
341+-- Otherwise, this function will take care of inserting a textual
342+-- representation of your values in the path segment string, under the
343+-- same matrix string parameter name.
344+--
345+-- You can control how values for your type are turned into text by
346+-- specifying a 'ToText' instance for your type.
347+--
348+-- Example:
349+--
350+-- > type MyApi = "books" :> MatrixParams "authors" Text :> Get [Book]
351+-- >
352+-- > myApi :: Proxy MyApi
353+-- > myApi = Proxy
354+-- >
355+-- > getBooksBy :: [Text] -> BaseUrl -> EitherT String IO [Book]
356+-- > getBooksBy = client myApi
357+-- > -- then you can just use "getBooksBy" to query that endpoint.
358+-- > -- 'getBooksBy []' for all books
359+-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
360+-- > -- to get all books by Asimov and Heinlein
361+instance (KnownSymbol sym, ToText a, HasClient sublayout)
362+ => HasClient (MatrixParams sym a :> sublayout) where
363+
364+ type Client (MatrixParams sym a :> sublayout) =
365+ [a] -> Client sublayout
366+
367+ clientWithRoute Proxy req paramlist =
368+ clientWithRoute (Proxy :: Proxy sublayout) $
369+ foldl' (\ req' value -> maybe req' (flip (appendToMatrixParams pname) req' . Just . cs) value) req paramlist'
370+
371+ where pname = cs pname'
372+ pname' = symbolVal (Proxy :: Proxy sym)
373+ paramlist' = map (Just . toText) paramlist
374+
375+-- | If you use a 'MatrixFlag' in one of your endpoints in your API,
376+-- the corresponding querying function will automatically take an
377+-- additional 'Bool' argument.
378+--
379+-- If you give 'False', nothing will be added to the path segment.
380+--
381+-- Otherwise, this function will insert a value-less matrix parameter
382+-- under the name associated to your 'MatrixFlag'.
383+--
384+-- Example:
385+--
386+-- > type MyApi = "books" :> MatrixFlag "published" :> Get [Book]
387+-- >
388+-- > myApi :: Proxy MyApi
389+-- > myApi = Proxy
390+-- >
391+-- > getBooks :: Bool -> BaseUrl -> EitherT String IO [Book]
392+-- > getBooks = client myApi
393+-- > -- then you can just use "getBooks" to query that endpoint.
394+-- > -- 'getBooksBy False' for all books
395+-- > -- 'getBooksBy True' to only get _already published_ books
396+instance (KnownSymbol sym, HasClient sublayout)
397+ => HasClient (MatrixFlag sym :> sublayout) where
398+
399+ type Client (MatrixFlag sym :> sublayout) =
400+ Bool -> Client sublayout
401+
402+ clientWithRoute Proxy req flag =
403+ clientWithRoute (Proxy :: Proxy sublayout) $
404+ if flag
405+ then appendToMatrixParams paramname Nothing req
406+ else req
407+
408+ where paramname = cs $ symbolVal (Proxy :: Proxy sym)
409+
295-- | Pick a 'Method' and specify where the server you want to query is. You get
410-- | Pick a 'Method' and specify where the server you want to query is. You get
296-- back the status code and the response body as a 'ByteString'.
411-- back the status code and the response body as a 'ByteString'.
297instance HasClient Raw where
412instance HasClient Raw where
src/Servant/Common/Req.hs+7
@@ -41,6 +41,13 @@ appendToPath :: String -> Req -> Req
41appendToPath p req =
41appendToPath p req =
42 req { reqPath = reqPath req ++ "/" ++ p }
42 req { reqPath = reqPath req ++ "/" ++ p }
43
43
44+appendToMatrixParams :: String
45+ -> Maybe String
46+ -> Req
47+ -> Req
48+appendToMatrixParams pname pvalue req =
49+ req { reqPath = reqPath req ++ ";" ++ pname ++ maybe "" ("=" ++) pvalue }
50+
44appendToQueryString :: Text -- ^ param name
51appendToQueryString :: Text -- ^ param name
45 -> Maybe Text -- ^ param value
52 -> Maybe Text -- ^ param value
46 -> Req
53 -> Req
test/Servant/ClientSpec.hs+31−1
@@ -4,7 +4,7 @@
4{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE TypeOperators #-}
5{-# LANGUAGE TypeOperators #-}
6{-# LANGUAGE OverloadedStrings #-}
6{-# LANGUAGE OverloadedStrings #-}
7−
7+{-# OPTIONS_GHC -fcontext-stack=25 #-}
8module Servant.ClientSpec where
8module Servant.ClientSpec where
9
9
10import Control.Concurrent
10import Control.Concurrent
@@ -50,6 +50,9 @@ type Api =
50 :<|> "param" :> QueryParam "name" String :> Get Person
50 :<|> "param" :> QueryParam "name" String :> Get Person
51 :<|> "params" :> QueryParams "names" String :> Get [Person]
51 :<|> "params" :> QueryParams "names" String :> Get [Person]
52 :<|> "flag" :> QueryFlag "flag" :> Get Bool
52 :<|> "flag" :> QueryFlag "flag" :> Get Bool
53+ :<|> "matrixparam" :> MatrixParam "name" String :> Get Person
54+ :<|> "matrixparams" :> MatrixParams "name" String :> Get [Person]
55+ :<|> "matrixflag" :> MatrixFlag "flag" :> Get Bool
53 :<|> "rawSuccess" :> Raw
56 :<|> "rawSuccess" :> Raw
54 :<|> "rawFailure" :> Raw
57 :<|> "rawFailure" :> Raw
55 :<|> "multiple" :>
58 :<|> "multiple" :>
@@ -72,6 +75,12 @@ server = serve api (
72 Nothing -> left (400, "missing parameter"))
75 Nothing -> left (400, "missing parameter"))
73 :<|> (\ names -> return (zipWith Person names [0..]))
76 :<|> (\ names -> return (zipWith Person names [0..]))
74 :<|> return
77 :<|> return
78+ :<|> (\ name -> case name of
79+ Just "alice" -> return alice
80+ Just name -> left (400, name ++ " not found")
81+ Nothing -> left (400, "missing parameter"))
82+ :<|> (\ names -> return (zipWith Person names [0..]))
83+ :<|> return
75 :<|> (\ _request respond -> respond $ responseLBS ok200 [] "rawSuccess")
84 :<|> (\ _request respond -> respond $ responseLBS ok200 [] "rawSuccess")
76 :<|> (\ _request respond -> respond $ responseLBS badRequest400 [] "rawFailure")
85 :<|> (\ _request respond -> respond $ responseLBS badRequest400 [] "rawFailure")
77 :<|> \ a b c d -> return (a, b, c, d)
86 :<|> \ a b c d -> return (a, b, c, d)
@@ -86,6 +95,9 @@ getBody :: Person -> BaseUrl -> EitherT String IO Person
86getQueryParam :: Maybe String -> BaseUrl -> EitherT String IO Person
95getQueryParam :: Maybe String -> BaseUrl -> EitherT String IO Person
87getQueryParams :: [String] -> BaseUrl -> EitherT String IO [Person]
96getQueryParams :: [String] -> BaseUrl -> EitherT String IO [Person]
88getQueryFlag :: Bool -> BaseUrl -> EitherT String IO Bool
97getQueryFlag :: Bool -> BaseUrl -> EitherT String IO Bool
98+getMatrixParam :: Maybe String -> BaseUrl -> EitherT String IO Person
99+getMatrixParams :: [String] -> BaseUrl -> EitherT String IO [Person]
100+getMatrixFlag :: Bool -> BaseUrl -> EitherT String IO Bool
89getRawSuccess :: Method -> BaseUrl -> EitherT String IO (Int, ByteString)
101getRawSuccess :: Method -> BaseUrl -> EitherT String IO (Int, ByteString)
90getRawFailure :: Method -> BaseUrl -> EitherT String IO (Int, ByteString)
102getRawFailure :: Method -> BaseUrl -> EitherT String IO (Int, ByteString)
91getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
103getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
@@ -97,6 +109,9 @@ getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
97 :<|> getQueryParam
109 :<|> getQueryParam
98 :<|> getQueryParams
110 :<|> getQueryParams
99 :<|> getQueryFlag
111 :<|> getQueryFlag
112+ :<|> getMatrixParam
113+ :<|> getMatrixParams
114+ :<|> getMatrixFlag
100 :<|> getRawSuccess
115 :<|> getRawSuccess
101 :<|> getRawFailure
116 :<|> getRawFailure
102 :<|> getMultiple)
117 :<|> getMultiple)
@@ -129,6 +144,21 @@ spec = do
129 it (show flag) $ withServer $ \ host -> do
144 it (show flag) $ withServer $ \ host -> do
130 runEitherT (getQueryFlag flag host) `shouldReturn` Right flag
145 runEitherT (getQueryFlag flag host) `shouldReturn` Right flag
131
146
147+ it "Servant.API.MatrixParam" $ withServer $ \ host -> do
148+ runEitherT (getMatrixParam (Just "alice") host) `shouldReturn` Right alice
149+ Left result <- runEitherT (getMatrixParam (Just "bob") host)
150+ result `shouldContain` "bob not found"
151+
152+ it "Servant.API.MatrixParam.MatrixParams" $ withServer $ \ host -> do
153+ runEitherT (getMatrixParams [] host) `shouldReturn` Right []
154+ runEitherT (getMatrixParams ["alice", "bob"] host)
155+ `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
156+
157+ context "Servant.API.MatrixParam.MatrixFlag" $
158+ forM_ [False, True] $ \ flag ->
159+ it (show flag) $ withServer $ \ host -> do
160+ runEitherT (getMatrixFlag flag host) `shouldReturn` Right flag
161+
132 it "Servant.API.Raw on success" $ withServer $ \ host -> do
162 it "Servant.API.Raw on success" $ withServer $ \ host -> do
133 runEitherT (getRawSuccess methodGet host) `shouldReturn` Right (200, "rawSuccess")
163 runEitherT (getRawSuccess methodGet host) `shouldReturn` Right (200, "rawSuccess")
134
164