@@ -21,34 +21,52 @@ module Servant.Client.Core
21 , AuthenticateReq(..)
21 , AuthenticateReq(..)
23 , HasClient(..)
23 , HasClient(..)
25− , runClientM, inClientM, clientM
26− , ClientEnv (ClientEnv)
27 , mkAuthenticateReq
24 , mkAuthenticateReq
28 , ServantError(..)
25 , ServantError(..)
29 , EmptyClient(..)
26 , EmptyClient(..)
30− , module Servant.Common.BaseUrl
27+ , module Servant.Client.Core.Internal.BaseUrl
33−import Data.ByteString.Lazy (ByteString)
30+import Control.Monad.Error.Class (throwError)
34−import Data.List
31+import Data.List (foldl')
35−import Data.Proxy
32+import Data.Proxy (Proxy (Proxy))
36−import Data.String.Conversions
33+import Data.String (fromString)
37−import Data.Text (unpack)
34+import Data.Text (pack)
35+import GHC.Exts (fromList, toList)
38−import GHC.TypeLits
36+import GHC.TypeLits (KnownSymbol, symbolVal)
39−import Network.HTTP.Client (Response)
40−import Network.HTTP.Media
41−import qualified Network.HTTP.Types as H
37+import qualified Network.HTTP.Types as H
42−import qualified Network.HTTP.Types.Header as HTTP
43−import Prelude ()
38+import Prelude ()
44import Prelude.Compat
39import Prelude.Compat
45−import Servant.API
40+import Servant.API ((:<|>) ((:<|>)), (:>),
41+ AuthProtect, BasicAuth,
45+ Description, EmptyAPI,
46+ Header, Headers (..),
47+ HttpVersion, IsSecure,
48+ MimeRender (mimeRender),
49+ MimeUnrender (mimeUnrender),
50+ NoContent (NoContent),
51+ QueryFlag, QueryParam,
55+ Summary, ToHttpApiData,
63+import Servant.API.ContentTypes (contentTypes)
46−import Servant.Client.Experimental.Auth
65+import Servant.Client.Core.Internal.Auth
47−import Servant.Client.HttpClient
66+import Servant.Client.Core.Internal.BaseUrl
48−import Servant.Client.Class
49−import Servant.Common.BaseUrl
50−import Servant.Common.BasicAuth
67+import Servant.Client.Core.Internal.BasicAuth
51−import Servant.Common.Req
68+import Servant.Client.Core.Internal.Class
69+import Servant.Client.Core.Internal.Request
53-- * Accessing APIs as a Client
71-- * Accessing APIs as a Client
@@ -68,25 +86,15 @@ import Servant.Common.Req
68-- > postNewBook :: Book -> ClientM Book
86-- > postNewBook :: Book -> ClientM Book
69-- > (getAllBooks :<|> postNewBook) = client clientM myApi
87-- > (getAllBooks :<|> postNewBook) = client clientM myApi
70client :: HasClient m api => Proxy m -> Proxy api -> Client m api
88client :: HasClient m api => Proxy m -> Proxy api -> Client m api
71−client pm p = clientWithRoute pm p defReq
89+client pm p = clientWithRoute pm p defaultRequest
73−-- | Helper proxy to simplify common case of working in `ClientM` monad
74−inClientM :: Proxy ClientM
77−-- | Convenience method to declare clients running in the `ClientM` monad.
79−-- Simply pass `inClientM` to `client`....
80−clientM :: (HasClient ClientM api) => Proxy api -> Client ClientM api
81−clientM = client inClientM
84-- | This class lets us define how each API combinator
92-- | This class lets us define how each API combinator
85-- influences the creation of an HTTP request. It's mostly
93-- influences the creation of an HTTP request. It's mostly
86-- an internal class, you can just use 'client'.
94-- an internal class, you can just use 'client'.
87−class HasClient m api where
95+class RunClient m => HasClient m api where
88 type Client (m :: * -> *) (api :: *) :: *
96 type Client (m :: * -> *) (api :: *) :: *
89− clientWithRoute :: Proxy m -> Proxy api -> Req -> Client m api
97+ clientWithRoute :: Proxy m -> Proxy api -> Request -> Client m api
92-- | A client querying function for @a ':<|>' b@ will actually hand you
100-- | A client querying function for @a ':<|>' b@ will actually hand you
@@ -121,7 +129,7 @@ data EmptyClient = EmptyClient deriving (Eq, Show, Bounded, Enum)
122-- > getAllBooks :: ClientM [Book]
130-- > getAllBooks :: ClientM [Book]
123-- > (getAllBooks :<|> EmptyClient) = client myApi
131-- > (getAllBooks :<|> EmptyClient) = client myApi
124−instance HasClient m EmptyAPI where
132+instance RunClient m => HasClient m EmptyAPI where
125 type Client m EmptyAPI = EmptyClient
133 type Client m EmptyAPI = EmptyClient
126 clientWithRoute _pm Proxy _ = EmptyClient
134 clientWithRoute _pm Proxy _ = EmptyClient
@@ -154,7 +162,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m api)
154 clientWithRoute pm (Proxy :: Proxy api)
162 clientWithRoute pm (Proxy :: Proxy api)
155 (appendToPath p req)
163 (appendToPath p req)
157− where p = unpack (toUrlPiece val)
165+ where p = (toUrlPiece val)
159-- | If you use a 'CaptureAll' in one of your endpoints in your API,
167-- | If you use a 'CaptureAll' in one of your endpoints in your API,
160-- the corresponding querying function will automatically take an
168-- the corresponding querying function will automatically take an
@@ -186,53 +194,65 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
186 clientWithRoute pm (Proxy :: Proxy sublayout)
194 clientWithRoute pm (Proxy :: Proxy sublayout)
187 (foldl' (flip appendToPath) req ps)
195 (foldl' (flip appendToPath) req ps)
189− where ps = map (unpack . toUrlPiece) vals
197+ where ps = map (toUrlPiece) vals
191instance OVERLAPPABLE_
199instance OVERLAPPABLE_
192 -- Note [Non-Empty Content Types]
200 -- Note [Non-Empty Content Types]
193− (RunClient m ct ([H.Header], a), MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
201+ ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
194 ) => HasClient m (Verb method status cts' a) where
202 ) => HasClient m (Verb method status cts' a) where
195 type Client m (Verb method status cts' a) = m a
203 type Client m (Verb method status cts' a) = m a
196 clientWithRoute _pm Proxy req = do
204 clientWithRoute _pm Proxy req = do
197− (_hdrs, a) :: ([H.Header], a) <- runRequest (Proxy :: Proxy ct) method req
205+ response <- runRequest req
206+ { requestAccept = fromList $ toList accept
207+ , requestMethod = method
209+ case mimeUnrender (Proxy :: Proxy ct) $ responseBody response of
210+ Left err -> throwError $ DecodeFailure (pack err) response
198− return a
211+ Right val -> return val
199 where method = reflectMethod (Proxy :: Proxy method)
212 where method = reflectMethod (Proxy :: Proxy method)
213+ accept = contentTypes (Proxy :: Proxy ct)
201instance OVERLAPPING_
215instance OVERLAPPING_
202− ( RunClient m NoContent [HTTP.Header]
216+ ( RunClient m, ReflectMethod method
203− , ReflectMethod method) => HasClient m (Verb method status cts NoContent) where
217+ ) => HasClient m (Verb method status cts NoContent) where
204 type Client m (Verb method status cts NoContent)
218 type Client m (Verb method status cts NoContent)
205 = m NoContent
219 = m NoContent
206 clientWithRoute _pm Proxy req = do
220 clientWithRoute _pm Proxy req = do
207− _hdrs :: [H.Header] <- runRequest (Proxy :: Proxy NoContent) method req
221+ _response <- runRequest req { requestMethod = method }
208 return NoContent
222 return NoContent
209 where method = reflectMethod (Proxy :: Proxy method)
223 where method = reflectMethod (Proxy :: Proxy method)
211instance OVERLAPPING_
225instance OVERLAPPING_
212 -- Note [Non-Empty Content Types]
226 -- Note [Non-Empty Content Types]
213− ( RunClient m ct ([H.Header], a)
227+ ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls
214− , MimeUnrender ct a, BuildHeadersTo ls, ReflectMethod method, cts' ~ (ct ': cts)
228+ , ReflectMethod method, cts' ~ (ct ': cts)
215 ) => HasClient m (Verb method status cts' (Headers ls a)) where
229 ) => HasClient m (Verb method status cts' (Headers ls a)) where
216 type Client m (Verb method status cts' (Headers ls a))
230 type Client m (Verb method status cts' (Headers ls a))
217 = m (Headers ls a)
231 = m (Headers ls a)
218 clientWithRoute _pm Proxy req = do
232 clientWithRoute _pm Proxy req = do
219− let method = reflectMethod (Proxy :: Proxy method)
220− (hdrs, resp) <- runRequest (Proxy :: Proxy ct) method req
233+ response <- runRequest req
234+ { requestMethod = method
235+ , requestAccept = fromList $ toList accept
237+ case mimeUnrender (Proxy :: Proxy ct) $ responseBody response of
238+ Left err -> throwError $ DecodeFailure (pack err) response
221− return $ Headers { getResponse = resp
239+ Right val -> return $ Headers
222− , getHeadersHList = buildHeadersTo hdrs
241+ , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
243+ where method = reflectMethod (Proxy :: Proxy method)
244+ accept = contentTypes (Proxy :: Proxy ct)
225instance OVERLAPPING_
246instance OVERLAPPING_
226− ( RunClient m NoContent [H.Header]
227− , BuildHeadersTo ls, ReflectMethod method
247+ ( RunClient m, BuildHeadersTo ls, ReflectMethod method
228 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
248 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
229 type Client m (Verb method status cts (Headers ls NoContent))
249 type Client m (Verb method status cts (Headers ls NoContent))
230 = m (Headers ls NoContent)
250 = m (Headers ls NoContent)
231 clientWithRoute _pm Proxy req = do
251 clientWithRoute _pm Proxy req = do
232 let method = reflectMethod (Proxy :: Proxy method)
252 let method = reflectMethod (Proxy :: Proxy method)
233− hdrs <- runRequest (Proxy :: Proxy NoContent) method req
253+ response <- runRequest req { requestMethod = method }
234 return $ Headers { getResponse = NoContent
254 return $ Headers { getResponse = NoContent
235− , getHeadersHList = buildHeadersTo hdrs
255+ , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
@@ -270,11 +290,11 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api)
270 clientWithRoute pm Proxy req mval =
290 clientWithRoute pm Proxy req mval =
271 clientWithRoute pm (Proxy :: Proxy api)
291 clientWithRoute pm (Proxy :: Proxy api)
272 (maybe req
292 (maybe req
273− (\value -> Servant.Common.Req.addHeader hname value req)
293+ (\value -> addHeader hname value req)
277− where hname = symbolVal (Proxy :: Proxy sym)
297+ where hname = fromString $ symbolVal (Proxy :: Proxy sym)
279-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
299-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
280-- functions.
300-- functions.
@@ -338,8 +358,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api)
338 mparamText
358 mparamText
341− where pname = cs pname'
342− pname' = symbolVal (Proxy :: Proxy sym)
361+ where pname = pack $ symbolVal (Proxy :: Proxy sym)
343 mparamText = fmap toQueryParam mparam
362 mparamText = fmap toQueryParam mparam
345-- | If you use a 'QueryParams' in one of your endpoints in your API,
364-- | If you use a 'QueryParams' in one of your endpoints in your API,
@@ -382,8 +401,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api)
382 paramlist'
401 paramlist'
385− where pname = cs pname'
386− pname' = symbolVal (Proxy :: Proxy sym)
404+ where pname = pack $ symbolVal (Proxy :: Proxy sym)
387 paramlist' = map (Just . toQueryParam) paramlist
405 paramlist' = map (Just . toQueryParam) paramlist
389-- | If you use a 'QueryFlag' in one of your endpoints in your API,
407-- | If you use a 'QueryFlag' in one of your endpoints in your API,
@@ -420,19 +438,18 @@ instance (KnownSymbol sym, HasClient m api)
423− where paramname = cs $ symbolVal (Proxy :: Proxy sym)
441+ where paramname = pack $ symbolVal (Proxy :: Proxy sym)
426-- | Pick a 'Method' and specify where the server you want to query is. You get
444-- | Pick a 'Method' and specify where the server you want to query is. You get
427-- back the full `Response`.
445-- back the full `Response`.
428−instance (RunClient m NoContent (Int, ByteString, MediaType, [HTTP.Header], Response ByteString))
429− => HasClient m Raw where
446+instance RunClient m => HasClient m Raw where
430 type Client m Raw
447 type Client m Raw
431− = H.Method -> m (Int, ByteString, MediaType, [HTTP.Header], Response ByteString)
448+ = H.Method -> m Response
433− clientWithRoute :: Proxy m -> Proxy Raw -> Req -> Client m Raw
450+ clientWithRoute :: Proxy m -> Proxy Raw -> Request -> Client m Raw
434 clientWithRoute _pm Proxy req httpMethod = do
451 clientWithRoute _pm Proxy req httpMethod = do
435− runRequest (Proxy :: Proxy NoContent) httpMethod req
452+ runRequest req { requestMethod = httpMethod }
437-- | If you use a 'ReqBody' in one of your endpoints in your API,
454-- | If you use a 'ReqBody' in one of your endpoints in your API,
438-- the corresponding querying function will automatically take
455-- the corresponding querying function will automatically take
@@ -461,10 +478,10 @@ instance (MimeRender ct a, HasClient m api)
461 clientWithRoute pm Proxy req body =
478 clientWithRoute pm Proxy req body =
462 clientWithRoute pm (Proxy :: Proxy api)
479 clientWithRoute pm (Proxy :: Proxy api)
463 (let ctProxy = Proxy :: Proxy ct
480 (let ctProxy = Proxy :: Proxy ct
464− in setReqBodyLBS (mimeRender ctProxy body)
481+ in setRequestBodyLBS (mimeRender ctProxy body)
465− -- We use first contentType from the Accept list
482+ -- We use first contentType from the Accept list
466− (contentType ctProxy)
483+ (contentType ctProxy)
470-- | Make the querying function append @path@ to the request path.
487-- | Make the querying function append @path@ to the request path.
@@ -475,7 +492,7 @@ instance (KnownSymbol path, HasClient m api) => HasClient m (path :> api) where
475 clientWithRoute pm (Proxy :: Proxy api)
492 clientWithRoute pm (Proxy :: Proxy api)
476 (appendToPath p req)
493 (appendToPath p req)
478− where p = symbolVal (Proxy :: Proxy path)
495+ where p = pack $ symbolVal (Proxy :: Proxy path)
480instance HasClient m api => HasClient m (Vault :> api) where
497instance HasClient m api => HasClient m (Vault :> api) where
481 type Client m (Vault :> api) = Client m api
498 type Client m (Vault :> api) = Client m api