servant-client 0.20.2 → 0.20.3.0
raw patch · 7 files changed
+148/−63 lines, 7 filesdep +generics-sopdep ~QuickCheckdep ~basedep ~bytestringnew-uploader
Dependencies added: generics-sop
Dependency ranges changed: QuickCheck, base, bytestring, containers, servant, servant-client-core
Files
- CHANGELOG.md +23/−0
- README.lhs +1/−1
- README.md +1/−1
- servant-client.cabal +7/−7
- src/Servant/Client/Internal/HttpClient.hs +9/−2
- test/Servant/ClientTestUtils.hs +106/−49
- test/Servant/MiddlewareSpec.hs +1/−3
CHANGELOG.md view
@@ -3,6 +3,29 @@ Package versions follow the [Package Versioning Policy](https://pvp.haskell.org/): in A.B.C, bumps to either A or B represent major versions. +0.20.3.0+--------++- Server-sent events (SSE) for client-side [#1811](https://github.com/haskell-servant/servant/issues/1811)++ Implement Server-sent events (SSE) for the Servant client using a new+ combinator "ServerSentEvents". The raw event messages, accumulated events and+ JSON-processed events can be exposed.++- Integrate MultiVerb [#1766](https://github.com/haskell-servant/servant/pull/1766) [#1804](https://github.com/haskell-servant/servant/pull/1804)++ Expose MultiVerb, a more ergonomic way of defining endpoints that return+ many kinds of responses. Read the cookbook https://docs.servant.dev/en/master/cookbook/multiverb/MultiVerb.html++- Add public re-export of renderCurlBasePath lens [#1706](https://github.com/haskell-servant/servant/pull/1706)+- Remove GHC <= 8.10.7 from the support window [#1778](https://github.com/haskell-servant/servant/pull/1778)+- Add Servant.API.Range type [#1805](https://github.com/haskell-servant/servant/pull/1805)+- Add missing HasLink instance for DeepQuery [#1784](https://github.com/haskell-servant/servant/issues/1784) [#1814](https://github.com/haskell-servant/servant/pull/1814)+- Add Host API combinator [#1800](https://github.com/haskell-servant/servant/pull/1800)++ Adding a Host combinator allows servant users to select APIs according+ to the Host header provided by clients.+ 0.20.2 ------
README.lhs view
@@ -11,7 +11,7 @@ {-# LANGUAGE TypeOperators #-} import Data.Proxy-import Data.Text+import Data.Text (Text) import Network.HTTP.Client (newManager, defaultManagerSettings) import Servant.API import Servant.Client
README.md view
@@ -11,7 +11,7 @@ {-# LANGUAGE TypeOperators #-} import Data.Proxy-import Data.Text+import Data.Text (Text) import Network.HTTP.Client (newManager, defaultManagerSettings) import Servant.API import Servant.Client
servant-client.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: servant-client-version: 0.20.2+version: 0.20.3.0 synopsis: Automatic derivation of querying functions for servant category: Servant, Web description:@@ -21,8 +21,7 @@ 2014-2016 Zalora South East Asia Pte Ltd, 2016-2019 Servant Contributors build-type: Simple-tested-with:- GHC ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8 || ==9.6.4 || ==9.8.2 || ==9.10.1+tested-with: GHC ==9.2.8 || ==9.4.8 || ==9.6.6 || ==9.8.4 || ==9.10.1 || ==9.12.1 extra-source-files: CHANGELOG.md@@ -94,13 +93,13 @@ -- Bundled with GHC: Lower bound to not force re-installs -- text and mtl are bundled starting with GHC-8.4 build-depends:- , base >=4.14 && <4.21- , bytestring >=0.10.8.1 && <0.13- , containers >=0.5.7.1 && <0.8+ , base >= 4.16.4.0 && < 4.22+ , bytestring >=0.11 && <0.13+ , containers >=0.6.5.1 && <0.9 , deepseq >=1.4.2.0 && <1.6 , mtl ^>=2.2.2 || ^>=2.3.1 , stm >=2.4.5.1 && <2.6- , time >=1.6.0.1 && <1.13+ , time >=1.6.0.1 && <1.15 , transformers >=0.5.2.0 && <0.7 -- Servant dependencies.@@ -162,6 +161,7 @@ , servant-client , servant-client-core , sop-core+ , generics-sop , stm , text , transformers
src/Servant/Client/Internal/HttpClient.hs view
@@ -232,8 +232,15 @@ -- | Create a @http-client@ 'Client.Request' from a @servant@ 'Request' -- The 'Client.host', 'Client.path' and 'Client.port' fields are extracted from the 'BaseUrl' -- otherwise the body, headers and query string are derived from the @servant@ 'Request'-defaultMakeClientRequest :: BaseUrl -> Request -> IO Client.Request-defaultMakeClientRequest burl r = return Client.defaultRequest+--+-- Note that @Applicative@ dependency is not really needed for this function+-- implementation. But in the past the return type was wrapped into @IO@+-- without a necessity breaking the API backward-compatibility. In order to not+-- break the API again it was changed to @Applicative@ so that you can just use+-- something like @Data.Functor.Identity@ without a need to involve @IO@ but+-- still keeping it compatible with the code written when it was typed as @IO@.+defaultMakeClientRequest :: Applicative f => BaseUrl -> Request -> f Client.Request+defaultMakeClientRequest burl r = pure Client.defaultRequest { Client.method = requestMethod r , Client.host = fromString $ baseUrlHost burl , Client.port = baseUrlPort burl
test/Servant/ClientTestUtils.hs view
@@ -1,10 +1,10 @@-{-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-}@@ -22,54 +22,49 @@ import Prelude () import Prelude.Compat -import Control.Concurrent- (ThreadId, forkIO, killThread)-import Control.Monad- (join)-import Control.Monad.Error.Class- (throwError)+import Control.Concurrent (ThreadId, forkIO, killThread)+import Control.Monad (join)+import Control.Monad.Error.Class (throwError) import Data.Aeson-import Data.ByteString- (ByteString)+import Data.ByteString (ByteString)+import Data.ByteString.Builder (byteString) import qualified Data.ByteString.Char8 as C8-import Data.ByteString.Builder- (byteString) import qualified Data.ByteString.Lazy as LazyByteString-import Data.Char- (chr, isPrint)-import Data.Maybe (fromMaybe)+import Data.Char (chr, isPrint)+import Data.Maybe (fromMaybe) import Data.Monoid () import Data.Proxy import Data.SOP-import Data.Text- (Text)+import Data.Text (Text) import qualified Data.Text as Text-import Data.Text.Encoding- (decodeUtf8, encodeUtf8)-import GHC.Generics- (Generic)+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import qualified Generics.SOP as GSOP+import GHC.Generics (Generic) import qualified Network.HTTP.Client as C import qualified Network.HTTP.Types as HTTP import Network.Socket import qualified Network.Wai as Wai import Network.Wai.Handler.Warp-import System.IO.Unsafe- (unsafePerformIO)+import System.IO.Unsafe (unsafePerformIO) import Test.QuickCheck-import Text.Read (readMaybe)-import Web.FormUrlEncoded- (FromForm, ToForm)+import Text.Read (readMaybe)+import Web.FormUrlEncoded (FromForm, ToForm) import Servant.API- ((:<|>) ((:<|>)), (:>), AuthProtect, BasicAuth,- BasicAuthData (..), Capture, CaptureAll, DeepQuery, DeleteNoContent,- EmptyAPI, FormUrlEncoded, Fragment, FromHttpApiData (..), Get, Header, Headers,- JSON, MimeRender (mimeRender), MimeUnrender (mimeUnrender),+ (AuthProtect, BasicAuth, BasicAuthData (..), Capture,+ CaptureAll, DeepQuery, DeleteNoContent, EmptyAPI,+ FormUrlEncoded, Fragment, FromHttpApiData (..), Get, Header,+ Headers, Host, JSON, MimeRender (mimeRender),+ MimeUnrender (mimeUnrender), NamedRoutes, NoContent (NoContent), PlainText, Post, QueryFlag, QueryParam,- QueryParams, QueryString, Raw, ReqBody, StdMethod (GET), ToHttpApiData (..),- UVerb, Union, Verb, WithStatus (WithStatus), NamedRoutes, addHeader)-import Servant.API.Generic ((:-))-import Servant.API.QueryString (FromDeepQuery(..), ToDeepQuery(..))+ QueryParams, QueryString, Raw, ReqBody, StdMethod (GET),+ ToHttpApiData (..), UVerb, Union, Verb,+ WithStatus (WithStatus), addHeader, (:<|>) ((:<|>)), (:>))+import Servant.API.Generic ((:-))+import Servant.API.MultiVerb+import Servant.API.QueryString+ (FromDeepQuery (..), ToDeepQuery (..))+import Servant.API.Range import Servant.Client import qualified Servant.Client.Core.Auth as Auth import Servant.Server@@ -119,7 +114,7 @@ , otherRoutes :: mode :- "other" :> Capture "someParam" Int :> NamedRoutes OtherRoutes } deriving Generic -data OtherRoutes mode = OtherRoutes+newtype OtherRoutes mode = OtherRoutes { something :: mode :- "something" :> Get '[JSON] [String] } deriving Generic @@ -145,6 +140,36 @@ , (["name"], Just (Text.pack name')) ] +-----------------------------+-- MultiVerb test endpoint --+-----------------------------++-- This is the list of all possible responses+type MultipleChoicesIntResponses =+ '[ RespondEmpty 400 "Negative"+ , Respond 200 "Even number" Bool+ , Respond 200 "Odd number" Int+ ]++data MultipleChoicesIntResult+ = NegativeNumber+ | Even Bool+ | Odd Int+ deriving stock (Generic)+ deriving (AsUnion MultipleChoicesIntResponses)+ via GenericAsUnion MultipleChoicesIntResponses MultipleChoicesIntResult++instance GSOP.Generic MultipleChoicesIntResult++-- This is our endpoint description+type MultipleChoicesInt =+ Capture "int" Int+ :> MultiVerb+ 'GET+ '[JSON]+ MultipleChoicesIntResponses+ MultipleChoicesIntResult+ type Api = Get '[JSON] Person :<|> "get" :> Get '[JSON] Person@@ -187,7 +212,10 @@ WithStatus 301 Text] :<|> "uverb-get-created" :> UVerb 'GET '[PlainText] '[WithStatus 201 Person] :<|> NamedRoutes RecordRoutes+ :<|> "multiple-choices-int" :> MultipleChoicesInt :<|> "captureVerbatim" :> Capture "someString" Verbatim :> Get '[PlainText] Text+ :<|> "host-test" :> Host "servant.example" :> Get '[JSON] Bool+ :<|> PaginatedAPI api :: Proxy Api api = Proxy@@ -221,6 +249,10 @@ WithStatus 301 Text]) uverbGetCreated :: ClientM (Union '[WithStatus 201 Person]) recordRoutes :: RecordRoutes (AsClientT ClientM)+multiChoicesInt :: Int -> ClientM MultipleChoicesIntResult+captureVerbatim :: Verbatim -> ClientM Text+getHost :: ClientM Bool+getPaginatedPerson :: Maybe (Range 1 100) -> ClientM [Person] getRoot :<|> getGet@@ -249,7 +281,10 @@ :<|> uverbGetSuccessOrRedirect :<|> uverbGetCreated :<|> recordRoutes- :<|> captureVerbatim = client api+ :<|> multiChoicesInt+ :<|> captureVerbatim+ :<|> getHost+ :<|> getPaginatedPerson = client api server :: Application server = serve api (@@ -260,10 +295,10 @@ :<|> (\ name -> return $ Person name 0) :<|> (\ names -> return (zipWith Person names [0..])) :<|> return- :<|> (\ name -> case name of- Just "alice" -> return alice- Just n -> throwError $ ServerError 400 (n ++ " not found") "" []- Nothing -> throwError $ ServerError 400 "missing parameter" "" [])+ :<|> (\case+ Just "alice" -> return alice+ Just n -> throwError $ ServerError 400 (n ++ " not found") "" []+ Nothing -> throwError $ ServerError 400 "missing parameter" "" []) :<|> const (Tagged $ \request respond -> respond . maybe (Wai.responseLBS HTTP.notFound404 [] "Missing: payload") (Wai.responseLBS HTTP.ok200 [] . LazyByteString.fromStrict)@@ -282,15 +317,15 @@ } ) :<|> return alice- :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "rawSuccess")- :<|> (Tagged $ \ request respond -> (respond $ Wai.responseLBS HTTP.ok200 (Wai.requestHeaders $ request) "rawSuccess"))- :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.badRequest400 [] "rawFailure")+ :<|> Tagged (\ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "rawSuccess")+ :<|> Tagged (\ request respond -> respond $ Wai.responseLBS HTTP.ok200 (Wai.requestHeaders request) "rawSuccess")+ :<|> Tagged (\ _request respond -> respond $ Wai.responseLBS HTTP.badRequest400 [] "rawFailure") :<|> (\ a b c d -> return (a, b, c, d))- :<|> (return $ addHeader 1729 $ addHeader "eg2" True)+ :<|> return (addHeader 1729 $ addHeader "eg2" True) :<|> (pure . Z . I . WithStatus $ addHeader 1729 $ addHeader "eg2" True)- :<|> (return $ addHeader "cookie1" $ addHeader "cookie2" True)+ :<|> return (addHeader "cookie1" $ addHeader "cookie2" True) :<|> return NoContent- :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.found302 [("Location", "testlocation"), ("Set-Cookie", "testcookie=test")] "")+ :<|> Tagged (\ _request respond -> respond $ Wai.responseLBS HTTP.found302 [("Location", "testlocation"), ("Set-Cookie", "testcookie=test")] "") :<|> emptyServer :<|> (\shouldRedirect -> if shouldRedirect then respond (WithStatus @301 ("redirecting" :: Text))@@ -299,11 +334,22 @@ :<|> RecordRoutes { version = pure 42 , echo = pure- , otherRoutes = \_ -> OtherRoutes+ , otherRoutes = const OtherRoutes { something = pure ["foo", "bar", "pweet"] } }+ :<|> (\param ->+ if param < 0+ then pure NegativeNumber+ else+ if even param+ then pure $ Odd 3+ else pure $ Even True+ )+ :<|> pure . decodeUtf8 . unVerbatim+ :<|> pure True+ :<|> usersServer ) -- * api for testing failures@@ -318,10 +364,10 @@ failServer :: Application failServer = serve failApi (- (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "")+ Tagged (\ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "") :<|> (\ _capture -> Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/json")] "")- :<|> (Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "fooooo")] "")- :<|> (Tagged $ \_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/x-www-form-urlencoded"), ("X-Example1", "1"), ("X-Example2", "foo")] "")+ :<|> Tagged (\_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "fooooo")] "")+ :<|> Tagged (\_request respond -> respond $ Wai.responseLBS HTTP.ok200 [("content-type", "application/x-www-form-urlencoded"), ("X-Example1", "1"), ("X-Example2", "foo")] "") ) -- * basic auth stuff@@ -424,3 +470,14 @@ instance FromHttpApiData Verbatim where parseUrlPiece = pure . Verbatim . encodeUtf8++-- * range type example++type PaginatedAPI =+ "users" :> QueryParam "page" (Range 1 100) :> Get '[JSON] [Person]++usersServer :: Maybe (Range 1 100) -> Handler [Person]+usersServer mpage = do+ let pageNum = maybe 1 unRange mpage+ -- pageNum is guaranteed to be between 1 and 100+ return [Person "Example" $ fromIntegral pageNum]
test/Servant/MiddlewareSpec.hs view
@@ -16,9 +16,7 @@ module Servant.MiddlewareSpec (spec) where -import Control.Arrow- ( left,- )+import Control.Arrow (left) import Control.Concurrent (newEmptyMVar, putMVar, takeMVar) import Control.Exception (Exception, throwIO, try) import Control.Monad.IO.Class