git.iko.soy/typeable/servant
on master help sign in
Merge pull request #1130 from phadej/refactor-servant-client

Refactor servant-client-core

commite922b9898c322cc73f6bb3e8d42cdbf7db3db15a authorOleg Grenrus <oleg.grenrus@iki.fi> dateMon 18 Feb 2019, 19:44 (8 years ago) parentc462019775b553
33 files changed+252−509
.gitignore+1−1 .travis.yml+2 Makefile+13 cabal.ghcjs.project−15 cabal.project+1 doc/cookbook/using-free-client/UsingFreeClient.lhs−2 servant-client-core/servant-client-core.cabal+11−11 servant-client-core/src/Servant/Client/Core.hs+16−15 servant-client-core/src/Servant/Client/Core/Internal/Auth.hs → servant-client-core/src/Servant/Client/Core/Auth.hs+6−2 servant-client-core/src/Servant/Client/Core/Internal/BaseUrl.hs → servant-client-core/src/Servant/Client/Core/BaseUrl.hs+7−1 servant-client-core/src/Servant/Client/Core/Internal/BasicAuth.hs → servant-client-core/src/Servant/Client/Core/BasicAuth.hs+4−3 servant-client-core/src/Servant/Client/Core/Internal/ClientError.hs → servant-client-core/src/Servant/Client/Core/ClientError.hs+11−7 servant-client-core/src/Servant/Client/Core/Internal/HasClient.hs → servant-client-core/src/Servant/Client/Core/HasClient.hs+41−7 servant-client-core/src/Servant/Client/Core/Internal.hs+12 servant-client-core/src/Servant/Client/Core/Internal/ClientF.hs−12 servant-client-core/src/Servant/Client/Core/Internal/Generic.hs−155 servant-client-core/src/Servant/Client/Core/Internal/RunClient.hs−67 servant-client-core/src/Servant/Client/Core/Reexport.hs+5−9 servant-client-core/src/Servant/Client/Core/Internal/Request.hs → servant-client-core/src/Servant/Client/Core/Request.hs+20−9 servant-client-core/src/Servant/Client/Core/Internal/Response.hs → servant-client-core/src/Servant/Client/Core/Response.hs+5−1 servant-client-core/src/Servant/Client/Core/RunClient.hs+46 servant-client-core/src/Servant/Client/Free.hs+1−1 servant-client-core/test/Servant/Client/Core/Internal/BaseUrlSpec.hs+1−1 servant-client-ghcjs/servant-client-ghcjs.cabal+6−7 servant-client-ghcjs/src/Servant/Client/Internal/XhrClient.hs+38−36 servant-client/servant-client.cabal−1 servant-client/src/Servant/Client/Internal/HttpClient.hs−3 servant-client/src/Servant/Client/Internal/HttpClient/Streaming.hs−3 servant-client/test/Servant/ClientSpec.hs+2−67 servant-http-streams/servant-http-streams.cabal+1−2 servant-http-streams/src/Servant/HttpStreams/Internal.hs−3 servant-http-streams/test/Servant/ClientSpec.hs+2−67 stack.yaml−1
.gitignore+1−1
@@ -1,5 +1,5 @@
11**/*/dist
2−dist-newstyle
2+dist-*
33.ghc.environment.*
44/bin
55/lib
.travis.yml+2
@@ -103,6 +103,7 @@ install:
103103 - "echo 'allow-newer: servant-quickcheck:servant-server' >> cabal.project"
104104 - "echo 'allow-newer: servant-quickcheck:hspec' >> cabal.project"
105105 - "echo 'allow-newer: servant-quickcheck:http-client' >> cabal.project"
106+ - "echo 'reorder-goals: True' >> cabal.project"
106107 - "echo 'optimization: False' >> cabal.project "
107108 - touch cabal.project.local
108109 - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(cookbook-basic-auth|cookbook-basic-streaming|cookbook-curl-mock|cookbook-db-postgres-pool|cookbook-db-sqlite-simple|cookbook-file-upload|cookbook-generic|cookbook-hoist-server-with-context|cookbook-https|cookbook-jwt-and-basic-auth|cookbook-structuring-apis|cookbook-testing|cookbook-using-custom-monad|cookbook-using-free-client|servant|servant-client|servant-client-core|servant-conduit|servant-docs|servant-foreign|servant-http-streams|servant-machines|servant-pipes|servant-server|tutorial)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
@@ -184,6 +185,7 @@ script:
184185 - "echo 'allow-newer: servant-quickcheck:servant-server' >> cabal.project"
185186 - "echo 'allow-newer: servant-quickcheck:hspec' >> cabal.project"
186187 - "echo 'allow-newer: servant-quickcheck:http-client' >> cabal.project"
188+ - "echo 'reorder-goals: True' >> cabal.project"
187189 - "echo 'optimization: False' >> cabal.project "
188190 - touch cabal.project.local
189191 - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | grep -vE -- '^(cookbook-basic-auth|cookbook-basic-streaming|cookbook-curl-mock|cookbook-db-postgres-pool|cookbook-db-sqlite-simple|cookbook-file-upload|cookbook-generic|cookbook-hoist-server-with-context|cookbook-https|cookbook-jwt-and-basic-auth|cookbook-structuring-apis|cookbook-testing|cookbook-using-custom-monad|cookbook-using-free-client|servant|servant-client|servant-client-core|servant-conduit|servant-docs|servant-foreign|servant-http-streams|servant-machines|servant-pipes|servant-server|tutorial)$' | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
Makefilenew file+13
@@ -0,0 +1,13 @@
1+# With common maintenance tasks
2+
3+all :
4+ @echo "Don't try to make all at once!"
5+
6+build-ghc :
7+ cabal v2-build all
8+
9+build-ghcjs :
10+ cabal v2-build --builddir=dist-newstyle-ghcjs --project-file=cabal.ghcjs.project all
11+
12+packdeps :
13+ packdeps */*.cabal
cabal.ghcjs.project−15
@@ -1,6 +1,4 @@
11-- Using https://launchpad.net/~hvr/+archive/ubuntu/ghcjs
2−--
3−-- $ cabal new-build --project-file cabal.ghcjs.project all -w /opt/ghcjs/8.4/bin/ghcjs
42
53packages:
64 servant/
@@ -9,16 +7,3 @@ packages:
97
108-- we need to tell cabal we are using GHCJS
119compiler: ghcjs
12−
13−-- https://github.com/ghcjs/ghcjs/issues/665
14−constraints: primitive <0.6.4
15−
16−-- ghcjs-base wants old aeson
17−allow-newer: ghcjs-base:aeson
18−
19−-- https://github.com/nomeata/hackage-ghcjs-overlay
20−repository ghcjs-overlay
21− url: http://hackage-ghcjs-overlay.nomeata.de/
22− secure: True
23− root-keys:
24− key-threshold: 0
cabal.project+1
@@ -33,6 +33,7 @@ packages:
3333
3434tests: True
3535optimization: False
36+reorder-goals: True
3637
3738allow-newer:
3839 servant-js:base
doc/cookbook/using-free-client/UsingFreeClient.lhs−2
@@ -99,8 +99,6 @@ test = case getSquare 42 of
9999 putStrLn $ "ERROR: got pure result: " ++ show n
100100 Free (Throw err) ->
101101 putStrLn $ "ERROR: got error right away: " ++ show err
102− Free (StreamingRequest _req _k) ->
103− putStrLn $ "ERROR: need to do streaming request" -- TODO: no Show Req :(
104102```
105103
106104We are interested in `RunRequest`, that's what client should block on:
servant-client-core/servant-client-core.cabal+11−11
@@ -36,16 +36,17 @@ library
3636 Servant.Client.Free
3737 Servant.Client.Generic
3838 Servant.Client.Core.Reexport
39− Servant.Client.Core.Internal.Auth
40− Servant.Client.Core.Internal.BaseUrl
41− Servant.Client.Core.Internal.BasicAuth
42− Servant.Client.Core.Internal.ClientError
43− Servant.Client.Core.Internal.ClientF
44− Servant.Client.Core.Internal.Generic
45− Servant.Client.Core.Internal.HasClient
46− Servant.Client.Core.Internal.Request
47− Servant.Client.Core.Internal.Response
48− Servant.Client.Core.Internal.RunClient
39+ Servant.Client.Core.Auth
40+ Servant.Client.Core.BaseUrl
41+ Servant.Client.Core.BasicAuth
42+ Servant.Client.Core.ClientError
43+ Servant.Client.Core.HasClient
44+ Servant.Client.Core.Request
45+ Servant.Client.Core.Response
46+ Servant.Client.Core.RunClient
47+
48+ other-modules:
49+ Servant.Client.Core.Internal
4950
5051 -- Bundled with GHC: Lower bound to not force re-installs
5152 -- text and mtl are bundled starting with GHC-8.4
@@ -76,7 +77,6 @@ library
7677 , base64-bytestring >= 1.0.0.1 && < 1.1
7778 , exceptions >= 0.10.0 && < 0.11
7879 , free >= 5.1 && < 5.2
79− , generics-sop >= 0.4.0.1 && < 0.5
8080 , http-media >= 0.7.1.3 && < 0.8
8181 , http-types >= 0.12.2 && < 0.13
8282 , network-uri >= 2.6.1.0 && < 2.7
servant-client-core/src/Servant/Client/Core.hs+16−15
@@ -32,18 +32,20 @@ module Servant.Client.Core
3232 , AuthClientData
3333
3434 -- * Generic Client
35− , ClientLike(..)
36− , genericMkClientL
37− , genericMkClientP
3835 , ServantError(..)
3936 , EmptyClient(..)
4037
41−
4238 -- * Response
4339 , Response
4440 , ResponseF (..)
4541 , RunClient(..)
46− , module Servant.Client.Core.Internal.BaseUrl
42+ -- * BaseUrl
43+ , BaseUrl (..)
44+ , Scheme (..)
45+ , showBaseUrl
46+ , parseBaseUrl
47+ , InvalidBaseUrlException (..)
48+
4749 -- ** Streaming
4850 , RunStreamingClient(..)
4951 , StreamingResponse
@@ -56,14 +58,13 @@ module Servant.Client.Core
5658 , setRequestBodyLBS
5759 , setRequestBody
5860 ) where
59−import Servant.Client.Core.Internal.Auth
60−import Servant.Client.Core.Internal.BaseUrl
61− (BaseUrl (..), InvalidBaseUrlException, Scheme (..),
61+import Servant.Client.Core.Auth
62+import Servant.Client.Core.BaseUrl
63+ (BaseUrl (..), InvalidBaseUrlException (..), Scheme (..),
6264 parseBaseUrl, showBaseUrl)
63−import Servant.Client.Core.Internal.BasicAuth
64−import Servant.Client.Core.Internal.Generic
65−import Servant.Client.Core.Internal.HasClient
66−import Servant.Client.Core.Internal.Request
67−import Servant.Client.Core.Internal.Response
68−import Servant.Client.Core.Internal.ClientError
69−import Servant.Client.Core.Internal.RunClient
65+import Servant.Client.Core.BasicAuth
66+import Servant.Client.Core.ClientError
67+import Servant.Client.Core.HasClient
68+import Servant.Client.Core.Request
69+import Servant.Client.Core.Response
70+import Servant.Client.Core.RunClient
servant-client-core/src/Servant/Client/Core/Internal/Auth.hs → servant-client-core/src/Servant/Client/Core/Auth.hsrenamed+6−2
@@ -4,9 +4,13 @@
44
55-- | Authentication for clients
66
7−module Servant.Client.Core.Internal.Auth where
7+module Servant.Client.Core.Auth (
8+ AuthClientData,
9+ AuthenticatedRequest (..),
10+ mkAuthenticatedRequest,
11+ ) where
812
9−import Servant.Client.Core.Internal.Request
13+import Servant.Client.Core.Request
1014 (Request)
1115
1216-- | For a resource protected by authentication (e.g. AuthProtect), we need
servant-client-core/src/Servant/Client/Core/Internal/BaseUrl.hs → servant-client-core/src/Servant/Client/Core/BaseUrl.hsrenamed+7−1
@@ -2,7 +2,13 @@
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE DeriveLift #-}
44{-# LANGUAGE ViewPatterns #-}
5−module Servant.Client.Core.Internal.BaseUrl where
5+module Servant.Client.Core.BaseUrl (
6+ BaseUrl (..),
7+ Scheme (..),
8+ showBaseUrl,
9+ parseBaseUrl,
10+ InvalidBaseUrlException (..),
11+ ) where
612
713import Control.DeepSeq
814 (NFData (..))
servant-client-core/src/Servant/Client/Core/Internal/BasicAuth.hs → servant-client-core/src/Servant/Client/Core/BasicAuth.hsrenamed+4−3
@@ -3,8 +3,9 @@
33{-# LANGUAGE TypeSynonymInstances #-}
44
55-- | Basic Authentication for clients
6−
7−module Servant.Client.Core.Internal.BasicAuth where
6+module Servant.Client.Core.BasicAuth (
7+ basicAuthReq,
8+ ) where
89
910import Data.ByteString.Base64
1011 (encode)
@@ -14,7 +15,7 @@ import Data.Text.Encoding
1415 (decodeUtf8)
1516import Servant.API.BasicAuth
1617 (BasicAuthData (BasicAuthData))
17−import Servant.Client.Core.Internal.Request
18+import Servant.Client.Core.Request
1819 (Request, addHeader)
1920
2021-- | Authenticate a request using Basic Authentication
servant-client-core/src/Servant/Client/Core/Internal/ClientError.hs → servant-client-core/src/Servant/Client/Core/ClientError.hsrenamed+11−7
@@ -8,7 +8,9 @@
88{-# LANGUAGE RankNTypes #-}
99{-# LANGUAGE ScopedTypeVariables #-}
1010{-# LANGUAGE TypeFamilies #-}
11−module Servant.Client.Core.Internal.ClientError where
11+module Servant.Client.Core.ClientError (
12+ ServantError (..),
13+ ) where
1214
1315import Prelude ()
1416import Prelude.Compat
@@ -19,20 +21,22 @@ import Control.Exception
1921 (SomeException (..))
2022import Control.Monad.Catch
2123 (Exception)
22−import qualified Data.ByteString as BS
24+import qualified Data.ByteString as BS
2325import Data.Text
2426 (Text)
2527import Data.Typeable
2628 (Typeable, typeOf)
2729import GHC.Generics
2830 (Generic)
29−import Network.HTTP.Media (MediaType)
31+import Network.HTTP.Media
32+ (MediaType)
3033import Network.HTTP.Types ()
3134
32−import Servant.Client.Core.Internal.BaseUrl
33−import Servant.Client.Core.Internal.Request
34−import Servant.Client.Core.Internal.Response
35−
35+import Servant.Client.Core.BaseUrl
36+import Servant.Client.Core.Internal
37+ (mediaTypeRnf)
38+import Servant.Client.Core.Request
39+import Servant.Client.Core.Response
3640
3741
3842-- | A type representing possible errors in a request
servant-client-core/src/Servant/Client/Core/Internal/HasClient.hs → servant-client-core/src/Servant/Client/Core/HasClient.hsrenamed+41−7
@@ -11,11 +11,17 @@
1111{-# LANGUAGE TypeOperators #-}
1212{-# LANGUAGE UndecidableInstances #-}
1313
14−module Servant.Client.Core.Internal.HasClient where
14+module Servant.Client.Core.HasClient (
15+ clientIn,
16+ HasClient (..),
17+ EmptyClient (..),
18+ ) where
1519
1620import Prelude ()
1721import Prelude.Compat
1822
23+import Control.Monad
24+ (unless)
1925import qualified Data.ByteString.Lazy as BL
2026import Data.Foldable
2127 (toList)
@@ -25,6 +31,9 @@ import Data.Proxy
2531 (Proxy (Proxy))
2632import Data.Sequence
2733 (fromList)
34+import qualified Data.Text as T
35+import Network.HTTP.Media
36+ (MediaType, matches, parseAccept, (//))
2837import Data.String
2938 (fromString)
3039import Data.Text
@@ -48,12 +57,12 @@ import Servant.API.ContentTypes
4857import Servant.API.Modifiers
4958 (FoldRequired, RequiredArgument, foldRequiredArgument)
5059
51−import Servant.Client.Core.Internal.Auth
52−import Servant.Client.Core.Internal.BasicAuth
53−import Servant.Client.Core.Internal.ClientError
54−import Servant.Client.Core.Internal.Request
55−import Servant.Client.Core.Internal.Response
56−import Servant.Client.Core.Internal.RunClient
60+import Servant.Client.Core.Auth
61+import Servant.Client.Core.BasicAuth
62+import Servant.Client.Core.ClientError
63+import Servant.Client.Core.Request
64+import Servant.Client.Core.Response
65+import Servant.Client.Core.RunClient
5766
5867-- * Accessing APIs as a Client
5968
@@ -627,6 +636,7 @@ instance HasClient m api => HasClient m (BasicAuth realm usr :> api) where
627636 hoistClientMonad pm (Proxy :: Proxy api) f (cl bauth)
628637
629638
639+
630640{- Note [Non-Empty Content Types]
631641~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
632642Rather than have
@@ -642,3 +652,27 @@ non-empty lists, but is otherwise more specific, no instance will be overall
642652more specific. This in turn generally means adding yet another instance (one
643653for empty and one for non-empty lists).
644654-}
655+
656+-------------------------------------------------------------------------------
657+-- helpers
658+-------------------------------------------------------------------------------
659+
660+checkContentTypeHeader :: RunClient m => Response -> m MediaType
661+checkContentTypeHeader response =
662+ case lookup "Content-Type" $ toList $ responseHeaders response of
663+ Nothing -> return $ "application"//"octet-stream"
664+ Just t -> case parseAccept t of
665+ Nothing -> throwServantError $ InvalidContentTypeHeader response
666+ Just t' -> return t'
667+
668+decodedAs :: forall ct a m. (MimeUnrender ct a, RunClient m)
669+ => Response -> Proxy ct -> m a
670+decodedAs response ct = do
671+ responseContentType <- checkContentTypeHeader response
672+ unless (any (matches responseContentType) accept) $
673+ throwServantError $ UnsupportedContentType responseContentType response
674+ case mimeUnrender ct $ responseBody response of
675+ Left err -> throwServantError $ DecodeFailure (T.pack err) response
676+ Right val -> return val
677+ where
678+ accept = toList $ contentTypes ct
servant-client-core/src/Servant/Client/Core/Internal.hsnew file+12
@@ -0,0 +1,12 @@
1+module Servant.Client.Core.Internal where
2+
3+import Control.DeepSeq
4+ (rnf)
5+import Network.HTTP.Media
6+ (MediaType, mainType, parameters, subType)
7+
8+mediaTypeRnf :: MediaType -> ()
9+mediaTypeRnf mt =
10+ rnf (mainType mt) `seq`
11+ rnf (subType mt) `seq`
12+ rnf (parameters mt)
servant-client-core/src/Servant/Client/Core/Internal/ClientF.hsdeleted−12
@@ -1,12 +0,0 @@
1−{-# LANGUAGE DeriveFunctor #-}
2−module Servant.Client.Core.Internal.ClientF where
3−
4−import Servant.Client.Core.Internal.Request
5−import Servant.Client.Core.Internal.Response
6−import Servant.Client.Core.Internal.ClientError
7−
8−data ClientF a
9− = RunRequest Request (Response -> a)
10− | StreamingRequest Request (StreamingResponse -> a)
11− | Throw ServantError
12− deriving (Functor)
servant-client-core/src/Servant/Client/Core/Internal/Generic.hsdeleted−155
@@ -1,155 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DefaultSignatures #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE FlexibleInstances #-}
5−{-# LANGUAGE MultiParamTypeClasses #-}
6−{-# LANGUAGE TypeFamilies #-}
7−{-# LANGUAGE TypeOperators #-}
8−{-# LANGUAGE UndecidableInstances #-}
9−
10−module Servant.Client.Core.Internal.Generic where
11−
12−import Generics.SOP
13− (Code, Generic, I (..), NP (..), NS (Z), SOP (..), to)
14−import Servant.API
15− ((:<|>) (..))
16−
17−-- | This class allows us to match client structure with client functions
18−-- produced with 'client' without explicit pattern-matching.
19−--
20−-- The client structure needs a 'Generics.SOP.Generic' instance.
21−--
22−-- Example:
23−--
24−-- > type API
25−-- > = "foo" :> Capture "x" Int :> Get '[JSON] Int
26−-- > :<|> "bar" :> QueryParam "a" Char :> QueryParam "b" String :> Post '[JSON] [Int]
27−-- > :<|> Capture "nested" Int :> NestedAPI
28−-- >
29−-- > type NestedAPI
30−-- > = Get '[JSON] String
31−-- > :<|> "baz" :> QueryParam "c" Char :> Post '[JSON] ()
32−-- >
33−-- > data APIClient = APIClient
34−-- > { getFoo :: Int -> ClientM Int
35−-- > , postBar :: Maybe Char -> Maybe String -> ClientM [Int]
36−-- > , mkNestedClient :: Int -> NestedClient
37−-- > } deriving GHC.Generic
38−-- >
39−-- > instance Generics.SOP.Generic APIClient
40−-- > instance (Client API ~ client) => ClientLike client APIClient
41−-- >
42−-- > data NestedClient = NestedClient
43−-- > { getString :: ClientM String
44−-- > , postBaz :: Maybe Char -> ClientM ()
45−-- > } deriving GHC.Generic
46−-- >
47−-- > instance Generics.SOP.Generic NestedClient
48−-- > instance (Client NestedAPI ~ client) => ClientLike client NestedClient
49−-- >
50−-- > mkAPIClient :: APIClient
51−-- > mkAPIClient = mkClient (client (Proxy :: Proxy API))
52−--
53−-- By default, left-nested alternatives are expanded:
54−--
55−-- > type API1
56−-- > = "foo" :> Capture "x" Int :> Get '[JSON] Int
57−-- > :<|> "bar" :> QueryParam "a" Char :> Post '[JSON] String
58−-- >
59−-- > type API2
60−-- > = "baz" :> QueryParam "c" Char :> Post '[JSON] ()
61−-- >
62−-- > type API = API1 :<|> API2
63−-- >
64−-- > data APIClient = APIClient
65−-- > { getFoo :: Int -> ClientM Int
66−-- > , postBar :: Maybe Char -> ClientM String
67−-- > , postBaz :: Maybe Char -> ClientM ()
68−-- > } deriving GHC.Generic
69−-- >
70−-- > instance Generics.SOP.Generic APIClient
71−-- > instance (Client API ~ client) => ClientLike client APIClient
72−-- >
73−-- > mkAPIClient :: APIClient
74−-- > mkAPIClient = mkClient (client (Proxy :: Proxy API))
75−--
76−-- If you want to define client for @API1@ as a separate data structure,
77−-- you can use 'genericMkClientP':
78−--
79−-- > data APIClient1 = APIClient1
80−-- > { getFoo :: Int -> ClientM Int
81−-- > , postBar :: Maybe Char -> ClientM String
82−-- > } deriving GHC.Generic
83−-- >
84−-- > instance Generics.SOP.Generic APIClient1
85−-- > instance (Client API1 ~ client) => ClientLike client APIClient1
86−-- >
87−-- > data APIClient = APIClient
88−-- > { mkAPIClient1 :: APIClient1
89−-- > , postBaz :: Maybe Char -> ClientM ()
90−-- > } deriving GHC.Generic
91−-- >
92−-- > instance Generics.SOP.Generic APIClient
93−-- > instance (Client API ~ client) => ClientLike client APIClient where
94−-- > mkClient = genericMkClientP
95−-- >
96−-- > mkAPIClient :: APIClient
97−-- > mkAPIClient = mkClient (client (Proxy :: Proxy API))
98−class ClientLike client custom where
99− mkClient :: client -> custom
100− default mkClient :: (Generic custom, Code custom ~ '[xs], GClientList client '[], GClientLikeL (ClientList client '[]) xs)
101− => client -> custom
102− mkClient = genericMkClientL
103−
104−instance ClientLike client custom
105− => ClientLike (a -> client) (a -> custom) where
106− mkClient c = mkClient . c
107−
108−-- | Match client structure with client functions, regarding left-nested API clients
109−-- as separate data structures.
110−class GClientLikeP client xs where
111− gMkClientP :: client -> NP I xs
112−
113−instance (GClientLikeP b (y ': xs), ClientLike a x)
114− => GClientLikeP (a :<|> b) (x ': y ': xs) where
115− gMkClientP (a :<|> b) = I (mkClient a) :* gMkClientP b
116−
117−instance ClientLike a x => GClientLikeP a '[x] where
118− gMkClientP a = I (mkClient a) :* Nil
119−
120−-- | Match client structure with client functions, expanding left-nested API clients
121−-- in the same structure.
122−class GClientLikeL (xs :: [*]) (ys :: [*]) where
123− gMkClientL :: NP I xs -> NP I ys
124−
125−instance GClientLikeL '[] '[] where
126− gMkClientL Nil = Nil
127−
128−instance (ClientLike x y, GClientLikeL xs ys) => GClientLikeL (x ': xs) (y ': ys) where
129− gMkClientL (I x :* xs) = I (mkClient x) :* gMkClientL xs
130−
131−type family ClientList (client :: *) (acc :: [*]) :: [*] where
132− ClientList (a :<|> b) acc = ClientList a (ClientList b acc)
133− ClientList a acc = a ': acc
134−
135−class GClientList client (acc :: [*]) where
136− gClientList :: client -> NP I acc -> NP I (ClientList client acc)
137−
138−instance (GClientList b acc, GClientList a (ClientList b acc))
139− => GClientList (a :<|> b) acc where
140− gClientList (a :<|> b) acc = gClientList a (gClientList b acc)
141−
142−instance {-# OVERLAPPABLE #-} (ClientList client acc ~ (client ': acc))
143− => GClientList client acc where
144− gClientList c acc = I c :* acc
145−
146−-- | Generate client structure from client type, expanding left-nested API (done by default).
147−genericMkClientL :: (Generic custom, Code custom ~ '[xs], GClientList client '[], GClientLikeL (ClientList client '[]) xs)
148− => client -> custom
149−genericMkClientL = to . SOP . Z . gMkClientL . flip gClientList Nil
150−
151−-- | Generate client structure from client type, regarding left-nested API clients as separate data structures.
152−genericMkClientP :: (Generic custom, Code custom ~ '[xs], GClientLikeP client xs)
153− => client -> custom
154−genericMkClientP = to . SOP . Z . gMkClientP
155−
servant-client-core/src/Servant/Client/Core/Internal/RunClient.hsdeleted−67
@@ -1,67 +0,0 @@
1−{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE MultiParamTypeClasses #-}
3−{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE ScopedTypeVariables #-}
5−{-# LANGUAGE TypeFamilies #-}
6−-- | Types for possible backends to run client-side `Request` queries
7−module Servant.Client.Core.Internal.RunClient where
8−
9−import Prelude ()
10−import Prelude.Compat
11−
12−import Control.Monad
13− (unless)
14−import Control.Monad.Free
15− (Free (..), liftF)
16−import Data.Foldable
17− (toList)
18−import Data.Proxy
19− (Proxy)
20−import qualified Data.Text as T
21−import Network.HTTP.Media
22− (MediaType, matches, parseAccept, (//))
23−import Servant.API
24− (MimeUnrender, contentTypes, mimeUnrender)
25−
26−import Servant.Client.Core.Internal.ClientF
27−import Servant.Client.Core.Internal.Request
28−import Servant.Client.Core.Internal.Response
29−import Servant.Client.Core.Internal.ClientError
30−
31−class Monad m => RunClient m where
32− -- | How to make a request.
33− runRequest :: Request -> m Response
34− throwServantError :: ServantError -> m a
35−
36−class RunClient m => RunStreamingClient m where
37− withStreamingRequest :: Request -> (StreamingResponse -> IO a) -> m a
38−
39−checkContentTypeHeader :: RunClient m => Response -> m MediaType
40−checkContentTypeHeader response =
41− case lookup "Content-Type" $ toList $ responseHeaders response of
42− Nothing -> return $ "application"//"octet-stream"
43− Just t -> case parseAccept t of
44− Nothing -> throwServantError $ InvalidContentTypeHeader response
45− Just t' -> return t'
46−
47−decodedAs :: forall ct a m. (MimeUnrender ct a, RunClient m)
48− => Response -> Proxy ct -> m a
49−decodedAs response contentType = do
50− responseContentType <- checkContentTypeHeader response
51− unless (any (matches responseContentType) accept) $
52− throwServantError $ UnsupportedContentType responseContentType response
53− case mimeUnrender contentType $ responseBody response of
54− Left err -> throwServantError $ DecodeFailure (T.pack err) response
55− Right val -> return val
56− where
57− accept = toList $ contentTypes contentType
58−
59−instance ClientF ~ f => RunClient (Free f) where
60− runRequest req = liftF (RunRequest req id)
61− throwServantError = liftF . Throw
62−
63−{-
64−Free and streaming?
65−instance ClientF ~ f => RunStreamingClient (Free f) where
66− streamingRequest req = liftF (StreamingRequest req id)
67−-}
servant-client-core/src/Servant/Client/Core/Reexport.hs+5−9
@@ -11,10 +11,7 @@ module Servant.Client.Core.Reexport
1111 , StreamingResponse
1212 , ResponseF(..)
1313
14− -- * Generic Client
15− , ClientLike(..)
16− , genericMkClientL
17− , genericMkClientP
14+ -- * Data types
1815 , ServantError(..)
1916 , EmptyClient(..)
2017
@@ -27,8 +24,7 @@ module Servant.Client.Core.Reexport
2724 ) where
2825
2926
30−import Servant.Client.Core.Internal.BaseUrl
31−import Servant.Client.Core.Internal.Generic
32−import Servant.Client.Core.Internal.HasClient
33−import Servant.Client.Core.Internal.Response
34−import Servant.Client.Core.Internal.ClientError
27+import Servant.Client.Core.BaseUrl
28+import Servant.Client.Core.HasClient
29+import Servant.Client.Core.Response
30+import Servant.Client.Core.ClientError
servant-client-core/src/Servant/Client/Core/Internal/Request.hs → servant-client-core/src/Servant/Client/Core/Request.hsrenamed+20−9
@@ -8,8 +8,18 @@
88{-# LANGUAGE RankNTypes #-}
99{-# LANGUAGE ScopedTypeVariables #-}
1010{-# LANGUAGE TypeFamilies #-}
11−
12−module Servant.Client.Core.Internal.Request where
11+module Servant.Client.Core.Request (
12+ Request,
13+ RequestF (..),
14+ RequestBody (..),
15+ defaultRequest,
16+ -- ** Modifiers
17+ addHeader,
18+ appendToPath,
19+ appendToQueryString,
20+ setRequestBody,
21+ setRequestBodyLBS,
22+ ) where
1323
1424import Prelude ()
1525import Prelude.Compat
@@ -37,18 +47,14 @@ import Data.Typeable
3747import GHC.Generics
3848 (Generic)
3949import Network.HTTP.Media
40− (MediaType, mainType, parameters, subType)
50+ (MediaType)
4151import Network.HTTP.Types
4252 (Header, HeaderName, HttpVersion (..), Method, QueryItem,
4353 http11, methodGet)
4454import Servant.API
4555 (ToHttpApiData, toEncodedUrlPiece, toHeader, SourceIO)
4656
47−mediaTypeRnf :: MediaType -> ()
48−mediaTypeRnf mt =
49− rnf (mainType mt) `seq`
50− rnf (subType mt) `seq`
51− rnf (parameters mt)
57+import Servant.Client.Core.Internal (mediaTypeRnf)
5258
5359data RequestF body path = Request
5460 { requestPath :: path
@@ -84,7 +90,7 @@ instance (NFData path, NFData body) => NFData (RequestF body path) where
8490
8591type Request = RequestF RequestBody Builder.Builder
8692
87−-- | The request body. A replica of the @http-client@ @RequestBody@.
93+-- | The request body. R replica of the @http-client@ @RequestBody@.
8894data RequestBody
8995 = RequestBodyLBS LBS.ByteString
9096 | RequestBodyBS BS.ByteString
@@ -95,6 +101,11 @@ instance Show RequestBody where
95101 showsPrec d (RequestBodyLBS lbs) = showParen (d > 10)
96102 $ showString "RequestBodyLBS "
97103 . showsPrec 11 lbs
104+ showsPrec d (RequestBodyBS bs) = showParen (d > 10)
105+ $ showString "RequestBodyBS "
106+ . showsPrec 11 bs
107+ showsPrec d (RequestBodySource _) = showParen (d > 10)
108+ $ showString "RequestBodySource <IO>"
98109
99110-- A GET request to the top-level path
100111defaultRequest :: Request
servant-client-core/src/Servant/Client/Core/Internal/Response.hs → servant-client-core/src/Servant/Client/Core/Response.hsrenamed+5−1
@@ -8,7 +8,11 @@
88{-# LANGUAGE ScopedTypeVariables #-}
99{-# LANGUAGE TypeFamilies #-}
1010
11−module Servant.Client.Core.Internal.Response where
11+module Servant.Client.Core.Response (
12+ Response,
13+ StreamingResponse,
14+ ResponseF (..),
15+ ) where
1216
1317import Prelude ()
1418import Prelude.Compat
servant-client-core/src/Servant/Client/Core/RunClient.hsnew file+46
@@ -0,0 +1,46 @@
1+{-# LANGUAGE DeriveFunctor #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE MultiParamTypeClasses #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+{-# LANGUAGE TypeFamilies #-}
7+-- | Types for possible backends to run client-side `Request` queries
8+module Servant.Client.Core.RunClient (
9+ RunClient (..),
10+ RunStreamingClient (..),
11+ ClientF (..),
12+ ) where
13+
14+import Prelude ()
15+import Prelude.Compat
16+
17+import Control.Monad.Free
18+ (Free (..), liftF)
19+
20+import Servant.Client.Core.ClientError
21+import Servant.Client.Core.Request
22+import Servant.Client.Core.Response
23+
24+class Monad m => RunClient m where
25+ -- | How to make a request.
26+ runRequest :: Request -> m Response
27+ throwServantError :: ServantError -> m a
28+
29+class RunClient m => RunStreamingClient m where
30+ withStreamingRequest :: Request -> (StreamingResponse -> IO a) -> m a
31+
32+-------------------------------------------------------------------------------
33+-- Free
34+-------------------------------------------------------------------------------
35+
36+-- | 'ClientF' cannot stream.
37+--
38+-- Compare to 'RunClient'.
39+data ClientF a
40+ = RunRequest Request (Response -> a)
41+ | Throw ServantError
42+ deriving (Functor)
43+
44+instance ClientF ~ f => RunClient (Free f) where
45+ runRequest req = liftF (RunRequest req id)
46+ throwServantError = liftF . Throw
servant-client-core/src/Servant/Client/Free.hs+1−1
@@ -11,8 +11,8 @@ import Control.Monad.Free
1111import Data.Proxy
1212 (Proxy (..))
1313import Servant.Client.Core
14−import Servant.Client.Core.Internal.ClientF
1514import Servant.Client.Core.Reexport
15+import Servant.Client.Core.RunClient
1616
1717client :: HasClient (Free ClientF) api => Proxy api -> Client (Free ClientF) api
1818client api = api `clientIn` (Proxy :: Proxy (Free ClientF))
servant-client-core/test/Servant/Client/Core/Internal/BaseUrlSpec.hs+1−1
@@ -8,7 +8,7 @@ import Prelude.Compat
88import Test.Hspec
99import Test.QuickCheck
1010
11−import Servant.Client.Core.Internal.BaseUrl
11+import Servant.Client.Core.BaseUrl
1212
1313spec :: Spec
1414spec = do
servant-client-ghcjs/servant-client-ghcjs.cabal+6−7
@@ -36,10 +36,10 @@ library
3636 Servant.Client.Ghcjs
3737 Servant.Client.Internal.XhrClient
3838 build-depends:
39− base >= 4.7 && < 4.12
39+ base >= 4.11 && < 4.12
4040 , bytestring >= 0.10 && < 0.11
4141 , case-insensitive >= 1.2.0.0 && < 1.3.0.0
42− , containers >= 0.5 && < 0.6
42+ , containers >= 0.5 && < 0.7
4343 , exceptions >= 0.8 && < 0.11
4444 , ghcjs-base >= 0.2.0.0 && < 0.3.0.0
4545 , ghcjs-prim >= 0.1.0.0 && < 0.2.0.0
@@ -47,7 +47,7 @@ library
4747 , http-types >= 0.12 && < 0.13
4848 , monad-control >= 1.0.0.4 && < 1.1
4949 , mtl >= 2.2.2 && < 2.3
50− , semigroupoids >= 4.3 && < 5.3
50+ , semigroupoids >= 5.3 && < 5.4
5151 , string-conversions >= 0.3 && < 0.5
5252 , transformers >= 0.3 && < 0.6
5353 , transformers-base >= 0.4.4 && < 0.5
@@ -55,10 +55,9 @@ library
5555
5656 -- strict, as we re-export stuff
5757 build-depends:
58− servant-client-core == 0.15
58+ servant == 0.15.*,
59+ servant-client-core == 0.15.*
5960
6061 hs-source-dirs: src
6162 default-language: Haskell2010
62− ghc-options: -Wall
63− if impl(ghc >= 8.0)
64− ghc-options: -Wno-redundant-constraints
63+ ghc-options: -Wall -Wno-redundant-constraints
servant-client-ghcjs/src/Servant/Client/Internal/XhrClient.hs+38−36
@@ -18,35 +18,48 @@ import Control.Arrow
1818import Control.Concurrent
1919import Control.Exception
2020import Control.Monad
21−import Control.Monad.Base (MonadBase (..))
22−import Control.Monad.Catch (MonadCatch, MonadThrow)
23−import Control.Monad.Error.Class (MonadError (..))
21+import Control.Monad.Base
22+ (MonadBase (..))
23+import Control.Monad.Catch
24+ (MonadCatch, MonadThrow)
25+import Control.Monad.Error.Class
26+ (MonadError (..))
2427import Control.Monad.Reader
25−import Control.Monad.Trans.Control (MonadBaseControl (..))
28+import Control.Monad.Trans.Control
29+ (MonadBaseControl (..))
2630import Control.Monad.Trans.Except
27−import Data.ByteString.Builder (toLazyByteString)
28−import qualified Data.ByteString.Char8 as BS
29−import qualified Data.ByteString.Lazy as BL
31+import Data.Bifunctor
32+ (bimap)
33+import Data.ByteString.Builder
34+ (toLazyByteString)
35+import qualified Data.ByteString.Char8 as BS
36+import qualified Data.ByteString.Lazy as BL
3037import Data.CaseInsensitive
3138import Data.Char
32−import Data.Foldable (toList)
33−import Data.Functor.Alt (Alt (..))
34−import Data.IORef (modifyIORef, newIORef, readIORef)
35−import Data.Proxy (Proxy (..))
36−import qualified Data.Sequence as Seq
39+import Data.Foldable
40+ (toList)
41+import Data.Functor.Alt
42+ (Alt (..))
43+import Data.Proxy
44+ (Proxy (..))
45+import qualified Data.Sequence as Seq
3746import Data.String.Conversions
38−import Data.Typeable (Typeable)
47+import Data.Typeable
48+ (Typeable)
3949import Foreign.StablePtr
4050import GHC.Generics
41−import qualified GHCJS.Buffer as Buffer
51+import qualified GHCJS.Buffer as Buffer
4252import GHCJS.Foreign.Callback
4353import GHCJS.Prim
4454import GHCJS.Types
45−import JavaScript.TypedArray.ArrayBuffer ( ArrayBuffer )
55+import JavaScript.TypedArray.ArrayBuffer
56+ (ArrayBuffer)
4657import JavaScript.Web.Location
47−import Network.HTTP.Media (renderHeader)
58+import Network.HTTP.Media
59+ (renderHeader)
4860import Network.HTTP.Types
4961import Servant.Client.Core
62+import qualified Servant.Types.SourceT as S
5063
5164newtype JSXMLHttpRequest = JSXMLHttpRequest JSVal
5265
@@ -110,9 +123,6 @@ instance RunClient ClientM where
110123 runRequest = performRequest
111124 throwServantError = throwError
112125
113−instance ClientLike (ClientM a) (ClientM a) where
114− mkClient = id
115−
116126runClientMOrigin :: ClientM a -> ClientEnv -> IO (Either ServantError a)
117127runClientMOrigin cm env = runExceptT $ flip runReaderT env $ runClientM' cm
118128
@@ -150,8 +160,9 @@ performRequest req = do
150160 resp <- toResponse xhr
151161
152162 let status = statusCode (responseStatusCode resp)
153− unless (status >= 200 && status < 300) $
154− throwError $ FailureResponse resp
163+ unless (status >= 200 && status < 300) $ do
164+ let f b = (burl, BL.toStrict $ toLazyByteString b)
165+ throwError $ FailureResponse (bimap (const ()) f req) resp
155166
156167 pure resp
157168
@@ -270,26 +281,17 @@ toBody request = case requestBody request of
270281 where
271282 go :: RequestBody -> IO ArrayBuffer
272283 go x = case x of
273− RequestBodyLBS x -> return $ mBody $ BL.toStrict x
274− RequestBodyBS x -> return $ mBody x
275− RequestBodyBuilder _ x -> return $ mBody $ BL.toStrict $ toLazyByteString x
276− RequestBodyStream _ x -> mBody <$> readBody x
277− RequestBodyStreamChunked x -> mBody <$> readBody x
278− RequestBodyIO x -> x >>= go
284+ RequestBodyLBS x -> return $ mBody $ BL.toStrict x
285+ RequestBodyBS x -> return $ mBody x
286+ RequestBodySource xs -> runExceptT (S.runSourceT xs) >>= \e -> case e of
287+ Left err -> fail err
288+ Right bss -> return $ mBody $ BL.toStrict $ mconcat bss
279289
280290 mBody :: BS.ByteString -> ArrayBuffer
281291 mBody bs = js_bufferSlice offset len $ Buffer.getArrayBuffer buffer
282292 where
283293 (buffer, offset, len) = Buffer.fromByteString bs
284294
285− readBody :: ((IO BS.ByteString -> IO ()) -> IO a) -> IO BS.ByteString
286− readBody writer = do
287− m <- newIORef mempty
288− _ <- writer (\bsAct -> do
289− bs <- bsAct
290− modifyIORef m (<> bs))
291− readIORef m
292−
293295foreign import javascript unsafe "$3.slice($1, $1 + $2)"
294296 js_bufferSlice :: Int -> Int -> ArrayBuffer -> ArrayBuffer
295297
@@ -301,7 +303,7 @@ toResponse :: JSXMLHttpRequest -> ClientM Response
301303toResponse xhr = do
302304 status <- liftIO $ getStatus xhr
303305 case status of
304− 0 -> throwError $ ConnectionError "connection error"
306+ 0 -> throwError $ ConnectionError (SomeException (userError "connection error"))
305307 _ -> liftIO $ do
306308 statusText <- cs <$> getStatusText xhr
307309 headers <- parseHeaders <$> getAllResponseHeaders xhr
servant-client/servant-client.cabal−1
@@ -116,7 +116,6 @@ test-suite spec
116116 -- Additonal dependencies
117117 build-depends:
118118 entropy >= 0.4.1.3 && < 0.5
119− , generics-sop >= 0.4.0.1 && < 0.5
120119 , hspec >= 2.6.0 && < 2.7
121120 , HUnit >= 1.6.0.0 && < 1.7
122121 , network >= 2.8.0.0 && < 3.1
servant-client/src/Servant/Client/Internal/HttpClient.hs−3
@@ -143,9 +143,6 @@ instance RunClient ClientM where
143143 runRequest = performRequest
144144 throwServantError = throwError
145145
146−instance ClientLike (ClientM a) (ClientM a) where
147− mkClient = id
148−
149146runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)
150147runClientM cm env = runExceptT $ flip runReaderT env $ unClientM cm
151148
servant-client/src/Servant/Client/Internal/HttpClient/Streaming.hs−3
@@ -118,9 +118,6 @@ instance RunClient ClientM where
118118instance RunStreamingClient ClientM where
119119 withStreamingRequest = performWithStreamingRequest
120120
121−instance ClientLike (ClientM a) (ClientM a) where
122− mkClient = id
123−
124121withClientM :: ClientM a -> ClientEnv -> (Either ServantError a -> IO b) -> IO b
125122withClientM cm env k =
126123 let Codensity f = runExceptT $ flip runReaderT env $ unClientM cm
servant-client/test/Servant/ClientSpec.hs+2−67
@@ -48,7 +48,6 @@ import Data.Monoid ()
4848import Data.Proxy
4949import Data.Semigroup
5050 ((<>))
51−import qualified Generics.SOP as SOP
5251import GHC.Generics
5352 (Generic)
5453import qualified Network.HTTP.Client as C
@@ -72,8 +71,8 @@ import Servant.API
7271 Headers, JSON, NoContent (NoContent), Post, Put, QueryFlag,
7372 QueryParam, QueryParams, Raw, ReqBody, addHeader, getHeaders)
7473import Servant.Client
75−import qualified Servant.Client.Core.Internal.Auth as Auth
76−import qualified Servant.Client.Core.Internal.Request as Req
74+import qualified Servant.Client.Core.Auth as Auth
75+import qualified Servant.Client.Core.Request as Req
7776import Servant.Server
7877import Servant.Server.Experimental.Auth
7978import Servant.Test.ComprehensiveAPI
@@ -88,7 +87,6 @@ spec = describe "Servant.Client" $ do
8887 wrappedApiSpec
8988 basicAuthSpec
9089 genAuthSpec
91− genericClientSpec
9290 hoistClientSpec
9391 connectionErrorSpec
9492
@@ -258,53 +256,6 @@ genAuthServerContext = genAuthHandler :. EmptyContext
258256genAuthServer :: Application
259257genAuthServer = serveWithContext genAuthAPI genAuthServerContext (const (return alice))
260258
261−-- * generic client stuff
262−
263−type GenericClientAPI
264− = QueryParam "sqr" Int :> Get '[JSON] Int
265− :<|> Capture "foo" String :> NestedAPI1
266−
267−data GenericClient = GenericClient
268− { getSqr :: Maybe Int -> ClientM Int
269− , mkNestedClient1 :: String -> NestedClient1
270− } deriving Generic
271−instance SOP.Generic GenericClient
272−instance (Client ClientM GenericClientAPI ~ client) => ClientLike client GenericClient
273−
274−type NestedAPI1
275− = QueryParam "int" Int :> NestedAPI2
276− :<|> QueryParam "id" Char :> Get '[JSON] Char
277−
278−data NestedClient1 = NestedClient1
279− { mkNestedClient2 :: Maybe Int -> NestedClient2
280− , idChar :: Maybe Char -> ClientM Char
281− } deriving Generic
282−instance SOP.Generic NestedClient1
283−instance (Client ClientM NestedAPI1 ~ client) => ClientLike client NestedClient1
284−
285−type NestedAPI2
286− = "sum" :> Capture "first" Int :> Capture "second" Int :> Get '[JSON] Int
287− :<|> "void" :> Post '[JSON] ()
288−
289−data NestedClient2 = NestedClient2
290− { getSum :: Int -> Int -> ClientM Int
291− , doNothing :: ClientM ()
292− } deriving Generic
293−instance SOP.Generic NestedClient2
294−instance (Client ClientM NestedAPI2 ~ client) => ClientLike client NestedClient2
295−
296−genericClientServer :: Application
297−genericClientServer = serve (Proxy :: Proxy GenericClientAPI) (
298− (\ mx -> case mx of
299− Just x -> return (x*x)
300− Nothing -> throwError $ ServantErr 400 "missing parameter" "" []
301− )
302− :<|> nestedServer1
303− )
304− where
305− nestedServer1 _str = nestedServer2 :<|> (maybe (throwError $ ServantErr 400 "missing parameter" "" []) return)
306− nestedServer2 _int = (\ x y -> return (x + y)) :<|> return ()
307−
308259{-# NOINLINE manager' #-}
309260manager' :: C.Manager
310261manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
@@ -495,22 +446,6 @@ genAuthSpec = beforeAll (startWaiApp genAuthServer) $ afterAll endWaiApp $ do
495446 Left (FailureResponse _ r) <- runClient (getProtected authRequest) baseUrl
496447 responseStatusCode r `shouldBe` (HTTP.Status 401 "Unauthorized")
497448
498−genericClientSpec :: Spec
499−genericClientSpec = beforeAll (startWaiApp genericClientServer) $ afterAll endWaiApp $ do
500− describe "Servant.Client.Generic" $ do
501−
502− let GenericClient{..} = mkClient (client (Proxy :: Proxy GenericClientAPI))
503− NestedClient1{..} = mkNestedClient1 "example"
504− NestedClient2{..} = mkNestedClient2 (Just 42)
505−
506− it "works for top-level client inClientM function" $ \(_, baseUrl) -> do
507− left show <$> runClient (getSqr (Just 5)) baseUrl `shouldReturn` Right 25
508−
509− it "works for nested clients" $ \(_, baseUrl) -> do
510− left show <$> runClient (idChar (Just 'c')) baseUrl `shouldReturn` Right 'c'
511− left show <$> runClient (getSum 3 4) baseUrl `shouldReturn` Right 7
512− left show <$> runClient doNothing baseUrl `shouldReturn` Right ()
513−
514449-- * hoistClient
515450
516451type HoistClientAPI = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int
servant-http-streams/servant-http-streams.cabal+1−2
@@ -68,7 +68,7 @@ library
6868 , case-insensitive
6969 , http-streams >= 0.8.6.1 && < 0.9
7070 , http-media >= 0.7.1.3 && < 0.8
71− , io-streams >=1.5.0.1 && < 1.6
71+ , io-streams >= 1.5.0.1 && < 1.6
7272 , http-types >= 0.12.2 && < 0.13
7373 , http-common >= 0.8.2.0 && < 0.9
7474 , exceptions >= 0.10.0 && < 0.11
@@ -116,7 +116,6 @@ test-suite spec
116116 -- Additonal dependencies
117117 build-depends:
118118 entropy >= 0.4.1.3 && < 0.5
119− , generics-sop >= 0.4.0.1 && < 0.5
120119 , hspec >= 2.6.0 && < 2.7
121120 , HUnit >= 1.6.0.0 && < 1.7
122121 , network >= 2.8.0.0 && < 3.1
servant-http-streams/src/Servant/HttpStreams/Internal.hs−3
@@ -141,9 +141,6 @@ instance RunClient ClientM where
141141instance RunStreamingClient ClientM where
142142 withStreamingRequest = performWithStreamingRequest
143143
144−instance ClientLike (ClientM a) (ClientM a) where
145− mkClient = id
146−
147144runClientM :: NFData a => ClientM a -> ClientEnv -> IO (Either ServantError a)
148145runClientM cm env = withClientM cm env (evaluate . force)
149146
servant-http-streams/test/Servant/ClientSpec.hs+2−67
@@ -46,7 +46,6 @@ import Data.Monoid ()
4646import Data.Proxy
4747import Data.Semigroup
4848 ((<>))
49−import qualified Generics.SOP as SOP
5049import GHC.Generics
5150 (Generic)
5251import qualified Network.HTTP.Types as HTTP
@@ -66,8 +65,8 @@ import Servant.API
6665 DeleteNoContent, EmptyAPI, FormUrlEncoded, Get, Header,
6766 Headers, JSON, NoContent (NoContent), Post, Put, QueryFlag,
6867 QueryParam, QueryParams, Raw, ReqBody, addHeader, getHeaders)
69−import qualified Servant.Client.Core.Internal.Auth as Auth
70−import qualified Servant.Client.Core.Internal.Request as Req
68+import qualified Servant.Client.Core.Auth as Auth
69+import qualified Servant.Client.Core.Request as Req
7170import Servant.HttpStreams
7271import Servant.Server
7372import Servant.Server.Experimental.Auth
@@ -83,7 +82,6 @@ spec = describe "Servant.HttpStreams" $ do
8382 wrappedApiSpec
8483 basicAuthSpec
8584 genAuthSpec
86− genericClientSpec
8785 hoistClientSpec
8886 connectionErrorSpec
8987
@@ -256,53 +254,6 @@ genAuthServerContext = genAuthHandler :. EmptyContext
256254genAuthServer :: Application
257255genAuthServer = serveWithContext genAuthAPI genAuthServerContext (const (return alice))
258256
259−-- * generic client stuff
260−
261−type GenericClientAPI
262− = QueryParam "sqr" Int :> Get '[JSON] Int
263− :<|> Capture "foo" String :> NestedAPI1
264−
265−data GenericClient = GenericClient
266− { getSqr :: Maybe Int -> ClientM Int
267− , mkNestedClient1 :: String -> NestedClient1
268− } deriving Generic
269−instance SOP.Generic GenericClient
270−instance (Client ClientM GenericClientAPI ~ client) => ClientLike client GenericClient
271−
272−type NestedAPI1
273− = QueryParam "int" Int :> NestedAPI2
274− :<|> QueryParam "id" Char :> Get '[JSON] Char
275−
276−data NestedClient1 = NestedClient1
277− { mkNestedClient2 :: Maybe Int -> NestedClient2
278− , idChar :: Maybe Char -> ClientM Char
279− } deriving Generic
280−instance SOP.Generic NestedClient1
281−instance (Client ClientM NestedAPI1 ~ client) => ClientLike client NestedClient1
282−
283−type NestedAPI2
284− = "sum" :> Capture "first" Int :> Capture "second" Int :> Get '[JSON] Int
285− :<|> "void" :> Post '[JSON] ()
286−
287−data NestedClient2 = NestedClient2
288− { getSum :: Int -> Int -> ClientM Int
289− , doNothing :: ClientM ()
290− } deriving Generic
291−instance SOP.Generic NestedClient2
292−instance (Client ClientM NestedAPI2 ~ client) => ClientLike client NestedClient2
293−
294−genericClientServer :: Application
295−genericClientServer = serve (Proxy :: Proxy GenericClientAPI) (
296− (\ mx -> case mx of
297− Just x -> return (x*x)
298− Nothing -> throwError $ ServantErr 400 "missing parameter" "" []
299− )
300− :<|> nestedServer1
301− )
302− where
303− nestedServer1 _str = nestedServer2 :<|> (maybe (throwError $ ServantErr 400 "missing parameter" "" []) return)
304− nestedServer2 _int = (\ x y -> return (x + y)) :<|> return ()
305−
306257runClient :: NFData a => ClientM a -> BaseUrl -> IO (Either ServantError a)
307258runClient x burl = withClientEnvIO burl (runClientM x)
308259
@@ -487,22 +438,6 @@ genAuthSpec = beforeAll (startWaiApp genAuthServer) $ afterAll endWaiApp $ do
487438 Left (FailureResponse _ r) <- runClient (getProtected authRequest) baseUrl
488439 responseStatusCode r `shouldBe` (HTTP.Status 401 "Unauthorized")
489440
490−genericClientSpec :: Spec
491−genericClientSpec = beforeAll (startWaiApp genericClientServer) $ afterAll endWaiApp $ do
492− describe "Servant.Client.Generic" $ do
493−
494− let GenericClient{..} = mkClient (client (Proxy :: Proxy GenericClientAPI))
495− NestedClient1{..} = mkNestedClient1 "example"
496− NestedClient2{..} = mkNestedClient2 (Just 42)
497−
498− it "works for top-level client inClientM function" $ \(_, baseUrl) -> do
499− left show <$> runClient (getSqr (Just 5)) baseUrl `shouldReturn` Right 25
500−
501− it "works for nested clients" $ \(_, baseUrl) -> do
502− left show <$> runClient (idChar (Just 'c')) baseUrl `shouldReturn` Right 'c'
503− left show <$> runClient (getSum 3 4) baseUrl `shouldReturn` Right 7
504− left show <$> runClient doNothing baseUrl `shouldReturn` Right ()
505−
506441-- * hoistClient
507442
508443type HoistClientAPI = Get '[JSON] Int :<|> Capture "n" Int :> Post '[JSON] Int
stack.yaml−1
@@ -18,7 +18,6 @@ packages:
1818extra-deps:
1919- base-compat-0.10.5
2020- conduit-1.3.1
21−- generics-sop-0.4.0.1
2221- hspec-2.6.0
2322- hspec-core-2.6.0
2423- hspec-discover-2.6.0