@@ -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
@@ -76,19 +74,22 @@ import Servant.API
76 FromSourceIO (..), Header', Headers (..), HttpVersion,
74 FromSourceIO (..), Header', Headers (..), HttpVersion,
77 IsSecure, MimeRender (mimeRender),
75 IsSecure, MimeRender (mimeRender),
78 MimeUnrender (mimeUnrender), NoContent (NoContent),
76 MimeUnrender (mimeUnrender), NoContent (NoContent),
79− NoContentVerb, QueryFlag, QueryParam', QueryParams, Raw,
77+ NoContentVerb, QueryFlag, QueryParam', QueryParams, Raw, RawM,
80 ReflectMethod (..), RemoteHost, ReqBody', SBoolI, Stream,
78 ReflectMethod (..), RemoteHost, ReqBody', SBoolI, Stream,
81 StreamBody', Summary, ToHttpApiData, ToSourceIO (..), Vault,
79 StreamBody', Summary, ToHttpApiData, ToSourceIO (..), Vault,
82− Verb, WithNamedContext, WithStatus (..), contentType, getHeadersHList,
80+ Verb, WithNamedContext, WithResource, WithStatus (..), contentType, getHeadersHList,
83 getResponse, toEncodedUrlPiece, toUrlPiece, NamedRoutes)
81 getResponse, toEncodedUrlPiece, toUrlPiece, NamedRoutes)
84import Servant.API.Generic
82import Servant.API.Generic
85 (GenericMode(..), ToServant, ToServantApi
83 (GenericMode(..), ToServant, ToServantApi
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)
@@ -669,6 +674,16 @@ instance RunClient m => HasClient m Raw where
670 hoistClientMonad _ _ f cl = \meth -> f (cl meth)
675 hoistClientMonad _ _ f cl = \meth -> f (cl meth)
677+instance RunClient m => HasClient m RawM where
679+ = H.Method -> m Response
681+ clientWithRoute :: Proxy m -> Proxy RawM -> Request -> Client m RawM
682+ clientWithRoute _pm Proxy req httpMethod = do
683+ runRequest req { requestMethod = httpMethod }
685+ hoistClientMonad _ _ f cl = \meth -> f (cl meth)
672-- | If you use a 'ReqBody' in one of your endpoints in your API,
687-- | If you use a 'ReqBody' in one of your endpoints in your API,
673-- the corresponding querying function will automatically take
688-- the corresponding querying function will automatically take
674-- an additional argument of the type specified by your 'ReqBody'.
689-- an additional argument of the type specified by your 'ReqBody'.
@@ -735,7 +750,7 @@ instance (KnownSymbol path, HasClient m api) => HasClient m (path :> api) where
735 clientWithRoute pm (Proxy :: Proxy api)
750 clientWithRoute pm (Proxy :: Proxy api)
736 (appendToPath p req)
751 (appendToPath p req)
738− where p = pack $ symbolVal (Proxy :: Proxy path)
753+ where p = toEncodedUrlPiece $ pack $ symbolVal (Proxy :: Proxy path)
740 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
755 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy api) f cl
@@ -771,6 +786,14 @@ instance HasClient m subapi =>
772 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy subapi) f cl
787 hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy subapi) f cl
789+instance HasClient m subapi =>
790+ HasClient m (WithResource res :> subapi) where
792+ type Client m (WithResource res :> subapi) = Client m subapi
793+ clientWithRoute pm Proxy = clientWithRoute pm (Proxy :: Proxy subapi)
795+ hoistClientMonad pm _ f cl = hoistClientMonad pm (Proxy :: Proxy subapi) f cl
774instance ( HasClient m api
797instance ( HasClient m api
775 ) => HasClient m (AuthProtect tag :> api) where
798 ) => HasClient m (AuthProtect tag :> api) where
776 type Client m (AuthProtect tag :> api)
799 type Client m (AuthProtect tag :> api)
@@ -784,7 +807,7 @@ instance ( HasClient m api
785-- | Ignore @'Fragment'@ in client functions.
808-- | Ignore @'Fragment'@ in client functions.
786-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
809-- See <https://ietf.org/rfc/rfc2616.html#section-15.1.3> for more details.
788-- Example:
811-- Example:
790-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
813-- > type MyApi = "books" :> Fragment Text :> Get '[JSON] [Book]
@@ -801,7 +824,7 @@ instance (AtLeastOneFragment api, FragmentUnique (Fragment a :> api), HasClient
802 type Client m (Fragment a :> api) = Client m api
825 type Client m (Fragment a :> api) = Client m api
804− clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
827+ clientWithRoute pm _ = clientWithRoute pm (Proxy :: Proxy api)
806 hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
829 hoistClientMonad pm _ = hoistClientMonad pm (Proxy :: Proxy api)
@@ -869,7 +892,7 @@ infixl 2 /:
870-- Example:
893-- Example:
873-- type Api = NamedRoutes RootApi
896-- type Api = NamedRoutes RootApi
875-- data RootApi mode = RootApi
898-- data RootApi mode = RootApi
@@ -889,8 +912,8 @@ infixl 2 /:
889-- rootClient = client api
912-- rootClient = client api
891-- endpointClient :: ClientM Person
914-- endpointClient :: ClientM Person
892−-- endpointClient = client // subApi // endpoint
915+-- endpointClient = client \/\/ subApi \/\/ endpoint
894(//) :: a -> (a -> b) -> b
917(//) :: a -> (a -> b) -> b
895x // f = f x
918x // f = f x
@@ -901,7 +924,7 @@ x // f = f x
902-- Example:
925-- Example:
905-- type Api = NamedRoutes RootApi
928-- type Api = NamedRoutes RootApi
907-- data RootApi mode = RootApi
930-- data RootApi mode = RootApi
@@ -922,11 +945,11 @@ x // f = f x
922-- rootClient = client api
945-- rootClient = client api
924-- hello :: String -> ClientM String
947-- hello :: String -> ClientM String
925−-- hello name = rootClient // hello /: name
948+-- hello name = rootClient \/\/ hello \/: name
927-- endpointClient :: ClientM Person
950-- endpointClient :: ClientM Person
928−-- endpointClient = client // subApi /: "foobar123" // endpoint
951+-- endpointClient = client \/\/ subApi \/: "foobar123" \/\/ endpoint
930(/:) :: (a -> b -> c) -> b -> a -> c
953(/:) :: (a -> b -> c) -> b -> a -> c
931(/:) = flip
954(/:) = flip
@@ -970,3 +993,19 @@ decodedAs response ct = do
970 Right val -> return val
993 Right val -> return val
972 accept = toList $ contentTypes ct
995 accept = toList $ contentTypes ct
997+-------------------------------------------------------------------------------
998+-- Custom type errors
999+-------------------------------------------------------------------------------
1001+-- Erroring instance for HasClient' when a combinator is not fully applied
1002+instance (RunClient m, TypeError (PartialApplication HasClient arr)) => HasClient m ((arr :: a -> b) :> sub)
1004+ type Client m (arr :> sub) = TypeError (PartialApplication HasClient arr)
1005+ clientWithRoute _ _ _ = error "unreachable"
1006+ hoistClientMonad _ _ _ _ = error "unreachable"
1008+-- Erroring instances for 'HasClient' for unknown API combinators
1009+instance {-# OVERLAPPABLE #-} (RunClient m, TypeError (NoInstanceForSub (HasClient m) ty)) => HasClient m (ty :> sub)
1011+instance {-# OVERLAPPABLE #-} (RunClient m, TypeError (NoInstanceFor (HasClient m api))) => HasClient m api