@@ -6,6 +6,7 @@
6{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE MultiParamTypeClasses #-}
7{-# LANGUAGE OverloadedStrings #-}
7{-# LANGUAGE OverloadedStrings #-}
8{-# LANGUAGE PolyKinds #-}
8{-# LANGUAGE PolyKinds #-}
9+{-# LANGUAGE QuantifiedConstraints #-}
9{-# LANGUAGE RankNTypes #-}
10{-# LANGUAGE RankNTypes #-}
10{-# LANGUAGE ScopedTypeVariables #-}
11{-# LANGUAGE ScopedTypeVariables #-}
11{-# LANGUAGE TypeApplications #-}
12{-# LANGUAGE TypeApplications #-}
@@ -17,6 +18,9 @@ module Servant.Client.Core.HasClient (
18 HasClient (..),
19 HasClient (..),
19 EmptyClient (..),
20 EmptyClient (..),
20 foldMapUnion,
24 foldMapUnion,
21 matchUnion,
25 matchUnion,
@@ -28,9 +32,11 @@ import Control.Arrow
28 (left, (+++))
32 (left, (+++))
29import Control.Monad
33import Control.Monad
31−import qualified Data.ByteString.Lazy as BL
35+import qualified Data.ByteString as BS
36+import qualified Data.ByteString.Lazy as BL
32import Data.Either
37import Data.Either
33 (partitionEithers)
38 (partitionEithers)
39+import Data.Constraint (Dict(..))
34import Data.Foldable
40import Data.Foldable
36import Data.List
42import Data.List
@@ -39,7 +45,8 @@ import Data.Sequence
39 (fromList)
45 (fromList)
40import qualified Data.Text as T
46import qualified Data.Text as T
41import Network.HTTP.Media
47import Network.HTTP.Media
42− (MediaType, matches, parseAccept, (//))
48+ (MediaType, matches, parseAccept)
49+import qualified Network.HTTP.Media as Media
43import qualified Data.Sequence as Seq
50import qualified Data.Sequence as Seq
44import Data.SOP.BasicFunctors
51import Data.SOP.BasicFunctors
45 (I (I), (:.:) (Comp))
52 (I (I), (:.:) (Comp))
@@ -56,26 +63,33 @@ import Data.Text
56import Data.Proxy
63import Data.Proxy
57 (Proxy (Proxy))
64 (Proxy (Proxy))
58import GHC.TypeLits
65import GHC.TypeLits
59− (KnownSymbol, symbolVal)
66+ (KnownNat, KnownSymbol, TypeError, symbolVal)
60import Network.HTTP.Types
67import Network.HTTP.Types
62import qualified Network.HTTP.Types as H
69import qualified Network.HTTP.Types as H
63import Servant.API
70import Servant.API
64 ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth, BasicAuthData,
71 ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth, BasicAuthData,
65 BuildHeadersTo (..), Capture', CaptureAll, Description,
72 BuildHeadersTo (..), Capture', CaptureAll, Description,
66− EmptyAPI, FramingRender (..), FramingUnrender (..),
73+ EmptyAPI, Fragment, FramingRender (..), FramingUnrender (..),
67 FromSourceIO (..), Header', Headers (..), HttpVersion,
74 FromSourceIO (..), Header', Headers (..), HttpVersion,
68 IsSecure, MimeRender (mimeRender),
75 IsSecure, MimeRender (mimeRender),
69− MimeUnrender (mimeUnrender), NoContent (NoContent), QueryFlag,
76+ MimeUnrender (mimeUnrender), NoContent (NoContent),
70− QueryParam', QueryParams, Raw, ReflectMethod (..), RemoteHost,
77+ NoContentVerb, QueryFlag, QueryParam', QueryParams, Raw,
78+ ReflectMethod (..), RemoteHost, ReqBody', SBoolI, Stream,
71− ReqBody', SBoolI, Stream, StreamBody', Summary, ToHttpApiData,
79+ StreamBody', Summary, ToHttpApiData, ToSourceIO (..), Vault,
72− ToSourceIO (..), Vault, Verb, NoContentVerb, WithNamedContext,
73− contentType, getHeadersHList, getResponse, toQueryParam,
80+ Verb, WithNamedContext, WithStatus (..), contentType, getHeadersHList,
74− toUrlPiece)
81+ getResponse, toEncodedUrlPiece, toUrlPiece, NamedRoutes)
82+import Servant.API.Generic
83+ (GenericMode(..), ToServant, ToServantApi
84+ , GenericServant, toServant, fromServant)
75import Servant.API.ContentTypes
85import Servant.API.ContentTypes
76 (contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
86 (contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
87+import Servant.API.Status
89+import Servant.API.TypeLevel (FragmentUnique, AtLeastOneFragment)
77import Servant.API.Modifiers
90import Servant.API.Modifiers
78 (FoldRequired, RequiredArgument, foldRequiredArgument)
91 (FoldRequired, RequiredArgument, foldRequiredArgument)
92+import Servant.API.TypeErrors
79import Servant.API.UVerb
93import Servant.API.UVerb
80 (HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
94 (HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
@@ -194,7 +208,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m api)
194 clientWithRoute pm (Proxy :: Proxy api)
208 clientWithRoute pm (Proxy :: Proxy api)
195 (appendToPath p req)
209 (appendToPath p req)
197− where p = (toUrlPiece val)
211+ where p = toEncodedUrlPiece val
199 hoistClientMonad pm _ f cl = \a ->
213 hoistClientMonad pm _ f cl = \a ->
200 hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
214 hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
@@ -229,7 +243,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
229 clientWithRoute pm (Proxy :: Proxy sublayout)
243 clientWithRoute pm (Proxy :: Proxy sublayout)
230 (foldl' (flip appendToPath) req ps)
244 (foldl' (flip appendToPath) req ps)
232− where ps = map (toUrlPiece) vals
246+ where ps = map toEncodedUrlPiece vals
234 hoistClientMonad pm _ f cl = \as ->
248 hoistClientMonad pm _ f cl = \as ->
235 hoistClientMonad pm (Proxy :: Proxy sublayout) f (cl as)
249 hoistClientMonad pm (Proxy :: Proxy sublayout) f (cl as)
@@ -237,10 +251,11 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
237instance {-# OVERLAPPABLE #-}
251instance {-# OVERLAPPABLE #-}
238 -- Note [Non-Empty Content Types]
252 -- Note [Non-Empty Content Types]
239 ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
253 ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
240 ) => HasClient m (Verb method status cts' a) where
255 ) => HasClient m (Verb method status cts' a) where
241 type Client m (Verb method status cts' a) = m a
256 type Client m (Verb method status cts' a) = m a
242 clientWithRoute _pm Proxy req = do
257 clientWithRoute _pm Proxy req = do
243− response <- runRequest req
258+ response <- runRequestAcceptStatus (Just [status]) req
244 { requestAccept = fromList $ toList accept
259 { requestAccept = fromList $ toList accept
245 , requestMethod = method
260 , requestMethod = method
@@ -248,18 +263,20 @@ instance {-# OVERLAPPABLE #-}
249 accept = contentTypes (Proxy :: Proxy ct)
264 accept = contentTypes (Proxy :: Proxy ct)
250 method = reflectMethod (Proxy :: Proxy method)
265 method = reflectMethod (Proxy :: Proxy method)
266+ status = statusFromNat (Proxy :: Proxy status)
252 hoistClientMonad _ _ f ma = f ma
268 hoistClientMonad _ _ f ma = f ma
254instance {-# OVERLAPPING #-}
270instance {-# OVERLAPPING #-}
255− ( RunClient m, ReflectMethod method
271+ ( RunClient m, ReflectMethod method, KnownNat status
256 ) => HasClient m (Verb method status cts NoContent) where
272 ) => HasClient m (Verb method status cts NoContent) where
257 type Client m (Verb method status cts NoContent)
273 type Client m (Verb method status cts NoContent)
258 = m NoContent
274 = m NoContent
259 clientWithRoute _pm Proxy req = do
275 clientWithRoute _pm Proxy req = do
260− _response <- runRequest req { requestMethod = method }
276+ _response <- runRequestAcceptStatus (Just [status]) req { requestMethod = method }
261 return NoContent
277 return NoContent
262 where method = reflectMethod (Proxy :: Proxy method)
278 where method = reflectMethod (Proxy :: Proxy method)
279+ status = statusFromNat (Proxy :: Proxy status)
264 hoistClientMonad _ _ f ma = f ma
281 hoistClientMonad _ _ f ma = f ma
@@ -276,13 +293,13 @@ instance (RunClient m, ReflectMethod method) =>
277instance {-# OVERLAPPING #-}
294instance {-# OVERLAPPING #-}
278 -- Note [Non-Empty Content Types]
295 -- Note [Non-Empty Content Types]
279− ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls
296+ ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls, KnownNat status
280 , ReflectMethod method, cts' ~ (ct ': cts)
297 , ReflectMethod method, cts' ~ (ct ': cts)
281 ) => HasClient m (Verb method status cts' (Headers ls a)) where
298 ) => HasClient m (Verb method status cts' (Headers ls a)) where
282 type Client m (Verb method status cts' (Headers ls a))
299 type Client m (Verb method status cts' (Headers ls a))
283 = m (Headers ls a)
300 = m (Headers ls a)
284 clientWithRoute _pm Proxy req = do
301 clientWithRoute _pm Proxy req = do
285− response <- runRequest req
302+ response <- runRequestAcceptStatus (Just [status]) req
286 { requestMethod = method
303 { requestMethod = method
287 , requestAccept = fromList $ toList accept
304 , requestAccept = fromList $ toList accept
@@ -290,28 +307,51 @@ instance {-# OVERLAPPING #-}
290 return $ Headers { getResponse = val
307 return $ Headers { getResponse = val
291 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
308 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
293− where method = reflectMethod (Proxy :: Proxy method)
311+ method = reflectMethod (Proxy :: Proxy method)
294− accept = contentTypes (Proxy :: Proxy ct)
312+ accept = contentTypes (Proxy :: Proxy ct)
313+ status = statusFromNat (Proxy :: Proxy status)
296 hoistClientMonad _ _ f ma = f ma
315 hoistClientMonad _ _ f ma = f ma
298instance {-# OVERLAPPING #-}
317instance {-# OVERLAPPING #-}
299− ( RunClient m, BuildHeadersTo ls, ReflectMethod method
318+ ( RunClient m, BuildHeadersTo ls, ReflectMethod method, KnownNat status
300 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
319 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
301 type Client m (Verb method status cts (Headers ls NoContent))
320 type Client m (Verb method status cts (Headers ls NoContent))
302 = m (Headers ls NoContent)
321 = m (Headers ls NoContent)
303 clientWithRoute _pm Proxy req = do
322 clientWithRoute _pm Proxy req = do
304− let method = reflectMethod (Proxy :: Proxy method)
305− response <- runRequest req { requestMethod = method }
323+ response <- runRequestAcceptStatus (Just [status]) req { requestMethod = method }
306 return $ Headers { getResponse = NoContent
324 return $ Headers { getResponse = NoContent
307 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
325 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
328+ method = reflectMethod (Proxy :: Proxy method)
329+ status = statusFromNat (Proxy :: Proxy status)
310 hoistClientMonad _ _ f ma = f ma
331 hoistClientMonad _ _ f ma = f ma
312data ClientParseError = ClientParseError MediaType String | ClientStatusMismatch | ClientNoMatchingStatus
333data ClientParseError = ClientParseError MediaType String | ClientStatusMismatch | ClientNoMatchingStatus
313 deriving (Eq, Show)
334 deriving (Eq, Show)
336+class UnrenderResponse (cts :: [*]) (a :: *) where
337+ unrenderResponse :: Seq.Seq H.Header -> BL.ByteString -> Proxy cts
338+ -> [Either (MediaType, String) a]
340+instance {-# OVERLAPPABLE #-} AllMimeUnrender cts a => UnrenderResponse cts a where
341+ unrenderResponse _ body = map parse . allMimeUnrender
342+ where parse (mediaType, parser) = left ((,) mediaType) (parser body)
344+instance {-# OVERLAPPING #-} forall cts a h . (UnrenderResponse cts a, BuildHeadersTo h)
345+ => UnrenderResponse cts (Headers h a) where
346+ unrenderResponse hs body = (map . fmap) setHeaders . unrenderResponse hs body
348+ setHeaders :: a -> Headers h a
349+ setHeaders x = Headers x (buildHeadersTo (toList hs))
351+instance {-# OVERLAPPING #-} UnrenderResponse cts a
352+ => UnrenderResponse cts (WithStatus n a) where
353+ unrenderResponse hs body = (map . fmap) WithStatus . unrenderResponse hs body
315instance {-# OVERLAPPING #-}
355instance {-# OVERLAPPING #-}
316 ( RunClient m,
356 ( RunClient m,
317 contentTypes ~ (contentType ': otherContentTypes),
357 contentTypes ~ (contentType ': otherContentTypes),
@@ -320,7 +360,7 @@ instance {-# OVERLAPPING #-}
320 as ~ (a ': as'),
360 as ~ (a ': as'),
321 AllMime contentTypes,
361 AllMime contentTypes,
322 ReflectMethod method,
362 ReflectMethod method,
323− All (AllMimeUnrender contentTypes) as,
363+ All (UnrenderResponse contentTypes) as,
324 All HasStatus as, HasStatuses as',
364 All HasStatus as, HasStatuses as',
325 Unique (Statuses as)
365 Unique (Statuses as)
@@ -343,7 +383,8 @@ instance {-# OVERLAPPING #-}
344 let status = responseStatusCode response
384 let status = responseStatusCode response
345 body = responseBody response
385 body = responseBody response
386+ headers = responseHeaders response
346− res = tryParsers status $ mimeUnrenders (Proxy @contentTypes) body
387+ res = tryParsers status $ mimeUnrenders (Proxy @contentTypes) headers body
347 case res of
388 case res of
348 Left errors -> throwClientError $ DecodeFailure (T.pack (show errors)) response
389 Left errors -> throwClientError $ DecodeFailure (T.pack (show errors)) response
349 Right x -> return x
390 Right x -> return x
@@ -364,13 +405,14 @@ instance {-# OVERLAPPING #-}
364 -- | Given a list of types, parses the given response body as each type
405 -- | Given a list of types, parses the given response body as each type
365 mimeUnrenders ::
406 mimeUnrenders ::
366 forall cts xs.
407 forall cts xs.
367− All (AllMimeUnrender cts) xs =>
408+ All (UnrenderResponse cts) xs =>
368 Proxy cts ->
409 Proxy cts ->
369 BL.ByteString ->
411 BL.ByteString ->
370 NP ([] :.: Either (MediaType, String)) xs
412 NP ([] :.: Either (MediaType, String)) xs
371− mimeUnrenders ctp body = cpure_NP
413+ mimeUnrenders ctp headers body = cpure_NP
372− (Proxy @(AllMimeUnrender cts))
414+ (Proxy @(UnrenderResponse cts))
373− (Comp . map (\(mediaType, parser) -> left ((,) mediaType) (parser body)) . allMimeUnrender $ ctp)
415+ (Comp . unrenderResponse headers body $ ctp)
375 hoistClientMonad _ _ nt s = nt s
417 hoistClientMonad _ _ nt s = nt s
@@ -527,7 +569,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api, SBoolI (FoldRequire
527 (Proxy :: Proxy mods) add (maybe req add) mparam
569 (Proxy :: Proxy mods) add (maybe req add) mparam
529 add :: a -> Request
571 add :: a -> Request
530− add param = appendToQueryString pname (Just $ toQueryParam param) req
572+ add param = appendToQueryString pname (Just $ encodeQueryParamValue param) req
532 pname :: Text
574 pname :: Text
533 pname = pack $ symbolVal (Proxy :: Proxy sym)
575 pname = pack $ symbolVal (Proxy :: Proxy sym)
@@ -576,7 +618,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api)
578 where pname = pack $ symbolVal (Proxy :: Proxy sym)
620 where pname = pack $ symbolVal (Proxy :: Proxy sym)
579− paramlist' = map (Just . toQueryParam) paramlist
621+ paramlist' = map (Just . encodeQueryParamValue) paramlist
581 hoistClientMonad pm _ f cl = \as ->
623 hoistClientMonad pm _ f cl = \as ->
582 hoistClientMonad pm (Proxy :: Proxy api) f (cl as)
624 hoistClientMonad pm (Proxy :: Proxy api) f (cl as)
@@ -698,7 +740,7 @@ instance (KnownSymbol path, HasClient m api) => HasClient m (path :> api) where
698 clientWithRoute pm (Proxy :: Proxy api)
740 clientWithRoute pm (Proxy :: Proxy api)
699 (appendToPath p req)
741 (appendToPath p req)
701− where p = pack $ symbolVal (Proxy :: Proxy path)
743+ where p = toEncodedUrlPiece $ pack $ symbolVal (Proxy :: Proxy path)
703 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
745 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
@@ -745,6 +787,29 @@ instance ( HasClient m api
745 hoistClientMonad pm _ f cl = \authreq ->
787 hoistClientMonad pm _ f cl = \authreq ->
746 hoistClientMonad pm (Proxy :: Proxy api) f (cl authreq)
788 hoistClientMonad pm (Proxy :: Proxy api) f (cl authreq)
790+-- | Ignore @'Fragment'@ in client functions.
791+-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
795+-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
797+-- > myApi :: Proxy MyApi
800+-- > getBooks :: ClientM [Book]
801+-- > getBooks = client myApi
802+-- > -- then you can just use "getBooksBy" to query that endpoint.
803+-- > -- 'getBooks' for all books.
804+instance (AtLeastOneFragment api, FragmentUnique (Fragment a :> api), HasClient m api
805+ ) => HasClient m (Fragment a :> api) where
807+ type Client m (Fragment a :> api) = Client m api
809+ clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
811+ hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
748-- * Basic Authentication
813-- * Basic Authentication
750instance HasClient m api => HasClient m (BasicAuth realm usr :> api) where
815instance HasClient m api => HasClient m (BasicAuth realm usr :> api) where
@@ -756,6 +821,119 @@ instance HasClient m api => HasClient m (BasicAuth realm usr :> api) where
756 hoistClientMonad pm _ f cl = \bauth ->
821 hoistClientMonad pm _ f cl = \bauth ->
757 hoistClientMonad pm (Proxy :: Proxy api) f (cl bauth)
822 hoistClientMonad pm (Proxy :: Proxy api) f (cl bauth)
824+-- | A type that specifies that an API record contains a client implementation.
825+data AsClientT (m :: * -> *)
826+instance GenericMode (AsClientT m) where
827+ type AsClientT m :- api = Client m api
830+type GClientConstraints api m =
831+ ( GenericServant api (AsClientT m)
832+ , Client m (ToServantApi api) ~ ToServant api (AsClientT m)
835+class GClient (api :: * -> *) m where
836+ gClientProof :: Dict (GClientConstraints api m)
838+instance GClientConstraints api m => GClient api m where
842+ ( forall n. GClient api n
843+ , HasClient m (ToServantApi api)
846+ => HasClient m (NamedRoutes api) where
847+ type Client m (NamedRoutes api) = api (AsClientT m)
849+ clientWithRoute :: Proxy m -> Proxy (NamedRoutes api) -> Request -> Client m (NamedRoutes api)
850+ clientWithRoute pm _ request =
851+ case gClientProof @api @m of
852+ Dict -> fromServant $ clientWithRoute pm (Proxy @(ToServantApi api)) request
857+ -> Proxy (NamedRoutes api)
858+ -> (forall x. ma x -> mb x)
859+ -> Client ma (NamedRoutes api)
860+ -> Client mb (NamedRoutes api)
861+ hoistClientMonad _ _ nat clientA =
862+ case (gClientProof @api @ma, gClientProof @api @mb) of
864+ fromServant @api @(AsClientT mb) $
865+ hoistClientMonad @m @(ToServantApi api) @ma @mb Proxy Proxy nat $
866+ toServant @api @(AsClientT ma) clientA
871+-- | Helper to make code using records of clients more readable.
873+-- Can be mixed with (/:) for supplying arguments.
878+-- type Api = NamedRoutes RootApi
880+-- data RootApi mode = RootApi
881+-- { subApi :: mode :- NamedRoutes SubApi
883+-- } deriving Generic
885+-- data SubApi mode = SubApi
886+-- { endpoint :: mode :- Get '[JSON] Person
888+-- } deriving Generic
893+-- rootClient :: RootApi (AsClientT ClientM)
894+-- rootClient = client api
896+-- endpointClient :: ClientM Person
897+-- endpointClient = client // subApi // endpoint
899+(//) :: a -> (a -> b) -> b
902+-- | Convenience function for supplying arguments to client functions when
903+-- working with records of clients.
905+-- Intended to be used in conjunction with '(//)'.
910+-- type Api = NamedRoutes RootApi
912+-- data RootApi mode = RootApi
913+-- { subApi :: mode :- Capture "token" String :> NamedRoutes SubApi
914+-- , hello :: mode :- Capture "name" String :> Get '[JSON] String
916+-- } deriving Generic
918+-- data SubApi mode = SubApi
919+-- { endpoint :: mode :- Get '[JSON] Person
921+-- } deriving Generic
926+-- rootClient :: RootApi (AsClientT ClientM)
927+-- rootClient = client api
929+-- hello :: String -> ClientM String
930+-- hello name = rootClient // hello /: name
932+-- endpointClient :: ClientM Person
933+-- endpointClient = client // subApi /: "foobar123" // endpoint
935+(/:) :: (a -> b -> c) -> b -> a -> c
761{- Note [Non-Empty Content Types]
939{- Note [Non-Empty Content Types]
@@ -781,7 +959,7 @@ for empty and one for non-empty lists).
781checkContentTypeHeader :: RunClient m => Response -> m MediaType
959checkContentTypeHeader :: RunClient m => Response -> m MediaType
782checkContentTypeHeader response =
960checkContentTypeHeader response =
783 case lookup "Content-Type" $ toList $ responseHeaders response of
961 case lookup "Content-Type" $ toList $ responseHeaders response of
784− Nothing -> return $ "application"//"octet-stream"
962+ Nothing -> return $ "application" Media.// "octet-stream"
785 Just t -> case parseAccept t of
963 Just t -> case parseAccept t of
786 Nothing -> throwClientError $ InvalidContentTypeHeader response
964 Nothing -> throwClientError $ InvalidContentTypeHeader response
787 Just t' -> return t'
965 Just t' -> return t'
@@ -797,3 +975,19 @@ decodedAs response ct = do
797 Right val -> return val
975 Right val -> return val
799 accept = toList $ contentTypes ct
977 accept = toList $ contentTypes ct
979+-------------------------------------------------------------------------------
980+-- Custom type errors
981+-------------------------------------------------------------------------------
983+-- Erroring instance for HasClient' when a combinator is not fully applied
984+instance (RunClient m, TypeError (PartialApplication HasClient arr)) => HasClient m ((arr :: a -> b) :> sub)
986+ type Client m (arr :> sub) = TypeError (PartialApplication HasClient arr)
987+ clientWithRoute _ _ _ = error "unreachable"
988+ hoistClientMonad _ _ _ _ = error "unreachable"
990+-- Erroring instances for 'HasClient' for unknown API combinators
991+instance {-# OVERLAPPABLE #-} (RunClient m, TypeError (NoInstanceForSub (HasClient m) ty)) => HasClient m (ty :> sub)
993+instance {-# OVERLAPPABLE #-} (RunClient m, TypeError (NoInstanceFor (HasClient m api))) => HasClient m api