@@ -33,9 +33,7 @@ import Control.Arrow
33import Control.Monad
33import Control.Monad
35import qualified Data.ByteString as BS
35import qualified Data.ByteString as BS
36−import Data.ByteString.Builder
38−import qualified Data.ByteString.Lazy as BL
36+import qualified Data.ByteString.Lazy as BL
39import Data.Either
37import Data.Either
40 (partitionEithers)
38 (partitionEithers)
41import Data.Constraint (Dict(..))
39import Data.Constraint (Dict(..))
@@ -65,7 +63,7 @@ import Data.Text
65import Data.Proxy
63import Data.Proxy
66 (Proxy (Proxy))
64 (Proxy (Proxy))
67import GHC.TypeLits
65import GHC.TypeLits
68− (KnownSymbol, symbolVal)
66+ (KnownNat, KnownSymbol, TypeError, symbolVal)
69import Network.HTTP.Types
67import Network.HTTP.Types
71import qualified Network.HTTP.Types as H
69import qualified Network.HTTP.Types as H
@@ -86,9 +84,12 @@ import Servant.API.Generic
86 , GenericServant, toServant, fromServant)
84 , GenericServant, toServant, fromServant)
87import Servant.API.ContentTypes
85import Servant.API.ContentTypes
88 (contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
86 (contentTypes, AllMime (allMime), AllMimeUnrender (allMimeUnrender))
87+import Servant.API.Status
89import Servant.API.TypeLevel (FragmentUnique, AtLeastOneFragment)
89import Servant.API.TypeLevel (FragmentUnique, AtLeastOneFragment)
90import Servant.API.Modifiers
90import Servant.API.Modifiers
91 (FoldRequired, RequiredArgument, foldRequiredArgument)
91 (FoldRequired, RequiredArgument, foldRequiredArgument)
92+import Servant.API.TypeErrors
92import Servant.API.UVerb
93import Servant.API.UVerb
93 (HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
94 (HasStatus, HasStatuses (Statuses, statuses), UVerb, Union, Unique, inject, statusOf, foldMapUnion, matchUnion)
@@ -207,7 +208,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m api)
207 clientWithRoute pm (Proxy :: Proxy api)
208 clientWithRoute pm (Proxy :: Proxy api)
208 (appendToPath p req)
209 (appendToPath p req)
210− where p = (toUrlPiece val)
211+ where p = toEncodedUrlPiece val
212 hoistClientMonad pm _ f cl = \a ->
213 hoistClientMonad pm _ f cl = \a ->
213 hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
214 hoistClientMonad pm (Proxy :: Proxy api) f (cl a)
@@ -242,7 +243,7 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
242 clientWithRoute pm (Proxy :: Proxy sublayout)
243 clientWithRoute pm (Proxy :: Proxy sublayout)
243 (foldl' (flip appendToPath) req ps)
244 (foldl' (flip appendToPath) req ps)
245− where ps = map (toUrlPiece) vals
246+ where ps = map toEncodedUrlPiece vals
247 hoistClientMonad pm _ f cl = \as ->
248 hoistClientMonad pm _ f cl = \as ->
248 hoistClientMonad pm (Proxy :: Proxy sublayout) f (cl as)
249 hoistClientMonad pm (Proxy :: Proxy sublayout) f (cl as)
@@ -250,10 +251,11 @@ instance (KnownSymbol capture, ToHttpApiData a, HasClient m sublayout)
250instance {-# OVERLAPPABLE #-}
251instance {-# OVERLAPPABLE #-}
251 -- Note [Non-Empty Content Types]
252 -- Note [Non-Empty Content Types]
252 ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
253 ( RunClient m, MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
253 ) => HasClient m (Verb method status cts' a) where
255 ) => HasClient m (Verb method status cts' a) where
254 type Client m (Verb method status cts' a) = m a
256 type Client m (Verb method status cts' a) = m a
255 clientWithRoute _pm Proxy req = do
257 clientWithRoute _pm Proxy req = do
256− response <- runRequest req
258+ response <- runRequestAcceptStatus (Just [status]) req
257 { requestAccept = fromList $ toList accept
259 { requestAccept = fromList $ toList accept
258 , requestMethod = method
260 , requestMethod = method
@@ -261,18 +263,20 @@ instance {-# OVERLAPPABLE #-}
262 accept = contentTypes (Proxy :: Proxy ct)
264 accept = contentTypes (Proxy :: Proxy ct)
263 method = reflectMethod (Proxy :: Proxy method)
265 method = reflectMethod (Proxy :: Proxy method)
266+ status = statusFromNat (Proxy :: Proxy status)
265 hoistClientMonad _ _ f ma = f ma
268 hoistClientMonad _ _ f ma = f ma
267instance {-# OVERLAPPING #-}
270instance {-# OVERLAPPING #-}
268− ( RunClient m, ReflectMethod method
271+ ( RunClient m, ReflectMethod method, KnownNat status
269 ) => HasClient m (Verb method status cts NoContent) where
272 ) => HasClient m (Verb method status cts NoContent) where
270 type Client m (Verb method status cts NoContent)
273 type Client m (Verb method status cts NoContent)
271 = m NoContent
274 = m NoContent
272 clientWithRoute _pm Proxy req = do
275 clientWithRoute _pm Proxy req = do
273− _response <- runRequest req { requestMethod = method }
276+ _response <- runRequestAcceptStatus (Just [status]) req { requestMethod = method }
274 return NoContent
277 return NoContent
275 where method = reflectMethod (Proxy :: Proxy method)
278 where method = reflectMethod (Proxy :: Proxy method)
279+ status = statusFromNat (Proxy :: Proxy status)
277 hoistClientMonad _ _ f ma = f ma
281 hoistClientMonad _ _ f ma = f ma
@@ -289,13 +293,13 @@ instance (RunClient m, ReflectMethod method) =>
290instance {-# OVERLAPPING #-}
294instance {-# OVERLAPPING #-}
291 -- Note [Non-Empty Content Types]
295 -- Note [Non-Empty Content Types]
292− ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls
296+ ( RunClient m, MimeUnrender ct a, BuildHeadersTo ls, KnownNat status
293 , ReflectMethod method, cts' ~ (ct ': cts)
297 , ReflectMethod method, cts' ~ (ct ': cts)
294 ) => HasClient m (Verb method status cts' (Headers ls a)) where
298 ) => HasClient m (Verb method status cts' (Headers ls a)) where
295 type Client m (Verb method status cts' (Headers ls a))
299 type Client m (Verb method status cts' (Headers ls a))
296 = m (Headers ls a)
300 = m (Headers ls a)
297 clientWithRoute _pm Proxy req = do
301 clientWithRoute _pm Proxy req = do
298− response <- runRequest req
302+ response <- runRequestAcceptStatus (Just [status]) req
299 { requestMethod = method
303 { requestMethod = method
300 , requestAccept = fromList $ toList accept
304 , requestAccept = fromList $ toList accept
@@ -303,22 +307,26 @@ instance {-# OVERLAPPING #-}
303 return $ Headers { getResponse = val
307 return $ Headers { getResponse = val
304 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
308 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
306− where method = reflectMethod (Proxy :: Proxy method)
311+ method = reflectMethod (Proxy :: Proxy method)
307− accept = contentTypes (Proxy :: Proxy ct)
312+ accept = contentTypes (Proxy :: Proxy ct)
313+ status = statusFromNat (Proxy :: Proxy status)
309 hoistClientMonad _ _ f ma = f ma
315 hoistClientMonad _ _ f ma = f ma
311instance {-# OVERLAPPING #-}
317instance {-# OVERLAPPING #-}
312− ( RunClient m, BuildHeadersTo ls, ReflectMethod method
318+ ( RunClient m, BuildHeadersTo ls, ReflectMethod method, KnownNat status
313 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
319 ) => HasClient m (Verb method status cts (Headers ls NoContent)) where
314 type Client m (Verb method status cts (Headers ls NoContent))
320 type Client m (Verb method status cts (Headers ls NoContent))
315 = m (Headers ls NoContent)
321 = m (Headers ls NoContent)
316 clientWithRoute _pm Proxy req = do
322 clientWithRoute _pm Proxy req = do
317− let method = reflectMethod (Proxy :: Proxy method)
318− response <- runRequest req { requestMethod = method }
323+ response <- runRequestAcceptStatus (Just [status]) req { requestMethod = method }
319 return $ Headers { getResponse = NoContent
324 return $ Headers { getResponse = NoContent
320 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
325 , getHeadersHList = buildHeadersTo . toList $ responseHeaders response
328+ method = reflectMethod (Proxy :: Proxy method)
329+ status = statusFromNat (Proxy :: Proxy status)
323 hoistClientMonad _ _ f ma = f ma
331 hoistClientMonad _ _ f ma = f ma
@@ -561,7 +569,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api, SBoolI (FoldRequire
561 (Proxy :: Proxy mods) add (maybe req add) mparam
569 (Proxy :: Proxy mods) add (maybe req add) mparam
563 add :: a -> Request
571 add :: a -> Request
564− add param = appendToQueryString pname (Just $ encodeQueryParam param) req
572+ add param = appendToQueryString pname (Just $ encodeQueryParamValue param) req
566 pname :: Text
574 pname :: Text
567 pname = pack $ symbolVal (Proxy :: Proxy sym)
575 pname = pack $ symbolVal (Proxy :: Proxy sym)
@@ -569,9 +577,6 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api, SBoolI (FoldRequire
569 hoistClientMonad pm _ f cl = \arg ->
577 hoistClientMonad pm _ f cl = \arg ->
570 hoistClientMonad pm (Proxy :: Proxy api) f (cl arg)
578 hoistClientMonad pm (Proxy :: Proxy api) f (cl arg)
572−encodeQueryParam :: ToHttpApiData a => a -> BS.ByteString
573−encodeQueryParam = BL.toStrict . toLazyByteString . toEncodedUrlPiece
575-- | If you use a 'QueryParams' in one of your endpoints in your API,
580-- | If you use a 'QueryParams' in one of your endpoints in your API,
576-- the corresponding querying function will automatically take
581-- the corresponding querying function will automatically take
577-- an additional argument, a list of values of the type specified
582-- an additional argument, a list of values of the type specified
@@ -613,7 +618,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient m api)
615 where pname = pack $ symbolVal (Proxy :: Proxy sym)
620 where pname = pack $ symbolVal (Proxy :: Proxy sym)
616− paramlist' = map (Just . encodeQueryParam) paramlist
621+ paramlist' = map (Just . encodeQueryParamValue) paramlist
618 hoistClientMonad pm _ f cl = \as ->
623 hoistClientMonad pm _ f cl = \as ->
619 hoistClientMonad pm (Proxy :: Proxy api) f (cl as)
624 hoistClientMonad pm (Proxy :: Proxy api) f (cl as)
@@ -735,7 +740,7 @@ instance (KnownSymbol path, HasClient m api) => HasClient m (path :> api) where
735 clientWithRoute pm (Proxy :: Proxy api)
740 clientWithRoute pm (Proxy :: Proxy api)
736 (appendToPath p req)
741 (appendToPath p req)
738− where p = pack $ symbolVal (Proxy :: Proxy path)
743+ where p = toEncodedUrlPiece $ pack $ symbolVal (Proxy :: Proxy path)
740 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
745 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
@@ -784,7 +789,7 @@ instance ( HasClient m api
785-- | Ignore @'Fragment'@ in client functions.
790-- | Ignore @'Fragment'@ in client functions.
786-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
791-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
788-- Example:
793-- Example:
790-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
795-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
@@ -801,7 +806,7 @@ instance (AtLeastOneFragment api, FragmentUnique (Fragment a :> api), HasClient
802 type Client m (Fragment a :> api) = Client m api
807 type Client m (Fragment a :> api) = Client m api
804− clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
809+ clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
806 hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
811 hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
@@ -828,10 +833,10 @@ type GClientConstraints api m =
830class GClient (api :: * -> *) m where
835class GClient (api :: * -> *) m where
831− proof :: Dict (GClientConstraints api m)
836+ gClientProof :: Dict (GClientConstraints api m)
833instance GClientConstraints api m => GClient api m where
838instance GClientConstraints api m => GClient api m where
834− proof = Dict
839+ gClientProof = Dict
837 ( forall n. GClient api n
842 ( forall n. GClient api n
@@ -843,7 +848,7 @@ instance
844 clientWithRoute :: Proxy m -> Proxy (NamedRoutes api) -> Request -> Client m (NamedRoutes api)
849 clientWithRoute :: Proxy m -> Proxy (NamedRoutes api) -> Request -> Client m (NamedRoutes api)
845 clientWithRoute pm _ request =
850 clientWithRoute pm _ request =
846− case proof @api @m of
851+ case gClientProof @api @m of
847 Dict -> fromServant $ clientWithRoute pm (Proxy @(ToServantApi api)) request
852 Dict -> fromServant $ clientWithRoute pm (Proxy @(ToServantApi api)) request
849 hoistClientMonad
854 hoistClientMonad
@@ -854,7 +859,7 @@ instance
854 -> Client ma (NamedRoutes api)
859 -> Client ma (NamedRoutes api)
855 -> Client mb (NamedRoutes api)
860 -> Client mb (NamedRoutes api)
856 hoistClientMonad _ _ nat clientA =
861 hoistClientMonad _ _ nat clientA =
857− case (proof @api @ma, proof @api @mb) of
862+ case (gClientProof @api @ma, gClientProof @api @mb) of
858 (Dict, Dict) ->
863 (Dict, Dict) ->
859 fromServant @api @(AsClientT mb) $
864 fromServant @api @(AsClientT mb) $
860 hoistClientMonad @m @(ToServantApi api) @ma @mb Proxy Proxy nat $
865 hoistClientMonad @m @(ToServantApi api) @ma @mb Proxy Proxy nat $
@@ -869,7 +874,7 @@ infixl 2 /:
870-- Example:
875-- Example:
873-- type Api = NamedRoutes RootApi
878-- type Api = NamedRoutes RootApi
875-- data RootApi mode = RootApi
880-- data RootApi mode = RootApi
@@ -890,7 +895,7 @@ infixl 2 /:
891-- endpointClient :: ClientM Person
896-- endpointClient :: ClientM Person
892-- endpointClient = client // subApi // endpoint
897-- endpointClient = client // subApi // endpoint
894(//) :: a -> (a -> b) -> b
899(//) :: a -> (a -> b) -> b
895x // f = f x
900x // f = f x
@@ -901,7 +906,7 @@ x // f = f x
902-- Example:
907-- Example:
905-- type Api = NamedRoutes RootApi
910-- type Api = NamedRoutes RootApi
907-- data RootApi mode = RootApi
912-- data RootApi mode = RootApi
@@ -926,7 +931,7 @@ x // f = f x
927-- endpointClient :: ClientM Person
932-- endpointClient :: ClientM Person
928-- endpointClient = client // subApi /: "foobar123" // endpoint
933-- endpointClient = client // subApi /: "foobar123" // endpoint
930(/:) :: (a -> b -> c) -> b -> a -> c
935(/:) :: (a -> b -> c) -> b -> a -> c
931(/:) = flip
936(/:) = flip
@@ -970,3 +975,19 @@ decodedAs response ct = do
970 Right val -> return val
975 Right val -> return val
972 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