on master help sign in
Format the source code with Ormolu
c2c2ba3Mark Karpov
Network/HTTP/Req.hs+502−498
@@ -1,3 +1,20 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE DeriveDataTypeable #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE KindSignatures #-}
8+{-# LANGUAGE LambdaCase #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE QuasiQuotes #-}
12+{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE RecordWildCards #-}
14+{-# LANGUAGE ScopedTypeVariables #-}
15+{-# LANGUAGE TypeFamilies #-}
16+{-# LANGUAGE UndecidableInstances #-}
17+
1-- |
18-- |
2-- Module : Network.HTTP.Req
19-- Module : Network.HTTP.Req
3-- Copyright : © 2016–present Mark Karpov
20-- Copyright : © 2016–present Mark Karpov
@@ -87,128 +104,128 @@
87-- the whole Haskell ecosystem uses, there is no risk in using @req@. The
104-- the whole Haskell ecosystem uses, there is no risk in using @req@. The
88-- machinery for performing requests is the same as with @http-conduit@ and
105-- machinery for performing requests is the same as with @http-conduit@ and
89-- @wreq@. The only difference is the API.
106-- @wreq@. The only difference is the API.
90−
91−{-# LANGUAGE BangPatterns #-}
92−{-# LANGUAGE DataKinds #-}
93−{-# LANGUAGE DeriveDataTypeable #-}
94−{-# LANGUAGE DeriveGeneric #-}
95−{-# LANGUAGE FlexibleInstances #-}
96−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
97−{-# LANGUAGE KindSignatures #-}
98−{-# LANGUAGE LambdaCase #-}
99−{-# LANGUAGE MultiParamTypeClasses #-}
100−{-# LANGUAGE OverloadedStrings #-}
101−{-# LANGUAGE QuasiQuotes #-}
102−{-# LANGUAGE RankNTypes #-}
103−{-# LANGUAGE RecordWildCards #-}
104−{-# LANGUAGE ScopedTypeVariables #-}
105−{-# LANGUAGE TypeFamilies #-}
106−{-# LANGUAGE UndecidableInstances #-}
107−
108module Network.HTTP.Req
107module Network.HTTP.Req
109 ( -- * Making a request
108 ( -- * Making a request
110 -- $making-a-request
109 -- $making-a-request
111− req
110+ req,
112− , reqBr
111+ reqBr,
113− , req'
112+ req',
114− , withReqManager
113+ withReqManager,
114+
115 -- * Embedding requests in your monad
115 -- * Embedding requests in your monad
116 -- $embedding-requests
116 -- $embedding-requests
117− , MonadHttp (..)
117+ MonadHttp (..),
118− , HttpConfig (..)
118+ HttpConfig (..),
119− , defaultHttpConfig
119+ defaultHttpConfig,
120− , Req
120+ Req,
121− , runReq
121+ runReq,
122+
122 -- * Request
123 -- * Request
124+
123 -- ** Method
125 -- ** Method
124 -- $method
126 -- $method
125− , GET (..)
127+ GET (..),
126− , POST (..)
128+ POST (..),
127− , HEAD (..)
129+ HEAD (..),
128− , PUT (..)
130+ PUT (..),
129− , DELETE (..)
131+ DELETE (..),
130− , TRACE (..)
132+ TRACE (..),
131− , CONNECT (..)
133+ CONNECT (..),
132− , OPTIONS (..)
134+ OPTIONS (..),
133− , PATCH (..)
135+ PATCH (..),
134− , HttpMethod (..)
136+ HttpMethod (..),
137+
135 -- ** URL
138 -- ** URL
136 -- $url
139 -- $url
137− , Url
140+ Url,
138− , http
141+ http,
139− , https
142+ https,
140− , (/~)
143+ (/~),
141− , (/:)
144+ (/:),
142− , useHttpURI
145+ useHttpURI,
143− , useHttpsURI
146+ useHttpsURI,
144− , useURI
147+ useURI,
148+
145 -- ** Body
149 -- ** Body
146 -- $body
150 -- $body
147− , NoReqBody (..)
151+ NoReqBody (..),
148− , ReqBodyJson (..)
152+ ReqBodyJson (..),
149− , ReqBodyFile (..)
153+ ReqBodyFile (..),
150− , ReqBodyBs (..)
154+ ReqBodyBs (..),
151− , ReqBodyLbs (..)
155+ ReqBodyLbs (..),
152− , ReqBodyUrlEnc (..)
156+ ReqBodyUrlEnc (..),
153− , FormUrlEncodedParam
157+ FormUrlEncodedParam,
154− , ReqBodyMultipart
158+ ReqBodyMultipart,
155− , reqBodyMultipart
159+ reqBodyMultipart,
156− , HttpBody (..)
160+ HttpBody (..),
157− , ProvidesBody
161+ ProvidesBody,
158− , HttpBodyAllowed
162+ HttpBodyAllowed,
163+
159 -- ** Optional parameters
164 -- ** Optional parameters
160 -- $optional-parameters
165 -- $optional-parameters
161− , Option
166+ Option,
167+
162 -- *** Query parameters
168 -- *** Query parameters
163 -- $query-parameters
169 -- $query-parameters
164− , (=:)
170+ (=:),
165− , queryFlag
171+ queryFlag,
166− , QueryParam (..)
172+ QueryParam (..),
173+
167 -- *** Headers
174 -- *** Headers
168− , header
175+ header,
169− , attachHeader
176+ attachHeader,
177+
170 -- *** Cookies
178 -- *** Cookies
171 -- $cookies
179 -- $cookies
172− , cookieJar
180+ cookieJar,
181+
173 -- *** Authentication
182 -- *** Authentication
174 -- $authentication
183 -- $authentication
175− , basicAuth
184+ basicAuth,
176− , basicAuthUnsafe
185+ basicAuthUnsafe,
177− , basicProxyAuth
186+ basicProxyAuth,
178− , oAuth1
187+ oAuth1,
179− , oAuth2Bearer
188+ oAuth2Bearer,
180− , oAuth2Token
189+ oAuth2Token,
181− , customAuth
190+ customAuth,
191+
182 -- *** Other
192 -- *** Other
183− , port
193+ port,
184− , decompress
194+ decompress,
185− , responseTimeout
195+ responseTimeout,
186− , httpVersion
196+ httpVersion,
197+
187 -- * Response
198 -- * Response
199+
188 -- ** Response interpretations
200 -- ** Response interpretations
189− , IgnoreResponse
201+ IgnoreResponse,
190− , ignoreResponse
202+ ignoreResponse,
191− , JsonResponse
203+ JsonResponse,
192− , jsonResponse
204+ jsonResponse,
193− , BsResponse
205+ BsResponse,
194− , bsResponse
206+ bsResponse,
195− , LbsResponse
207+ LbsResponse,
196− , lbsResponse
208+ lbsResponse,
209+
197 -- ** Inspecting a response
210 -- ** Inspecting a response
198− , responseBody
211+ responseBody,
199− , responseStatusCode
212+ responseStatusCode,
200− , responseStatusMessage
213+ responseStatusMessage,
201− , responseHeader
214+ responseHeader,
202− , responseCookieJar
215+ responseCookieJar,
216+
203 -- ** Defining your own interpretation
217 -- ** Defining your own interpretation
204 -- $new-response-interpretation
218 -- $new-response-interpretation
205− , HttpResponse (..)
219+ HttpResponse (..),
220+
206 -- * Other
221 -- * Other
207− , HttpException (..)
222+ HttpException (..),
208− , CanHaveBody (..)
223+ CanHaveBody (..),
209− , Scheme (..) )
224+ Scheme (..),
225+ )
210where
226where
211
227
228+import qualified Blaze.ByteString.Builder as BB
212import Control.Applicative
229import Control.Applicative
213import Control.Arrow (first, second)
230import Control.Arrow (first, second)
214import Control.Exception hiding (TypeError)
231import Control.Exception hiding (TypeError)
@@ -217,40 +234,39 @@ import Control.Monad.IO.Class
217import Control.Monad.Reader
234import Control.Monad.Reader
218import Control.Monad.Trans.Control
235import Control.Monad.Trans.Control
219import Control.Retry
236import Control.Retry
220−import Data.Aeson (ToJSON (..), FromJSON (..))
237+import Data.Aeson (FromJSON (..), ToJSON (..))
238+import qualified Data.Aeson as A
221import Data.ByteString (ByteString)
239import Data.ByteString (ByteString)
240+import qualified Data.ByteString as B
241+import qualified Data.ByteString.Lazy as BL
242+import qualified Data.CaseInsensitive as CI
222import Data.Data (Data)
243import Data.Data (Data)
223import Data.Function (on)
244import Data.Function (on)
224import Data.IORef
245import Data.IORef
225import Data.Kind (Constraint, Type)
246import Data.Kind (Constraint, Type)
226−import Data.List (nubBy, foldl')
247+import Data.List (foldl', nubBy)
227import Data.List.NonEmpty (NonEmpty (..))
248import Data.List.NonEmpty (NonEmpty (..))
249+import qualified Data.List.NonEmpty as NE
228import Data.Maybe (fromMaybe)
250import Data.Maybe (fromMaybe)
229import Data.Proxy
251import Data.Proxy
230import Data.Semigroup hiding (Option, option)
252import Data.Semigroup hiding (Option, option)
231import Data.Text (Text)
253import Data.Text (Text)
254+import qualified Data.Text.Encoding as T
232import Data.Typeable (Typeable)
255import Data.Typeable (Typeable)
233import GHC.Generics
256import GHC.Generics
234import GHC.TypeLits
257import GHC.TypeLits
258+import qualified Network.Connection as NC
259+import qualified Network.HTTP.Client as L
260+import qualified Network.HTTP.Client.Internal as LI
261+import qualified Network.HTTP.Client.MultipartFormData as LM
262+import qualified Network.HTTP.Client.TLS as L
263+import qualified Network.HTTP.Types as Y
235import System.IO.Unsafe (unsafePerformIO)
264import System.IO.Unsafe (unsafePerformIO)
236import Text.URI (URI)
265import Text.URI (URI)
266+import qualified Text.URI as URI
267+import qualified Text.URI.QQ as QQ
268+import qualified Web.Authenticate.OAuth as OAuth
237import Web.HttpApiData (ToHttpApiData (..))
269import Web.HttpApiData (ToHttpApiData (..))
238−import qualified Blaze.ByteString.Builder as BB
239−import qualified Data.Aeson as A
240−import qualified Data.ByteString as B
241−import qualified Data.ByteString.Lazy as BL
242−import qualified Data.CaseInsensitive as CI
243−import qualified Data.List.NonEmpty as NE
244−import qualified Data.Text.Encoding as T
245−import qualified Network.Connection as NC
246−import qualified Network.HTTP.Client as L
247−import qualified Network.HTTP.Client.Internal as LI
248−import qualified Network.HTTP.Client.MultipartFormData as LM
249−import qualified Network.HTTP.Client.TLS as L
250−import qualified Network.HTTP.Types as Y
251−import qualified Text.URI as URI
252−import qualified Text.URI.QQ as QQ
253−import qualified Web.Authenticate.OAuth as OAuth
254
270
255----------------------------------------------------------------------------
271----------------------------------------------------------------------------
256-- Making a request
272-- Making a request
@@ -382,19 +398,25 @@ import qualified Web.Authenticate.OAuth as OAuth
382-- > options <> -- contains the ?foo=bar part
398-- > options <> -- contains the ?foo=bar part
383-- > port 443 -- here you can put any port of course
399-- > port 443 -- here you can put any port of course
384-- > liftIO $ print (responseBody response :: Value)
400-- > liftIO $ print (responseBody response :: Value)
385−
386−req
401+req ::
387− :: ( MonadHttp m
402+ ( MonadHttp m,
388− , HttpMethod method
403+ HttpMethod method,
389− , HttpBody body
404+ HttpBody body,
390− , HttpResponse response
405+ HttpResponse response,
391− , HttpBodyAllowed (AllowsBody method) (ProvidesBody body) )
406+ HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
407+ ) =>
392− => method -- ^ HTTP method
408+ -- | HTTP method
409+ method ->
393− -> Url scheme -- ^ 'Url'—location of resource
410+ -- | 'Url'—location of resource
411+ Url scheme ->
394− -> body -- ^ Body of the request
412+ -- | Body of the request
413+ body ->
395− -> Proxy response -- ^ A hint how to interpret response
414+ -- | A hint how to interpret response
415+ Proxy response ->
396− -> Option scheme -- ^ Collection of optional parameters
416+ -- | Collection of optional parameters
417+ Option scheme ->
397− -> m response -- ^ Response
418+ -- | Response
419+ m response
398req method url body responseProxy options =
420req method url body responseProxy options =
399 reqBr method url body (options <> extraOptions) getHttpResponse
421 reqBr method url body (options <> extraOptions) getHttpResponse
400 where
422 where
@@ -408,39 +430,49 @@ req method url body responseProxy options =
408-- 'L.BodyReader'@ manually, in a custom way.
430-- 'L.BodyReader'@ manually, in a custom way.
409--
431--
410-- @since 1.0.0
432-- @since 1.0.0
411−
412−reqBr
433+reqBr ::
413− :: ( MonadHttp m
434+ ( MonadHttp m,
414− , HttpMethod method
435+ HttpMethod method,
415− , HttpBody body
436+ HttpBody body,
416− , HttpBodyAllowed (AllowsBody method) (ProvidesBody body) )
437+ HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
438+ ) =>
417− => method -- ^ HTTP method
439+ -- | HTTP method
440+ method ->
418− -> Url scheme -- ^ 'Url'—location of resource
441+ -- | 'Url'—location of resource
442+ Url scheme ->
419− -> body -- ^ Body of the request
443+ -- | Body of the request
444+ body ->
420− -> Option scheme -- ^ Collection of optional parameters
445+ -- | Collection of optional parameters
446+ Option scheme ->
421− -> (L.Response L.BodyReader -> IO a) -- ^ How to consume response
447+ -- | How to consume response
448+ (L.Response L.BodyReader -> IO a) ->
422− -> m a -- ^ Result
449+ -- | Result
450+ m a
423reqBr method url body options consume = req' method url body options $ \request manager -> do
451reqBr method url body options consume = req' method url body options $ \request manager -> do
424− HttpConfig {..} <- getHttpConfig
452+ HttpConfig {..} <- getHttpConfig
425 let wrapVanilla = handle (throwIO . VanillaHttpException)
453 let wrapVanilla = handle (throwIO . VanillaHttpException)
426− wrapExc = handle (throwIO . LI.toHttpException request)
454+ wrapExc = handle (throwIO . LI.toHttpException request)
427− withRRef = bracket
455+ withRRef =
456+ bracket
428− (newIORef Nothing)
457+ (newIORef Nothing)
429− (readIORef >=> mapM_ L.responseClose)
458+ (readIORef >=> mapM_ L.responseClose)
430− (liftIO . try . wrapVanilla . wrapExc) (withRRef $ \rref -> do
459+ (liftIO . try . wrapVanilla . wrapExc)
460+ ( withRRef $ \rref -> do
431− let openResponse = mask_ $ do
461+ let openResponse = mask_ $ do
432− r <- readIORef rref
462+ r <- readIORef rref
433− mapM_ L.responseClose r
463+ mapM_ L.responseClose r
434− r' <- L.responseOpen request manager
464+ r' <- L.responseOpen request manager
435− writeIORef rref (Just r')
465+ writeIORef rref (Just r')
436− return r'
466+ return r'
467+ r <-
437− r <- retrying
468+ retrying
438− httpConfigRetryPolicy
469+ httpConfigRetryPolicy
439− (\st r -> return $ httpConfigRetryJudge st r)
470+ (\st r -> return $ httpConfigRetryJudge st r)
440− (const openResponse)
471+ (const openResponse)
441− (preview, r') <- grabPreview bodyPreviewLength r
472+ (preview, r') <- grabPreview bodyPreviewLength r
442− mapM_ LI.throwHttp (httpConfigCheckResponse request r' preview)
473+ mapM_ LI.throwHttp (httpConfigCheckResponse request r' preview)
443− consume r')
474+ consume r'
475+ )
444 >>= either handleHttpException return
476 >>= either handleHttpException return
445
477
446-- | Mostly like 'req' with respect to its arguments, but accepts a callback
478-- | Mostly like 'req' with respect to its arguments, but accepts a callback
@@ -451,44 +483,50 @@ reqBr method url body options consume = req' method url body options $ \request
451-- 'L.Request' and allows you to use it.
483-- 'L.Request' and allows you to use it.
452--
484--
453-- @since 0.3.0
485-- @since 0.3.0
454−
455−req'
486+req' ::
456− :: forall m method body scheme a.
487+ forall m method body scheme a.
457− ( MonadHttp m
488+ ( MonadHttp m,
458− , HttpMethod method
489+ HttpMethod method,
459− , HttpBody body
490+ HttpBody body,
460− , HttpBodyAllowed (AllowsBody method) (ProvidesBody body) )
491+ HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
492+ ) =>
461− => method -- ^ HTTP method
493+ -- | HTTP method
494+ method ->
462− -> Url scheme -- ^ 'Url'—location of resource
495+ -- | 'Url'—location of resource
496+ Url scheme ->
463− -> body -- ^ Body of the request
497+ -- | Body of the request
498+ body ->
464− -> Option scheme -- ^ Collection of optional parameters
499+ -- | Collection of optional parameters
500+ Option scheme ->
465− -> (L.Request -> L.Manager -> m a) -- ^ How to perform request
501+ -- | How to perform request
502+ (L.Request -> L.Manager -> m a) ->
466− -> m a -- ^ Result
503+ -- | Result
504+ m a
467req' method url body options m = do
505req' method url body options m = do
468 config <- getHttpConfig
506 config <- getHttpConfig
469 let -- NOTE First appearance of any given header wins. This allows to
507 let -- NOTE First appearance of any given header wins. This allows to
470 -- “overwrite” headers when we construct a request by cons-ing.
508 -- “overwrite” headers when we construct a request by cons-ing.
471 nubHeaders = Endo $ \x ->
509 nubHeaders = Endo $ \x ->
472− x { L.requestHeaders = nubBy ((==) `on` fst) (L.requestHeaders x) }
510+ x {L.requestHeaders = nubBy ((==) `on` fst) (L.requestHeaders x)}
511+ request' =
473− request' = flip appEndo L.defaultRequest $
512+ flip appEndo L.defaultRequest $
474− -- NOTE The order of 'mappend's matters, here method is overwritten
513+ -- NOTE The order of 'mappend's matters, here method is overwritten
475− -- first and 'options' take effect last. In particular, this means
514+ -- first and 'options' take effect last. In particular, this means
476− -- that 'options' can overwrite things set by other request
515+ -- that 'options' can overwrite things set by other request
477− -- components, which is useful for setting port number,
516+ -- components, which is useful for setting port number,
478− -- "Content-Type" header, etc.
517+ -- "Content-Type" header, etc.
479− nubHeaders <>
518+ nubHeaders
480− getRequestMod options <>
519+ <> getRequestMod options
481− getRequestMod config <>
520+ <> getRequestMod config
482− getRequestMod (Womb body :: Womb "body" body) <>
521+ <> getRequestMod (Womb body :: Womb "body" body)
483− getRequestMod url <>
522+ <> getRequestMod url
484− getRequestMod (Womb method :: Womb "method" method)
523+ <> getRequestMod (Womb method :: Womb "method" method)
485 request <- finalizeRequest options request'
524 request <- finalizeRequest options request'
486 withReqManager (m request)
525 withReqManager (m request)
487
526
488-- | Perform an action using the global implicit 'L.Manager' that the rest
527-- | Perform an action using the global implicit 'L.Manager' that the rest
489-- of the library uses. This allows to reuse connections that the
528-- of the library uses. This allows to reuse connections that the
490-- 'L.Manager' controls.
529-- 'L.Manager' controls.
491−
492withReqManager :: MonadIO m => (L.Manager -> m a) -> m a
530withReqManager :: MonadIO m => (L.Manager -> m a) -> m a
493withReqManager m = liftIO (readIORef globalManager) >>= m
531withReqManager m = liftIO (readIORef globalManager) >>= m
494
532
@@ -503,14 +541,14 @@ withReqManager m = liftIO (readIORef globalManager) >>= m
503-- manager. From that moment on the 'IORef' will be just reused—exactly the
541-- manager. From that moment on the 'IORef' will be just reused—exactly the
504-- behavior we want here in order to maximize connection sharing. GHC could
542-- behavior we want here in order to maximize connection sharing. GHC could
505-- spoil the plan by inlining the definition, hence the @NOINLINE@ pragma.
543-- spoil the plan by inlining the definition, hence the @NOINLINE@ pragma.
506−
507globalManager :: IORef L.Manager
544globalManager :: IORef L.Manager
508globalManager = unsafePerformIO $ do
545globalManager = unsafePerformIO $ do
509 context <- NC.initConnectionContext
546 context <- NC.initConnectionContext
547+ let settings =
510− let settings = L.mkManagerSettingsContext
548+ L.mkManagerSettingsContext
511− (Just context)
549+ (Just context)
512− (NC.TLSSettingsSimple False False False)
550+ (NC.TLSSettingsSimple False False False)
513− Nothing
551+ Nothing
514 manager <- L.newManager settings
552 manager <- L.newManager settings
515 newIORef manager
553 newIORef manager
516{-# NOINLINE globalManager #-}
554{-# NOINLINE globalManager #-}
@@ -532,15 +570,12 @@ globalManager = unsafePerformIO $ do
532-- | A type class for monads that support performing HTTP requests.
570-- | A type class for monads that support performing HTTP requests.
533-- Typically, you only need to define the 'handleHttpException' method
571-- Typically, you only need to define the 'handleHttpException' method
534-- unless you want to tweak 'HttpConfig'.
572-- unless you want to tweak 'HttpConfig'.
535−
536class MonadIO m => MonadHttp m where
573class MonadIO m => MonadHttp m where
537−
538 -- | This method describes how to deal with 'HttpException' that was
574 -- | This method describes how to deal with 'HttpException' that was
539 -- caught by the library. One option is to re-throw it if you are OK with
575 -- caught by the library. One option is to re-throw it if you are OK with
540 -- exceptions, but if you prefer working with something like
576 -- exceptions, but if you prefer working with something like
541 -- 'Control.Monad.Except.MonadError', this is the right place to pass it to
577 -- 'Control.Monad.Except.MonadError', this is the right place to pass it to
542 -- 'Control.Monad.Except.throwError'.
578 -- 'Control.Monad.Except.throwError'.
543−
544 handleHttpException :: HttpException -> m a
579 handleHttpException :: HttpException -> m a
545
580
546 -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
581 -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
@@ -549,32 +584,24 @@ class MonadIO m => MonadHttp m where
549 -- 'getHttpConfig' is to return some hard-coded value, or a value
584 -- 'getHttpConfig' is to return some hard-coded value, or a value
550 -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
585 -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
551 -- approach to configuration is desirable.
586 -- approach to configuration is desirable.
552−
553 getHttpConfig :: m HttpConfig
587 getHttpConfig :: m HttpConfig
554 getHttpConfig = return defaultHttpConfig
588 getHttpConfig = return defaultHttpConfig
555
589
556-- | 'HttpConfig' contains general and default settings to be used when
590-- | 'HttpConfig' contains general and default settings to be used when
557-- making HTTP requests.
591-- making HTTP requests.
558−
559data HttpConfig = HttpConfig
592data HttpConfig = HttpConfig
560− { httpConfigProxy :: Maybe L.Proxy
561− -- ^ Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
593+ { -- | Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
562 -- environment variables are respected, this setting overwrites them.
594 -- environment variables are respected, this setting overwrites them.
563 -- Default value: 'Nothing'.
595 -- Default value: 'Nothing'.
564− , httpConfigRedirectCount :: Int
596+ httpConfigProxy :: Maybe L.Proxy,
565− -- ^ How many redirects to follow when getting a resource. Default
597+ -- | How many redirects to follow when getting a resource. Default
566 -- value: 10.
598 -- value: 10.
567− , httpConfigAltManager :: Maybe L.Manager
599+ httpConfigRedirectCount :: Int,
568− -- ^ Alternative 'L.Manager' to use. 'Nothing' (default value) means
600+ -- | Alternative 'L.Manager' to use. 'Nothing' (default value) means
569 -- that the default implicit manager will be used (that's what you want
601 -- that the default implicit manager will be used (that's what you want
570 -- in 99% of cases).
602 -- in 99% of cases).
571− , httpConfigCheckResponse
603+ httpConfigAltManager :: Maybe L.Manager,
572− :: forall b.
573− L.Request
574− -> L.Response b
575− -> ByteString
576− -> Maybe L.HttpExceptionContent
577− -- ^ Function to check the response immediately after receiving the
604+ -- | Function to check the response immediately after receiving the
578 -- status and headers, before streaming of response body. The third
605 -- status and headers, before streaming of response body. The third
579 -- argument is the beginning of response body (typically first 1024
606 -- argument is the beginning of response body (typically first 1024
580 -- bytes). This is used for throwing exceptions on non-success status
607 -- bytes). This is used for throwing exceptions on non-success status
@@ -596,67 +623,77 @@ data HttpConfig = HttpConfig
596 -- /1.0.0/.
623 -- /1.0.0/.
597 --
624 --
598 -- @since 0.3.0
625 -- @since 0.3.0
599− , httpConfigRetryPolicy :: RetryPolicyM IO
626+ httpConfigCheckResponse ::
627+ forall b.
628+ L.Request ->
629+ L.Response b ->
630+ ByteString ->
631+ Maybe L.HttpExceptionContent,
600− -- ^ The retry policy to use for request retrying. By default 'def' is
632+ -- | The retry policy to use for request retrying. By default 'def' is
601 -- used (see 'RetryPolicyM').
633 -- used (see 'RetryPolicyM').
602 --
634 --
603 -- __Note__: signature of this function was changed in the version
635 -- __Note__: signature of this function was changed in the version
604 -- /1.0.0/.
636 -- /1.0.0/.
605 --
637 --
606 -- @since 0.3.0
638 -- @since 0.3.0
607− , httpConfigRetryJudge :: forall b. RetryStatus -> L.Response b -> Bool
639+ httpConfigRetryPolicy :: RetryPolicyM IO,
608− -- ^ The function is used to decide whether to retry a request. 'True'
640+ -- | The function is used to decide whether to retry a request. 'True'
609 -- means that the request should be retried.
641 -- means that the request should be retried.
610 --
642 --
611 -- __Note__: signature of this function was changed in the version
643 -- __Note__: signature of this function was changed in the version
612 -- /1.0.0/.
644 -- /1.0.0/.
613 --
645 --
614 -- @since 0.3.0
646 -- @since 0.3.0
647+ httpConfigRetryJudge :: forall b. RetryStatus -> L.Response b -> Bool
648+ }
615− } deriving Typeable
649+ deriving (Typeable)
616
650
617-- | Default value of 'HttpConfig'.
651-- | Default value of 'HttpConfig'.
618--
652--
619-- @since 2.0.0
653-- @since 2.0.0
620−
621defaultHttpConfig :: HttpConfig
654defaultHttpConfig :: HttpConfig
622−defaultHttpConfig = HttpConfig
655+defaultHttpConfig =
656+ HttpConfig
623− { httpConfigProxy = Nothing
657+ { httpConfigProxy = Nothing,
624− , httpConfigRedirectCount = 10
658+ httpConfigRedirectCount = 10,
625− , httpConfigAltManager = Nothing
659+ httpConfigAltManager = Nothing,
626− , httpConfigCheckResponse = \_ response preview ->
660+ httpConfigCheckResponse = \_ response preview ->
627− let scode = statusCode response
661+ let scode = statusCode response
628− in if 200 <= scode && scode < 300
662+ in if 200 <= scode && scode < 300
629− then Nothing
663+ then Nothing
630− else Just (L.StatusCodeException (void response) preview)
664+ else Just (L.StatusCodeException (void response) preview),
631− , httpConfigRetryPolicy = retryPolicyDefault
665+ httpConfigRetryPolicy = retryPolicyDefault,
632− , httpConfigRetryJudge = \_ response ->
666+ httpConfigRetryJudge = \_ response ->
633− statusCode response `elem`
667+ statusCode response
634− [ 408 -- Request timeout
668+ `elem` [ 408, -- Request timeout
635− , 504 -- Gateway timeout
669+ 504, -- Gateway timeout
636− , 524 -- A timeout occurred
670+ 524, -- A timeout occurred
637− , 598 -- (Informal convention) Network read timeout error
671+ 598, -- (Informal convention) Network read timeout error
638− , 599 -- (Informal convention) Network connect timeout error
672+ 599 -- (Informal convention) Network connect timeout error
639− ]
673+ ]
640− }
674+ }
641 where
675 where
642 statusCode = Y.statusCode . L.responseStatus
676 statusCode = Y.statusCode . L.responseStatus
643
677
644instance RequestComponent HttpConfig where
678instance RequestComponent HttpConfig where
645 getRequestMod HttpConfig {..} = Endo $ \x ->
679 getRequestMod HttpConfig {..} = Endo $ \x ->
680+ x
646− x { L.proxy = httpConfigProxy
681+ { L.proxy = httpConfigProxy,
647− , L.redirectCount = httpConfigRedirectCount
682+ L.redirectCount = httpConfigRedirectCount,
648− , LI.requestManagerOverride = httpConfigAltManager }
683+ LI.requestManagerOverride = httpConfigAltManager
684+ }
649
685
650-- | A monad that allows to run 'req' in any 'IO'-enabled monad without
686-- | A monad that allows to run 'req' in any 'IO'-enabled monad without
651-- having to define new instances.
687-- having to define new instances.
652--
688--
653-- @since 0.4.0
689-- @since 0.4.0
654−
655newtype Req a = Req (ReaderT HttpConfig IO a)
690newtype Req a = Req (ReaderT HttpConfig IO a)
656− deriving ( Functor
691+ deriving
692+ ( Functor,
657− , Applicative
693+ Applicative,
658− , Monad
694+ Monad,
659− , MonadIO )
695+ MonadIO
696+ )
660
697
661instance MonadBase IO Req where
698instance MonadBase IO Req where
662 liftBase = liftIO
699 liftBase = liftIO
@@ -665,22 +702,24 @@ instance MonadBaseControl IO Req where
665 type StM Req a = a
702 type StM Req a = a
666 liftBaseWith f = Req . ReaderT $ \r -> f (runReq r)
703 liftBaseWith f = Req . ReaderT $ \r -> f (runReq r)
667 {-# INLINEABLE liftBaseWith #-}
704 {-# INLINEABLE liftBaseWith #-}
668− restoreM = Req . ReaderT . const . return
705+ restoreM = Req . ReaderT . const . return
669 {-# INLINEABLE restoreM #-}
706 {-# INLINEABLE restoreM #-}
670
707
671instance MonadHttp Req where
708instance MonadHttp Req where
672 handleHttpException = Req . lift . throwIO
709 handleHttpException = Req . lift . throwIO
673− getHttpConfig = Req ask
710+ getHttpConfig = Req ask
674
711
675-- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
712-- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
676-- case of exceptional situation an 'HttpException' will be thrown.
713-- case of exceptional situation an 'HttpException' will be thrown.
677--
714--
678-- @since 0.4.0
715-- @since 0.4.0
679−
716+runReq ::
680−runReq :: MonadIO m
717+ MonadIO m =>
681− => HttpConfig -- ^ 'HttpConfig' to use
718+ -- | 'HttpConfig' to use
719+ HttpConfig ->
682− -> Req a -- ^ Computation to run
720+ -- | Computation to run
721+ Req a ->
683− -> m a
722+ m a
684runReq config (Req m) = liftIO (runReaderT m config)
723runReq config (Req m) = liftIO (runReaderT m config)
685
724
686----------------------------------------------------------------------------
725----------------------------------------------------------------------------
@@ -696,7 +735,6 @@ runReq config (Req m) = liftIO (runReaderT m config)
696-- define a new method that will works seamlessly, see 'HttpMethod'.
735-- define a new method that will works seamlessly, see 'HttpMethod'.
697
736
698-- | 'GET' method.
737-- | 'GET' method.
699−
700data GET = GET
738data GET = GET
701
739
702instance HttpMethod GET where
740instance HttpMethod GET where
@@ -704,7 +742,6 @@ instance HttpMethod GET where
704 httpMethodName Proxy = Y.methodGet
742 httpMethodName Proxy = Y.methodGet
705
743
706-- | 'POST' method.
744-- | 'POST' method.
707−
708data POST = POST
745data POST = POST
709
746
710instance HttpMethod POST where
747instance HttpMethod POST where
@@ -712,7 +749,6 @@ instance HttpMethod POST where
712 httpMethodName Proxy = Y.methodPost
749 httpMethodName Proxy = Y.methodPost
713
750
714-- | 'HEAD' method.
751-- | 'HEAD' method.
715−
716data HEAD = HEAD
752data HEAD = HEAD
717
753
718instance HttpMethod HEAD where
754instance HttpMethod HEAD where
@@ -720,7 +756,6 @@ instance HttpMethod HEAD where
720 httpMethodName Proxy = Y.methodHead
756 httpMethodName Proxy = Y.methodHead
721
757
722-- | 'PUT' method.
758-- | 'PUT' method.
723−
724data PUT = PUT
759data PUT = PUT
725
760
726instance HttpMethod PUT where
761instance HttpMethod PUT where
@@ -731,7 +766,6 @@ instance HttpMethod PUT where
731-- 'DELETE' requests, as it should be. However some APIs may expect 'DELETE'
766-- 'DELETE' requests, as it should be. However some APIs may expect 'DELETE'
732-- requests to have bodies, in that case define your own variation of
767-- requests to have bodies, in that case define your own variation of
733-- 'DELETE' method and allow it to have a body.
768-- 'DELETE' method and allow it to have a body.
734−
735data DELETE = DELETE
769data DELETE = DELETE
736
770
737instance HttpMethod DELETE where
771instance HttpMethod DELETE where
@@ -739,7 +773,6 @@ instance HttpMethod DELETE where
739 httpMethodName Proxy = Y.methodDelete
773 httpMethodName Proxy = Y.methodDelete
740
774
741-- | 'TRACE' method.
775-- | 'TRACE' method.
742−
743data TRACE = TRACE
776data TRACE = TRACE
744
777
745instance HttpMethod TRACE where
778instance HttpMethod TRACE where
@@ -747,7 +780,6 @@ instance HttpMethod TRACE where
747 httpMethodName Proxy = Y.methodTrace
780 httpMethodName Proxy = Y.methodTrace
748
781
749-- | 'CONNECT' method.
782-- | 'CONNECT' method.
750−
751data CONNECT = CONNECT
783data CONNECT = CONNECT
752
784
753instance HttpMethod CONNECT where
785instance HttpMethod CONNECT where
@@ -755,7 +787,6 @@ instance HttpMethod CONNECT where
755 httpMethodName Proxy = Y.methodConnect
787 httpMethodName Proxy = Y.methodConnect
756
788
757-- | 'OPTIONS' method.
789-- | 'OPTIONS' method.
758−
759data OPTIONS = OPTIONS
790data OPTIONS = OPTIONS
760
791
761instance HttpMethod OPTIONS where
792instance HttpMethod OPTIONS where
@@ -763,7 +794,6 @@ instance HttpMethod OPTIONS where
763 httpMethodName Proxy = Y.methodOptions
794 httpMethodName Proxy = Y.methodOptions
764
795
765-- | 'PATCH' method.
796-- | 'PATCH' method.
766−
767data PATCH = PATCH
797data PATCH = PATCH
768
798
769instance HttpMethod PATCH where
799instance HttpMethod PATCH where
@@ -778,23 +808,19 @@ instance HttpMethod PATCH where
778-- > instance HttpMethod COPY where
808-- > instance HttpMethod COPY where
779-- > type AllowsBody COPY = 'CanHaveBody
809-- > type AllowsBody COPY = 'CanHaveBody
780-- > httpMethodName Proxy = "COPY"
810-- > httpMethodName Proxy = "COPY"
781−
782class HttpMethod a where
811class HttpMethod a where
783−
784 -- | Type function 'AllowsBody' returns a type of kind 'CanHaveBody' which
812 -- | Type function 'AllowsBody' returns a type of kind 'CanHaveBody' which
785 -- tells the rest of the library whether the method can have body or not.
813 -- tells the rest of the library whether the method can have body or not.
786 -- We use the special type 'CanHaveBody' lifted to the kind level instead
814 -- We use the special type 'CanHaveBody' lifted to the kind level instead
787 -- of 'Bool' to get more user-friendly compiler messages.
815 -- of 'Bool' to get more user-friendly compiler messages.
788−
789 type AllowsBody a :: CanHaveBody
816 type AllowsBody a :: CanHaveBody
790
817
791 -- | Return name of the method as a 'ByteString'.
818 -- | Return name of the method as a 'ByteString'.
792−
793 httpMethodName :: Proxy a -> ByteString
819 httpMethodName :: Proxy a -> ByteString
794
820
795instance HttpMethod method => RequestComponent (Womb "method" method) where
821instance HttpMethod method => RequestComponent (Womb "method" method) where
796 getRequestMod _ = Endo $ \x ->
822 getRequestMod _ = Endo $ \x ->
797− x { L.method = httpMethodName (Proxy :: Proxy method) }
823+ x {L.method = httpMethodName (Proxy :: Proxy method)}
798
824
799----------------------------------------------------------------------------
825----------------------------------------------------------------------------
800-- Request—URL
826-- Request—URL
@@ -832,34 +858,31 @@ instance HttpMethod method => RequestComponent (Womb "method" method) where
832--
858--
833-- > https "юникод.рф"
859-- > https "юникод.рф"
834-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
860-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
835−
836data Url (scheme :: Scheme) = Url Scheme (NonEmpty Text)
861data Url (scheme :: Scheme) = Url Scheme (NonEmpty Text)
837 -- NOTE The second value is the path segments in reversed order.
862 -- NOTE The second value is the path segments in reversed order.
838 deriving (Eq, Ord, Show, Data, Typeable, Generic)
863 deriving (Eq, Ord, Show, Data, Typeable, Generic)
839
864
840-- | Given host name, produce a 'Url' which has “http” as its scheme and
865-- | Given host name, produce a 'Url' which has “http” as its scheme and
841-- empty path. This also sets port to @80@.
866-- empty path. This also sets port to @80@.
842−
843http :: Text -> Url 'Http
867http :: Text -> Url 'Http
844http = Url Http . pure
868http = Url Http . pure
845
869
846-- | Given host name, produce a 'Url' which has “https” as its scheme and
870-- | Given host name, produce a 'Url' which has “https” as its scheme and
847-- empty path. This also sets port to @443@.
871-- empty path. This also sets port to @443@.
848−
849https :: Text -> Url 'Https
872https :: Text -> Url 'Https
850https = Url Https . pure
873https = Url Https . pure
851
874
852-- | Grow given 'Url' appending a single path segment to it. Note that the
875-- | Grow given 'Url' appending a single path segment to it. Note that the
853-- path segment can be of any type that is an instance of 'ToHttpApiData'.
876-- path segment can be of any type that is an instance of 'ToHttpApiData'.
854−
855infixl 5 /~
877infixl 5 /~
878+
856(/~) :: ToHttpApiData a => Url scheme -> a -> Url scheme
879(/~) :: ToHttpApiData a => Url scheme -> a -> Url scheme
857Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
880Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
858
881
859-- | Type-constrained version of @('/~')@ to remove ambiguity in the cases
882-- | Type-constrained version of @('/~')@ to remove ambiguity in the cases
860-- when next URL piece is a 'Data.Text.Text' literal.
883-- when next URL piece is a 'Data.Text.Text' literal.
861−
862infixl 5 /:
884infixl 5 /:
885+
863(/:) :: Url scheme -> Text -> Url scheme
886(/:) :: Url scheme -> Text -> Url scheme
864(/:) = (/~)
887(/:) = (/~)
865
888
@@ -870,7 +893,6 @@ infixl 5 /:
870-- This function expects the scheme to be “http” and host to be present.
893-- This function expects the scheme to be “http” and host to be present.
871--
894--
872-- @since 3.0.0
895-- @since 3.0.0
873−
874useHttpURI :: URI -> Maybe (Url 'Http, Option scheme)
896useHttpURI :: URI -> Maybe (Url 'Http, Option scheme)
875useHttpURI uri = do
897useHttpURI uri = do
876 guard (URI.uriScheme uri == Just [QQ.scheme|http|])
898 guard (URI.uriScheme uri == Just [QQ.scheme|http|])
@@ -884,7 +906,6 @@ useHttpURI uri = do
884-- | Just like 'useHttpURI', but expects the “https” scheme.
906-- | Just like 'useHttpURI', but expects the “https” scheme.
885--
907--
886-- @since 3.0.0
908-- @since 3.0.0
887−
888useHttpsURI :: URI -> Maybe (Url 'Https, Option scheme)
909useHttpsURI :: URI -> Maybe (Url 'Https, Option scheme)
889useHttpsURI uri = do
910useHttpsURI uri = do
890 guard (URI.uriScheme uri == Just [QQ.scheme|https|])
911 guard (URI.uriScheme uri == Just [QQ.scheme|https|])
@@ -899,16 +920,17 @@ useHttpsURI uri = do
899-- is not known in advance.
920-- is not known in advance.
900--
921--
901-- @since 3.0.0
922-- @since 3.0.0
902−
903−useURI
923+useURI ::
904− :: URI
924+ URI ->
925+ Maybe
926+ ( Either
905− -> Maybe (Either (Url 'Http, Option scheme0)
927+ (Url 'Http, Option scheme0)
906− (Url 'Https, Option scheme1))
928+ (Url 'Https, Option scheme1)
929+ )
907useURI uri =
930useURI uri =
908 (Left <$> useHttpURI uri) <|> (Right <$> useHttpsURI uri)
931 (Left <$> useHttpURI uri) <|> (Right <$> useHttpsURI uri)
909
932
910-- | An internal helper function to extract host from a 'URI'.
933-- | An internal helper function to extract host from a 'URI'.
911−
912uriHost :: URI -> Maybe Text
934uriHost :: URI -> Maybe Text
913uriHost uri = case URI.uriAuthority uri of
935uriHost uri = case URI.uriAuthority uri of
914 Left _ -> Nothing
936 Left _ -> Nothing
@@ -916,14 +938,14 @@ uriHost uri = case URI.uriAuthority uri of
916 Just (URI.unRText authHost)
938 Just (URI.unRText authHost)
917
939
918-- | An internal helper function to extract 'Option's from a 'URI'.
940-- | An internal helper function to extract 'Option's from a 'URI'.
919−
920uriOptions :: forall scheme. URI -> Option scheme
941uriOptions :: forall scheme. URI -> Option scheme
921−uriOptions uri = mconcat
942+uriOptions uri =
943+ mconcat
922− [ auth
944+ [ auth,
923− , query
945+ query,
924− , port'
946+ port'
925− -- , fragment'
947+ -- , fragment'
926− ]
948+ ]
927 where
949 where
928 (auth, port') =
950 (auth, port') =
929 case URI.uriAuthority uri of
951 case URI.uriAuthority uri of
@@ -934,34 +956,37 @@ uriOptions uri = mconcat
934 Just URI.UserInfo {..} ->
956 Just URI.UserInfo {..} ->
935 let username = T.encodeUtf8 (URI.unRText uiUsername)
957 let username = T.encodeUtf8 (URI.unRText uiUsername)
936 password = maybe "" (T.encodeUtf8 . URI.unRText) uiPassword
958 password = maybe "" (T.encodeUtf8 . URI.unRText) uiPassword
937− in basicAuthUnsafe username password
959+ in basicAuthUnsafe username password
938 port0 = case authPort of
960 port0 = case authPort of
939 Nothing -> mempty
961 Nothing -> mempty
940 Just port'' -> port (fromIntegral port'')
962 Just port'' -> port (fromIntegral port'')
941− in (auth0, port0)
963+ in (auth0, port0)
942 query =
964 query =
943 let liftQueryParam = \case
965 let liftQueryParam = \case
944 URI.QueryFlag t -> queryFlag (URI.unRText t)
966 URI.QueryFlag t -> queryFlag (URI.unRText t)
945 URI.QueryParam k v -> URI.unRText k =: URI.unRText v
967 URI.QueryParam k v -> URI.unRText k =: URI.unRText v
946− in mconcat (liftQueryParam <$> URI.uriQuery uri)
968+ in mconcat (liftQueryParam <$> URI.uriQuery uri)
969+
947− -- TODO Blocked on upstream: https://github.com/snoyberg/http-client/issues/424
970+-- TODO Blocked on upstream: https://github.com/snoyberg/http-client/issues/424
948− -- fragment' =
971+-- fragment' =
949− -- case URI.uriFragment uri of
972+-- case URI.uriFragment uri of
950− -- Nothing -> mempty
973+-- Nothing -> mempty
951− -- Just fragment'' -> fragment (URI.unRText fragment'')
974+-- Just fragment'' -> fragment (URI.unRText fragment'')
952
975
953instance RequestComponent (Url scheme) where
976instance RequestComponent (Url scheme) where
954 getRequestMod (Url scheme segments) = Endo $ \x ->
977 getRequestMod (Url scheme segments) = Endo $ \x ->
955− let (host :| path) = NE.reverse segments in
978+ let (host :| path) = NE.reverse segments
979+ in x
956− x { L.secure = case scheme of
980+ { L.secure = case scheme of
957− Http -> False
981+ Http -> False
958− Https -> True
982+ Https -> True,
959− , L.port = case scheme of
983+ L.port = case scheme of
960− Http -> 80
984+ Http -> 80
961− Https -> 443
985+ Https -> 443,
962− , L.host = Y.urlEncode False (T.encodeUtf8 host)
986+ L.host = Y.urlEncode False (T.encodeUtf8 host),
963− , L.path =
987+ L.path =
964− (BL.toStrict . BB.toLazyByteString . Y.encodePathSegments) path }
988+ (BL.toStrict . BB.toLazyByteString . Y.encodePathSegments) path
989+ }
965
990
966----------------------------------------------------------------------------
991----------------------------------------------------------------------------
967-- Request—Body
992-- Request—Body
@@ -979,7 +1004,6 @@ instance RequestComponent (Url scheme) where
979-- only type for which 'ProvidesBody' returns 'NoBody'.
1004-- only type for which 'ProvidesBody' returns 'NoBody'.
980--
1005--
981-- Using of this body option does not set the @Content-Type@ header.
1006-- Using of this body option does not set the @Content-Type@ header.
982−
983data NoReqBody = NoReqBody
1007data NoReqBody = NoReqBody
984
1008
985instance HttpBody NoReqBody where
1009instance HttpBody NoReqBody where
@@ -992,7 +1016,6 @@ instance HttpBody NoReqBody where
992--
1016--
993-- This body option sets the @Content-Type@ header to @\"application/json;
1017-- This body option sets the @Content-Type@ header to @\"application/json;
994-- charset=utf-8\"@ value.
1018-- charset=utf-8\"@ value.
995−
996newtype ReqBodyJson a = ReqBodyJson a
1019newtype ReqBodyJson a = ReqBodyJson a
997
1020
998instance ToJSON a => HttpBody (ReqBodyJson a) where
1021instance ToJSON a => HttpBody (ReqBodyJson a) where
@@ -1003,7 +1026,6 @@ instance ToJSON a => HttpBody (ReqBodyJson a) where
1003-- the file size does not change during the streaming.
1026-- the file size does not change during the streaming.
1004--
1027--
1005-- Using of this body option does not set the @Content-Type@ header.
1028-- Using of this body option does not set the @Content-Type@ header.
1006−
1007newtype ReqBodyFile = ReqBodyFile FilePath
1029newtype ReqBodyFile = ReqBodyFile FilePath
1008
1030
1009instance HttpBody ReqBodyFile where
1031instance HttpBody ReqBodyFile where
@@ -1013,7 +1035,6 @@ instance HttpBody ReqBodyFile where
1013-- | HTTP request body represented by a strict 'ByteString'.
1035-- | HTTP request body represented by a strict 'ByteString'.
1014--
1036--
1015-- Using of this body option does not set the @Content-Type@ header.
1037-- Using of this body option does not set the @Content-Type@ header.
1016−
1017newtype ReqBodyBs = ReqBodyBs ByteString
1038newtype ReqBodyBs = ReqBodyBs ByteString
1018
1039
1019instance HttpBody ReqBodyBs where
1040instance HttpBody ReqBodyBs where
@@ -1022,7 +1043,6 @@ instance HttpBody ReqBodyBs where
1022-- | HTTP request body represented by a lazy 'BL.ByteString'.
1043-- | HTTP request body represented by a lazy 'BL.ByteString'.
1023--
1044--
1024-- Using of this body option does not set the @Content-Type@ header.
1045-- Using of this body option does not set the @Content-Type@ header.
1025−
1026newtype ReqBodyLbs = ReqBodyLbs BL.ByteString
1046newtype ReqBodyLbs = ReqBodyLbs BL.ByteString
1027
1047
1028instance HttpBody ReqBodyLbs where
1048instance HttpBody ReqBodyLbs where
@@ -1037,7 +1057,6 @@ instance HttpBody ReqBodyLbs where
1037--
1057--
1038-- This body option sets the @Content-Type@ header to
1058-- This body option sets the @Content-Type@ header to
1039-- @\"application/x-www-form-urlencoded\"@ value.
1059-- @\"application/x-www-form-urlencoded\"@ value.
1040−
1041newtype ReqBodyUrlEnc = ReqBodyUrlEnc FormUrlEncodedParam
1060newtype ReqBodyUrlEnc = ReqBodyUrlEnc FormUrlEncodedParam
1042
1061
1043instance HttpBody ReqBodyUrlEnc where
1062instance HttpBody ReqBodyUrlEnc where
@@ -1047,7 +1066,6 @@ instance HttpBody ReqBodyUrlEnc where
1047
1066
1048-- | An opaque monoidal value that allows to collect URL-encoded parameters
1067-- | An opaque monoidal value that allows to collect URL-encoded parameters
1049-- to be wrapped in 'ReqBodyUrlEnc'.
1068-- to be wrapped in 'ReqBodyUrlEnc'.
1050−
1051newtype FormUrlEncodedParam = FormUrlEncodedParam [(Text, Maybe Text)]
1069newtype FormUrlEncodedParam = FormUrlEncodedParam [(Text, Maybe Text)]
1052 deriving (Semigroup, Monoid)
1070 deriving (Semigroup, Monoid)
1053
1071
@@ -1083,7 +1101,6 @@ instance QueryParam FormUrlEncodedParam where
1083-- > bsResponse
1101-- > bsResponse
1084-- > mempty
1102-- > mempty
1085-- > liftIO $ print (responseBody response)
1103-- > liftIO $ print (responseBody response)
1086−
1087data ReqBodyMultipart = ReqBodyMultipart ByteString LI.RequestBody
1104data ReqBodyMultipart = ReqBodyMultipart ByteString LI.RequestBody
1088
1105
1089instance HttpBody ReqBodyMultipart where
1106instance HttpBody ReqBodyMultipart where
@@ -1094,36 +1111,30 @@ instance HttpBody ReqBodyMultipart where
1094-- | Create 'ReqBodyMultipart' request body from a collection of 'LM.Part's.
1111-- | Create 'ReqBodyMultipart' request body from a collection of 'LM.Part's.
1095--
1112--
1096-- @since 0.2.0
1113-- @since 0.2.0
1097−
1098reqBodyMultipart :: MonadIO m => [LM.Part] -> m ReqBodyMultipart
1114reqBodyMultipart :: MonadIO m => [LM.Part] -> m ReqBodyMultipart
1099reqBodyMultipart parts = liftIO $ do
1115reqBodyMultipart parts = liftIO $ do
1100 boundary <- LM.webkitBoundary
1116 boundary <- LM.webkitBoundary
1101− body <- LM.renderParts boundary parts
1117+ body <- LM.renderParts boundary parts
1102 return (ReqBodyMultipart boundary body)
1118 return (ReqBodyMultipart boundary body)
1103
1119
1104-- | A type class for things that can be interpreted as an HTTP
1120-- | A type class for things that can be interpreted as an HTTP
1105-- 'L.RequestBody'.
1121-- 'L.RequestBody'.
1106−
1107class HttpBody body where
1122class HttpBody body where
1108−
1109 -- | How to get actual 'L.RequestBody'.
1123 -- | How to get actual 'L.RequestBody'.
1110−
1111 getRequestBody :: body -> L.RequestBody
1124 getRequestBody :: body -> L.RequestBody
1112
1125
1113 -- | This method allows us to optionally specify the value of
1126 -- | This method allows us to optionally specify the value of
1114 -- @Content-Type@ header that should be used with particular body option.
1127 -- @Content-Type@ header that should be used with particular body option.
1115 -- By default it returns 'Nothing' and so @Content-Type@ is not set.
1128 -- By default it returns 'Nothing' and so @Content-Type@ is not set.
1116−
1117 getRequestContentType :: body -> Maybe ByteString
1129 getRequestContentType :: body -> Maybe ByteString
1118 getRequestContentType = const Nothing
1130 getRequestContentType = const Nothing
1119
1131
1120-- | The type function recognizes 'NoReqBody' as having 'NoBody', while any
1132-- | The type function recognizes 'NoReqBody' as having 'NoBody', while any
1121-- other body option 'CanHaveBody'. This forces the user to use 'NoReqBody'
1133-- other body option 'CanHaveBody'. This forces the user to use 'NoReqBody'
1122-- with 'GET' method and other methods that should not have body.
1134-- with 'GET' method and other methods that should not have body.
1123−
1124type family ProvidesBody body :: CanHaveBody where
1135type family ProvidesBody body :: CanHaveBody where
1125 ProvidesBody NoReqBody = 'NoBody
1136 ProvidesBody NoReqBody = 'NoBody
1126− ProvidesBody body = 'CanHaveBody
1137+ ProvidesBody body = 'CanHaveBody
1127
1138
1128-- | This type function allows any HTTP body if method says it
1139-- | This type function allows any HTTP body if method says it
1129-- 'CanHaveBody'. When the method says it should have 'NoBody', the only
1140-- 'CanHaveBody'. When the method says it should have 'NoBody', the only
@@ -1131,24 +1142,29 @@ type family ProvidesBody body :: CanHaveBody where
1131--
1142--
1132-- __Note__: users of GHC 8.0.1 and later will see a slightly more friendly
1143-- __Note__: users of GHC 8.0.1 and later will see a slightly more friendly
1133-- error message when method does not allow a body and body is provided.
1144-- error message when method does not allow a body and body is provided.
1134−
1135−type family HttpBodyAllowed
1145+type family
1146+ HttpBodyAllowed
1136− (allowsBody :: CanHaveBody)
1147+ (allowsBody :: CanHaveBody)
1137− (providesBody :: CanHaveBody) :: Constraint where
1148+ (providesBody :: CanHaveBody) ::
1149+ Constraint
1150+ where
1138− HttpBodyAllowed 'NoBody 'NoBody = ()
1151+ HttpBodyAllowed 'NoBody 'NoBody = ()
1139− HttpBodyAllowed 'CanHaveBody body = ()
1152+ HttpBodyAllowed 'CanHaveBody body = ()
1140− HttpBodyAllowed 'NoBody 'CanHaveBody = TypeError
1153+ HttpBodyAllowed 'NoBody 'CanHaveBody =
1154+ TypeError
1141− ('Text "This HTTP method does not allow attaching a request body.")
1155+ ('Text "This HTTP method does not allow attaching a request body.")
1142
1156
1143instance HttpBody body => RequestComponent (Womb "body" body) where
1157instance HttpBody body => RequestComponent (Womb "body" body) where
1144 getRequestMod (Womb body) = Endo $ \x ->
1158 getRequestMod (Womb body) = Endo $ \x ->
1159+ x
1145− x { L.requestBody = getRequestBody body
1160+ { L.requestBody = getRequestBody body,
1146− , L.requestHeaders =
1161+ L.requestHeaders =
1147− let old = L.requestHeaders x in
1162+ let old = L.requestHeaders x
1148− case getRequestContentType body of
1163+ in case getRequestContentType body of
1149− Nothing -> old
1164+ Nothing -> old
1150− Just contentType ->
1165+ Just contentType ->
1151− (Y.hContentType, contentType) : old }
1166+ (Y.hContentType, contentType) : old
1167+ }
1152
1168
1153----------------------------------------------------------------------------
1169----------------------------------------------------------------------------
1154-- Request—Optional parameters
1170-- Request—Optional parameters
@@ -1164,45 +1180,44 @@ instance HttpBody body => RequestComponent (Womb "body" body) where
1164-- | The opaque 'Option' type is a 'Monoid' you can use to pack collection
1180-- | The opaque 'Option' type is a 'Monoid' you can use to pack collection
1165-- of optional parameters like query parameters and headers. See sections
1181-- of optional parameters like query parameters and headers. See sections
1166-- below to learn which 'Option' primitives are available.
1182-- below to learn which 'Option' primitives are available.
1183+data Option (scheme :: Scheme)
1184+ = Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
1167
1185
1168−data Option (scheme :: Scheme) =
1169− Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
1170− -- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
1186+-- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
1171− -- Request to avoid appending to existing query string in request every
1187+-- Request to avoid appending to existing query string in request every
1172− -- time new parameter is added. Additional Maybe (L.Request -> IO
1188+-- time new parameter is added. Additional Maybe (L.Request -> IO
1173− -- L.Request) is a finalizer that will be applied after all other
1189+-- L.Request) is a finalizer that will be applied after all other
1174− -- transformations. This is for authentication methods that sign requests
1190+-- transformations. This is for authentication methods that sign requests
1175− -- based on data in Request.
1191+-- based on data in Request.
1176
1192
1177instance Semigroup (Option scheme) where
1193instance Semigroup (Option scheme) where
1178− Option er0 mr0 <> Option er1 mr1 = Option
1194+ Option er0 mr0 <> Option er1 mr1 =
1195+ Option
1179− (er0 <> er1) (mr0 <|> mr1)
1196+ (er0 <> er1)
1197+ (mr0 <|> mr1)
1180
1198
1181instance Monoid (Option scheme) where
1199instance Monoid (Option scheme) where
1182− mempty = Option mempty Nothing
1200+ mempty = Option mempty Nothing
1183 mappend = (<>)
1201 mappend = (<>)
1184
1202
1185-- | A helper to create an 'Option' that modifies only collection of query
1203-- | A helper to create an 'Option' that modifies only collection of query
1186-- parameters. This helper is not a part of the public API.
1204-- parameters. This helper is not a part of the public API.
1187−
1188withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
1205withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
1189withQueryParams f = Option (Endo (first f)) Nothing
1206withQueryParams f = Option (Endo (first f)) Nothing
1190
1207
1191-- | A helper to create an 'Option' that modifies only 'L.Request'. This
1208-- | A helper to create an 'Option' that modifies only 'L.Request'. This
1192-- helper is not a part of public API.
1209-- helper is not a part of public API.
1193−
1194withRequest :: (L.Request -> L.Request) -> Option scheme
1210withRequest :: (L.Request -> L.Request) -> Option scheme
1195withRequest f = Option (Endo (second f)) Nothing
1211withRequest f = Option (Endo (second f)) Nothing
1196
1212
1197instance RequestComponent (Option scheme) where
1213instance RequestComponent (Option scheme) where
1198 getRequestMod (Option f _) = Endo $ \x ->
1214 getRequestMod (Option f _) = Endo $ \x ->
1199 let (qparams, x') = appEndo f ([], x)
1215 let (qparams, x') = appEndo f ([], x)
1200− query = Y.renderQuery True (Y.queryTextToQuery qparams)
1216+ query = Y.renderQuery True (Y.queryTextToQuery qparams)
1201− in x' { L.queryString = query }
1217+ in x' {L.queryString = query}
1202
1218
1203-- | Finalize given 'L.Request' by applying a finalizer from the given
1219-- | Finalize given 'L.Request' by applying a finalizer from the given
1204-- 'Option' (if it has any).
1220-- 'Option' (if it has any).
1205−
1206finalizeRequest :: MonadIO m => Option scheme -> L.Request -> m L.Request
1221finalizeRequest :: MonadIO m => Option scheme -> L.Request -> m L.Request
1207finalizeRequest (Option _ mfinalizer) = liftIO . fromMaybe pure mfinalizer
1222finalizeRequest (Option _ mfinalizer) = liftIO . fromMaybe pure mfinalizer
1208
1223
@@ -1222,8 +1237,8 @@ finalizeRequest (Option _ mfinalizer) = liftIO . fromMaybe pure mfinalizer
1222-- This operator is defined in terms of 'queryParam':
1237-- This operator is defined in terms of 'queryParam':
1223--
1238--
1224-- > name =: value = queryParam name (pure value)
1239-- > name =: value = queryParam name (pure value)
1225−
1226infix 7 =:
1240infix 7 =:
1241+
1227(=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
1242(=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
1228name =: value = queryParam name (pure value)
1243name =: value = queryParam name (pure value)
1229
1244
@@ -1236,7 +1251,6 @@ name =: value = queryParam name (pure value)
1236-- This operator is defined in terms of 'queryParam':
1251-- This operator is defined in terms of 'queryParam':
1237--
1252--
1238-- > queryFlag name = queryParam name (Nothing :: Maybe ())
1253-- > queryFlag name = queryParam name (Nothing :: Maybe ())
1239−
1240queryFlag :: QueryParam param => Text -> param
1254queryFlag :: QueryParam param => Text -> param
1241queryFlag name = queryParam name (Nothing :: Maybe ())
1255queryFlag name = queryParam name (Nothing :: Maybe ())
1242
1256
@@ -1244,14 +1258,11 @@ queryFlag name = queryParam name (Nothing :: Maybe ())
1244-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
1258-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
1245-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
1259-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
1246-- Having the same syntax for these cases seems natural and user-friendly.
1260-- Having the same syntax for these cases seems natural and user-friendly.
1247−
1248class QueryParam param where
1261class QueryParam param where
1249−
1250 -- | Create a query parameter with given name and value. If value is
1262 -- | Create a query parameter with given name and value. If value is
1251 -- 'Nothing', it won't be included at all (i.e. you create a flag this
1263 -- 'Nothing', it won't be included at all (i.e. you create a flag this
1252 -- way). It's recommended to use @('=:')@ and 'queryFlag' instead of this
1264 -- way). It's recommended to use @('=:')@ and 'queryFlag' instead of this
1253 -- method, because they are easier to read.
1265 -- method, because they are easier to read.
1254−
1255 queryParam :: ToHttpApiData a => Text -> Maybe a -> param
1266 queryParam :: ToHttpApiData a => Text -> Maybe a -> param
1256
1267
1257instance QueryParam (Option scheme) where
1268instance QueryParam (Option scheme) where
@@ -1265,20 +1276,20 @@ instance QueryParam (Option scheme) where
1265-- headers with the same names the leftmost header will win. This means, in
1276-- headers with the same names the leftmost header will win. This means, in
1266-- particular, that you cannot create a request with several headers of the
1277-- particular, that you cannot create a request with several headers of the
1267-- same name.
1278-- same name.
1268−
1269−header
1279+header ::
1270− :: ByteString -- ^ Header name
1280+ -- | Header name
1281+ ByteString ->
1271− -> ByteString -- ^ Header value
1282+ -- | Header value
1283+ ByteString ->
1272− -> Option scheme
1284+ Option scheme
1273header name value = withRequest (attachHeader name value)
1285header name value = withRequest (attachHeader name value)
1274
1286
1275-- | Attach a header with given name and content to a 'L.Request'.
1287-- | Attach a header with given name and content to a 'L.Request'.
1276--
1288--
1277-- @since 1.1.0
1289-- @since 1.1.0
1278−
1279attachHeader :: ByteString -> ByteString -> L.Request -> L.Request
1290attachHeader :: ByteString -> ByteString -> L.Request -> L.Request
1280attachHeader name value x =
1291attachHeader name value x =
1281− x { L.requestHeaders = (CI.mk name, value) : L.requestHeaders x }
1292+ x {L.requestHeaders = (CI.mk name, value) : L.requestHeaders x}
1282
1293
1283----------------------------------------------------------------------------
1294----------------------------------------------------------------------------
1284-- Request—Optional parameters—Cookies
1295-- Request—Optional parameters—Cookies
@@ -1291,10 +1302,9 @@ attachHeader name value x =
1291
1302
1292-- | Use the given 'L.CookieJar'. A 'L.CookieJar' can be obtained from a
1303-- | Use the given 'L.CookieJar'. A 'L.CookieJar' can be obtained from a
1293-- 'L.Response' record.
1304-- 'L.Response' record.
1294−
1295cookieJar :: L.CookieJar -> Option scheme
1305cookieJar :: L.CookieJar -> Option scheme
1296cookieJar jar = withRequest $ \x ->
1306cookieJar jar = withRequest $ \x ->
1297− x { L.cookieJar = Just jar }
1307+ x {L.cookieJar = Just jar}
1298
1308
1299----------------------------------------------------------------------------
1309----------------------------------------------------------------------------
1300-- Request—Optional parameters—Authentication
1310-- Request—Optional parameters—Authentication
@@ -1312,11 +1322,13 @@ cookieJar jar = withRequest $ \x ->
1312-- | The 'Option' adds basic authentication.
1322-- | The 'Option' adds basic authentication.
1313--
1323--
1314-- See also: <https://en.wikipedia.org/wiki/Basic_access_authentication>.
1324-- See also: <https://en.wikipedia.org/wiki/Basic_access_authentication>.
1315−
1316−basicAuth
1325+basicAuth ::
1317− :: ByteString -- ^ Username
1326+ -- | Username
1318− -> ByteString -- ^ Password
1327+ ByteString ->
1328+ -- | Password
1329+ ByteString ->
1319− -> Option 'Https -- ^ Auth 'Option'
1330+ -- | Auth 'Option'
1331+ Option 'Https
1320basicAuth = basicAuthUnsafe
1332basicAuth = basicAuthUnsafe
1321
1333
1322-- | An alternative to 'basicAuth' which works for any scheme. Note that
1334-- | An alternative to 'basicAuth' which works for any scheme. Note that
@@ -1324,41 +1336,52 @@ basicAuth = basicAuthUnsafe
1324-- attacks. Use 'basicAuth' instead unless you know what you are doing.
1336-- attacks. Use 'basicAuth' instead unless you know what you are doing.
1325--
1337--
1326-- @since 0.3.1
1338-- @since 0.3.1
1327−
1328−basicAuthUnsafe
1339+basicAuthUnsafe ::
1329− :: ByteString -- ^ Username
1340+ -- | Username
1330− -> ByteString -- ^ Password
1341+ ByteString ->
1342+ -- | Password
1343+ ByteString ->
1344+ -- | Auth 'Option'
1331− -> Option scheme -- ^ Auth 'Option'
1345+ Option scheme
1332−basicAuthUnsafe username password = customAuth
1346+basicAuthUnsafe username password =
1347+ customAuth
1333− (pure . L.applyBasicAuth username password)
1348+ (pure . L.applyBasicAuth username password)
1334
1349
1335-- | The 'Option' set basic proxy authentication header.
1350-- | The 'Option' set basic proxy authentication header.
1336--
1351--
1337-- @since 1.1.0
1352-- @since 1.1.0
1338−
1339−basicProxyAuth
1353+basicProxyAuth ::
1340− :: ByteString -- ^ Username
1354+ -- | Username
1341− -> ByteString -- ^ Password
1355+ ByteString ->
1356+ -- | Password
1357+ ByteString ->
1342− -> Option scheme -- ^ Auth 'Option'
1358+ -- | Auth 'Option'
1359+ Option scheme
1343basicProxyAuth username password =
1360basicProxyAuth username password =
1344 withRequest (L.applyBasicProxyAuth username password)
1361 withRequest (L.applyBasicProxyAuth username password)
1345
1362
1346-- | The 'Option' adds OAuth1 authentication.
1363-- | The 'Option' adds OAuth1 authentication.
1347--
1364--
1348-- @since 0.2.0
1365-- @since 0.2.0
1349−
1350−oAuth1
1366+oAuth1 ::
1351− :: ByteString -- ^ Consumer token
1367+ -- | Consumer token
1368+ ByteString ->
1352− -> ByteString -- ^ Consumer secret
1369+ -- | Consumer secret
1353− -> ByteString -- ^ OAuth token
1370+ ByteString ->
1371+ -- | OAuth token
1372+ ByteString ->
1354− -> ByteString -- ^ OAuth token secret
1373+ -- | OAuth token secret
1374+ ByteString ->
1355− -> Option scheme -- ^ Auth 'Option'
1375+ -- | Auth 'Option'
1376+ Option scheme
1356oAuth1 consumerToken consumerSecret token tokenSecret =
1377oAuth1 consumerToken consumerSecret token tokenSecret =
1357 customAuth (OAuth.signOAuth app creds)
1378 customAuth (OAuth.signOAuth app creds)
1358 where
1379 where
1380+ app =
1359− app = OAuth.newOAuth
1381+ OAuth.newOAuth
1360− { OAuth.oauthConsumerKey = consumerToken
1382+ { OAuth.oauthConsumerKey = consumerToken,
1361− , OAuth.oauthConsumerSecret = consumerSecret }
1383+ OAuth.oauthConsumerSecret = consumerSecret
1384+ }
1362 creds = OAuth.newCredential token tokenSecret
1385 creds = OAuth.newCredential token tokenSecret
1363
1386
1364-- | The 'Option' adds an OAuth2 bearer token. This is treated by many
1387-- | The 'Option' adds an OAuth2 bearer token. This is treated by many
@@ -1369,12 +1392,14 @@ oAuth1 consumerToken consumerSecret token tokenSecret =
1369-- > oAuth2Bearer token = header "Authorization" ("Bearer " <> token)
1392-- > oAuth2Bearer token = header "Authorization" ("Bearer " <> token)
1370--
1393--
1371-- See also: <https://en.wikipedia.org/wiki/OAuth>.
1394-- See also: <https://en.wikipedia.org/wiki/OAuth>.
1372−
1373−oAuth2Bearer
1395+oAuth2Bearer ::
1396+ -- | Token
1374− :: ByteString -- ^ Token
1397+ ByteString ->
1375− -> Option 'Https -- ^ Auth 'Option'
1398+ -- | Auth 'Option'
1399+ Option 'Https
1376−oAuth2Bearer token = customAuth
1400+oAuth2Bearer token =
1401+ customAuth
1377− (pure . attachHeader "Authorization" ("Bearer " <> token))
1402+ (pure . attachHeader "Authorization" ("Bearer " <> token))
1378
1403
1379-- | The 'Option' adds a not-quite-standard OAuth2 bearer token (that seems
1404-- | The 'Option' adds a not-quite-standard OAuth2 bearer token (that seems
1380-- to be used only by GitHub). This will be treated by whatever services
1405-- to be used only by GitHub). This will be treated by whatever services
@@ -1385,19 +1410,20 @@ oAuth2Bearer token = customAuth
1385-- > oAuth2Token token = header "Authorization" ("token" <> token)
1410-- > oAuth2Token token = header "Authorization" ("token" <> token)
1386--
1411--
1387-- See also: <https://developer.github.com/v3/oauth#3-use-the-access-token-to-access-the-api>.
1412-- See also: <https://developer.github.com/v3/oauth#3-use-the-access-token-to-access-the-api>.
1388−
1389−oAuth2Token
1413+oAuth2Token ::
1414+ -- | Token
1390− :: ByteString -- ^ Token
1415+ ByteString ->
1391− -> Option 'Https -- ^ Auth 'Option'
1416+ -- | Auth 'Option'
1417+ Option 'Https
1392−oAuth2Token token = customAuth
1418+oAuth2Token token =
1419+ customAuth
1393− (pure . attachHeader "Authorization" ("token " <> token))
1420+ (pure . attachHeader "Authorization" ("token " <> token))
1394
1421
1395-- | A helper to create custom authentication 'Option's. The given
1422-- | A helper to create custom authentication 'Option's. The given
1396-- 'IO'-enabled request transformation is applied after all other
1423-- 'IO'-enabled request transformation is applied after all other
1397-- modifications when constructing a request. Use wisely.
1424-- modifications when constructing a request. Use wisely.
1398--
1425--
1399-- @since 1.1.0
1426-- @since 1.1.0
1400−
1401customAuth :: (L.Request -> IO L.Request) -> Option scheme
1427customAuth :: (L.Request -> IO L.Request) -> Option scheme
1402customAuth = Option mempty . pure
1428customAuth = Option mempty . pure
1403
1429
@@ -1407,10 +1433,9 @@ customAuth = Option mempty . pure
1407-- | Specify the port to connect to explicitly. Normally, 'Url' you use
1433-- | Specify the port to connect to explicitly. Normally, 'Url' you use
1408-- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
1434-- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
1409-- 'Option' allows to choose an arbitrary port overwriting the defaults.
1435-- 'Option' allows to choose an arbitrary port overwriting the defaults.
1410−
1411port :: Int -> Option scheme
1436port :: Int -> Option scheme
1412port n = withRequest $ \x ->
1437port n = withRequest $ \x ->
1413− x { L.port = n }
1438+ x {L.port = n}
1414
1439
1415-- | This 'Option' controls whether gzipped data should be decompressed on
1440-- | This 'Option' controls whether gzipped data should be decompressed on
1416-- the fly. By default everything except for @\"application\/x-tar\"@ is
1441-- the fly. By default everything except for @\"application\/x-tar\"@ is
@@ -1421,38 +1446,38 @@ port n = withRequest $ \x ->
1421-- You can also choose to decompress everything like this:
1446-- You can also choose to decompress everything like this:
1422--
1447--
1423-- > decompress (const True)
1448-- > decompress (const True)
1424−
1425−decompress
1449+decompress ::
1426− :: (ByteString -> Bool) -- ^ Predicate that is given MIME type, it
1450+ -- | Predicate that is given MIME type, it
1427− -- returns 'True' when content should be decompressed on the fly.
1451+ -- returns 'True' when content should be decompressed on the fly.
1452+ (ByteString -> Bool) ->
1428− -> Option scheme
1453+ Option scheme
1429decompress f = withRequest $ \x ->
1454decompress f = withRequest $ \x ->
1430− x { L.decompress = f }
1455+ x {L.decompress = f}
1431
1456
1432-- | Specify the number of microseconds to wait for response. The default
1457-- | Specify the number of microseconds to wait for response. The default
1433-- value is 30 seconds (defined in 'L.ManagerSettings' of connection
1458-- value is 30 seconds (defined in 'L.ManagerSettings' of connection
1434-- 'L.Manager').
1459-- 'L.Manager').
1435−
1436−responseTimeout
1460+responseTimeout ::
1437− :: Int -- ^ Number of microseconds to wait
1461+ -- | Number of microseconds to wait
1462+ Int ->
1438− -> Option scheme
1463+ Option scheme
1439responseTimeout n = withRequest $ \x ->
1464responseTimeout n = withRequest $ \x ->
1440− x { L.responseTimeout = LI.ResponseTimeoutMicro n }
1465+ x {L.responseTimeout = LI.ResponseTimeoutMicro n}
1441
1466
1442-- | HTTP version to send to the server, the default is HTTP 1.1.
1467-- | HTTP version to send to the server, the default is HTTP 1.1.
1443−
1444−httpVersion
1468+httpVersion ::
1445− :: Int -- ^ Major version number
1469+ -- | Major version number
1470+ Int ->
1446− -> Int -- ^ Minor version number
1471+ -- | Minor version number
1472+ Int ->
1447− -> Option scheme
1473+ Option scheme
1448httpVersion major minor = withRequest $ \x ->
1474httpVersion major minor = withRequest $ \x ->
1449− x { L.requestVersion = Y.HttpVersion major minor }
1475+ x {L.requestVersion = Y.HttpVersion major minor}
1450
1476
1451----------------------------------------------------------------------------
1477----------------------------------------------------------------------------
1452-- Response interpretations
1478-- Response interpretations
1453
1479
1454-- | Make a request and ignore the body of the response.
1480-- | Make a request and ignore the body of the response.
1455−
1456newtype IgnoreResponse = IgnoreResponse (L.Response ())
1481newtype IgnoreResponse = IgnoreResponse (L.Response ())
1457
1482
1458instance HttpResponse IgnoreResponse where
1483instance HttpResponse IgnoreResponse where
@@ -1462,7 +1487,6 @@ instance HttpResponse IgnoreResponse where
1462
1487
1463-- | Use this as the fourth argument of 'req' to specify that you want it to
1488-- | Use this as the fourth argument of 'req' to specify that you want it to
1464-- ignore the response body.
1489-- ignore the response body.
1465−
1466ignoreResponse :: Proxy IgnoreResponse
1490ignoreResponse :: Proxy IgnoreResponse
1467ignoreResponse = Proxy
1491ignoreResponse = Proxy
1468
1492
@@ -1470,7 +1494,6 @@ ignoreResponse = Proxy
1470-- 'handleHttpException' method of 'MonadHttp' instance corresponding to
1494-- 'handleHttpException' method of 'MonadHttp' instance corresponding to
1471-- monad in which you use 'req' will determine what to do in the case when
1495-- monad in which you use 'req' will determine what to do in the case when
1472-- parsing fails (the 'JsonHttpException' constructor will be used).
1496-- parsing fails (the 'JsonHttpException' constructor will be used).
1473−
1474newtype JsonResponse a = JsonResponse (L.Response a)
1497newtype JsonResponse a = JsonResponse (L.Response a)
1475
1498
1476instance FromJSON a => HttpResponse (JsonResponse a) where
1499instance FromJSON a => HttpResponse (JsonResponse a) where
@@ -1479,19 +1502,17 @@ instance FromJSON a => HttpResponse (JsonResponse a) where
1479 getHttpResponse r = do
1502 getHttpResponse r = do
1480 chunks <- L.brConsume (L.responseBody r)
1503 chunks <- L.brConsume (L.responseBody r)
1481 case A.eitherDecode (BL.fromChunks chunks) of
1504 case A.eitherDecode (BL.fromChunks chunks) of
1482− Left e -> throwIO (JsonHttpException e)
1505+ Left e -> throwIO (JsonHttpException e)
1483 Right x -> return $ JsonResponse (x <$ r)
1506 Right x -> return $ JsonResponse (x <$ r)
1484 acceptHeader Proxy = Just "application/json"
1507 acceptHeader Proxy = Just "application/json"
1485
1508
1486-- | Use this as the fourth argument of 'req' to specify that you want it to
1509-- | Use this as the fourth argument of 'req' to specify that you want it to
1487-- return the 'JsonResponse' interpretation.
1510-- return the 'JsonResponse' interpretation.
1488−
1489jsonResponse :: Proxy (JsonResponse a)
1511jsonResponse :: Proxy (JsonResponse a)
1490jsonResponse = Proxy
1512jsonResponse = Proxy
1491
1513
1492-- | Make a request and interpret the body of the response as a strict
1514-- | Make a request and interpret the body of the response as a strict
1493-- 'ByteString'.
1515-- 'ByteString'.
1494−
1495newtype BsResponse = BsResponse (L.Response ByteString)
1516newtype BsResponse = BsResponse (L.Response ByteString)
1496
1517
1497instance HttpResponse BsResponse where
1518instance HttpResponse BsResponse where
@@ -1503,13 +1524,11 @@ instance HttpResponse BsResponse where
1503
1524
1504-- | Use this as the fourth argument of 'req' to specify that you want to
1525-- | Use this as the fourth argument of 'req' to specify that you want to
1505-- interpret the response body as a strict 'ByteString'.
1526-- interpret the response body as a strict 'ByteString'.
1506−
1507bsResponse :: Proxy BsResponse
1527bsResponse :: Proxy BsResponse
1508bsResponse = Proxy
1528bsResponse = Proxy
1509
1529
1510-- | Make a request and interpret the body of the response as a lazy
1530-- | Make a request and interpret the body of the response as a lazy
1511-- 'BL.ByteString'.
1531-- 'BL.ByteString'.
1512−
1513newtype LbsResponse = LbsResponse (L.Response BL.ByteString)
1532newtype LbsResponse = LbsResponse (L.Response BL.ByteString)
1514
1533
1515instance HttpResponse LbsResponse where
1534instance HttpResponse LbsResponse where
@@ -1521,7 +1540,6 @@ instance HttpResponse LbsResponse where
1521
1540
1522-- | Use this as the fourth argument of 'req' to specify that you want to
1541-- | Use this as the fourth argument of 'req' to specify that you want to
1523-- interpret the response body as a lazy 'BL.ByteString'.
1542-- interpret the response body as a lazy 'BL.ByteString'.
1524−
1525lbsResponse :: Proxy LbsResponse
1543lbsResponse :: Proxy LbsResponse
1526lbsResponse = Proxy
1544lbsResponse = Proxy
1527
1545
@@ -1531,14 +1549,13 @@ lbsResponse = Proxy
1531-- | Fetch beginning of response and return it together with new
1549-- | Fetch beginning of response and return it together with new
1532-- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
1550-- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
1533-- such.
1551-- such.
1534−
1535−grabPreview
1552+grabPreview ::
1536− :: Int
1537− -- ^ How many bytes to fetch
1553+ -- | How many bytes to fetch
1554+ Int ->
1555+ -- | Response with body reader inside
1538− -> L.Response L.BodyReader
1556+ L.Response L.BodyReader ->
1539− -- ^ Response with body reader inside
1557+ -- | Preview 'ByteString' and new response with body reader inside
1540− -> IO (ByteString, L.Response L.BodyReader)
1558+ IO (ByteString, L.Response L.BodyReader)
1541− -- ^ Preview 'ByteString' and new response with body reader inside
1542grabPreview nbytes r = do
1559grabPreview nbytes r = do
1543 let br = L.responseBody r
1560 let br = L.responseBody r
1544 (target, leftover, done) <- brReadN br nbytes
1561 (target, leftover, done) <- brReadN br nbytes
@@ -1561,18 +1578,17 @@ grabPreview nbytes r = do
1561 if done
1578 if done
1562 then return B.empty
1579 then return B.empty
1563 else br
1580 else br
1564− return (target, r { L.responseBody = br' })
1581+ return (target, r {L.responseBody = br'})
1565
1582
1566-- | Consume N bytes from 'L.BodyReader', return the target chunk, the
1583-- | Consume N bytes from 'L.BodyReader', return the target chunk, the
1567-- leftover (may be empty), and whether we're done consuming the body.
1584-- leftover (may be empty), and whether we're done consuming the body.
1568−
1569−brReadN
1585+brReadN ::
1570− :: L.BodyReader
1571− -- ^ Body reader to stream from
1586+ -- | Body reader to stream from
1572− -> Int
1587+ L.BodyReader ->
1573− -- ^ How many bytes to consume
1588+ -- | How many bytes to consume
1574− -> IO (ByteString, ByteString, Bool)
1589+ Int ->
1575− -- ^ Target chunk, the leftover, whether we're done
1590+ -- | Target chunk, the leftover, whether we're done
1591+ IO (ByteString, ByteString, Bool)
1576brReadN br n = go 0 id id
1592brReadN br n = go 0 id id
1577 where
1593 where
1578 go !tlen t l = do
1594 go !tlen t l = do
@@ -1581,9 +1597,9 @@ brReadN br n = go 0 id id
1581 then return (r t, r l, True)
1597 then return (r t, r l, True)
1582 else do
1598 else do
1583 let (target, leftover) = B.splitAt (n - tlen) chunk
1599 let (target, leftover) = B.splitAt (n - tlen) chunk
1584− tlen' = B.length target
1600+ tlen' = B.length target
1585− t' = t . (target:)
1601+ t' = t . (target :)
1586− l' = l . (leftover:)
1602+ l' = l . (leftover :)
1587 if tlen + tlen' < n
1603 if tlen + tlen' < n
1588 then go (tlen + tlen') t' l'
1604 then go (tlen + tlen') t' l'
1589 else return (r t', r l', False)
1605 else return (r t', r l', False)
@@ -1593,47 +1609,45 @@ brReadN br n = go 0 id id
1593-- Inspecting a response
1609-- Inspecting a response
1594
1610
1595-- | Get the response body.
1611-- | Get the response body.
1596−
1597−responseBody
1612+responseBody ::
1598− :: HttpResponse response
1613+ HttpResponse response =>
1599− => response
1614+ response ->
1600− -> HttpResponseBody response
1615+ HttpResponseBody response
1601responseBody = L.responseBody . toVanillaResponse
1616responseBody = L.responseBody . toVanillaResponse
1602
1617
1603-- | Get the response status code.
1618-- | Get the response status code.
1604−
1605−responseStatusCode
1619+responseStatusCode ::
1606− :: HttpResponse response
1620+ HttpResponse response =>
1607− => response
1621+ response ->
1608− -> Int
1622+ Int
1609responseStatusCode =
1623responseStatusCode =
1610 Y.statusCode . L.responseStatus . toVanillaResponse
1624 Y.statusCode . L.responseStatus . toVanillaResponse
1611
1625
1612-- | Get the response status message.
1626-- | Get the response status message.
1613−
1614−responseStatusMessage
1627+responseStatusMessage ::
1615− :: HttpResponse response
1628+ HttpResponse response =>
1616− => response
1629+ response ->
1617− -> ByteString
1630+ ByteString
1618responseStatusMessage =
1631responseStatusMessage =
1619 Y.statusMessage . L.responseStatus . toVanillaResponse
1632 Y.statusMessage . L.responseStatus . toVanillaResponse
1620
1633
1621-- | Lookup a particular header from a response.
1634-- | Lookup a particular header from a response.
1622−
1623−responseHeader
1635+responseHeader ::
1624− :: HttpResponse response
1636+ HttpResponse response =>
1625− => response -- ^ Response interpretation
1637+ -- | Response interpretation
1638+ response ->
1626− -> ByteString -- ^ Header to lookup
1639+ -- | Header to lookup
1640+ ByteString ->
1627− -> Maybe ByteString -- ^ Header value if found
1641+ -- | Header value if found
1642+ Maybe ByteString
1628responseHeader r h =
1643responseHeader r h =
1629 (lookup (CI.mk h) . L.responseHeaders . toVanillaResponse) r
1644 (lookup (CI.mk h) . L.responseHeaders . toVanillaResponse) r
1630
1645
1631-- | Get the response 'L.CookieJar'.
1646-- | Get the response 'L.CookieJar'.
1632−
1633−responseCookieJar
1647+responseCookieJar ::
1634− :: HttpResponse response
1648+ HttpResponse response =>
1635− => response
1649+ response ->
1636− -> L.CookieJar
1650+ L.CookieJar
1637responseCookieJar = L.responseCookieJar . toVanillaResponse
1651responseCookieJar = L.responseCookieJar . toVanillaResponse
1638
1652
1639----------------------------------------------------------------------------
1653----------------------------------------------------------------------------
@@ -1647,16 +1661,12 @@ responseCookieJar = L.responseCookieJar . toVanillaResponse
1647-- | A type class for response interpretations. It allows to describe how to
1661-- | A type class for response interpretations. It allows to describe how to
1648-- consume response from a @'L.Response' 'L.BodyReader'@ and produce the
1662-- consume response from a @'L.Response' 'L.BodyReader'@ and produce the
1649-- final result that is to be returned to the user.
1663-- final result that is to be returned to the user.
1650−
1651class HttpResponse response where
1664class HttpResponse response where
1652−
1653 -- | The associated type is the type of body that can be extracted from an
1665 -- | The associated type is the type of body that can be extracted from an
1654 -- instance of 'HttpResponse'.
1666 -- instance of 'HttpResponse'.
1655−
1656 type HttpResponseBody response :: Type
1667 type HttpResponseBody response :: Type
1657
1668
1658 -- | The method describes how to get the underlying 'L.Response' record.
1669 -- | The method describes how to get the underlying 'L.Response' record.
1659−
1660 toVanillaResponse :: response -> L.Response (HttpResponseBody response)
1670 toVanillaResponse :: response -> L.Response (HttpResponseBody response)
1661
1671
1662 -- | This method describes how to consume response body and, more
1672 -- | This method describes how to consume response body and, more
@@ -1672,12 +1682,11 @@ class HttpResponse response where
1672 --
1682 --
1673 -- __Note__: signature of this function was changed in the version
1683 -- __Note__: signature of this function was changed in the version
1674 -- /1.0.0/.
1684 -- /1.0.0/.
1675−
1676− getHttpResponse
1685+ getHttpResponse ::
1677− :: L.Response L.BodyReader
1678− -- ^ Response with body reader inside
1686+ -- | Response with body reader inside
1679− -> IO response
1687+ L.Response L.BodyReader ->
1680− -- ^ The final result
1688+ -- | The final result
1689+ IO response
1681
1690
1682 -- | The value of @\"Accept\"@ header. This is useful, for example, if a
1691 -- | The value of @\"Accept\"@ header. This is useful, for example, if a
1683 -- website supports both @XML@ and @JSON@ responses, and decides what to
1692 -- website supports both @XML@ and @JSON@ responses, and decides what to
@@ -1687,7 +1696,6 @@ class HttpResponse response where
1687 -- will take precedence.
1696 -- will take precedence.
1688 --
1697 --
1689 -- @since 2.1.0
1698 -- @since 2.1.0
1690−
1691 acceptHeader :: Proxy response -> Maybe ByteString
1699 acceptHeader :: Proxy response -> Maybe ByteString
1692 acceptHeader Proxy = Nothing
1700 acceptHeader Proxy = Nothing
1693
1701
@@ -1701,15 +1709,12 @@ class HttpResponse response where
1701-- request components easier using @('<>')@.
1709-- request components easier using @('<>')@.
1702--
1710--
1703-- __Note__: this type class is not a part of the public API.
1711-- __Note__: this type class is not a part of the public API.
1704−
1705class RequestComponent a where
1712class RequestComponent a where
1706−
1707 -- | Get a function that takes a 'L.Request' and changes it somehow
1713 -- | Get a function that takes a 'L.Request' and changes it somehow
1708 -- returning another 'L.Request'. For example, the 'HttpMethod' instance
1714 -- returning another 'L.Request'. For example, the 'HttpMethod' instance
1709 -- of 'RequestComponent' just overwrites method. The function is wrapped
1715 -- of 'RequestComponent' just overwrites method. The function is wrapped
1710 -- in 'Endo' so it's easier to chain such “modifying applications”
1716 -- in 'Endo' so it's easier to chain such “modifying applications”
1711 -- together building bigger and bigger 'RequestComponent's.
1717 -- together building bigger and bigger 'RequestComponent's.
1712−
1713 getRequestMod :: a -> Endo L.Request
1718 getRequestMod :: a -> Endo L.Request
1714
1719
1715-- | This wrapper is only used to attach a type-level tag to a given type.
1720-- | This wrapper is only used to attach a type-level tag to a given type.
@@ -1719,17 +1724,15 @@ class RequestComponent a where
1719-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
1724-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
1720-- which instance to use (i.e. the constraints are taken into account later,
1725-- which instance to use (i.e. the constraints are taken into account later,
1721-- when instance is already chosen).
1726-- when instance is already chosen).
1722−
1723newtype Womb (tag :: Symbol) a = Womb a
1727newtype Womb (tag :: Symbol) a = Womb a
1724
1728
1725-- | Exceptions that this library throws.
1729-- | Exceptions that this library throws.
1726−
1727data HttpException
1730data HttpException
1728− = VanillaHttpException L.HttpException
1729− -- ^ A wrapper with an 'L.HttpException' from "Network.HTTP.Client"
1731+ = -- | A wrapper with an 'L.HttpException' from "Network.HTTP.Client"
1730− | JsonHttpException String
1732+ VanillaHttpException L.HttpException
1731− -- ^ A wrapper with Aeson-produced 'String' describing why decoding
1733+ | -- | A wrapper with Aeson-produced 'String' describing why decoding
1732 -- failed
1734 -- failed
1735+ JsonHttpException String
1733 deriving (Show, Typeable, Generic)
1736 deriving (Show, Typeable, Generic)
1734
1737
1735instance Exception HttpException
1738instance Exception HttpException
@@ -1739,24 +1742,25 @@ instance Exception HttpException
1739-- tags.
1742-- tags.
1740--
1743--
1741-- See also: 'HttpMethod' and 'HttpBody'.
1744-- See also: 'HttpMethod' and 'HttpBody'.
1742−
1743data CanHaveBody
1745data CanHaveBody
1744− = CanHaveBody -- ^ Indeed can have a body
1746+ = -- | Indeed can have a body
1747+ CanHaveBody
1745− | NoBody -- ^ Should not have a body
1748+ | -- | Should not have a body
1749+ NoBody
1746
1750
1747-- | A type-level tag that specifies URL scheme used (and thus if HTTPS is
1751-- | A type-level tag that specifies URL scheme used (and thus if HTTPS is
1748-- enabled). This is used to force TLS requirement for some authentication
1752-- enabled). This is used to force TLS requirement for some authentication
1749-- 'Option's.
1753-- 'Option's.
1750−
1751data Scheme
1754data Scheme
1755+ = -- | HTTP
1752− = Http -- ^ HTTP
1756+ Http
1757+ | -- | HTTPS
1753− | Https -- ^ HTTPS
1758+ Https
1754 deriving (Eq, Ord, Show, Data, Typeable, Generic)
1759 deriving (Eq, Ord, Show, Data, Typeable, Generic)
1755
1760
1756----------------------------------------------------------------------------
1761----------------------------------------------------------------------------
1757-- Constants
1762-- Constants
1758
1763
1759-- | Max length of preview fragment of response body.
1764-- | Max length of preview fragment of response body.
1760−
1761bodyPreviewLength :: Num a => a
1765bodyPreviewLength :: Num a => a
1762bodyPreviewLength = 1024
1766bodyPreviewLength = 1024
README.md+12−8
@@ -26,16 +26,20 @@ main :: IO ()
26-- You can either make your monad an instance of 'MonadHttp', or use
26-- You can either make your monad an instance of 'MonadHttp', or use
27-- 'runReq' in any IO-enabled monad without defining new instances.
27-- 'runReq' in any IO-enabled monad without defining new instances.
28main = runReq defaultHttpConfig $ do
28main = runReq defaultHttpConfig $ do
29− let payload = object
29+ let payload =
30+ object
30− [ "foo" .= (10 :: Int)
31+ [ "foo" .= (10 :: Int),
31− , "bar" .= (20 :: Int) ]
32+ "bar" .= (20 :: Int)
33+ ]
32 -- One function—full power and flexibility, automatic retrying on timeouts
34 -- One function—full power and flexibility, automatic retrying on timeouts
33 -- and such, automatic connection sharing.
35 -- and such, automatic connection sharing.
36+ r <-
37+ req
34− r <- req POST -- method
38+ POST -- method
35− (https "httpbin.org" /: "post") -- safe by construction URL
39+ (https "httpbin.org" /: "post") -- safe by construction URL
36− (ReqBodyJson payload) -- use built-in options or add your own
40+ (ReqBodyJson payload) -- use built-in options or add your own
37− jsonResponse -- specify how to interpret response
41+ jsonResponse -- specify how to interpret response
38− mempty -- query params, headers, explicit port number, etc.
42+ mempty -- query params, headers, explicit port number, etc.
39 liftIO $ print (responseBody r :: Value)
43 liftIO $ print (responseBody r :: Value)
40```
44```
41
45
httpbin-tests/Network/HTTP/ReqSpec.hs+324−225
@@ -1,33 +1,34 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE FlexibleInstances #-}
4−{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE RankNTypes #-}
6{-# OPTIONS_GHC -fno-warn-orphans #-}
6{-# OPTIONS_GHC -fno-warn-orphans #-}
7
7
8module Network.HTTP.ReqSpec
8module Network.HTTP.ReqSpec
9− ( spec )
9+ ( spec,
10+ )
10where
11where
11
12
12import Control.Exception
13import Control.Exception
13import Control.Monad.Reader
14import Control.Monad.Reader
14import Control.Monad.Trans.Control
15import Control.Monad.Trans.Control
15−import Data.Aeson (Value (..), ToJSON (..), object, (.=))
16+import Data.Aeson ((.=), ToJSON (..), Value (..), object)
17+import qualified Data.Aeson as A
18+import qualified Data.ByteString as B
19+import qualified Data.ByteString.Lazy as BL
20+import qualified Data.HashMap.Strict as HM
16import Data.Proxy
21import Data.Proxy
17import Data.Text (Text)
22import Data.Text (Text)
23+import qualified Data.Text as T
24+import qualified Data.Text.Encoding as T
25+import qualified Data.Text.IO as TIO
26+import qualified Network.HTTP.Client as L
27+import qualified Network.HTTP.Client.MultipartFormData as LM
18import Network.HTTP.Req
28import Network.HTTP.Req
29+import qualified Network.HTTP.Types as Y
19import Test.Hspec
30import Test.Hspec
20import Test.QuickCheck
31import Test.QuickCheck
21−import qualified Data.Aeson as A
22−import qualified Data.ByteString as B
23−import qualified Data.ByteString.Lazy as BL
24−import qualified Data.HashMap.Strict as HM
25−import qualified Data.Text as T
26−import qualified Data.Text.Encoding as T
27−import qualified Data.Text.IO as TIO
28−import qualified Network.HTTP.Client as L
29−import qualified Network.HTTP.Client.MultipartFormData as LM
30−import qualified Network.HTTP.Types as Y
31
32
32#if !MIN_VERSION_base(4,13,0)
33#if !MIN_VERSION_base(4,13,0)
33import Data.Semigroup ((<>))
34import Data.Semigroup ((<>))
@@ -35,191 +36,257 @@ import Data.Semigroup ((<>))
35
36
36spec :: Spec
37spec :: Spec
37spec = do
38spec = do
38−
39− describe "exception throwing on non-2xx status codes" $
39+ describe "exception throwing on non-2xx status codes"
40+ $ it "throws indeed for non-2xx"
41+ $ req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty
42+ `shouldThrow` selector404
43+
44+ describe "exception throwing on non-2xx status codes (Req monad)"
40− it "throws indeed for non-2xx" $
45+ $ it "throws indeed for non-2xx"
46+ $ asIO . runReq defaultHttpConfig
47+ $ liftBaseWith
48+ $ \run ->
41− req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty
49+ run (req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty)
42 `shouldThrow` selector404
50 `shouldThrow` selector404
43
51
44− describe "exception throwing on non-2xx status codes (Req monad)" $
45− it "throws indeed for non-2xx" $
46− asIO . runReq defaultHttpConfig $
47− liftBaseWith $ \run ->
48− run (req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty)
49− `shouldThrow` selector404
50−
51− describe "response check via httpConfigCheckResponse" $
52+ describe "response check via httpConfigCheckResponse"
52− context "if it's set to always throw" $
53+ $ context "if it's set to always throw"
53− it "throws indeed" $
54+ $ it "throws indeed"
54− blindlyThrowing (req GET httpbin NoReqBody ignoreResponse mempty)
55+ $ blindlyThrowing (req GET httpbin NoReqBody ignoreResponse mempty)
55− `shouldThrow` anyException
56+ `shouldThrow` anyException
56−
57+
57− describe "receiving user-agent header back" $
58+ describe "receiving user-agent header back"
58− it "works" $ do
59+ $ it "works"
60+ $ do
61+ r <-
62+ req
63+ GET
59− r <- req GET (httpbin /: "user-agent")
64+ (httpbin /: "user-agent")
65+ NoReqBody
66+ jsonResponse
60− NoReqBody jsonResponse (header "user-agent" "Req")
67+ (header "user-agent" "Req")
68+ responseBody r
61− responseBody r `shouldBe` object
69+ `shouldBe` object
62− ["user-agent" .= ("Req" :: Text)]
70+ ["user-agent" .= ("Req" :: Text)]
63− responseStatusCode r `shouldBe` 200
71+ responseStatusCode r `shouldBe` 200
64 responseStatusMessage r `shouldBe` "OK"
72 responseStatusMessage r `shouldBe` "OK"
65
73
66− describe "receiving request headers back" $
74+ describe "receiving request headers back"
67− it "works" $ do
75+ $ it "works"
76+ $ do
77+ r <-
78+ req
79+ GET
68− r <- req GET (httpbin /: "headers")
80+ (httpbin /: "headers")
81+ NoReqBody
82+ jsonResponse
69− NoReqBody jsonResponse (header "Foo" "bar" <> header "Baz" "quux")
83+ (header "Foo" "bar" <> header "Baz" "quux")
70− stripFunnyHeaders (responseBody r) `shouldBe` object
84+ stripFunnyHeaders (responseBody r)
85+ `shouldBe` object
71− [ "headers" .= object
86+ [ "headers"
87+ .= object
72− [ "Accept-Encoding" .= ("gzip" :: Text)
88+ [ "Accept-Encoding" .= ("gzip" :: Text),
73− , "Foo" .= ("bar" :: Text)
89+ "Foo" .= ("bar" :: Text),
74− , "Baz" .= ("quux" :: Text)
90+ "Baz" .= ("quux" :: Text),
75− , "Host" .= ("httpbin.org" :: Text) ] ]
91+ "Host" .= ("httpbin.org" :: Text)
92+ ]
93+ ]
76− responseStatusCode r `shouldBe` 200
94+ responseStatusCode r `shouldBe` 200
77 responseStatusMessage r `shouldBe` "OK"
95 responseStatusMessage r `shouldBe` "OK"
78
96
79− describe "receiving GET data back" $
97+ describe "receiving GET data back"
80− it "works" $ do
98+ $ it "works"
99+ $ do
81 r <- req GET (httpbin /: "get") NoReqBody jsonResponse mempty
100 r <- req GET (httpbin /: "get") NoReqBody jsonResponse mempty
82− (stripFunnyHeaders . stripOrigin) (responseBody r) `shouldBe` object
101+ (stripFunnyHeaders . stripOrigin) (responseBody r)
102+ `shouldBe` object
83− [ "args" .= emptyObject
103+ [ "args" .= emptyObject,
84− , "url" .= ("https://httpbin.org/get" :: Text)
104+ "url" .= ("https://httpbin.org/get" :: Text),
85− , "headers" .= object
105+ "headers"
106+ .= object
86− [ "Accept-Encoding" .= ("gzip" :: Text)
107+ [ "Accept-Encoding" .= ("gzip" :: Text),
87− , "Host" .= ("httpbin.org" :: Text) ] ]
108+ "Host" .= ("httpbin.org" :: Text)
109+ ]
110+ ]
88 responseHeader r "Content-Type" `shouldBe` return "application/json"
111 responseHeader r "Content-Type" `shouldBe` return "application/json"
89− responseStatusCode r `shouldBe` 200
112+ responseStatusCode r `shouldBe` 200
90 responseStatusMessage r `shouldBe` "OK"
113 responseStatusMessage r `shouldBe` "OK"
91
114
92− describe "receiving POST JSON data back" $
115+ describe "receiving POST JSON data back"
93− it "works" $ do
116+ $ it "works"
117+ $ do
94 let text = "foo" :: Text
118 let text = "foo" :: Text
95 reflected = reflectJSON text
119 reflected = reflectJSON text
96 r <- req POST (httpbin /: "post") (ReqBodyJson text) jsonResponse mempty
120 r <- req POST (httpbin /: "post") (ReqBodyJson text) jsonResponse mempty
97− (stripFunnyHeaders . stripOrigin) (responseBody r) `shouldBe` object
121+ (stripFunnyHeaders . stripOrigin) (responseBody r)
122+ `shouldBe` object
98− [ "args" .= emptyObject
123+ [ "args" .= emptyObject,
99− , "json" .= text
124+ "json" .= text,
100− , "data" .= reflected
125+ "data" .= reflected,
101− , "url" .= ("https://httpbin.org/post" :: Text)
126+ "url" .= ("https://httpbin.org/post" :: Text),
102− , "headers" .= object
127+ "headers"
128+ .= object
103− [ "Content-Type" .= ("application/json; charset=utf-8" :: Text)
129+ [ "Content-Type" .= ("application/json; charset=utf-8" :: Text),
104− , "Accept-Encoding" .= ("gzip" :: Text)
130+ "Accept-Encoding" .= ("gzip" :: Text),
105− , "Host" .= ("httpbin.org" :: Text)
131+ "Host" .= ("httpbin.org" :: Text),
106− , "Content-Length" .= show (T.length reflected) ]
132+ "Content-Length" .= show (T.length reflected)
133+ ],
107− , "files" .= emptyObject
134+ "files" .= emptyObject,
108− , "form" .= emptyObject ]
135+ "form" .= emptyObject
136+ ]
109 responseHeader r "Content-Type" `shouldBe` return "application/json"
137 responseHeader r "Content-Type" `shouldBe` return "application/json"
110− responseStatusCode r `shouldBe` 200
138+ responseStatusCode r `shouldBe` 200
111 responseStatusMessage r `shouldBe` "OK"
139 responseStatusMessage r `shouldBe` "OK"
112
140
113− describe "receiving POST data back (multipart form data)" $
141+ describe "receiving POST data back (multipart form data)"
114− it "works" $ do
142+ $ it "works"
143+ $ do
144+ body <-
115− body <- reqBodyMultipart
145+ reqBodyMultipart
116− [ LM.partBS "foo" "foo data!"
146+ [ LM.partBS "foo" "foo data!",
117− , LM.partBS "bar" "bar data!" ]
147+ LM.partBS "bar" "bar data!"
148+ ]
118 r <- req POST (httpbin /: "post") body jsonResponse mempty
149 r <- req POST (httpbin /: "post") body jsonResponse mempty
119 let Just contentType = getRequestContentType body
150 let Just contentType = getRequestContentType body
120− (stripFunnyHeaders . stripOrigin) (responseBody r) `shouldBe` object
151+ (stripFunnyHeaders . stripOrigin) (responseBody r)
152+ `shouldBe` object
121− [ "args" .= emptyObject
153+ [ "args" .= emptyObject,
122− , "json" .= Null
154+ "json" .= Null,
123− , "data" .= ("" :: Text)
155+ "data" .= ("" :: Text),
124− , "url" .= ("https://httpbin.org/post" :: Text)
156+ "url" .= ("https://httpbin.org/post" :: Text),
125− , "headers" .= object
157+ "headers"
158+ .= object
126− [ "Content-Type" .= T.decodeUtf8 contentType
159+ [ "Content-Type" .= T.decodeUtf8 contentType,
127− , "Accept-Encoding" .= ("gzip" :: Text)
160+ "Accept-Encoding" .= ("gzip" :: Text),
128− , "Host" .= ("httpbin.org" :: Text)
161+ "Host" .= ("httpbin.org" :: Text),
129− , "Content-Length" .= ("242" :: Text)
162+ "Content-Length" .= ("242" :: Text)
163+ ],
164+ "files" .= emptyObject,
165+ "form"
166+ .= object
167+ [ "foo" .= ("foo data!" :: Text),
168+ "bar" .= ("bar data!" :: Text)
169+ ]
130 ]
170 ]
131− , "files" .= emptyObject
132− , "form" .= object
133− [ "foo" .= ("foo data!" :: Text)
134− , "bar" .= ("bar data!" :: Text) ]
135− ]
136 responseHeader r "Content-Type" `shouldBe` return "application/json"
171 responseHeader r "Content-Type" `shouldBe` return "application/json"
137− responseStatusCode r `shouldBe` 200
172+ responseStatusCode r `shouldBe` 200
138 responseStatusMessage r `shouldBe` "OK"
173 responseStatusMessage r `shouldBe` "OK"
139
174
140− describe "receiving PATCHed file back" $
175+ describe "receiving PATCHed file back"
141− it "works" $ do
176+ $ it "works"
177+ $ do
142 let file :: FilePath
178 let file :: FilePath
143 file = "httpbin-data/robots.txt"
179 file = "httpbin-data/robots.txt"
144 contents <- TIO.readFile file
180 contents <- TIO.readFile file
145 r <- req PATCH (httpbin /: "patch") (ReqBodyFile file) jsonResponse mempty
181 r <- req PATCH (httpbin /: "patch") (ReqBodyFile file) jsonResponse mempty
146− (stripFunnyHeaders . stripOrigin) (responseBody r) `shouldBe` object
182+ (stripFunnyHeaders . stripOrigin) (responseBody r)
183+ `shouldBe` object
147− [ "args" .= emptyObject
184+ [ "args" .= emptyObject,
148− , "json" .= Null
185+ "json" .= Null,
149− , "data" .= contents
186+ "data" .= contents,
150− , "url" .= ("https://httpbin.org/patch" :: Text)
187+ "url" .= ("https://httpbin.org/patch" :: Text),
151− , "headers" .= object
188+ "headers"
189+ .= object
152− [ "Accept-Encoding" .= ("gzip" :: Text)
190+ [ "Accept-Encoding" .= ("gzip" :: Text),
153− , "Host" .= ("httpbin.org" :: Text)
191+ "Host" .= ("httpbin.org" :: Text),
154− , "Content-Length" .= show (T.length contents) ]
192+ "Content-Length" .= show (T.length contents)
193+ ],
155− , "files" .= emptyObject
194+ "files" .= emptyObject,
156− , "form" .= emptyObject ]
195+ "form" .= emptyObject
196+ ]
157 responseHeader r "Content-Type" `shouldBe` return "application/json"
197 responseHeader r "Content-Type" `shouldBe` return "application/json"
158− responseStatusCode r `shouldBe` 200
198+ responseStatusCode r `shouldBe` 200
159 responseStatusMessage r `shouldBe` "OK"
199 responseStatusMessage r `shouldBe` "OK"
160
200
161− describe "receiving PUT form URL-encoded data back" $
201+ describe "receiving PUT form URL-encoded data back"
162− it "works" $ do
202+ $ it "works"
203+ $ do
204+ let params =
163− let params = "foo" =: ("bar" :: Text) <>
205+ "foo" =: ("bar" :: Text)
164− "baz" =: (5 :: Int) <>
206+ <> "baz" =: (5 :: Int)
165− queryFlag "quux"
207+ <> queryFlag "quux"
166 r <- req PUT (httpbin /: "put") (ReqBodyUrlEnc params) jsonResponse mempty
208 r <- req PUT (httpbin /: "put") (ReqBodyUrlEnc params) jsonResponse mempty
167− (stripFunnyHeaders . stripOrigin) (responseBody r) `shouldBe` object
209+ (stripFunnyHeaders . stripOrigin) (responseBody r)
210+ `shouldBe` object
168− [ "args" .= emptyObject
211+ [ "args" .= emptyObject,
169− , "json" .= Null
212+ "json" .= Null,
170− , "data" .= ("" :: Text)
213+ "data" .= ("" :: Text),
171− , "url" .= ("https://httpbin.org/put" :: Text)
214+ "url" .= ("https://httpbin.org/put" :: Text),
172− , "headers" .= object
215+ "headers"
216+ .= object
173− [ "Content-Type" .= ("application/x-www-form-urlencoded" :: Text)
217+ [ "Content-Type" .= ("application/x-www-form-urlencoded" :: Text),
174− , "Accept-Encoding" .= ("gzip" :: Text)
218+ "Accept-Encoding" .= ("gzip" :: Text),
175− , "Host" .= ("httpbin.org" :: Text)
219+ "Host" .= ("httpbin.org" :: Text),
176− , "Content-Length" .= ("18" :: Text) ]
220+ "Content-Length" .= ("18" :: Text)
221+ ],
177− , "files" .= emptyObject
222+ "files" .= emptyObject,
223+ "form"
178− , "form" .= object
224+ .= object
179− [ "foo" .= ("bar" :: Text)
225+ [ "foo" .= ("bar" :: Text),
180− , "baz" .= ("5" :: Text)
226+ "baz" .= ("5" :: Text),
181− , "quux" .= ("" :: Text) ] ]
227+ "quux" .= ("" :: Text)
228+ ]
229+ ]
182 responseHeader r "Content-Type" `shouldBe` return "application/json"
230 responseHeader r "Content-Type" `shouldBe` return "application/json"
183− responseStatusCode r `shouldBe` 200
231+ responseStatusCode r `shouldBe` 200
184 responseStatusMessage r `shouldBe` "OK"
232 responseStatusMessage r `shouldBe` "OK"
185
233
186 -- TODO /delete
234 -- TODO /delete
187
235
188− describe "receiving UTF-8 encoded Unicode data" $
236+ describe "receiving UTF-8 encoded Unicode data"
189− it "works" $ do
237+ $ it "works"
238+ $ do
239+ r <-
240+ req
241+ GET
190− r <- req GET (httpbin /: "encoding" /: "utf8")
242+ (httpbin /: "encoding" /: "utf8")
243+ NoReqBody
191− NoReqBody bsResponse mempty
244+ bsResponse
245+ mempty
192 utf8data <- B.readFile "httpbin-data/utf8.html"
246 utf8data <- B.readFile "httpbin-data/utf8.html"
193− responseBody r `shouldBe` utf8data
247+ responseBody r `shouldBe` utf8data
194− responseStatusCode r `shouldBe` 200
248+ responseStatusCode r `shouldBe` 200
195 responseStatusMessage r `shouldBe` "OK"
249 responseStatusMessage r `shouldBe` "OK"
196
250
197 -- TODO /gzip
251 -- TODO /gzip
198 -- TODO /deflate
252 -- TODO /deflate
199
253
200− describe "retrying" $
254+ describe "retrying"
201− it "retries as many times as specified" $ do
255+ $ it "retries as many times as specified"
256+ $ do
202 -- FIXME We no longer can count retries because all the functions
257 -- FIXME We no longer can count retries because all the functions
203 -- responsible for controlling retrying are pure now.
258 -- responsible for controlling retrying are pure now.
204 let status = 408 :: Int
259 let status = 408 :: Int
260+ r <-
261+ prepareForShit $
262+ req
263+ GET
205− r <- prepareForShit $ req GET (httpbin /: "status" /~ status)
264+ (httpbin /: "status" /~ status)
265+ NoReqBody
206− NoReqBody ignoreResponse mempty
266+ ignoreResponse
267+ mempty
207 responseStatusCode r `shouldBe` status
268 responseStatusCode r `shouldBe` status
208
269
209 -- forM_ [101..102] checkStatusCode
270 -- forM_ [101..102] checkStatusCode
210− forM_ [200..208] checkStatusCode
271+ forM_ [200 .. 208] checkStatusCode
211 -- forM_ [300..308] checkStatusCode
272 -- forM_ [300..308] checkStatusCode
212− forM_ [400..431] checkStatusCode
273+ forM_ [400 .. 431] checkStatusCode
213− forM_ [500..511] checkStatusCode
274+ forM_ [500 .. 511] checkStatusCode
214
275
215 -- TODO /response-headers
276 -- TODO /response-headers
216 -- TODO /redirect
277 -- TODO /redirect
217
278
218− describe "redirects" $
279+ describe "redirects"
219− it "follows redirects" $ do
280+ $ it "follows redirects"
281+ $ do
282+ r <-
283+ req
284+ GET
220− r <- req GET (httpbin /: "redirect-to") NoReqBody ignoreResponse
285+ (httpbin /: "redirect-to")
286+ NoReqBody
287+ ignoreResponse
221− ("url" =: ("https://httpbin.org" :: Text))
288+ ("url" =: ("https://httpbin.org" :: Text))
222− responseStatusCode r `shouldBe` 200
289+ responseStatusCode r `shouldBe` 200
223 responseStatusMessage r `shouldBe` "OK"
290 responseStatusMessage r `shouldBe` "OK"
224
291
225 -- TODO /relative-redicet
292 -- TODO /relative-redicet
@@ -228,21 +295,32 @@ spec = do
228
295
229 describe "basicAuth" $ do
296 describe "basicAuth" $ do
230 let user, password :: Text
297 let user, password :: Text
231− user = "Scooby"
298+ user = "Scooby"
232 password = "Doo"
299 password = "Doo"
233− context "when we do not send appropriate basic auth data" $
300+ context "when we do not send appropriate basic auth data"
234− it "fails with 401" $ do
301+ $ it "fails with 401"
302+ $ do
303+ r <-
235− r <- prepareForShit $ req GET
304+ prepareForShit $
305+ req
306+ GET
236− (httpbin /: "basic-auth" /~ user /~ password)
307+ (httpbin /: "basic-auth" /~ user /~ password)
308+ NoReqBody
237− NoReqBody ignoreResponse mempty
309+ ignoreResponse
310+ mempty
238− responseStatusCode r `shouldBe` 401
311+ responseStatusCode r `shouldBe` 401
239 responseStatusMessage r `shouldBe` "UNAUTHORIZED"
312 responseStatusMessage r `shouldBe` "UNAUTHORIZED"
240− context "when we provide appropriate basic auth data" $
313+ context "when we provide appropriate basic auth data"
241− it "succeeds" $ do
314+ $ it "succeeds"
315+ $ do
316+ r <-
317+ req
318+ GET
242− r <- req GET (httpbin /: "basic-auth" /~ user /~ password)
319+ (httpbin /: "basic-auth" /~ user /~ password)
320+ NoReqBody
243− NoReqBody ignoreResponse
321+ ignoreResponse
244− (basicAuth (T.encodeUtf8 user) (T.encodeUtf8 password))
322+ (basicAuth (T.encodeUtf8 user) (T.encodeUtf8 password))
245− responseStatusCode r `shouldBe` 200
323+ responseStatusCode r `shouldBe` 200
246 responseStatusMessage r `shouldBe` "OK"
324 responseStatusMessage r `shouldBe` "OK"
247
325
248 -- TODO /hidden-basic-auth
326 -- TODO /hidden-basic-auth
@@ -253,57 +331,75 @@ spec = do
253 -- TODO /range
331 -- TODO /range
254 -- TODO /html
332 -- TODO /html
255
333
256− describe "robots.txt" $
334+ describe "robots.txt"
257− it "works" $ do
335+ $ it "works"
336+ $ do
258 r <- req GET (httpbin /: "robots.txt") NoReqBody bsResponse mempty
337 r <- req GET (httpbin /: "robots.txt") NoReqBody bsResponse mempty
259 robots <- B.readFile "httpbin-data/robots.txt"
338 robots <- B.readFile "httpbin-data/robots.txt"
260− responseBody r `shouldBe` robots
339+ responseBody r `shouldBe` robots
261− responseStatusCode r `shouldBe` 200
340+ responseStatusCode r `shouldBe` 200
262 responseStatusMessage r `shouldBe` "OK"
341 responseStatusMessage r `shouldBe` "OK"
263
342
264 -- TODO /deny
343 -- TODO /deny
265 -- TODO /cache
344 -- TODO /cache
266
345
267 describe "getting random bytes" $ do
346 describe "getting random bytes" $ do
268− it "works" $
347+ it "works"
269− property $ \n' -> do
348+ $ property
349+ $ \n' -> do
270 let n :: Word
350 let n :: Word
271 n = getSmall n'
351 n = getSmall n'
352+ r <-
353+ req
354+ GET
272− r <- req GET (httpbin /: "bytes" /~ n)
355+ (httpbin /: "bytes" /~ n)
356+ NoReqBody
273− NoReqBody lbsResponse mempty
357+ lbsResponse
358+ mempty
274 responseBody r `shouldSatisfy` ((== n) . fromIntegral . BL.length)
359 responseBody r `shouldSatisfy` ((== n) . fromIntegral . BL.length)
275− responseStatusCode r `shouldBe` 200
360+ responseStatusCode r `shouldBe` 200
276 responseStatusMessage r `shouldBe` "OK"
361 responseStatusMessage r `shouldBe` "OK"
277− context "when we try to interpret 1000 random bytes as JSON" $
362+ context "when we try to interpret 1000 random bytes as JSON"
278− it "throws correct exception" $ do
363+ $ it "throws correct exception"
364+ $ do
279 let selector :: HttpException -> Bool
365 let selector :: HttpException -> Bool
280 selector (JsonHttpException _) = True
366 selector (JsonHttpException _) = True
281 selector _ = False
367 selector _ = False
282 n :: Int
368 n :: Int
283 n = 1000
369 n = 1000
370+ req
371+ GET
284− req GET (httpbin /: "bytes" /~ n) NoReqBody
372+ (httpbin /: "bytes" /~ n)
373+ NoReqBody
285− (Proxy :: Proxy (JsonResponse Value)) mempty
374+ (Proxy :: Proxy (JsonResponse Value))
375+ mempty
286− `shouldThrow` selector
376+ `shouldThrow` selector
287−
377+
288− describe "streaming random bytes" $
378+ describe "streaming random bytes"
289− it "works" $
379+ $ it "works"
290− property $ \n' -> do
380+ $ property
381+ $ \n' -> do
291− let n :: Word
382+ let n :: Word
292− n = getSmall n'
383+ n = getSmall n'
384+ r <-
385+ req
386+ GET
293− r <- req GET (httpbin /: "stream-bytes" /~ n)
387+ (httpbin /: "stream-bytes" /~ n)
388+ NoReqBody
294− NoReqBody bsResponse mempty
389+ bsResponse
390+ mempty
295− responseBody r `shouldSatisfy` ((== n) . fromIntegral . B.length)
391+ responseBody r `shouldSatisfy` ((== n) . fromIntegral . B.length)
296− responseStatusCode r `shouldBe` 200
392+ responseStatusCode r `shouldBe` 200
297− responseStatusMessage r `shouldBe` "OK"
393+ responseStatusMessage r `shouldBe` "OK"
298
394
299− -- TODO /links
395+-- TODO /links
300− -- TODO /image
396+-- TODO /image
301− -- TODO /image/png
397+-- TODO /image/png
302− -- TODO /image/jpeg
398+-- TODO /image/jpeg
303− -- TODO /image/webp
399+-- TODO /image/webp
304− -- TODO /image/svg
400+-- TODO /image/svg
305− -- TODO /forms/post
401+-- TODO /forms/post
306− -- TODO /xml
402+-- TODO /xml
307
403
308----------------------------------------------------------------------------
404----------------------------------------------------------------------------
309-- Instances
405-- Instances
@@ -316,78 +412,81 @@ instance MonadHttp IO where
316
412
317-- | Run request with such settings that it does not signal error on adverse
413-- | Run request with such settings that it does not signal error on adverse
318-- response status codes.
414-- response status codes.
319−
320prepareForShit :: Req a -> IO a
415prepareForShit :: Req a -> IO a
321−prepareForShit = runReq defaultHttpConfig { httpConfigCheckResponse = noNoise }
416+prepareForShit = runReq defaultHttpConfig {httpConfigCheckResponse = noNoise}
322 where
417 where
323 noNoise _ _ _ = Nothing
418 noNoise _ _ _ = Nothing
324
419
325-- | Run request with such settings that it throws on any response.
420-- | Run request with such settings that it throws on any response.
326−
327blindlyThrowing :: Req a -> IO a
421blindlyThrowing :: Req a -> IO a
328−blindlyThrowing = runReq defaultHttpConfig { httpConfigCheckResponse = doit }
422+blindlyThrowing = runReq defaultHttpConfig {httpConfigCheckResponse = doit}
329 where
423 where
330 doit _ _ = error "Oops!"
424 doit _ _ = error "Oops!"
331
425
332-- | 'Url' representing <https://httpbin.org>.
426-- | 'Url' representing <https://httpbin.org>.
333−
334httpbin :: Url 'Https
427httpbin :: Url 'Https
335httpbin = https "httpbin.org"
428httpbin = https "httpbin.org"
336
429
337-- | Remove “origin” field from JSON value. Origin may change, we don't want
430-- | Remove “origin” field from JSON value. Origin may change, we don't want
338-- to depend on that.
431-- to depend on that.
339−
340stripOrigin :: Value -> Value
432stripOrigin :: Value -> Value
341stripOrigin (Object m) = Object (HM.delete "origin" m)
433stripOrigin (Object m) = Object (HM.delete "origin" m)
342−stripOrigin value = value
434+stripOrigin value = value
343
435
344-- | Remove funny headers that might break the tests.
436-- | Remove funny headers that might break the tests.
345−
346stripFunnyHeaders :: Value -> Value
437stripFunnyHeaders :: Value -> Value
347stripFunnyHeaders (Object m) =
438stripFunnyHeaders (Object m) =
348 let f (Object p) = Object $ HM.filterWithKey (\k _ -> k `elem` hs) p
439 let f (Object p) = Object $ HM.filterWithKey (\k _ -> k `elem` hs) p
349− f value = value
440+ f value = value
441+ hs =
350− hs = [ "Content-Type"
442+ [ "Content-Type",
351− , "Accept-Encoding"
443+ "Accept-Encoding",
352− , "Host"
444+ "Host",
353− , "Content-Length"
445+ "Content-Length",
354− , "Foo"
446+ "Foo",
355− , "Baz" ]
447+ "Baz"
448+ ]
356− in Object (HM.adjust f "headers" m)
449+ in Object (HM.adjust f "headers" m)
357stripFunnyHeaders value = value
450stripFunnyHeaders value = value
358
451
359-- | This is a complete test case that makes use of <https://httpbin.org> to
452-- | This is a complete test case that makes use of <https://httpbin.org> to
360-- get various response status codes.
453-- get various response status codes.
361−
362checkStatusCode :: Int -> SpecWith ()
454checkStatusCode :: Int -> SpecWith ()
363checkStatusCode code =
455checkStatusCode code =
364− describe ("receiving status code " ++ show code) $
456+ describe ("receiving status code " ++ show code)
365− it "works" $ do
457+ $ it "works"
458+ $ do
459+ r <-
460+ prepareForShit $
461+ req
462+ GET
366− r <- prepareForShit $ req GET (httpbin /: "status" /~ code)
463+ (httpbin /: "status" /~ code)
464+ NoReqBody
367− NoReqBody ignoreResponse mempty
465+ ignoreResponse
466+ mempty
368 responseStatusCode r `shouldBe` code
467 responseStatusCode r `shouldBe` code
369
468
370-- | Exception selector that selects only 404 “Not found” exceptions.
469-- | Exception selector that selects only 404 “Not found” exceptions.
371−
372selector404 :: HttpException -> Bool
470selector404 :: HttpException -> Bool
471+selector404
373−selector404 (VanillaHttpException
472+ ( VanillaHttpException
374− (L.HttpExceptionRequest _
473+ ( L.HttpExceptionRequest
474+ _
375− (L.StatusCodeException response chunk))) =
475+ (L.StatusCodeException response chunk)
476+ )
477+ ) =
376− L.responseStatus response == Y.status404 && not (B.null chunk)
478+ L.responseStatus response == Y.status404 && not (B.null chunk)
377selector404 _ = False
479selector404 _ = False
378
480
379-- | Empty JSON 'Object'.
481-- | Empty JSON 'Object'.
380−
381emptyObject :: Value
482emptyObject :: Value
382emptyObject = Object HM.empty
483emptyObject = Object HM.empty
383
484
384-- | Get rendered JSON value as 'Text'.
485-- | Get rendered JSON value as 'Text'.
385−
386reflectJSON :: ToJSON a => a -> Text
486reflectJSON :: ToJSON a => a -> Text
387reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
487reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
388
488
389-- | Clarify to the type checker that the inner computation is in the 'IO'
489-- | Clarify to the type checker that the inner computation is in the 'IO'
390-- monad.
490-- monad.
391−
392asIO :: IO a -> IO a
491asIO :: IO a -> IO a
393asIO = id
492asIO = id
pure-tests/Network/HTTP/ReqSpec.hs+345−265
@@ -1,50 +1,51 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DataKinds #-}
2+{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE DeriveGeneric #-}
4−{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE FlexibleInstances #-}
5−{-# LANGUAGE LambdaCase #-}
5+{-# LANGUAGE LambdaCase #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE QuasiQuotes #-}
8+{-# LANGUAGE RankNTypes #-}
9+{-# LANGUAGE RecordWildCards #-}
10+{-# LANGUAGE ScopedTypeVariables #-}
6{-# LANGUAGE NoMonomorphismRestriction #-}
11{-# LANGUAGE NoMonomorphismRestriction #-}
7−{-# LANGUAGE OverloadedStrings #-}
8−{-# LANGUAGE QuasiQuotes #-}
9−{-# LANGUAGE RankNTypes #-}
10−{-# LANGUAGE RecordWildCards #-}
11−{-# LANGUAGE ScopedTypeVariables #-}
12−{-# OPTIONS_GHC -fno-warn-orphans #-}
12+{-# OPTIONS_GHC -fno-warn-orphans #-}
13
13
14module Network.HTTP.ReqSpec
14module Network.HTTP.ReqSpec
15− ( spec )
15+ ( spec,
16+ )
16where
17where
17
18
19+import qualified Blaze.ByteString.Builder as BB
18import Control.Exception (throwIO)
20import Control.Exception (throwIO)
19import Control.Monad
21import Control.Monad
20import Control.Retry
22import Control.Retry
21import Data.Aeson (ToJSON (..))
23import Data.Aeson (ToJSON (..))
24+import qualified Data.Aeson as A
22import Data.ByteString (ByteString)
25import Data.ByteString (ByteString)
26+import qualified Data.ByteString as B
27+import qualified Data.ByteString.Char8 as B8
28+import qualified Data.ByteString.Lazy as BL
29+import qualified Data.CaseInsensitive as CI
23import Data.Either (isRight)
30import Data.Either (isRight)
31+import qualified Data.List.NonEmpty as NE
24−import Data.Maybe (isNothing, fromJust, fromMaybe)
32+import Data.Maybe (fromJust, fromMaybe, isNothing)
25import Data.Proxy
33import Data.Proxy
26import Data.Text (Text)
34import Data.Text (Text)
35+import qualified Data.Text as T
36+import qualified Data.Text.Encoding as T
27import Data.Time
37import Data.Time
28import GHC.Generics
38import GHC.Generics
39+import qualified Network.HTTP.Client as L
29import Network.HTTP.Req
40import Network.HTTP.Req
41+import qualified Network.HTTP.Types as Y
42+import qualified Network.HTTP.Types.Header as Y
30import Test.Hspec
43import Test.Hspec
31import Test.Hspec.Core.Spec (SpecM)
44import Test.Hspec.Core.Spec (SpecM)
32import Test.QuickCheck
45import Test.QuickCheck
33import Text.URI (URI)
46import Text.URI (URI)
34−import qualified Blaze.ByteString.Builder as BB
47+import qualified Text.URI as URI
35−import qualified Data.Aeson as A
36−import qualified Data.ByteString as B
37−import qualified Data.ByteString.Char8 as B8
38−import qualified Data.ByteString.Lazy as BL
39−import qualified Data.CaseInsensitive as CI
40−import qualified Data.List.NonEmpty as NE
41−import qualified Data.Text as T
42−import qualified Data.Text.Encoding as T
43−import qualified Network.HTTP.Client as L
44−import qualified Network.HTTP.Types as Y
45−import qualified Network.HTTP.Types.Header as Y
46−import qualified Text.URI as URI
47−import qualified Text.URI.QQ as QQ
48+import qualified Text.URI.QQ as QQ
48
49
49#if !MIN_VERSION_base(4,13,0)
50#if !MIN_VERSION_base(4,13,0)
50import Data.Semigroup ((<>))
51import Data.Semigroup ((<>))
@@ -52,25 +53,27 @@ import Data.Semigroup ((<>))
52
53
53spec :: Spec
54spec :: Spec
54spec = do
55spec = do
55−
56− describe "config" $
56+ describe "config"
57− it "getHttpConfig has effect on resulting request" $
57+ $ it "getHttpConfig has effect on resulting request"
58− property $ \config -> do
58+ $ property
59+ $ \config -> do
59− request <- runReq config (req_ GET url NoReqBody mempty)
60+ request <- runReq config (req_ GET url NoReqBody mempty)
60− L.proxy request `shouldBe` httpConfigProxy config
61+ L.proxy request `shouldBe` httpConfigProxy config
61− L.redirectCount request `shouldBe` httpConfigRedirectCount config
62+ L.redirectCount request `shouldBe` httpConfigRedirectCount config
62
63
63 describe "methods" $ do
64 describe "methods" $ do
64− let mnth
65+ let mnth ::
65− :: forall method.
66+ forall method.
66− ( HttpMethod method
67+ ( HttpMethod method,
67− , HttpBodyAllowed (AllowsBody method) 'NoBody )
68+ HttpBodyAllowed (AllowsBody method) 'NoBody
68− => method
69+ ) =>
70+ method ->
69− -> SpecM () ()
71+ SpecM () ()
70 mnth method = do
72 mnth method = do
71 let name = httpMethodName (Proxy :: Proxy method)
73 let name = httpMethodName (Proxy :: Proxy method)
72− describe (B8.unpack name) $
74+ describe (B8.unpack name)
73− it "affects name of HTTP method" $ do
75+ $ it "affects name of HTTP method"
76+ $ do
74 request <- req_ method url NoReqBody mempty
77 request <- req_ method url NoReqBody mempty
75 L.method request `shouldBe` name
78 L.method request `shouldBe` name
76 mnth GET
79 mnth GET
@@ -84,44 +87,50 @@ spec = do
84 mnth PATCH
87 mnth PATCH
85
88
86 describe "urls" $ do
89 describe "urls" $ do
87− describe "http" $
90+ describe "http"
88− it "sets all the params correctly" $
91+ $ it "sets all the params correctly"
92+ $ property
89− property $ \host -> do
93+ $ \host -> do
90− request <- req_ GET (http host) NoReqBody mempty
94+ request <- req_ GET (http host) NoReqBody mempty
91− L.secure request `shouldBe` False
95+ L.secure request `shouldBe` False
92− L.port request `shouldBe` 80
96+ L.port request `shouldBe` 80
93− L.host request `shouldBe` urlEncode host
97+ L.host request `shouldBe` urlEncode host
94− describe "https" $
98+ describe "https"
95− it "sets all the params correctly" $
99+ $ it "sets all the params correctly"
96− property $ \host -> do
100+ $ property
101+ $ \host -> do
97− request <- req_ GET (https host) NoReqBody mempty
102+ request <- req_ GET (https host) NoReqBody mempty
98− L.secure request `shouldBe` True
103+ L.secure request `shouldBe` True
99− L.port request `shouldBe` 443
104+ L.port request `shouldBe` 443
100− L.host request `shouldBe` urlEncode host
105+ L.host request `shouldBe` urlEncode host
101− describe "(/~)" $
106+ describe "(/~)"
102− it "attaches a path piece that is URL-encoded" $
107+ $ it "attaches a path piece that is URL-encoded"
108+ $ property
103− property $ \host pieces -> do
109+ $ \host pieces -> do
104− let url' = foldl (/~) (https host) pieces
110+ let url' = foldl (/~) (https host) pieces
105− request <- req_ GET url' NoReqBody mempty
111+ request <- req_ GET url' NoReqBody mempty
106− L.host request `shouldBe` urlEncode host
112+ L.host request `shouldBe` urlEncode host
107− L.path request `shouldBe` encodePathPieces pieces
113+ L.path request `shouldBe` encodePathPieces pieces
108− describe "(/:)" $
114+ describe "(/:)"
109− it "attaches a path piece that is URL-encoded" $
115+ $ it "attaches a path piece that is URL-encoded"
116+ $ property
110− property $ \host pieces -> do
117+ $ \host pieces -> do
111− let url' = foldl (/:) (https host) pieces
118+ let url' = foldl (/:) (https host) pieces
112− request <- req_ GET url' NoReqBody mempty
119+ request <- req_ GET url' NoReqBody mempty
113− L.host request `shouldBe` urlEncode host
120+ L.host request `shouldBe` urlEncode host
114− L.path request `shouldBe` encodePathPieces pieces
121+ L.path request `shouldBe` encodePathPieces pieces
115
122
116 describe "useHttpURI" $ do
123 describe "useHttpURI" $ do
117− it "does not recognize non-http schemes" $
124+ it "does not recognize non-http schemes"
118− property $ \uri ->
125+ $ property
126+ $ \uri ->
119 when (URI.uriScheme uri /= Just [QQ.scheme|http|]) $
127 when (URI.uriScheme uri /= Just [QQ.scheme|http|]) $
120 useHttpURI uri `shouldSatisfy` isNothing
128 useHttpURI uri `shouldSatisfy` isNothing
121− it "accepts correct URLs" $
129+ it "accepts correct URLs"
122− property $ \uri' -> do
130+ $ property
131+ $ \uri' -> do
123 unless (isRight (URI.uriAuthority uri')) discard
132 unless (isRight (URI.uriAuthority uri')) discard
124− let uri = uri' { URI.uriScheme = Just [QQ.scheme|http|] }
133+ let uri = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
125 (url', options) = fromJust (useHttpURI uri)
134 (url', options) = fromJust (useHttpURI uri)
126 request <- req_ GET url' NoReqBody options
135 request <- req_ GET url' NoReqBody options
127 L.host request `shouldBe` uriHost uri
136 L.host request `shouldBe` uriHost uri
@@ -131,14 +140,16 @@ spec = do
131 lookup "Authorization" (L.requestHeaders request)
140 lookup "Authorization" (L.requestHeaders request)
132 `shouldBe` uriBasicAuth uri
141 `shouldBe` uriBasicAuth uri
133 describe "useHttpsURI" $ do
142 describe "useHttpsURI" $ do
134− it "does not recognize non-https schemes" $
143+ it "does not recognize non-https schemes"
135− property $ \uri ->
144+ $ property
145+ $ \uri ->
136 when (URI.uriScheme uri /= Just [QQ.scheme|https|]) $
146 when (URI.uriScheme uri /= Just [QQ.scheme|https|]) $
137 useHttpsURI uri `shouldSatisfy` isNothing
147 useHttpsURI uri `shouldSatisfy` isNothing
138− it "parses correct URLs" $
148+ it "parses correct URLs"
139− property $ \uri' -> do
149+ $ property
150+ $ \uri' -> do
140 unless (isRight (URI.uriAuthority uri')) discard
151 unless (isRight (URI.uriAuthority uri')) discard
141− let uri = uri' { URI.uriScheme = Just [QQ.scheme|https|] }
152+ let uri = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
142 (url', options) = fromJust (useHttpsURI uri)
153 (url', options) = fromJust (useHttpsURI uri)
143 request <- req_ GET url' NoReqBody options
154 request <- req_ GET url' NoReqBody options
144 L.host request `shouldBe` uriHost uri
155 L.host request `shouldBe` uriHost uri
@@ -148,22 +159,27 @@ spec = do
148 lookup "Authorization" (L.requestHeaders request)
159 lookup "Authorization" (L.requestHeaders request)
149 `shouldBe` uriBasicAuth uri
160 `shouldBe` uriBasicAuth uri
150 describe "useURI" $ do
161 describe "useURI" $ do
151− it "does not recognize non-http and non-https schemes" $
162+ it "does not recognize non-http and non-https schemes"
163+ $ property
152− property $ \uri ->
164+ $ \uri ->
165+ when
153− when ((URI.uriScheme uri /= Just [QQ.scheme|http|] &&
166+ ( ( URI.uriScheme uri /= Just [QQ.scheme|http|]
154− (URI.uriScheme uri /= Just [QQ.scheme|https|]))) $
167+ && (URI.uriScheme uri /= Just [QQ.scheme|https|])
168+ )
169+ )
155− useURI uri `shouldSatisfy` isNothing
170+ $ useURI uri `shouldSatisfy` isNothing
156− it "parses correct URLs" $
171+ it "parses correct URLs"
172+ $ property
157− property $ \uri' -> do
173+ $ \uri' -> do
158 unless (isRight (URI.uriAuthority uri')) discard
174 unless (isRight (URI.uriAuthority uri')) discard
159− let uriHttp = uri' { URI.uriScheme = Just [QQ.scheme|http|] }
175+ let uriHttp = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
160− uriHttps = uri' { URI.uriScheme = Just [QQ.scheme|https|] }
176+ uriHttps = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
161 requestHttp <-
177 requestHttp <-
162 let Left (url', options) = fromJust (useURI uriHttp)
178 let Left (url', options) = fromJust (useURI uriHttp)
163− in req_ GET url' NoReqBody options
179+ in req_ GET url' NoReqBody options
164 requestHttps <-
180 requestHttps <-
165 let Right (url', options) = fromJust (useURI uriHttps)
181 let Right (url', options) = fromJust (useURI uriHttps)
166− in req_ GET url' NoReqBody options
182+ in req_ GET url' NoReqBody options
167 L.host requestHttp `shouldBe` uriHost uriHttp
183 L.host requestHttp `shouldBe` uriHost uriHttp
168 L.host requestHttps `shouldBe` uriHost uriHttps
184 L.host requestHttps `shouldBe` uriHost uriHttps
169 L.port requestHttp `shouldBe` uriPort 80 uriHttp
185 L.port requestHttp `shouldBe` uriPort 80 uriHttp
@@ -178,153 +194,219 @@ spec = do
178 `shouldBe` uriBasicAuth uriHttps
194 `shouldBe` uriBasicAuth uriHttps
179
195
180 describe "bodies" $ do
196 describe "bodies" $ do
181− describe "NoReqBody" $
197+ describe "NoReqBody"
182− it "sets body to empty byte string" $ do
198+ $ it "sets body to empty byte string"
199+ $ do
183 request <- req_ POST url NoReqBody mempty
200 request <- req_ POST url NoReqBody mempty
184 case L.requestBody request of
201 case L.requestBody request of
185 L.RequestBodyBS x -> x `shouldBe` B.empty
202 L.RequestBodyBS x -> x `shouldBe` B.empty
186 _ -> expectationFailure "Wrong request body constructor."
203 _ -> expectationFailure "Wrong request body constructor."
187− describe "ReqBodyJson" $
204+ describe "ReqBodyJson"
188− it "sets body to correct lazy byte string" $
205+ $ it "sets body to correct lazy byte string"
206+ $ property
189− property $ \thing -> do
207+ $ \thing -> do
190− request <- req_ POST url (ReqBodyJson thing) mempty
208+ request <- req_ POST url (ReqBodyJson thing) mempty
191− case L.requestBody request of
209+ case L.requestBody request of
192− L.RequestBodyLBS x -> x `shouldBe` A.encode (thing :: Thing)
210+ L.RequestBodyLBS x -> x `shouldBe` A.encode (thing :: Thing)
193− _ -> expectationFailure "Wrong request body constructor."
211+ _ -> expectationFailure "Wrong request body constructor."
194− describe "ReqBodyBs" $
212+ describe "ReqBodyBs"
195− it "sets body to specified strict byte string" $
213+ $ it "sets body to specified strict byte string"
214+ $ property
196− property $ \bs -> do
215+ $ \bs -> do
197− request <- req_ POST url (ReqBodyBs bs) mempty
216+ request <- req_ POST url (ReqBodyBs bs) mempty
198− case L.requestBody request of
217+ case L.requestBody request of
199− L.RequestBodyBS x -> x `shouldBe` bs
218+ L.RequestBodyBS x -> x `shouldBe` bs
200− _ -> expectationFailure "Wrong request body constructor."
219+ _ -> expectationFailure "Wrong request body constructor."
201− describe "ReqBodyLbs" $
220+ describe "ReqBodyLbs"
202− it "sets body to specified lazy byte string" $
221+ $ it "sets body to specified lazy byte string"
222+ $ property
203− property $ \lbs -> do
223+ $ \lbs -> do
204− request <- req_ POST url (ReqBodyLbs lbs) mempty
224+ request <- req_ POST url (ReqBodyLbs lbs) mempty
205− case L.requestBody request of
225+ case L.requestBody request of
206− L.RequestBodyLBS x -> x `shouldBe` lbs
226+ L.RequestBodyLBS x -> x `shouldBe` lbs
207− _ -> expectationFailure "Wrong request body constructor."
227+ _ -> expectationFailure "Wrong request body constructor."
208− describe "ReqBodyUrlEnc" $
228+ describe "ReqBodyUrlEnc"
209− it "sets body to correct lazy byte string" $
229+ $ it "sets body to correct lazy byte string"
230+ $ property
210− property $ \params -> do
231+ $ \params -> do
211− request <- req_ POST url (ReqBodyUrlEnc (formUrlEnc params)) mempty
232+ request <- req_ POST url (ReqBodyUrlEnc (formUrlEnc params)) mempty
212− case L.requestBody request of
233+ case L.requestBody request of
213− L.RequestBodyLBS x -> x `shouldBe` renderQuery params
234+ L.RequestBodyLBS x -> x `shouldBe` renderQuery params
214− _ -> expectationFailure "Wrong request body constructor."
235+ _ -> expectationFailure "Wrong request body constructor."
215
236
216 describe "optional parameters" $ do
237 describe "optional parameters" $ do
217 describe "header" $ do
238 describe "header" $ do
218− it "sets specified header value" $
239+ it "sets specified header value"
240+ $ property
219− property $ \name value -> do
241+ $ \name value -> do
220 request <- req_ GET url NoReqBody (header name value)
242 request <- req_ GET url NoReqBody (header name value)
221 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value
243 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value
222− it "left header wins" $
244+ it "left header wins"
245+ $ property
223− property $ \name value0 value1 -> do
246+ $ \name value0 value1 -> do
247+ request <-
248+ req_
249+ GET
250+ url
224− request <- req_ GET url NoReqBody
251+ NoReqBody
225− (header name value0 <> header name value1)
252+ (header name value0 <> header name value1)
226 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value0
253 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value0
227− it "overwrites headers set by other parts of the lib" $
254+ it "overwrites headers set by other parts of the lib"
228− property $ \value -> do
255+ $ property
256+ $ \value -> do
257+ request <-
258+ req_
259+ POST
260+ url
229− request <- req_ POST url (ReqBodyUrlEnc mempty)
261+ (ReqBodyUrlEnc mempty)
230− (header "Content-Type" value)
262+ (header "Content-Type" value)
231 lookup "Content-Type" (L.requestHeaders request) `shouldBe` pure value
263 lookup "Content-Type" (L.requestHeaders request) `shouldBe` pure value
232− describe "cookieJar" $
264+ describe "cookieJar"
233− it "cookie jar is set without modifications" $
265+ $ it "cookie jar is set without modifications"
234− property $ \cjar -> do
266+ $ property
267+ $ \cjar -> do
235− request <- req_ GET url NoReqBody (cookieJar cjar)
268+ request <- req_ GET url NoReqBody (cookieJar cjar)
236− L.cookieJar request `shouldBe` pure cjar
269+ L.cookieJar request `shouldBe` pure cjar
237 describe "basicAuth" $ do
270 describe "basicAuth" $ do
238− it "sets Authorization header to correct value" $
271+ it "sets Authorization header to correct value"
272+ $ property
239− property $ \username password -> do
273+ $ \username password -> do
240 request <- req_ GET url NoReqBody (basicAuth username password)
274 request <- req_ GET url NoReqBody (basicAuth username password)
241− lookup "Authorization" (L.requestHeaders request) `shouldBe`
275+ lookup "Authorization" (L.requestHeaders request)
242− Just (basicAuthHeader username password)
276+ `shouldBe` Just (basicAuthHeader username password)
243− it "overwrites manual setting of header" $
277+ it "overwrites manual setting of header"
278+ $ property
244− property $ \username password value -> do
279+ $ \username password value -> do
280+ request0 <-
281+ req_
282+ GET
283+ url
245− request0 <- req_ GET url NoReqBody
284+ NoReqBody
246− (basicAuth username password <> header "Authorization" value)
285+ (basicAuth username password <> header "Authorization" value)
286+ request1 <-
287+ req_
288+ GET
289+ url
247− request1 <- req_ GET url NoReqBody
290+ NoReqBody
248− (header "Authorization" value <> basicAuth username password)
291+ (header "Authorization" value <> basicAuth username password)
249 let result = Just (basicAuthHeader username password)
292 let result = Just (basicAuthHeader username password)
250 lookup "Authorization" (L.requestHeaders request0) `shouldBe` result
293 lookup "Authorization" (L.requestHeaders request0) `shouldBe` result
251 lookup "Authorization" (L.requestHeaders request1) `shouldBe` result
294 lookup "Authorization" (L.requestHeaders request1) `shouldBe` result
252− it "left auth option wins" $
295+ it "left auth option wins"
296+ $ property
253− property $ \username0 password0 username1 password1 -> do
297+ $ \username0 password0 username1 password1 -> do
298+ request <-
299+ req_
300+ GET
301+ url
254− request <- req_ GET url NoReqBody
302+ NoReqBody
255− (basicAuth username0 password0 <> basicAuth username1 password1)
303+ (basicAuth username0 password0 <> basicAuth username1 password1)
256− lookup "Authorization" (L.requestHeaders request) `shouldBe`
304+ lookup "Authorization" (L.requestHeaders request)
257− Just (basicAuthHeader username0 password0)
305+ `shouldBe` Just (basicAuthHeader username0 password0)
258 describe "oAuth2Bearer" $ do
306 describe "oAuth2Bearer" $ do
259− it "sets Authorization header to correct value" $
307+ it "sets Authorization header to correct value"
260− property $ \token -> do
308+ $ property
309+ $ \token -> do
261 request <- req_ GET url NoReqBody (oAuth2Bearer token)
310 request <- req_ GET url NoReqBody (oAuth2Bearer token)
262− lookup "Authorization" (L.requestHeaders request) `shouldBe`
311+ lookup "Authorization" (L.requestHeaders request)
263− pure ("Bearer " <> token)
312+ `shouldBe` pure ("Bearer " <> token)
264− it "overwrites manual setting of header" $
313+ it "overwrites manual setting of header"
314+ $ property
265− property $ \token value -> do
315+ $ \token value -> do
316+ request0 <-
317+ req_
318+ GET
319+ url
266− request0 <- req_ GET url NoReqBody
320+ NoReqBody
267− (oAuth2Bearer token <> header "Authorization" value)
321+ (oAuth2Bearer token <> header "Authorization" value)
322+ request1 <-
323+ req_
324+ GET
325+ url
268− request1 <- req_ GET url NoReqBody
326+ NoReqBody
269− (header "Authorization" value <> oAuth2Bearer token)
327+ (header "Authorization" value <> oAuth2Bearer token)
270 let result = "Bearer " <> token
328 let result = "Bearer " <> token
271− lookup "Authorization" (L.requestHeaders request0) `shouldBe`
329+ lookup "Authorization" (L.requestHeaders request0)
272− pure result
330+ `shouldBe` pure result
273− lookup "Authorization" (L.requestHeaders request1) `shouldBe`
331+ lookup "Authorization" (L.requestHeaders request1)
274− pure result
332+ `shouldBe` pure result
275− it "left auth option wins" $
333+ it "left auth option wins"
334+ $ property
276− property $ \token0 token1 -> do
335+ $ \token0 token1 -> do
336+ request <-
337+ req_
338+ GET
339+ url
277− request <- req_ GET url NoReqBody
340+ NoReqBody
278− (oAuth2Bearer token0 <> oAuth2Bearer token1)
341+ (oAuth2Bearer token0 <> oAuth2Bearer token1)
279− lookup "Authorization" (L.requestHeaders request) `shouldBe`
342+ lookup "Authorization" (L.requestHeaders request)
280− pure ("Bearer " <> token0)
343+ `shouldBe` pure ("Bearer " <> token0)
281− describe "ProxyAuthorization" $
344+ describe "ProxyAuthorization"
282− it "sets Authorization header to correct value" $
345+ $ it "sets Authorization header to correct value"
346+ $ property
283− property $ \username password -> do
347+ $ \username password -> do
284− request <- req_ GET url NoReqBody (basicProxyAuth username password)
348+ request <- req_ GET url NoReqBody (basicProxyAuth username password)
285− lookup "Proxy-Authorization" (L.requestHeaders request) `shouldBe`
349+ lookup "Proxy-Authorization" (L.requestHeaders request)
286− pure (basicProxyAuthHeader username password)
350+ `shouldBe` pure (basicProxyAuthHeader username password)
287 describe "oAuth2Token" $ do
351 describe "oAuth2Token" $ do
288− it "sets Authorization header to correct value" $
352+ it "sets Authorization header to correct value"
289− property $ \token -> do
353+ $ property
354+ $ \token -> do
290 request <- req_ GET url NoReqBody (oAuth2Token token)
355 request <- req_ GET url NoReqBody (oAuth2Token token)
291− lookup "Authorization" (L.requestHeaders request) `shouldBe`
356+ lookup "Authorization" (L.requestHeaders request)
292− pure ("token " <> token)
357+ `shouldBe` pure ("token " <> token)
293− it "overwrites manual setting of header" $
358+ it "overwrites manual setting of header"
359+ $ property
294− property $ \token value -> do
360+ $ \token value -> do
361+ request0 <-
362+ req_
363+ GET
364+ url
295− request0 <- req_ GET url NoReqBody
365+ NoReqBody
296− (oAuth2Token token <> header "Authorization" value)
366+ (oAuth2Token token <> header "Authorization" value)
367+ request1 <-
368+ req_
369+ GET
370+ url
297− request1 <- req_ GET url NoReqBody
371+ NoReqBody
298− (header "Authorization" value <> oAuth2Token token)
372+ (header "Authorization" value <> oAuth2Token token)
299 let result = "token " <> token
373 let result = "token " <> token
300− lookup "Authorization" (L.requestHeaders request0) `shouldBe`
374+ lookup "Authorization" (L.requestHeaders request0)
301− pure result
375+ `shouldBe` pure result
302− lookup "Authorization" (L.requestHeaders request1) `shouldBe`
376+ lookup "Authorization" (L.requestHeaders request1)
303− pure result
377+ `shouldBe` pure result
304− it "left auth option wins" $
378+ it "left auth option wins"
379+ $ property
305− property $ \token0 token1 -> do
380+ $ \token0 token1 -> do
381+ request <-
382+ req_
383+ GET
384+ url
306− request <- req_ GET url NoReqBody
385+ NoReqBody
307− (oAuth2Token token0 <> oAuth2Token token1)
386+ (oAuth2Token token0 <> oAuth2Token token1)
308− lookup "Authorization" (L.requestHeaders request) `shouldBe`
387+ lookup "Authorization" (L.requestHeaders request)
309− pure ("token " <> token0)
388+ `shouldBe` pure ("token " <> token0)
310− describe "port" $
389+ describe "port"
311− it "sets port overwriting the defaults" $
390+ $ it "sets port overwriting the defaults"
391+ $ property
312− property $ \n -> do
392+ $ \n -> do
313− request <- req_ GET url NoReqBody (port n)
393+ request <- req_ GET url NoReqBody (port n)
314− L.port request `shouldBe` n
394+ L.port request `shouldBe` n
315− describe "decompress" $
395+ describe "decompress"
316− it "sets decompress function overwriting the defaults" $
396+ $ it "sets decompress function overwriting the defaults"
397+ $ property
317− property $ \token -> do
398+ $ \token -> do
318− request <- req_ GET url NoReqBody (decompress (/= token))
399+ request <- req_ GET url NoReqBody (decompress (/= token))
319− L.decompress request token `shouldBe` False
400+ L.decompress request token `shouldBe` False
320 -- FIXME Can't really test responseTimeout right new because the
401 -- FIXME Can't really test responseTimeout right new because the
321 -- ResponseTimeout data type does not implement Eq and its constructors
402 -- ResponseTimeout data type does not implement Eq and its constructors
322 -- are also not exported. Sent a PR.
403 -- are also not exported. Sent a PR.
323− describe "httpVersion" $
404+ describe "httpVersion"
324− it "sets HTTP version overwriting the defaults" $
405+ $ it "sets HTTP version overwriting the defaults"
406+ $ property
325− property $ \major minor -> do
407+ $ \major minor -> do
326− request <- req_ GET url NoReqBody (httpVersion major minor)
408+ request <- req_ GET url NoReqBody (httpVersion major minor)
327− L.requestVersion request `shouldBe` Y.HttpVersion major minor
409+ L.requestVersion request `shouldBe` Y.HttpVersion major minor
328
410
329----------------------------------------------------------------------------
411----------------------------------------------------------------------------
330-- Instances
412-- Instances
@@ -334,21 +416,25 @@ instance MonadHttp IO where
334
416
335instance Arbitrary HttpConfig where
417instance Arbitrary HttpConfig where
336 arbitrary = do
418 arbitrary = do
337− httpConfigProxy <- arbitrary
419+ httpConfigProxy <- arbitrary
338 httpConfigRedirectCount <- arbitrary
420 httpConfigRedirectCount <- arbitrary
339− let httpConfigAltManager = Nothing
421+ let httpConfigAltManager = Nothing
340 httpConfigCheckResponse _ _ _ = Nothing
422 httpConfigCheckResponse _ _ _ = Nothing
341− httpConfigRetryPolicy = retryPolicyDefault
423+ httpConfigRetryPolicy = retryPolicyDefault
342− httpConfigRetryJudge _ _ = False
424+ httpConfigRetryJudge _ _ = False
343 return HttpConfig {..}
425 return HttpConfig {..}
344
426
345instance Show HttpConfig where
427instance Show HttpConfig where
346 show HttpConfig {..} =
428 show HttpConfig {..} =
347− "HttpConfig\n" ++
429+ "HttpConfig\n"
430+ ++ "{ httpConfigProxy="
348− "{ httpConfigProxy=" ++ show httpConfigProxy ++ "\n" ++
431+ ++ show httpConfigProxy
432+ ++ "\n"
433+ ++ ", httpRedirectCount="
349− ", httpRedirectCount=" ++ show httpConfigRedirectCount ++ "\n" ++
434+ ++ show httpConfigRedirectCount
435+ ++ "\n"
350− ", httpConfigAltManager=<cannot be shown>\n" ++
436+ ++ ", httpConfigAltManager=<cannot be shown>\n"
351− ", httpConfigCheckResponse=<cannot be shown>}\n"
437+ ++ ", httpConfigCheckResponse=<cannot be shown>}\n"
352
438
353instance Arbitrary L.Proxy where
439instance Arbitrary L.Proxy where
354 arbitrary = L.Proxy <$> arbitrary <*> arbitrary
440 arbitrary = L.Proxy <$> arbitrary <*> arbitrary
@@ -366,9 +452,10 @@ instance Show (Option scheme) where
366 show _ = "<cannot be shown>"
452 show _ = "<cannot be shown>"
367
453
368data Thing = Thing
454data Thing = Thing
369− { _thingBool :: Bool
455+ { _thingBool :: Bool,
370− , _thingText :: Text
456+ _thingText :: Text
457+ }
371− } deriving (Eq, Show, Generic)
458+ deriving (Eq, Show, Generic)
372
459
373instance ToJSON Thing
460instance ToJSON Thing
374
461
@@ -380,17 +467,17 @@ instance Arbitrary L.CookieJar where
380
467
381instance Arbitrary L.Cookie where
468instance Arbitrary L.Cookie where
382 arbitrary = do
469 arbitrary = do
383− cookie_name <- arbitrary
470+ cookie_name <- arbitrary
384− cookie_value <- arbitrary
471+ cookie_value <- arbitrary
385− cookie_expiry_time <- arbitrary
472+ cookie_expiry_time <- arbitrary
386− cookie_domain <- arbitrary
473+ cookie_domain <- arbitrary
387− cookie_path <- arbitrary
474+ cookie_path <- arbitrary
388 cookie_creation_time <- arbitrary
475 cookie_creation_time <- arbitrary
389 cookie_last_access_time <- arbitrary
476 cookie_last_access_time <- arbitrary
390− cookie_persistent <- arbitrary
477+ cookie_persistent <- arbitrary
391− cookie_host_only <- arbitrary
478+ cookie_host_only <- arbitrary
392− cookie_secure_only <- arbitrary
479+ cookie_secure_only <- arbitrary
393− cookie_http_only <- arbitrary
480+ cookie_http_only <- arbitrary
394 return L.Cookie {..}
481 return L.Cookie {..}
395
482
396instance Arbitrary UTCTime where
483instance Arbitrary UTCTime where
@@ -406,15 +493,13 @@ instance Arbitrary DiffTime where
406-- Helper types
493-- Helper types
407
494
408-- | A wrapper to generate correct hosts.
495-- | A wrapper to generate correct hosts.
409−
410−newtype Host = Host { unHost :: Text }
496+newtype Host = Host {unHost :: Text}
411 deriving (Eq, Show)
497 deriving (Eq, Show)
412
498
413instance Arbitrary Host where
499instance Arbitrary Host where
414 arbitrary = Host . T.pack <$> listOf1 (arbitrary `suchThat` (/= ':'))
500 arbitrary = Host . T.pack <$> listOf1 (arbitrary `suchThat` (/= ':'))
415
501
416-- | A wrapper to generate correct query parameters.
502-- | A wrapper to generate correct query parameters.
417−
418newtype QueryParams = QueryParams [(Text, Maybe Text)]
503newtype QueryParams = QueryParams [(Text, Maybe Text)]
419 deriving (Eq, Show)
504 deriving (Eq, Show)
420
505
@@ -427,57 +512,57 @@ instance Arbitrary QueryParams where
427-- Helpers
512-- Helpers
428
513
429-- | 'req' that just returns the prepared 'L.Request'.
514-- | 'req' that just returns the prepared 'L.Request'.
430−
431−req_
515+req_ ::
432− :: ( MonadHttp m
516+ ( MonadHttp m,
433− , HttpMethod method
517+ HttpMethod method,
434− , HttpBody body
518+ HttpBody body,
435− , HttpBodyAllowed (AllowsBody method) (ProvidesBody body) )
519+ HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
520+ ) =>
436− => method -- ^ HTTP method
521+ -- | HTTP method
522+ method ->
437− -> Url scheme -- ^ 'Url' — location of resource
523+ -- | 'Url' — location of resource
524+ Url scheme ->
438− -> body -- ^ Body of the request
525+ -- | Body of the request
526+ body ->
439− -> Option scheme -- ^ Collection of optional parameters
527+ -- | Collection of optional parameters
528+ Option scheme ->
529+ -- | Vanilla request
440− -> m L.Request -- ^ Vanilla request
530+ m L.Request
441req_ method url' body options = req' method url' body options $
531req_ method url' body options = req' method url' body options $
442 \request _ -> return request
532 \request _ -> return request
443
533
444-- | A dummy 'Url'.
534-- | A dummy 'Url'.
445−
446url :: Url 'Https
535url :: Url 'Https
447url = https "httpbin.org"
536url = https "httpbin.org"
448
537
449-- | Percent encode given 'Text'.
538-- | Percent encode given 'Text'.
450−
451urlEncode :: Text -> ByteString
539urlEncode :: Text -> ByteString
452urlEncode = Y.urlEncode False . T.encodeUtf8
540urlEncode = Y.urlEncode False . T.encodeUtf8
453
541
454-- | Build URL path from given path pieces.
542-- | Build URL path from given path pieces.
455−
456encodePathPieces :: [Text] -> ByteString
543encodePathPieces :: [Text] -> ByteString
457encodePathPieces = BL.toStrict . BB.toLazyByteString . Y.encodePathSegments
544encodePathPieces = BL.toStrict . BB.toLazyByteString . Y.encodePathSegments
458
545
459-- | Get host from a 'URI'. This function is not total.
546-- | Get host from a 'URI'. This function is not total.
460−
461uriHost :: URI -> ByteString
547uriHost :: URI -> ByteString
462−uriHost uri = fromJust $
548+uriHost uri =
549+ fromJust $
463− urlEncode . URI.unRText . URI.authHost
550+ urlEncode . URI.unRText . URI.authHost
464− <$> either (const Nothing) Just (URI.uriAuthority uri)
551+ <$> either (const Nothing) Just (URI.uriAuthority uri)
465
552
466-- | Get part from a 'URI' defaulting to the provided value.
553-- | Get part from a 'URI' defaulting to the provided value.
467−
468uriPort :: Int -> URI -> Int
554uriPort :: Int -> URI -> Int
555+uriPort def uri =
469−uriPort def uri = maybe def (fromIntegral) $
556+ maybe def (fromIntegral) $
470− either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
557+ either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
471
558
472-- | Get path from 'URI'.
559-- | Get path from 'URI'.
473−
474uriPath :: URI -> ByteString
560uriPath :: URI -> ByteString
475uriPath uri = fromMaybe "" $ do
561uriPath uri = fromMaybe "" $ do
476 (_, xs) <- URI.uriPath uri
562 (_, xs) <- URI.uriPath uri
477 (return . encodePathPieces . fmap URI.unRText . NE.toList) xs
563 (return . encodePathPieces . fmap URI.unRText . NE.toList) xs
478
564
479-- | Get query string from 'URI'.
565-- | Get query string from 'URI'.
480−
481uriQuery :: URI -> ByteString
566uriQuery :: URI -> ByteString
482uriQuery uri = do
567uriQuery uri = do
483 let liftQueryParam = \case
568 let liftQueryParam = \case
@@ -487,7 +572,6 @@ uriQuery uri = do
487
572
488-- | Predict the headrs that should be set if the given 'URI' has username
573-- | Predict the headrs that should be set if the given 'URI' has username
489-- and password in it.
574-- and password in it.
490−
491uriBasicAuth :: URI -> Maybe ByteString
575uriBasicAuth :: URI -> Maybe ByteString
492uriBasicAuth uri = do
576uriBasicAuth uri = do
493 auth <- either (const Nothing) Just (URI.uriAuthority uri)
577 auth <- either (const Nothing) Just (URI.uriAuthority uri)
@@ -497,17 +581,14 @@ uriBasicAuth uri = do
497 return (basicAuthHeader username password)
581 return (basicAuthHeader username password)
498
582
499-- | Render a query as lazy 'BL.ByteString'.
583-- | Render a query as lazy 'BL.ByteString'.
500−
501renderQuery :: [(Text, Maybe Text)] -> BL.ByteString
584renderQuery :: [(Text, Maybe Text)] -> BL.ByteString
502renderQuery = BL.fromStrict . Y.renderQuery False . Y.queryTextToQuery
585renderQuery = BL.fromStrict . Y.renderQuery False . Y.queryTextToQuery
503
586
504-- | Convert collection of query parameters to 'FormUrlEncodedParam' thing.
587-- | Convert collection of query parameters to 'FormUrlEncodedParam' thing.
505−
506formUrlEnc :: [(Text, Maybe Text)] -> FormUrlEncodedParam
588formUrlEnc :: [(Text, Maybe Text)] -> FormUrlEncodedParam
507formUrlEnc = foldMap (uncurry queryParam)
589formUrlEnc = foldMap (uncurry queryParam)
508
590
509-- | Get “Authorization” basic auth header given username and password.
591-- | Get “Authorization” basic auth header given username and password.
510−
511basicAuthHeader :: ByteString -> ByteString -> ByteString
592basicAuthHeader :: ByteString -> ByteString -> ByteString
512basicAuthHeader username password =
593basicAuthHeader username password =
513 fromJust . lookup Y.hAuthorization . L.requestHeaders $
594 fromJust . lookup Y.hAuthorization . L.requestHeaders $
@@ -515,7 +596,6 @@ basicAuthHeader username password =
515
596
516-- | Get "Proxy-Authorization" basic proxy auth header given username and
597-- | Get "Proxy-Authorization" basic proxy auth header given username and
517-- password.
598-- password.
518−
519basicProxyAuthHeader :: ByteString -> ByteString -> ByteString
599basicProxyAuthHeader :: ByteString -> ByteString -> ByteString
520basicProxyAuthHeader username password =
600basicProxyAuthHeader username password =
521 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
601 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $