on a920ec0 help sign in
.github/dependabot.ymldeleted−6
since splitsnapshots
unifiedsplit
@@ -1,6 +0,0 @@
1−version: 2
2−updates:
3− - package-ecosystem: "github-actions"
4− directory: "/"
5− schedule:
6− interval: "weekly"
.github/workflows/ci.yamldeleted−47
@@ -1,47 +0,0 @@
1−name: CI
2−on:
3− push:
4− branches:
5− - master
6− pull_request:
7− types:
8− - opened
9− - synchronize
10−jobs:
11− ormolu:
12− runs-on: ubuntu-latest
13− steps:
14− - uses: actions/checkout@v4
15− - uses: haskell-actions/run-ormolu@v17
16− build:
17− runs-on: ubuntu-latest
18− needs: ormolu
19− strategy:
20− matrix:
21− cabal: ["3.12"]
22− ghc: ["9.6.3", "9.8.2", "9.10.1"]
23− steps:
24− - uses: actions/checkout@v4
25− - uses: haskell-actions/setup@v2
26− id: setup-haskell-cabal
27− with:
28− ghc-version: ${{ matrix.ghc }}
29− cabal-version: ${{ matrix.cabal }}
30− - run: cabal update
31− - run: cabal freeze
32− - uses: actions/cache@v4.2.3
33− with:
34− path: |
35− ${{ steps.setup-haskell-cabal.outputs.cabal-store }}
36− dist-newstyle
37− key: ${{ runner.os }}-${{ matrix.ghc }}-${{ hashFiles('cabal.project.freeze') }}
38− restore-keys: |
39− ${{ runner.os }}-${{ matrix.ghc }}-
40− - run: cabal format && git diff --exit-code --color=always
41− - run: cabal build
42− - run: |
43− docker pull kennethreitz/httpbin
44− docker run -p 1234:80 kennethreitz/httpbin &
45− cabal test
46− - run: cabal haddock
47− - run: cabal sdist
.gitignore−1
@@ -25,6 +25,5 @@ cabal.project.local
25cabal.sandbox.config
25cabal.sandbox.config
26dist-*/
26dist-*/
27dist/
27dist/
28−hie.yaml
29stack.yaml
28stack.yaml
30stack.yaml.lock
29stack.yaml.lock
.travis.ymlnew file+39
@@ -0,0 +1,39 @@
1+language: c
2+
3+sudo: false
4+
5+cache:
6+ directories:
7+ - ~/.cabal/packages
8+ - ~/.cabal/store
9+
10+matrix:
11+ include:
12+ - env: CABALVER=3.0 GHCVER=8.6.5
13+ addons: {apt: {packages: [cabal-install-3.0,ghc-8.6.5], sources: [hvr-ghc]}}
14+ - env: CABALVER=3.0 GHCVER=8.8.3
15+ addons: {apt: {packages: [cabal-install-3.0,ghc-8.8.3], sources: [hvr-ghc]}}
16+ - env: CABALVER=3.0 GHCVER=8.10.1
17+ addons: {apt: {packages: [cabal-install-3.0,ghc-8.10.1], sources: [hvr-ghc]}}
18+
19+before_install:
20+ - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
21+
22+install:
23+ - cabal --version
24+ - ghc --version
25+ - travis_retry cabal new-update
26+
27+script:
28+ # To avoid re-compilation it is important to use exactly the same
29+ # configuration for all steps.
30+ - export CONFIG="--enable-tests --enable-benchmarks --flags=dev"
31+ - cabal format
32+ - git diff --exit-code --color=always
33+ - cabal new-build $CONFIG
34+ - cabal new-test $CONFIG
35+ - cabal new-haddock $CONFIG
36+ - cabal new-sdist
37+
38+notifications:
39+ email: false
CHANGELOG.md−89
@@ -1,97 +1,8 @@
1−## Req 3.13.4
2−
3−* Fixed empty ciphersuite list when compiling against `tls < 2.0.6`
4− (see [PR 175](https://github.com/mrkkrp/req/pull/175)). As a side effect, now
5− compatible with older versions of `crypton-connection` (>= 0.3).
6−
7−## Req 3.13.3
8−
9−* Works with `crypton-connection-0.4` and newer.
10−
11−## Req 3.13.2
12−
13−* Disable the problematic `httpbin-tests` test suite by default. Only enable
14− it when the `dev` flag is enabled. In that case it is expected that an
15− httpbin server is run locally at `localhost:1234`.
16−
17−## Req 3.13.1
18−
19−* Switched the non-pure test suite to use https://httpbun.org instead of
20− https://httpbin.org since the latter proved to be highly unreliable
21− lately.
22−
23−* Switched from `connection` to `crypton-connection`.
24−
25−* Builds with GHC 9.6.1.
26−
27−## Req 3.13.0
28−
29−* Add `headerRedacted` function to add header fields, which will be with
30− redacted values on print.
31−
32−## Req 3.12.0
33−
34−* Add `isStatusCodeException` function.
35−* Add `instance HttpResponse (Network.HTTP.Client.Response ())`.
36−
37−## Req 3.11.0
38−
39−* Add the `queryParamToList` method to the `QueryParam` type class.
40−* Add the `formToQuery` function. [Issue 126](https://github.com/mrkkrp/req/issues/126).
41−* Add `FromForm` instances (in the `Web.FormUrlEncoded` module) to the
42− `Option` and `FormUrlEncodedParam` types.
43−
44−## Req 3.10.0
45−
46−* Add `MonadHttp` instances for `transformers` types.
47−
48−## Req 3.9.2
49−
50−* The test suite works with `aeson-2.x.x.x`.
51−
52−## Req 3.9.1
53−
54−* Builds with GHC 9.0.
55−
56−## Req 3.9.0
57−
58−* The `useHttpURI` and `useHttpsURI` functions now preserve trailing
59− slashes.
60−
61−## Req 3.8.0
62−
63−* Adjusted the value of the `httpConfigRetryJudgeException` field of
64− `defaultHttpConfig` to retry on response timeouts and connection timeouts.
65−
66−## Req 3.7.0
67−
68−* Added `reqCb`, a function that allows you to modify the `Request` object
69− but otherwise performs the requst for you.
70−
71−* Derived `MonadThrow`, `MonadCatch`, and `MonadMask` for the `Req` monad.
72−
73−## Req 3.6.0
74−
75−* Added the `httpConfigBodyPreviewLength` configuration parameter to
76− `HttpConfig`.
77−
78−## Req 3.5.0
79−
80−* Made `Req` an instance of `MonadUnliftIO`. [Issue
81− 100](https://github.com/mrkkrp/req/issues/100).
82−
83## Req 3.4.0
1## Req 3.4.0
84
2
85* Requests using `DELETE` method can now have a body. [Issue
3* Requests using `DELETE` method can now have a body. [Issue
86 89](https://github.com/mrkkrp/req/issues/89).
4 89](https://github.com/mrkkrp/req/issues/89).
87
5
88−* Added the `httpConfigRetryJudgeException` field to `HttpConfig` so that
89− requests that result in exceptions can be retried. [Issue
90− 93](https://github.com/mrkkrp/req/issues/93).
91−
92−* Added the function `renderUrl`. [Issue
93− 83](https://github.com/mrkkrp/req/issues/83).
94−
95## Req 3.3.0
6## Req 3.3.0
96
7
97* Derived `Show` instances for response types `IgnoreResponse`,
8* Derived `Show` instances for response types `IgnoreResponse`,
Network/HTTP/Req.hs+186−419
@@ -1,9 +1,19 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveDataTypeable #-}
5+{-# LANGUAGE DeriveGeneric #-}
6+{-# LANGUAGE DeriveLift #-}
7+{-# LANGUAGE FlexibleInstances #-}
8+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2{-# LANGUAGE LambdaCase #-}
9{-# LANGUAGE LambdaCase #-}
10+{-# LANGUAGE MultiParamTypeClasses #-}
3{-# LANGUAGE OverloadedStrings #-}
11{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE QuasiQuotes #-}
12{-# LANGUAGE QuasiQuotes #-}
13+{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RecordWildCards #-}
14{-# LANGUAGE RecordWildCards #-}
6{-# LANGUAGE RoleAnnotations #-}
15{-# LANGUAGE RoleAnnotations #-}
16+{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TemplateHaskellQuotes #-}
17{-# LANGUAGE TemplateHaskellQuotes #-}
8{-# LANGUAGE TypeFamilies #-}
18{-# LANGUAGE TypeFamilies #-}
9{-# LANGUAGE UndecidableInstances #-}
19{-# LANGUAGE UndecidableInstances #-}
@@ -19,40 +29,61 @@
19--
29--
20-- The documentation below is structured in such a way that the most
30-- The documentation below is structured in such a way that the most
21-- important information is presented first: you learn how to do HTTP
31-- important information is presented first: you learn how to do HTTP
22−-- requests, how to embed them in the monad you have, and then it gives you
32+-- requests, how to embed them in any monad you have, and then it gives you
23-- details about less-common things you may want to know about. The
33-- details about less-common things you may want to know about. The
24-- documentation is written with sufficient coverage of details and
34-- documentation is written with sufficient coverage of details and
25-- examples, and it's designed to be a complete tutorial on its own.
35-- examples, and it's designed to be a complete tutorial on its own.
26--
36--
37+-- /(A modest intro goes here, click on 'req' to start making requests.)/
38+--
27-- === About the library
39-- === About the library
28--
40--
29−-- Req is an HTTP client library that attempts to be easy-to-use, type-safe,
41+-- Req is an easy-to-use, type-safe, expandable, high-level HTTP client
30−-- and expandable.
42+-- library that just works without any fooling around.
31−--
43+--
32−-- “Easy-to-use” means that the library is designed to be beginner-friendly
44+-- What does the phrase “easy-to-use” mean? It means that the library is
33−-- so it's simple to add to your monad stack, intuitive to work with,
45+-- designed to be beginner-friendly so it's simple to add to your monad
34−-- well-documented, and does not get in your way. Doing HTTP requests is a
46+-- stack, intuitive to work with, well-documented, and does not get in your
35−-- common task and a Haskell library for this should be approachable and
47+-- way. Doing HTTP requests is a common task and Haskell library for this
48+-- should be very approachable and clear to beginners, thus certain
36−-- clear to beginners, thus certain compromises were made. For example, one
49+-- compromises were made. For example, one cannot currently modify
37−-- cannot currently modify 'L.ManagerSettings' of the default manager
50+-- 'L.ManagerSettings' of the default manager because the library always
38−-- because the library always uses the same implicit global manager for
51+-- uses the same implicit global manager for simplicity and maximal
39−-- simplicity and maximal connection sharing. There is a way to use your own
52+-- connection sharing. There is a way to use your own manager with different
40−-- manager with different settings, but it requires more typing.
53+-- settings, but it requires a bit more typing.
41−--
54+--
42−-- “Type-safe” means that the library tries to eliminate certain classes of
55+-- “Type-safe” means that the library is protective and eliminates certain
43−-- errors. For example, we have correct-by-construction URLs; it is
56+-- classes of errors. For example, we have correct-by-construction 'Url's,
44−-- guaranteed that the user does not send the request body when using
57+-- it's guaranteed that the user does not send the request body when using
45−-- methods like GET or OPTIONS, and the amount of implicit assumptions is
58+-- methods like 'GET' or 'OPTIONS', and the amount of implicit assumptions
46−-- minimized by making the user specify their intentions in an explicit
59+-- is minimized by making the user specify his\/her intentions in an
47−-- form. For example, it's not possible to avoid specifying the body or the
60+-- explicit form (for example, it's not possible to avoid specifying the
48−-- method of a request. Authentication methods that assume HTTPS force the
61+-- body or method of request). Authentication methods that assume HTTPS
49−-- user to use HTTPS at the type level.
62+-- force the user to use HTTPS at the type level. The library also carefully
63+-- hides underlying types from the lower-level @http-client@ package because
64+-- those types are not safe enough (for example 'L.Request' is an instance
65+-- of 'Data.String.IsString' and, if it's malformed, it will blow up at
66+-- run-time).
50−--
67+--
51−-- “Expandable” refers to the ability to create new components without
68+-- “Expandable” refers to the ability of the library to be expanded without
52−-- having to resort to hacking. For example, it's possible to define your
69+-- having to resort to ugly hacking. For example, it's possible to define
53−-- own HTTP methods, create new ways to construct the body of a request,
70+-- your own HTTP methods, create new ways to construct the body of a
54−-- create new authorization options, perform a request in a different way,
71+-- request, create new authorization options, perform a request in a
55−-- and create your own methods to parse a response.
72+-- different way, and create your own methods to parse and represent a
73+-- response. As the user extends the library to satisfy his\/her special
74+-- needs, the new solutions will work just like the built-ins. However, all
75+-- of the common cases are also covered by the library out-of-the-box.
76+--
77+-- “High-level” means that there are less details to worry about. The
78+-- library is a result of my experiences as a Haskell consultant. Working
79+-- for several clients, who had very different projects, showed me that the
80+-- library should adapt easily to any particular style of writing Haskell
81+-- applications. For example, some people prefer throwing exceptions, while
82+-- others are concerned with purity. Just define 'handleHttpException'
83+-- accordingly when making your monad instance of 'MonadHttp' and it will
84+-- play together seamlessly. Finally, the library cuts down boilerplate
85+-- considerably, and helps you write concise, easy to read, and maintainable
86+-- code.
56--
87--
57-- === Using with other libraries
88-- === Using with other libraries
58--
89--
@@ -81,7 +112,6 @@ module Network.HTTP.Req
81 -- $making-a-request
112 -- $making-a-request
82 req,
113 req,
83 reqBr,
114 reqBr,
84− reqCb,
85 req',
115 req',
86 withReqManager,
116 withReqManager,
87
117
@@ -119,7 +149,6 @@ module Network.HTTP.Req
119 useHttpsURI,
149 useHttpsURI,
120 useURI,
150 useURI,
121 urlQ,
151 urlQ,
122− renderUrl,
123
152
124 -- ** Body
153 -- ** Body
125 -- $body
154 -- $body
@@ -144,13 +173,11 @@ module Network.HTTP.Req
144 -- $query-parameters
173 -- $query-parameters
145 (=:),
174 (=:),
146 queryFlag,
175 queryFlag,
147− formToQuery,
148 QueryParam (..),
176 QueryParam (..),
149
177
150 -- *** Headers
178 -- *** Headers
151 header,
179 header,
152 attachHeader,
180 attachHeader,
153− headerRedacted,
154
181
155 -- *** Cookies
182 -- *** Cookies
156 -- $cookies
183 -- $cookies
@@ -197,78 +224,56 @@ module Network.HTTP.Req
197
224
198 -- * Other
225 -- * Other
199 HttpException (..),
226 HttpException (..),
200− isStatusCodeException,
201 CanHaveBody (..),
227 CanHaveBody (..),
202 Scheme (..),
228 Scheme (..),
203 )
229 )
204where
230where
205
231
206−import Blaze.ByteString.Builder qualified as BB
232+import qualified Blaze.ByteString.Builder as BB
207import Control.Applicative
233import Control.Applicative
208import Control.Arrow (first, second)
234import Control.Arrow (first, second)
209−import Control.Exception hiding (Handler (..), TypeError)
235+import Control.Exception hiding (TypeError)
210−import Control.Monad (guard, void, (>=>))
211import Control.Monad.Base
236import Control.Monad.Base
212−import Control.Monad.Catch (Handler (..), MonadCatch, MonadMask, MonadThrow)
213import Control.Monad.IO.Class
237import Control.Monad.IO.Class
214−import Control.Monad.IO.Unlift
238+import Control.Monad.Reader
215−import Control.Monad.Reader (ReaderT (ReaderT), ask, lift, runReaderT)
216−import Control.Monad.Trans.Accum (AccumT)
217−import Control.Monad.Trans.Cont (ContT)
218import Control.Monad.Trans.Control
239import Control.Monad.Trans.Control
219−import Control.Monad.Trans.Except (ExceptT)
220−import Control.Monad.Trans.Identity (IdentityT)
221−import Control.Monad.Trans.Maybe (MaybeT)
222−import Control.Monad.Trans.RWS.CPS qualified as RWS.CPS
223−import Control.Monad.Trans.RWS.Lazy qualified as RWS.Lazy
224−import Control.Monad.Trans.RWS.Strict qualified as RWS.Strict
225−import Control.Monad.Trans.Select (SelectT)
226−import Control.Monad.Trans.State.Lazy qualified as State.Lazy
227−import Control.Monad.Trans.State.Strict qualified as State.Strict
228−import Control.Monad.Trans.Writer.CPS qualified as Writer.CPS
229−import Control.Monad.Trans.Writer.Lazy qualified as Writer.Lazy
230−import Control.Monad.Trans.Writer.Strict qualified as Writer.Strict
231import Control.Retry
240import Control.Retry
232import Data.Aeson (FromJSON (..), ToJSON (..))
241import Data.Aeson (FromJSON (..), ToJSON (..))
233−import Data.Aeson qualified as A
242+import qualified Data.Aeson as A
234import Data.ByteString (ByteString)
243import Data.ByteString (ByteString)
235−import Data.ByteString qualified as B
244+import qualified Data.ByteString as B
236−import Data.ByteString.Lazy qualified as BL
245+import qualified Data.ByteString.Lazy as BL
237−import Data.CaseInsensitive qualified as CI
246+import qualified Data.CaseInsensitive as CI
238import Data.Data (Data)
247import Data.Data (Data)
239−import Data.Default.Class (def)
240import Data.Function (on)
248import Data.Function (on)
241import Data.IORef
249import Data.IORef
242import Data.Kind (Constraint, Type)
250import Data.Kind (Constraint, Type)
243import Data.List (foldl', nubBy)
251import Data.List (foldl', nubBy)
244import Data.List.NonEmpty (NonEmpty (..))
252import Data.List.NonEmpty (NonEmpty (..))
245−import Data.List.NonEmpty qualified as NE
253+import qualified Data.List.NonEmpty as NE
246import Data.Maybe (fromMaybe)
254import Data.Maybe (fromMaybe)
247import Data.Proxy
255import Data.Proxy
248−import Data.Semigroup (Endo (..))
256+import Data.Semigroup hiding (Option, option)
249−import Data.Set qualified as S
250import Data.Text (Text)
257import Data.Text (Text)
251−import Data.Text qualified as T
258+import qualified Data.Text as T
252−import Data.Text.Encoding qualified as T
259+import qualified Data.Text.Encoding as T
253import Data.Typeable (Typeable, cast)
260import Data.Typeable (Typeable, cast)
254import GHC.Generics
261import GHC.Generics
255import GHC.TypeLits
262import GHC.TypeLits
256−import Language.Haskell.TH qualified as TH
263+import qualified Language.Haskell.TH as TH
257−import Language.Haskell.TH.Quote qualified as TH
264+import qualified Language.Haskell.TH.Quote as TH
258−import Language.Haskell.TH.Syntax qualified as TH
265+import qualified Language.Haskell.TH.Syntax as TH
259−import Network.Connection qualified as NC
266+import qualified Network.Connection as NC
260−import Network.HTTP.Client qualified as L
267+import qualified Network.HTTP.Client as L
261−import Network.HTTP.Client.Internal qualified as LI
268+import qualified Network.HTTP.Client.Internal as LI
262−import Network.HTTP.Client.MultipartFormData qualified as LM
269+import qualified Network.HTTP.Client.MultipartFormData as LM
263−import Network.HTTP.Client.TLS qualified as L
270+import qualified Network.HTTP.Client.TLS as L
264−import Network.HTTP.Types qualified as Y
271+import qualified Network.HTTP.Types as Y
265import System.IO.Unsafe (unsafePerformIO)
272import System.IO.Unsafe (unsafePerformIO)
266import Text.URI (URI)
273import Text.URI (URI)
267−import Text.URI qualified as URI
274+import qualified Text.URI as URI
268−import Text.URI.QQ qualified as QQ
275+import qualified Text.URI.QQ as QQ
269−import Web.Authenticate.OAuth qualified as OAuth
276+import qualified Web.Authenticate.OAuth as OAuth
270−import Web.FormUrlEncoded (FromForm (..), ToForm (..))
271−import Web.FormUrlEncoded qualified as Form
272import Web.HttpApiData (ToHttpApiData (..))
277import Web.HttpApiData (ToHttpApiData (..))
273
278
274----------------------------------------------------------------------------
279----------------------------------------------------------------------------
@@ -293,7 +298,7 @@ import Web.HttpApiData (ToHttpApiData (..))
293--
298--
294-- @body@ is a body option such as 'NoReqBody' or 'ReqBodyJson'. The
299-- @body@ is a body option such as 'NoReqBody' or 'ReqBodyJson'. The
295-- tutorial has a section about HTTP bodies, but usage is very
300-- tutorial has a section about HTTP bodies, but usage is very
296−-- straightforward and should be clear from the examples.
301+-- straightforward and should be clear from the examples below.
297--
302--
298-- @response@ is a type hint how to make and interpret response of an HTTP
303-- @response@ is a type hint how to make and interpret response of an HTTP
299-- request. Out-of-the-box it can be the following:
304-- request. Out-of-the-box it can be the following:
@@ -421,7 +426,12 @@ req ::
421 -- | Response
426 -- | Response
422 m response
427 m response
423req method url body responseProxy options =
428req method url body responseProxy options =
424− reqCb method url body responseProxy options pure
429+ reqBr method url body (options <> extraOptions) getHttpResponse
430+ where
431+ extraOptions =
432+ case acceptHeader responseProxy of
433+ Nothing -> mempty
434+ Just accept -> header "Accept" accept
425
435
426-- | A version of 'req' that does not use one of the predefined instances of
436-- | A version of 'req' that does not use one of the predefined instances of
427-- 'HttpResponse' but instead allows the user to consume @'L.Response'
437-- 'HttpResponse' but instead allows the user to consume @'L.Response'
@@ -446,58 +456,7 @@ reqBr ::
446 (L.Response L.BodyReader -> IO a) ->
456 (L.Response L.BodyReader -> IO a) ->
447 -- | Result
457 -- | Result
448 m a
458 m a
449−reqBr method url body options consume =
459+reqBr method url body options consume = req' method url body options $ \request manager -> do
450− req' method url body options (reqHandler consume)
451−
452−-- | A version of 'req' that takes a callback to modify the 'L.Request', but
453−-- otherwise performs the request identically.
454−--
455−-- @since 3.7.0
456−reqCb ::
457− ( MonadHttp m,
458− HttpMethod method,
459− HttpBody body,
460− HttpResponse response,
461− HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
462− ) =>
463− -- | HTTP method
464− method ->
465− -- | 'Url'—location of resource
466− Url scheme ->
467− -- | Body of the request
468− body ->
469− -- | A hint how to interpret response
470− Proxy response ->
471− -- | Collection of optional parameters
472− Option scheme ->
473− -- | Callback to modify the request
474− (L.Request -> m L.Request) ->
475− -- | Response
476− m response
477−reqCb method url body responseProxy options adjustRequest =
478− req' method url body (options <> extraOptions) $ \request manager -> do
479− request' <- adjustRequest request
480− reqHandler getHttpResponse request' manager
481− where
482− extraOptions =
483− case acceptHeader responseProxy of
484− Nothing -> mempty
485− Just accept -> header "Accept" accept
486−
487−-- | The default handler function that the higher-level request functions
488−-- pass to 'req''. Internal function.
489−--
490−-- @since 3.7.0
491−reqHandler ::
492− (MonadHttp m) =>
493− -- | How to get final result from a 'L.Response'
494− (L.Response L.BodyReader -> IO b) ->
495− -- | 'L.Request' to perform
496− L.Request ->
497− -- | 'L.Manager' to use
498− L.Manager ->
499− m b
500−reqHandler consume request manager = do
501 HttpConfig {..} <- getHttpConfig
460 HttpConfig {..} <- getHttpConfig
502 let wrapVanilla = handle (throwIO . VanillaHttpException)
461 let wrapVanilla = handle (throwIO . VanillaHttpException)
503 wrapExc = handle (throwIO . LI.toHttpException request)
462 wrapExc = handle (throwIO . LI.toHttpException request)
@@ -513,23 +472,12 @@ reqHandler consume request manager = do
513 r' <- L.responseOpen request manager
472 r' <- L.responseOpen request manager
514 writeIORef rref (Just r')
473 writeIORef rref (Just r')
515 return r'
474 return r'
516− exceptionRetryPolicies =
517− skipAsyncExceptions
518− ++ [ \retryStatus -> Handler $ \e ->
519− return $ httpConfigRetryJudgeException retryStatus e
520− ]
521 r <-
475 r <-
522 retrying
476 retrying
523 httpConfigRetryPolicy
477 httpConfigRetryPolicy
524− (\retryStatus r -> return $ httpConfigRetryJudge retryStatus r)
478+ (\st r -> return $ httpConfigRetryJudge st r)
525− ( const
526− ( recovering
527− httpConfigRetryPolicy
528− exceptionRetryPolicies
529− (const openResponse)
479+ (const openResponse)
530− )
531− )
532− (preview, r') <- grabPreview httpConfigBodyPreviewLength r
480+ (preview, r') <- grabPreview bodyPreviewLength r
533 mapM_ LI.throwHttp (httpConfigCheckResponse request r' preview)
481 mapM_ LI.throwHttp (httpConfigCheckResponse request r' preview)
534 consume r'
482 consume r'
535 )
483 )
@@ -578,22 +526,22 @@ req' method url body options m = do
578 nubHeaders
526 nubHeaders
579 <> getRequestMod options
527 <> getRequestMod options
580 <> getRequestMod config
528 <> getRequestMod config
581− <> getRequestMod (Tagged body :: Tagged "body" body)
529+ <> getRequestMod (Womb body :: Womb "body" body)
582 <> getRequestMod url
530 <> getRequestMod url
583− <> getRequestMod (Tagged method :: Tagged "method" method)
531+ <> getRequestMod (Womb method :: Womb "method" method)
584 request <- finalizeRequest options request'
532 request <- finalizeRequest options request'
585 withReqManager (m request)
533 withReqManager (m request)
586
534
587-- | Perform an action using the global implicit 'L.Manager' that the rest
535-- | Perform an action using the global implicit 'L.Manager' that the rest
588−-- of the library uses. This allows us to reuse connections that the
536+-- of the library uses. This allows to reuse connections that the
589-- 'L.Manager' controls.
537-- 'L.Manager' controls.
590−withReqManager :: (MonadIO m) => (L.Manager -> m a) -> m a
538+withReqManager :: MonadIO m => (L.Manager -> m a) -> m a
591−withReqManager m = m globalManager
539+withReqManager m = liftIO (readIORef globalManager) >>= m
592
540
593−-- | The global 'L.Manager' that 'req' uses. Here we just go with the
541+-- | Global 'L.Manager' that 'req' uses. Here we just go with the default
594−-- default settings, so users don't need to deal with this manager stuff at
542+-- settings, so users don't need to deal with this manager stuff at all, but
595−-- all, but when we create a request, instance 'HttpConfig' can affect the
543+-- when we create a request, instance 'HttpConfig' can affect the default
596−-- default settings via 'getHttpConfig'.
544+-- settings via 'getHttpConfig'.
597--
545--
598-- A note about safety, in case 'unsafePerformIO' looks suspicious to you.
546-- A note about safety, in case 'unsafePerformIO' looks suspicious to you.
599-- The value of 'globalManager' is named and lives on top level. This means
547-- The value of 'globalManager' is named and lives on top level. This means
@@ -601,16 +549,16 @@ withReqManager m = m globalManager
601-- manager. From that moment on the 'IORef' will be just reused—exactly the
549-- manager. From that moment on the 'IORef' will be just reused—exactly the
602-- behavior we want here in order to maximize connection sharing. GHC could
550-- behavior we want here in order to maximize connection sharing. GHC could
603-- spoil the plan by inlining the definition, hence the @NOINLINE@ pragma.
551-- spoil the plan by inlining the definition, hence the @NOINLINE@ pragma.
604−globalManager :: L.Manager
552+globalManager :: IORef L.Manager
605globalManager = unsafePerformIO $ do
553globalManager = unsafePerformIO $ do
606 context <- NC.initConnectionContext
554 context <- NC.initConnectionContext
607 let settings =
555 let settings =
608 L.mkManagerSettingsContext
556 L.mkManagerSettingsContext
609 (Just context)
557 (Just context)
610− def
558+ (NC.TLSSettingsSimple False False False)
611 Nothing
559 Nothing
612 manager <- L.newManager settings
560 manager <- L.newManager settings
613− return manager
561+ newIORef manager
614{-# NOINLINE globalManager #-}
562{-# NOINLINE globalManager #-}
615
563
616----------------------------------------------------------------------------
564----------------------------------------------------------------------------
@@ -624,13 +572,13 @@ globalManager = unsafePerformIO $ do
624-- When writing a library, keep your API polymorphic in terms of
572-- When writing a library, keep your API polymorphic in terms of
625-- 'MonadHttp', only define instance of 'MonadHttp' in final application.
573-- 'MonadHttp', only define instance of 'MonadHttp' in final application.
626-- Another option is to use a @newtype@-wrapped monad stack and define
574-- Another option is to use a @newtype@-wrapped monad stack and define
627−-- 'MonadHttp' for it. As of the version /0.4.0/, the 'Req' monad that
575+-- 'MonadHttp' for it. As of version /0.4.0/, the 'Req' monad that follows
628−-- follows this strategy is provided out-of-the-box (see below).
576+-- this strategy is provided out-of-the-box (see below).
629
577
630-- | A type class for monads that support performing HTTP requests.
578-- | A type class for monads that support performing HTTP requests.
631-- Typically, you only need to define the 'handleHttpException' method
579-- Typically, you only need to define the 'handleHttpException' method
632-- unless you want to tweak 'HttpConfig'.
580-- unless you want to tweak 'HttpConfig'.
633−class (MonadIO m) => MonadHttp m where
581+class MonadIO m => MonadHttp m where
634 -- | This method describes how to deal with 'HttpException' that was
582 -- | This method describes how to deal with 'HttpException' that was
635 -- caught by the library. One option is to re-throw it if you are OK with
583 -- caught by the library. One option is to re-throw it if you are OK with
636 -- exceptions, but if you prefer working with something like
584 -- exceptions, but if you prefer working with something like
@@ -638,16 +586,17 @@ class (MonadIO m) => MonadHttp m where
638 -- 'Control.Monad.Except.throwError'.
586 -- 'Control.Monad.Except.throwError'.
639 handleHttpException :: HttpException -> m a
587 handleHttpException :: HttpException -> m a
640
588
641− -- | Return the 'HttpConfig' to be used when performing HTTP requests.
589+ -- | Return 'HttpConfig' to be used when performing HTTP requests. Default
642− -- Default implementation returns its 'def' value, which is described in
590+ -- implementation returns its 'def' value, which is described in the
643− -- the documentation for the type. Common usage pattern with manually
591+ -- documentation for the type. Common usage pattern with manually defined
644− -- defined 'getHttpConfig' is to return some hard-coded value, or a value
592+ -- 'getHttpConfig' is to return some hard-coded value, or a value
645 -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
593 -- extracted from 'Control.Monad.Reader.MonadReader' if a more flexible
646 -- approach to configuration is desirable.
594 -- approach to configuration is desirable.
647 getHttpConfig :: m HttpConfig
595 getHttpConfig :: m HttpConfig
648 getHttpConfig = return defaultHttpConfig
596 getHttpConfig = return defaultHttpConfig
649
597
650−-- | 'HttpConfig' contains settings to be used when making HTTP requests.
598+-- | 'HttpConfig' contains general and default settings to be used when
599+-- making HTTP requests.
651data HttpConfig = HttpConfig
600data HttpConfig = HttpConfig
652 { -- | Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
601 { -- | Proxy to use. By default values of @HTTP_PROXY@ and @HTTPS_PROXY@
653 -- environment variables are respected, this setting overwrites them.
602 -- environment variables are respected, this setting overwrites them.
@@ -691,8 +640,8 @@ data HttpConfig = HttpConfig
691 -- | The retry policy to use for request retrying. By default 'def' is
640 -- | The retry policy to use for request retrying. By default 'def' is
692 -- used (see 'RetryPolicyM').
641 -- used (see 'RetryPolicyM').
693 --
642 --
694− -- __Note__: signature of this function was changed to disallow 'IO' in
643+ -- __Note__: signature of this function was changed in the version
695− -- version /1.0.0/ and then changed back to its current form in /3.1.0/.
644+ -- /1.0.0/.
696 --
645 --
697 -- @since 0.3.0
646 -- @since 0.3.0
698 httpConfigRetryPolicy :: RetryPolicyM IO,
647 httpConfigRetryPolicy :: RetryPolicyM IO,
@@ -703,21 +652,11 @@ data HttpConfig = HttpConfig
703 -- /1.0.0/.
652 -- /1.0.0/.
704 --
653 --
705 -- @since 0.3.0
654 -- @since 0.3.0
706− httpConfigRetryJudge :: forall b. RetryStatus -> L.Response b -> Bool,
655+ httpConfigRetryJudge :: forall b. RetryStatus -> L.Response b -> Bool
707− -- | Similar to 'httpConfigRetryJudge', but is used to decide when to
708− -- retry requests that resulted in an exception. By default it retries
709− -- on response timeout and connection timeout (changed in version
710− -- /3.8.0/).
711− --
712− -- @since 3.4.0
713− httpConfigRetryJudgeException :: RetryStatus -> SomeException -> Bool,
714− -- | Max length of preview fragment of response body.
715− --
716− -- @since 3.6.0
717− httpConfigBodyPreviewLength :: forall a. (Num a) => a
718 }
656 }
657+ deriving (Typeable)
719
658
720−-- | The default value of 'HttpConfig'.
659+-- | Default value of 'HttpConfig'.
721--
660--
722-- @since 2.0.0
661-- @since 2.0.0
723defaultHttpConfig :: HttpConfig
662defaultHttpConfig :: HttpConfig
@@ -739,16 +678,7 @@ defaultHttpConfig =
739 524, -- A timeout occurred
678 524, -- A timeout occurred
740 598, -- (Informal convention) Network read timeout error
679 598, -- (Informal convention) Network read timeout error
741 599 -- (Informal convention) Network connect timeout error
680 599 -- (Informal convention) Network connect timeout error
742− ],
681+ ]
743− httpConfigRetryJudgeException = \_ e ->
744− case fromException e of
745− Just (L.HttpExceptionRequest _ c) ->
746− case c of
747− L.ResponseTimeout -> True
748− L.ConnectionTimeout -> True
749− _ -> False
750− _ -> False,
751− httpConfigBodyPreviewLength = 1024
752 }
682 }
753 where
683 where
754 statusCode = Y.statusCode . L.responseStatus
684 statusCode = Y.statusCode . L.responseStatus
@@ -761,7 +691,7 @@ instance RequestComponent HttpConfig where
761 LI.requestManagerOverride = httpConfigAltManager
691 LI.requestManagerOverride = httpConfigAltManager
762 }
692 }
763
693
764−-- | A monad that allows us to run 'req' in any 'IO'-enabled monad without
694+-- | A monad that allows to run 'req' in any 'IO'-enabled monad without
765-- having to define new instances.
695-- having to define new instances.
766--
696--
767-- @since 0.4.0
697-- @since 0.4.0
@@ -770,19 +700,9 @@ newtype Req a = Req (ReaderT HttpConfig IO a)
770 ( Functor,
700 ( Functor,
771 Applicative,
701 Applicative,
772 Monad,
702 Monad,
773− MonadIO,
703+ MonadIO
774− MonadUnliftIO
775 )
704 )
776
705
777−-- | @since 3.7.0
778−deriving instance MonadThrow Req
779−
780−-- | @since 3.7.0
781−deriving instance MonadCatch Req
782−
783−-- | @since 3.7.0
784−deriving instance MonadMask Req
785−
786instance MonadBase IO Req where
706instance MonadBase IO Req where
787 liftBase = liftIO
707 liftBase = liftIO
788
708
@@ -797,87 +717,12 @@ instance MonadHttp Req where
797 handleHttpException = Req . lift . throwIO
717 handleHttpException = Req . lift . throwIO
798 getHttpConfig = Req ask
718 getHttpConfig = Req ask
799
719
800−-- | @since 3.10.0
801−instance (MonadHttp m, Monoid w) => MonadHttp (AccumT w m) where
802− handleHttpException = lift . handleHttpException
803− getHttpConfig = lift getHttpConfig
804−
805−-- | @since 3.10.0
806−instance (MonadHttp m) => MonadHttp (ContT r m) where
807− handleHttpException = lift . handleHttpException
808− getHttpConfig = lift getHttpConfig
809−
810−-- | @since 3.10.0
811−instance (MonadHttp m) => MonadHttp (ExceptT e m) where
812− handleHttpException = lift . handleHttpException
813− getHttpConfig = lift getHttpConfig
814−
815−-- | @since 3.10.0
816−instance (MonadHttp m) => MonadHttp (IdentityT m) where
817− handleHttpException = lift . handleHttpException
818− getHttpConfig = lift getHttpConfig
819−
820−-- | @since 3.10.0
821−instance (MonadHttp m) => MonadHttp (MaybeT m) where
822− handleHttpException = lift . handleHttpException
823− getHttpConfig = lift getHttpConfig
824−
825−-- | @since 3.10.0
826−instance (MonadHttp m) => MonadHttp (ReaderT r m) where
827− handleHttpException = lift . handleHttpException
828− getHttpConfig = lift getHttpConfig
829−
830−-- | @since 3.10.0
831−instance (MonadHttp m, Monoid w) => MonadHttp (RWS.CPS.RWST r w s m) where
832− handleHttpException = lift . handleHttpException
833− getHttpConfig = lift getHttpConfig
834−
835−-- | @since 3.10.0
836−instance (MonadHttp m, Monoid w) => MonadHttp (RWS.Lazy.RWST r w s m) where
837− handleHttpException = lift . handleHttpException
838− getHttpConfig = lift getHttpConfig
839−
840−-- | @since 3.10.0
841−instance (MonadHttp m, Monoid w) => MonadHttp (RWS.Strict.RWST r w s m) where
842− handleHttpException = lift . handleHttpException
843− getHttpConfig = lift getHttpConfig
844−
845−-- | @since 3.10.0
846−instance (MonadHttp m) => MonadHttp (SelectT r m) where
847− handleHttpException = lift . handleHttpException
848− getHttpConfig = lift getHttpConfig
849−
850−-- | @since 3.10.0
851−instance (MonadHttp m) => MonadHttp (State.Lazy.StateT s m) where
852− handleHttpException = lift . handleHttpException
853− getHttpConfig = lift getHttpConfig
854−
855−-- | @since 3.10.0
856−instance (MonadHttp m) => MonadHttp (State.Strict.StateT s m) where
857− handleHttpException = lift . handleHttpException
858− getHttpConfig = lift getHttpConfig
859−
860−-- | @since 3.10.0
861−instance (MonadHttp m, Monoid w) => MonadHttp (Writer.CPS.WriterT w m) where
862− handleHttpException = lift . handleHttpException
863− getHttpConfig = lift getHttpConfig
864−
865−-- | @since 3.10.0
866−instance (MonadHttp m, Monoid w) => MonadHttp (Writer.Lazy.WriterT w m) where
867− handleHttpException = lift . handleHttpException
868− getHttpConfig = lift getHttpConfig
869−
870−-- | @since 3.10.0
871−instance (MonadHttp m, Monoid w) => MonadHttp (Writer.Strict.WriterT w m) where
872− handleHttpException = lift . handleHttpException
873− getHttpConfig = lift getHttpConfig
874−
875-- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
720-- | Run a computation in the 'Req' monad with the given 'HttpConfig'. In
876−-- the case of an exceptional situation an 'HttpException' will be thrown.
721+-- case of exceptional situation an 'HttpException' will be thrown.
877--
722--
878-- @since 0.4.0
723-- @since 0.4.0
879runReq ::
724runReq ::
880− (MonadIO m) =>
725+ MonadIO m =>
881 -- | 'HttpConfig' to use
726 -- | 'HttpConfig' to use
882 HttpConfig ->
727 HttpConfig ->
883 -- | Computation to run
728 -- | Computation to run
@@ -982,7 +827,7 @@ class HttpMethod a where
982 -- | Return name of the method as a 'ByteString'.
827 -- | Return name of the method as a 'ByteString'.
983 httpMethodName :: Proxy a -> ByteString
828 httpMethodName :: Proxy a -> ByteString
984
829
985−instance (HttpMethod method) => RequestComponent (Tagged "method" method) where
830+instance HttpMethod method => RequestComponent (Womb "method" method) where
986 getRequestMod _ = Endo $ \x ->
831 getRequestMod _ = Endo $ \x ->
987 x {L.method = httpMethodName (Proxy :: Proxy method)}
832 x {L.method = httpMethodName (Proxy :: Proxy method)}
988
833
@@ -1024,20 +869,23 @@ instance (HttpMethod method) => RequestComponent (Tagged "method" method) where
1024-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
869-- > -- https://%D1%8E%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4.%D1%80%D1%84
1025data Url (scheme :: Scheme) = Url Scheme (NonEmpty Text)
870data Url (scheme :: Scheme) = Url Scheme (NonEmpty Text)
1026 -- NOTE The second value is the path segments in reversed order.
871 -- NOTE The second value is the path segments in reversed order.
1027− deriving (Eq, Ord, Show, Data, Generic)
872+ deriving (Eq, Ord, Show, Data, Typeable, Generic)
1028
873
1029type role Url nominal
874type role Url nominal
1030
875
1031-- With template-haskell >=2.15 and text >=1.2.4 Lift can be derived, however
876-- With template-haskell >=2.15 and text >=1.2.4 Lift can be derived, however
1032−-- the derived lift forgets the type of the scheme.
877+-- the derived lift forgets the type of scheme.
1033−instance (Typeable scheme) => TH.Lift (Url scheme) where
878+instance Typeable scheme => TH.Lift (Url scheme) where
1034 lift url =
879 lift url =
1035 TH.dataToExpQ (fmap liftText . cast) url `TH.sigE` case url of
880 TH.dataToExpQ (fmap liftText . cast) url `TH.sigE` case url of
1036 Url Http _ -> [t|Url 'Http|]
881 Url Http _ -> [t|Url 'Http|]
1037 Url Https _ -> [t|Url 'Https|]
882 Url Https _ -> [t|Url 'Https|]
1038 where
883 where
1039 liftText t = TH.AppE (TH.VarE 'T.pack) <$> TH.lift (T.unpack t)
884 liftText t = TH.AppE (TH.VarE 'T.pack) <$> TH.lift (T.unpack t)
885+
886+#if MIN_VERSION_template_haskell(2,16,0)
1040− liftTyped = TH.Code . TH.unsafeTExpCoerce . TH.lift
887+ liftTyped url = TH.TExp <$> TH.lift url
888+#endif
1041
889
1042-- | Given host name, produce a 'Url' which has “http” as its scheme and
890-- | Given host name, produce a 'Url' which has “http” as its scheme and
1043-- empty path. This also sets port to @80@.
891-- empty path. This also sets port to @80@.
@@ -1049,33 +897,20 @@ http = Url Http . pure
1049https :: Text -> Url 'Https
897https :: Text -> Url 'Https
1050https = Url Https . pure
898https = Url Https . pure
1051
899
1052−-- | Grow a given 'Url' appending a single path segment to it. Note that the
900+-- | Grow given 'Url' appending a single path segment to it. Note that the
1053-- path segment can be of any type that is an instance of 'ToHttpApiData'.
901-- path segment can be of any type that is an instance of 'ToHttpApiData'.
1054infixl 5 /~
902infixl 5 /~
1055
903
1056−(/~) :: (ToHttpApiData a) => Url scheme -> a -> Url scheme
904+(/~) :: ToHttpApiData a => Url scheme -> a -> Url scheme
1057Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
905Url secure path /~ segment = Url secure (NE.cons (toUrlPiece segment) path)
1058
906
1059−-- | A type-constrained version of @('/~')@ to remove ambiguity in the cases
907+-- | Type-constrained version of @('/~')@ to remove ambiguity in the cases
1060-- when next URL piece is a 'Data.Text.Text' literal.
908-- when next URL piece is a 'Data.Text.Text' literal.
1061infixl 5 /:
909infixl 5 /:
1062
910
1063(/:) :: Url scheme -> Text -> Url scheme
911(/:) :: Url scheme -> Text -> Url scheme
1064(/:) = (/~)
912(/:) = (/~)
1065
913
1066−-- | Render a 'Url' as 'Text'.
1067−--
1068−-- @since 3.4.0
1069−renderUrl :: Url scheme -> Text
1070−renderUrl = \case
1071− Url Https parts ->
1072− "https://" <> renderParts parts
1073− Url Http parts ->
1074− "http://" <> renderParts parts
1075− where
1076− renderParts parts =
1077− T.intercalate "/" (reverse $ NE.toList parts)
1078−
1079-- | The 'useHttpURI' function provides an alternative method to get 'Url'
914-- | The 'useHttpURI' function provides an alternative method to get 'Url'
1080-- (possibly with some 'Option's) from a 'URI'. This is useful when you are
915-- (possibly with some 'Option's) from a 'URI'. This is useful when you are
1081-- given a URL to query dynamically and don't know it beforehand.
916-- given a URL to query dynamically and don't know it beforehand.
@@ -1089,7 +924,8 @@ useHttpURI uri = do
1089 urlHead <- http <$> uriHost uri
924 urlHead <- http <$> uriHost uri
1090 let url = case URI.uriPath uri of
925 let url = case URI.uriPath uri of
1091 Nothing -> urlHead
926 Nothing -> urlHead
1092− Just uriPath -> uriPathToUrl uriPath urlHead
927+ Just (_, xs) ->
928+ foldl' (/:) urlHead (URI.unRText <$> NE.toList xs)
1093 return (url, uriOptions uri)
929 return (url, uriOptions uri)
1094
930
1095-- | Just like 'useHttpURI', but expects the “https” scheme.
931-- | Just like 'useHttpURI', but expects the “https” scheme.
@@ -1101,21 +937,10 @@ useHttpsURI uri = do
1101 urlHead <- https <$> uriHost uri
937 urlHead <- https <$> uriHost uri
1102 let url = case URI.uriPath uri of
938 let url = case URI.uriPath uri of
1103 Nothing -> urlHead
939 Nothing -> urlHead
1104− Just uriPath -> uriPathToUrl uriPath urlHead
940+ Just (_, xs) ->
941+ foldl' (/:) urlHead (URI.unRText <$> NE.toList xs)
1105 return (url, uriOptions uri)
942 return (url, uriOptions uri)
1106
943
1107−-- | Convert URI path to a 'Url'. Internal.
1108−--
1109−-- @since 3.9.0
1110−uriPathToUrl ::
1111− (Bool, NonEmpty (URI.RText 'URI.PathPiece)) ->
1112− Url scheme ->
1113− Url scheme
1114−uriPathToUrl (trailingSlash, xs) urlHead =
1115− if trailingSlash then path /: T.empty else path
1116− where
1117− path = foldl' (/:) urlHead (URI.unRText <$> NE.toList xs)
1118−
1119-- | A combination of 'useHttpURI' and 'useHttpsURI' for cases when scheme
944-- | A combination of 'useHttpURI' and 'useHttpsURI' for cases when scheme
1120-- is not known in advance.
945-- is not known in advance.
1121--
946--
@@ -1232,21 +1057,21 @@ data NoReqBody = NoReqBody
1232instance HttpBody NoReqBody where
1057instance HttpBody NoReqBody where
1233 getRequestBody NoReqBody = L.RequestBodyBS B.empty
1058 getRequestBody NoReqBody = L.RequestBodyBS B.empty
1234
1059
1235−-- | This body option allows us to use a JSON object as the request
1060+-- | This body option allows to use a JSON object as request body—probably
1236−-- body—probably the most popular format right now. Just wrap a data type
1061+-- the most popular format right now. Just wrap a data type that is an
1237−-- that is an instance of 'ToJSON' type class and you are done: it will be
1062+-- instance of 'ToJSON' type class and you are done: it will be converted to
1238−-- converted to JSON and inserted as request body.
1063+-- JSON and inserted as request body.
1239--
1064--
1240-- This body option sets the @Content-Type@ header to @\"application/json;
1065-- This body option sets the @Content-Type@ header to @\"application/json;
1241-- charset=utf-8\"@ value.
1066-- charset=utf-8\"@ value.
1242newtype ReqBodyJson a = ReqBodyJson a
1067newtype ReqBodyJson a = ReqBodyJson a
1243
1068
1244−instance (ToJSON a) => HttpBody (ReqBodyJson a) where
1069+instance ToJSON a => HttpBody (ReqBodyJson a) where
1245 getRequestBody (ReqBodyJson a) = L.RequestBodyLBS (A.encode a)
1070 getRequestBody (ReqBodyJson a) = L.RequestBodyLBS (A.encode a)
1246 getRequestContentType _ = pure "application/json; charset=utf-8"
1071 getRequestContentType _ = pure "application/json; charset=utf-8"
1247
1072
1248-- | This body option streams request body from a file. It is expected that
1073-- | This body option streams request body from a file. It is expected that
1249−-- the file size does not change during streaming.
1074+-- the file size does not change during the streaming.
1250--
1075--
1251-- Using of this body option does not set the @Content-Type@ header.
1076-- Using of this body option does not set the @Content-Type@ header.
1252newtype ReqBodyFile = ReqBodyFile FilePath
1077newtype ReqBodyFile = ReqBodyFile FilePath
@@ -1271,11 +1096,12 @@ newtype ReqBodyLbs = ReqBodyLbs BL.ByteString
1271instance HttpBody ReqBodyLbs where
1096instance HttpBody ReqBodyLbs where
1272 getRequestBody (ReqBodyLbs bs) = L.RequestBodyLBS bs
1097 getRequestBody (ReqBodyLbs bs) = L.RequestBodyLBS bs
1273
1098
1274−-- | URL-encoded body. This can hold a collection of parameters which are
1099+-- | Form URL-encoded body. This can hold a collection of parameters which
1275−-- encoded similarly to query parameters at the end of query string, with
1100+-- are encoded similarly to query parameters at the end of query string,
1276−-- the only difference that they are stored in request body. The similarity
1101+-- with the only difference that they are stored in request body. The
1277−-- is reflected in the API as well, as you can use the same combinators you
1102+-- similarity is reflected in the API as well, as you can use the same
1278−-- would use to add query parameters: @('=:')@ and 'queryFlag'.
1103+-- combinators you would use to add query parameters: @('=:')@ and
1104+-- 'queryFlag'.
1279--
1105--
1280-- This body option sets the @Content-Type@ header to
1106-- This body option sets the @Content-Type@ header to
1281-- @\"application/x-www-form-urlencoded\"@ value.
1107-- @\"application/x-www-form-urlencoded\"@ value.
@@ -1294,13 +1120,6 @@ newtype FormUrlEncodedParam = FormUrlEncodedParam [(Text, Maybe Text)]
1294instance QueryParam FormUrlEncodedParam where
1120instance QueryParam FormUrlEncodedParam where
1295 queryParam name mvalue =
1121 queryParam name mvalue =
1296 FormUrlEncodedParam [(name, toQueryParam <$> mvalue)]
1122 FormUrlEncodedParam [(name, toQueryParam <$> mvalue)]
1297− queryParamToList (FormUrlEncodedParam p) = p
1298−
1299−-- | Use 'formToQuery'.
1300−--
1301−-- @since 3.11.0
1302−instance FromForm FormUrlEncodedParam where
1303− fromForm = Right . formToQuery
1304
1123
1305-- | Multipart form data. Please consult the
1124-- | Multipart form data. Please consult the
1306-- "Network.HTTP.Client.MultipartFormData" module for how to construct
1125-- "Network.HTTP.Client.MultipartFormData" module for how to construct
@@ -1340,7 +1159,7 @@ instance HttpBody ReqBodyMultipart where
1340-- | Create 'ReqBodyMultipart' request body from a collection of 'LM.Part's.
1159-- | Create 'ReqBodyMultipart' request body from a collection of 'LM.Part's.
1341--
1160--
1342-- @since 0.2.0
1161-- @since 0.2.0
1343−reqBodyMultipart :: (MonadIO m) => [LM.Part] -> m ReqBodyMultipart
1162+reqBodyMultipart :: MonadIO m => [LM.Part] -> m ReqBodyMultipart
1344reqBodyMultipart parts = liftIO $ do
1163reqBodyMultipart parts = liftIO $ do
1345 boundary <- LM.webkitBoundary
1164 boundary <- LM.webkitBoundary
1346 body <- LM.renderParts boundary parts
1165 body <- LM.renderParts boundary parts
@@ -1368,6 +1187,9 @@ type family ProvidesBody body :: CanHaveBody where
1368-- | This type function allows any HTTP body if method says it
1187-- | This type function allows any HTTP body if method says it
1369-- 'CanHaveBody'. When the method says it should have 'NoBody', the only
1188-- 'CanHaveBody'. When the method says it should have 'NoBody', the only
1370-- body option to use is 'NoReqBody'.
1189-- body option to use is 'NoReqBody'.
1190+--
1191+-- __Note__: users of GHC 8.0.1 and later will see a slightly more friendly
1192+-- error message when method does not allow a body and body is provided.
1371type family
1193type family
1372 HttpBodyAllowed
1194 HttpBodyAllowed
1373 (allowsBody :: CanHaveBody)
1195 (allowsBody :: CanHaveBody)
@@ -1380,8 +1202,8 @@ type family
1380 TypeError
1202 TypeError
1381 ('Text "This HTTP method does not allow attaching a request body.")
1203 ('Text "This HTTP method does not allow attaching a request body.")
1382
1204
1383−instance (HttpBody body) => RequestComponent (Tagged "body" body) where
1205+instance HttpBody body => RequestComponent (Womb "body" body) where
1384− getRequestMod (Tagged body) = Endo $ \x ->
1206+ getRequestMod (Womb body) = Endo $ \x ->
1385 x
1207 x
1386 { L.requestBody = getRequestBody body,
1208 { L.requestBody = getRequestBody body,
1387 L.requestHeaders =
1209 L.requestHeaders =
@@ -1410,8 +1232,8 @@ data Option (scheme :: Scheme)
1410 = Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
1232 = Option (Endo (Y.QueryText, L.Request)) (Maybe (L.Request -> IO L.Request))
1411
1233
1412-- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
1234-- NOTE 'QueryText' is just [(Text, Maybe Text)], we keep it along with
1413−-- Request to avoid appending to an existing query string in request every
1235+-- Request to avoid appending to existing query string in request every
1414−-- time new parameter is added. The additional Maybe (L.Request -> IO
1236+-- time new parameter is added. Additional Maybe (L.Request -> IO
1415-- L.Request) is a finalizer that will be applied after all other
1237-- L.Request) is a finalizer that will be applied after all other
1416-- transformations. This is for authentication methods that sign requests
1238-- transformations. This is for authentication methods that sign requests
1417-- based on data in Request.
1239-- based on data in Request.
@@ -1426,12 +1248,6 @@ instance Monoid (Option scheme) where
1426 mempty = Option mempty Nothing
1248 mempty = Option mempty Nothing
1427 mappend = (<>)
1249 mappend = (<>)
1428
1250
1429−-- | Use 'formToQuery'.
1430−--
1431−-- @since 3.11.0
1432−instance FromForm (Option scheme) where
1433− fromForm = Right . formToQuery
1434−
1435-- | A helper to create an 'Option' that modifies only collection of query
1251-- | A helper to create an 'Option' that modifies only collection of query
1436-- parameters. This helper is not a part of the public API.
1252-- parameters. This helper is not a part of the public API.
1437withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
1253withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
@@ -1450,7 +1266,7 @@ instance RequestComponent (Option scheme) where
1450
1266
1451-- | Finalize given 'L.Request' by applying a finalizer from the given
1267-- | Finalize given 'L.Request' by applying a finalizer from the given
1452-- 'Option' (if it has any).
1268-- 'Option' (if it has any).
1453−finalizeRequest :: (MonadIO m) => Option scheme -> L.Request -> m L.Request
1269+finalizeRequest :: MonadIO m => Option scheme -> L.Request -> m L.Request
1454finalizeRequest (Option _ mfinalizer) = liftIO . fromMaybe pure mfinalizer
1270finalizeRequest (Option _ mfinalizer) = liftIO . fromMaybe pure mfinalizer
1455
1271
1456----------------------------------------------------------------------------
1272----------------------------------------------------------------------------
@@ -1474,7 +1290,7 @@ infix 7 =:
1474(=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
1290(=:) :: (QueryParam param, ToHttpApiData a) => Text -> a -> param
1475name =: value = queryParam name (pure value)
1291name =: value = queryParam name (pure value)
1476
1292
1477−-- | Construct a flag, that is, a valueless query parameter. For example, in
1293+-- | Construct a flag, that is, valueless query parameter. For example, in
1478-- the following URL @\"a\"@ is a flag, while @\"b\"@ is a query parameter
1294-- the following URL @\"a\"@ is a flag, while @\"b\"@ is a query parameter
1479-- with a value:
1295-- with a value:
1480--
1296--
@@ -1483,27 +1299,9 @@ name =: value = queryParam name (pure value)
1483-- This operator is defined in terms of 'queryParam':
1299-- This operator is defined in terms of 'queryParam':
1484--
1300--
1485-- > queryFlag name = queryParam name (Nothing :: Maybe ())
1301-- > queryFlag name = queryParam name (Nothing :: Maybe ())
1486−queryFlag :: (QueryParam param) => Text -> param
1302+queryFlag :: QueryParam param => Text -> param
1487queryFlag name = queryParam name (Nothing :: Maybe ())
1303queryFlag name = queryParam name (Nothing :: Maybe ())
1488
1304
1489−-- | Construct query parameters from a 'ToForm' instance. This function
1490−-- produces the same query params as 'Form.urlEncodeAsFormStable'.
1491−--
1492−-- Note that 'Form.Form' doesn't have the concept of parameters with the
1493−-- empty value (i.e. what you can get by @key =: ""@). If the value is
1494−-- empty, it will be encoded as a valueless parameter (i.e. what you can get
1495−-- by @queryFlag key@).
1496−--
1497−-- @since 3.11.0
1498−formToQuery :: (QueryParam param, Monoid param, ToForm f) => f -> param
1499−formToQuery f = mconcat . fmap toParam . Form.toListStable $ toForm f
1500− where
1501− toParam (key, val) =
1502− queryParam key $
1503− if val == ""
1504− then Nothing
1505− else Just val
1506−
1507-- | A type class for query-parameter-like things. The reason to have an
1305-- | A type class for query-parameter-like things. The reason to have an
1508-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
1306-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
1509-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
1307-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
@@ -1513,17 +1311,11 @@ class QueryParam param where
1513 -- 'Nothing', it won't be included at all (i.e. you create a flag this
1311 -- 'Nothing', it won't be included at all (i.e. you create a flag this
1514 -- way). It's recommended to use @('=:')@ and 'queryFlag' instead of this
1312 -- way). It's recommended to use @('=:')@ and 'queryFlag' instead of this
1515 -- method, because they are easier to read.
1313 -- method, because they are easier to read.
1516− queryParam :: (ToHttpApiData a) => Text -> Maybe a -> param
1314+ queryParam :: ToHttpApiData a => Text -> Maybe a -> param
1517−
1518− -- | Get the query parameter names and values set by 'queryParam'.
1519− --
1520− -- @since 3.11.0
1521− queryParamToList :: param -> [(Text, Maybe Text)]
1522
1315
1523instance QueryParam (Option scheme) where
1316instance QueryParam (Option scheme) where
1524 queryParam name mvalue =
1317 queryParam name mvalue =
1525 withQueryParams ((:) (name, toQueryParam <$> mvalue))
1318 withQueryParams ((:) (name, toQueryParam <$> mvalue))
1526− queryParamToList (Option f _) = fst $ appEndo f ([], L.defaultRequest)
1527
1319
1528----------------------------------------------------------------------------
1320----------------------------------------------------------------------------
1529-- Request—Optional parameters—Headers
1321-- Request—Optional parameters—Headers
@@ -1547,14 +1339,6 @@ attachHeader :: ByteString -> ByteString -> L.Request -> L.Request
1547attachHeader name value x =
1339attachHeader name value x =
1548 x {L.requestHeaders = (CI.mk name, value) : L.requestHeaders x}
1340 x {L.requestHeaders = (CI.mk name, value) : L.requestHeaders x}
1549
1341
1550−-- | Same as 'header', but with redacted values on print.
1551−--
1552−-- @since 3.13.0
1553−headerRedacted :: ByteString -> ByteString -> Option scheme
1554−headerRedacted name value = withRequest $ \x ->
1555− let y = attachHeader name value x
1556− in y {L.redactHeaders = CI.mk name `S.insert` L.redactHeaders y}
1557−
1558----------------------------------------------------------------------------
1342----------------------------------------------------------------------------
1559-- Request—Optional parameters—Cookies
1343-- Request—Optional parameters—Cookies
1560
1344
@@ -1696,7 +1480,7 @@ customAuth = Option mempty . pure
1696
1480
1697-- | Specify the port to connect to explicitly. Normally, 'Url' you use
1481-- | Specify the port to connect to explicitly. Normally, 'Url' you use
1698-- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
1482-- determines the default port: @80@ for HTTP and @443@ for HTTPS. This
1699−-- 'Option' allows us to choose an arbitrary port overwriting the defaults.
1483+-- 'Option' allows to choose an arbitrary port overwriting the defaults.
1700port :: Int -> Option scheme
1484port :: Int -> Option scheme
1701port n = withRequest $ \x ->
1485port n = withRequest $ \x ->
1702 x {L.port = n}
1486 x {L.port = n}
@@ -1711,8 +1495,8 @@ port n = withRequest $ \x ->
1711--
1495--
1712-- > decompress (const True)
1496-- > decompress (const True)
1713decompress ::
1497decompress ::
1714− -- | Predicate that is given MIME type, it returns 'True' when content
1498+ -- | Predicate that is given MIME type, it
1715− -- should be decompressed on the fly.
1499+ -- returns 'True' when content should be decompressed on the fly.
1716 (ByteString -> Bool) ->
1500 (ByteString -> Bool) ->
1717 Option scheme
1501 Option scheme
1718decompress f = withRequest $ \x ->
1502decompress f = withRequest $ \x ->
@@ -1762,7 +1546,7 @@ ignoreResponse = Proxy
1762newtype JsonResponse a = JsonResponse (L.Response a)
1546newtype JsonResponse a = JsonResponse (L.Response a)
1763 deriving (Show)
1547 deriving (Show)
1764
1548
1765−instance (FromJSON a) => HttpResponse (JsonResponse a) where
1549+instance FromJSON a => HttpResponse (JsonResponse a) where
1766 type HttpResponseBody (JsonResponse a) = a
1550 type HttpResponseBody (JsonResponse a) = a
1767 toVanillaResponse (JsonResponse r) = r
1551 toVanillaResponse (JsonResponse r) = r
1768 getHttpResponse r = do
1552 getHttpResponse r = do
@@ -1814,7 +1598,7 @@ lbsResponse = Proxy
1814----------------------------------------------------------------------------
1598----------------------------------------------------------------------------
1815-- Helpers for response interpretations
1599-- Helpers for response interpretations
1816
1600
1817−-- | Fetch beginning of the response and return it together with a new
1601+-- | Fetch beginning of response and return it together with new
1818-- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
1602-- @'L.Response' 'L.BodyReader'@ that can be passed to 'getHttpResponse' and
1819-- such.
1603-- such.
1820grabPreview ::
1604grabPreview ::
@@ -1878,14 +1662,14 @@ brReadN br n = go 0 id id
1878
1662
1879-- | Get the response body.
1663-- | Get the response body.
1880responseBody ::
1664responseBody ::
1881− (HttpResponse response) =>
1665+ HttpResponse response =>
1882 response ->
1666 response ->
1883 HttpResponseBody response
1667 HttpResponseBody response
1884responseBody = L.responseBody . toVanillaResponse
1668responseBody = L.responseBody . toVanillaResponse
1885
1669
1886-- | Get the response status code.
1670-- | Get the response status code.
1887responseStatusCode ::
1671responseStatusCode ::
1888− (HttpResponse response) =>
1672+ HttpResponse response =>
1889 response ->
1673 response ->
1890 Int
1674 Int
1891responseStatusCode =
1675responseStatusCode =
@@ -1893,7 +1677,7 @@ responseStatusCode =
1893
1677
1894-- | Get the response status message.
1678-- | Get the response status message.
1895responseStatusMessage ::
1679responseStatusMessage ::
1896− (HttpResponse response) =>
1680+ HttpResponse response =>
1897 response ->
1681 response ->
1898 ByteString
1682 ByteString
1899responseStatusMessage =
1683responseStatusMessage =
@@ -1901,7 +1685,7 @@ responseStatusMessage =
1901
1685
1902-- | Lookup a particular header from a response.
1686-- | Lookup a particular header from a response.
1903responseHeader ::
1687responseHeader ::
1904− (HttpResponse response) =>
1688+ HttpResponse response =>
1905 -- | Response interpretation
1689 -- | Response interpretation
1906 response ->
1690 response ->
1907 -- | Header to lookup
1691 -- | Header to lookup
@@ -1913,7 +1697,7 @@ responseHeader r h =
1913
1697
1914-- | Get the response 'L.CookieJar'.
1698-- | Get the response 'L.CookieJar'.
1915responseCookieJar ::
1699responseCookieJar ::
1916− (HttpResponse response) =>
1700+ HttpResponse response =>
1917 response ->
1701 response ->
1918 L.CookieJar
1702 L.CookieJar
1919responseCookieJar = L.responseCookieJar . toVanillaResponse
1703responseCookieJar = L.responseCookieJar . toVanillaResponse
@@ -1926,9 +1710,9 @@ responseCookieJar = L.responseCookieJar . toVanillaResponse
1926-- To create a new response interpretation you just need to make your data
1710-- To create a new response interpretation you just need to make your data
1927-- type an instance of the 'HttpResponse' type class.
1711-- type an instance of the 'HttpResponse' type class.
1928
1712
1929−-- | A type class for response interpretations. It allows us to describe how
1713+-- | A type class for response interpretations. It allows to describe how to
1930−-- to consume the response from a @'L.Response' 'L.BodyReader'@ and produce
1714+-- consume response from a @'L.Response' 'L.BodyReader'@ and produce the
1931−-- the final result that is to be returned to the user.
1715+-- final result that is to be returned to the user.
1932class HttpResponse response where
1716class HttpResponse response where
1933 -- | The associated type is the type of body that can be extracted from an
1717 -- | The associated type is the type of body that can be extracted from an
1934 -- instance of 'HttpResponse'.
1718 -- instance of 'HttpResponse'.
@@ -1967,16 +1751,6 @@ class HttpResponse response where
1967 acceptHeader :: Proxy response -> Maybe ByteString
1751 acceptHeader :: Proxy response -> Maybe ByteString
1968 acceptHeader Proxy = Nothing
1752 acceptHeader Proxy = Nothing
1969
1753
1970−-- | This instance has been added to make it easier to inspect 'L.Response'
1971−-- using Req's functions like 'responseStatusCode', 'responseStatusMessage',
1972−-- etc.
1973−--
1974−-- @since 3.12.0
1975−instance HttpResponse (L.Response ()) where
1976− type HttpResponseBody (L.Response ()) = ()
1977− toVanillaResponse = id
1978− getHttpResponse = return . void
1979−
1980----------------------------------------------------------------------------
1754----------------------------------------------------------------------------
1981-- Other
1755-- Other
1982
1756
@@ -2002,7 +1776,7 @@ class RequestComponent a where
2002-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
1776-- method@ and @'HttpBody' body => 'RequestComponent' body@ when it decides
2003-- which instance to use (i.e. the constraints are taken into account later,
1777-- which instance to use (i.e. the constraints are taken into account later,
2004-- when instance is already chosen).
1778-- when instance is already chosen).
2005−newtype Tagged (tag :: Symbol) a = Tagged a
1779+newtype Womb (tag :: Symbol) a = Womb a
2006
1780
2007-- | Exceptions that this library throws.
1781-- | Exceptions that this library throws.
2008data HttpException
1782data HttpException
@@ -2011,24 +1785,10 @@ data HttpException
2011 | -- | A wrapper with Aeson-produced 'String' describing why decoding
1785 | -- | A wrapper with Aeson-produced 'String' describing why decoding
2012 -- failed
1786 -- failed
2013 JsonHttpException String
1787 JsonHttpException String
2014− deriving (Show, Generic)
1788+ deriving (Show, Typeable, Generic)
2015
1789
2016instance Exception HttpException
1790instance Exception HttpException
2017
1791
2018−-- | Return 'Just' if the given 'HttpException' is wrapping a http-client's
2019−-- 'L.StatusCodeException'. Otherwise, return 'Nothing'.
2020−--
2021−-- @since 3.12.0
2022−isStatusCodeException :: HttpException -> Maybe (L.Response ())
2023−isStatusCodeException
2024− ( VanillaHttpException
2025− ( L.HttpExceptionRequest
2026− _
2027− (L.StatusCodeException r _)
2028− )
2029− ) = Just r
2030−isStatusCodeException _ = Nothing
2031−
2032-- | A simple type isomorphic to 'Bool' that we only have for better error
1792-- | A simple type isomorphic to 'Bool' that we only have for better error
2033-- messages. We use it as a kind and its data constructors as type-level
1793-- messages. We use it as a kind and its data constructors as type-level
2034-- tags.
1794-- tags.
@@ -2048,4 +1808,11 @@ data Scheme
2048 Http
1808 Http
2049 | -- | HTTPS
1809 | -- | HTTPS
2050 Https
1810 Https
2051− deriving (Eq, Ord, Show, Data, Generic, TH.Lift)
1811+ deriving (Eq, Ord, Show, Data, Typeable, Generic, TH.Lift)
1812+
1813+----------------------------------------------------------------------------
1814+-- Constants
1815+
1816+-- | Max length of preview fragment of response body.
1817+bodyPreviewLength :: Num a => a
1818+bodyPreviewLength = 1024
README.md+124−28
@@ -4,8 +4,10 @@
4[![Hackage](https://img.shields.io/hackage/v/req.svg?style=flat)](https://hackage.haskell.org/package/req)
4[![Hackage](https://img.shields.io/hackage/v/req.svg?style=flat)](https://hackage.haskell.org/package/req)
5[![Stackage Nightly](http://stackage.org/package/req/badge/nightly)](http://stackage.org/nightly/package/req)
5[![Stackage Nightly](http://stackage.org/package/req/badge/nightly)](http://stackage.org/nightly/package/req)
6[![Stackage LTS](http://stackage.org/package/req/badge/lts)](http://stackage.org/lts/package/req)
6[![Stackage LTS](http://stackage.org/package/req/badge/lts)](http://stackage.org/lts/package/req)
7−[![CI](https://github.com/mrkkrp/req/actions/workflows/ci.yaml/badge.svg)](https://github.com/mrkkrp/req/actions/workflows/ci.yaml)
7+[![Build Status](https://travis-ci.org/mrkkrp/req.svg?branch=master)](https://travis-ci.org/mrkkrp/req)
8
8
9+* [Motivation and Req vs other libraries](#motivation-and-req-vs-other-libraries)
10+* [Unsolved problems](#unsolved-problems)
9* [Related packages](#related-packages)
11* [Related packages](#related-packages)
10* [Blog posts](#blog-posts)
12* [Blog posts](#blog-posts)
11* [Contribution](#contribution)
13* [Contribution](#contribution)
@@ -41,33 +43,49 @@ main = runReq defaultHttpConfig $ do
41 liftIO $ print (responseBody r :: Value)
43 liftIO $ print (responseBody r :: Value)
42```
44```
43
45
44−Req is an HTTP client library that attempts to be easy-to-use, type-safe,
46+Req is an easy-to-use, type-safe, expandable, high-level HTTP client library
45−and expandable.
47+that just works without any fooling around.
46−
48+
47−“Easy-to-use” means that the library is designed to be beginner-friendly so
49+What does the phrase “easy-to-use” mean? It means that the library is
48−it's simple to add to your monad stack, intuitive to work with,
50+designed to be beginner-friendly so it's simple to add to your monad stack,
49−well-documented, and does not get in your way. Doing HTTP requests is a
51+intuitive to work with, well-documented, and does not get in your way. Doing
50−common task and a Haskell library for this should be approachable and clear
52+HTTP requests is a common task and Haskell library for this should be very
51−to beginners, thus certain compromises were made. For example, one cannot
53+approachable and clear to beginners, thus certain compromises were made. For
52−currently modify `ManagerSettings` of the default manager because the
54+example, one cannot currently modify `ManagerSettings` of the default
53−library always uses the same implicit global manager for simplicity and
55+manager because the library always uses the same implicit global manager for
54−maximal connection sharing. There is a way to use your own manager with
56+simplicity and maximal connection sharing. There is a way to use your own
55−different settings, but it requires more typing.
57+manager with different settings, but it requires a bit more typing.
56−
58+
57−“Type-safe” means that the library tries to eliminate certain classes of
59+“Type-safe” means that the library is protective and eliminates certain
58−errors. For example, we have correct-by-construction URLs; it is guaranteed
60+classes of errors. For example, we have correct-by-construction URLs, it's
59−that the user does not send the request body when using methods like GET or
61+guaranteed that the user does not send the request body when using methods
60−OPTIONS, and the amount of implicit assumptions is minimized by making the
62+like GET or OPTIONS, and the amount of implicit assumptions is minimized by
61−user specify their intentions in an explicit form. For example, it's not
63+making the user specify his/her intentions in an explicit form (for example,
62−possible to avoid specifying the body or the method of a request.
64+it's not possible to avoid specifying the body or method of request).
63Authentication methods that assume HTTPS force the user to use HTTPS at the
65Authentication methods that assume HTTPS force the user to use HTTPS at the
64−type level.
66+type level. The library also carefully hides underlying types from the
65−
67+lower-level `http-client` package because those types are not safe enough
68+(for example `Request` is an instance of `IsString` and, if it's malformed,
69+it will blow up at run-time).
70+
66−“Expandable” refers to the ability to create new components without having
71+“Expandable” refers to the ability to create new components for dealing with
67−to resort to hacking. For example, it's possible to define your own HTTP
72+HTTP without having to resort to ugly hacking. For example, it's possible to
68−methods, create new ways to construct the body of a request, create new
73+define your own HTTP methods, create new ways to construct the body of a
69−authorization options, perform a request in a different way, and create your
74+request, create new authorization options, perform a request in a different
70−own methods to parse a response.
75+way, and create your own methods to parse and represent a response. As the
76+user extends the library to satisfy his/her special needs, the new solutions
77+will work just like the built-ins. However, all of the common cases are also
78+covered by the library out-of-the-box.
79+
80+“High-level” means that there are less details to worry about. The library
81+is a result of my experiences as a Haskell consultant. Working for several
82+clients, who had very different projects, showed me that the library should
83+adapt easily to any particular style of writing Haskell applications. For
84+example, some people prefer throwing exceptions, while others are concerned
85+with purity. Just define `handleHttpException` accordingly when making your
86+monad instance of `MonadHttp` and it will play together seamlessly. Finally,
87+the library cuts down boilerplate considerably, and helps you write concise,
88+easy to read, and maintainable code.
71
89
72The library uses the following mature packages under the hood to guarantee
90The library uses the following mature packages under the hood to guarantee
73you the best experience:
91you the best experience:
@@ -77,11 +95,89 @@ you the best experience:
77* [`http-client-tls`](https://hackage.haskell.org/package/http-client-tls)—TLS
95* [`http-client-tls`](https://hackage.haskell.org/package/http-client-tls)—TLS
78 (HTTPS) support for `http-client`.
96 (HTTPS) support for `http-client`.
79
97
80−It is important to note that since we leverage well-known libraries that the
98+It's important to note that since we leverage well-known libraries that the
81whole Haskell ecosystem uses, there is no risk in using Req. The machinery
99whole Haskell ecosystem uses, there is no risk in using Req. The machinery
82for performing requests is the same as with `http-conduit` and Wreq. The
100for performing requests is the same as with `http-conduit` and Wreq. The
83only difference is the API.
101only difference is the API.
84
102
103+## Motivation and Req vs other libraries
104+
105+*This section is my opinion and it contains criticisms of other well-known
106+libraries. If you're a user/fan of one of these libraries, please remember
107+not to react aggressively and respect the fact that I may have different
108+views on API design from yours.*
109+
110+I have spent time to write the library because sending HTTP requests is a
111+common need, but there is no high-level library for that in Haskell that I
112+could use with pleasure. I'll explain why.
113+
114+First of all, there is `http-client` and `http-client-tls`. They just work.
115+I have no issues with the libraries except that they are too low-level for
116+my taste. Indeed, even the docs say that they are low-level and “intended as
117+a base layer for more user-friendly packages”. This is exactly how I use
118+them in Req, as base level. Req is nothing but a different API to
119+`http-client`, so it only works because of the hard work put into
120+`http-client`.
121+
122+`http-conduit` definitely has its place. For one thing it allows you to
123+stream request and response bodies in constant memory, what other library
124+allows you to do that? On the other hand if you take a look at
125+`Network.HTTP.Simple`, then although it's said that it's a “higher level
126+API”, it's mostly the same as vanilla `http-client` in spirit/approach and
127+just adds `conduit`-powered functions to perform requests and allows to use
128+global implicit `Manager` (Req does the same). If I tried to frame what
129+exactly I don't like about `http-conduit` in words, then it would be “the
130+way requests are constructed”. You *set* parameters instead of *being
131+forced* to declare necessary bits and *being allowed* to declare optional
132+bits in a way that their combination is valid. Also, with `http-conduit` you
133+parse request from a string without the protection of TH that otherwise
134+saves the day as in Yesod.
135+
136+Then there is Wreq. `wreq` [doesn't see much development
137+lately](https://github.com/bos/wreq/issues/93). `wreq` is by itself a weird
138+library, IMO. You have functions per method—not very good, as there may be
139+new methods, like PATCH which is not new but still missing (well, you have
140+`customMethod`, but what is the point of having per-method functions if you
141+have a more general way to use any method? you should be able to just insert
142+methods in the “argument slot” of `customMethod` and end up with a more
143+general solution). Now, every method function has a companion that takes
144+`Options` (like you have `get` and `getWith`). Why the duplication? Where is
145+generality and flexibility? This is not all though, because you cannot
146+really use `get` you see in the main module, because you want to have
147+connection sharing. Wreq's author does not take the gift of automatic
148+connection re-use `Manager` from `http-client` provides, he invents the
149+whole new thing of “sessions”. Only inside a session your connections will
150+be shared and re-used. However with the session stuff you have yet another
151+set of per-method functions like `get` and `getWith`—these are different
152+ones, to be used with sessions! Now if you have a multi-threaded app, here
153+is a surprise for you: you can't share connections between threads as
154+connections are shared only inside of the `withSession` friend and “session
155+will no longer be valid after that function returns”. There are valid uses
156+for sessions, but the point is that they are just too inconvenient for
157+common tasks.
158+
159+I used `servant-client` a couple of times but the amount of boilerplate it
160+requires is frightening. If you have several query parameters, and you use
161+just one of them, you'll have to pass lots of `Nothing`s.
162+
163+## Unsolved problems
164+
165+AWS request signing is problematic because request body can be in the form
166+of an action to execute (and all that “popper” stuff for streaming), not
167+just a `ByteString` and so getting its digest (hash) is not trivial without
168+running the action and consuming body in its entirety before the request in
169+made. In Wreq the author chose to just use `error` when body is not a
170+(strict or lazy) `ByteString`. Maybe it's OK for Wreq, but I don't consider
171+this a proper solution for Req as we support full variety of body options.
172+For example, what if I want to upload 1 Gb file to S3? I want to stream it
173+in constant memory but at the same time I need to calculate its hash before
174+I start streaming. One solution to the problem seems to be in taking the
175+hash explicitly (as an argument of the hypothetical `awsAuth`) and making it
176+a responsibility of the user to calculate the hash correctly. I don't like
177+this because it's not user-friendly. So the question stays open, for now
178+there is no AWS signing functionality provided out-of-the-box. The best
179+solution for talking to AWS is the `amazonka` package so far.
180+
85## Related packages
181## Related packages
86
182
87The following packages are designed to be used with Req:
183The following packages are designed to be used with Req:
Setup.hsnew file+6
@@ -0,0 +1,6 @@
1+module Main (main) where
2+
3+import Distribution.Simple
4+
5+main :: IO ()
6+main = defaultMain
cabal.projectdeleted−4
@@ -1,4 +0,0 @@
1−packages: .
2−tests: True
3−benchmarks: True
4−constraints: req +dev
httpbin-tests/Network/HTTP/ReqSpec.hs+137−160
@@ -1,6 +1,6 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE FlexibleInstances #-}
3−{-# LANGUAGE LambdaCase #-}
4{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE RankNTypes #-}
5{-# LANGUAGE RankNTypes #-}
6{-# OPTIONS_GHC -fno-warn-orphans #-}
6{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -8,55 +8,53 @@
8module Network.HTTP.ReqSpec (spec) where
8module Network.HTTP.ReqSpec (spec) where
9
9
10import Control.Exception
10import Control.Exception
11−import Control.Monad (forM_)
11+import Control.Monad.Reader
12import Control.Monad.Trans.Control
12import Control.Monad.Trans.Control
13−import Data.Aeson (ToJSON (..), Value (..), object, (.=))
13+import Data.Aeson ((.=), ToJSON (..), Value (..), object)
14−import Data.Aeson qualified as A
14+import qualified Data.Aeson as A
15−import Data.Aeson.KeyMap qualified as Aeson.KeyMap
16−import Data.ByteString qualified as B
15+import qualified Data.ByteString as B
17−import Data.ByteString.Lazy qualified as BL
16+import qualified Data.ByteString.Lazy as BL
18−import Data.Functor.Identity (runIdentity)
17+import qualified Data.HashMap.Strict as HM
19−import Data.Maybe (fromJust)
20import Data.Proxy
18import Data.Proxy
21import Data.Text (Text)
19import Data.Text (Text)
22−import Data.Text qualified as T
20+import qualified Data.Text as T
23−import Data.Text.Encoding qualified as T
21+import qualified Data.Text.Encoding as T
24−import Data.Text.IO qualified as TIO
22+import qualified Data.Text.IO as TIO
25−import Network.HTTP.Client qualified as L
23+import qualified Network.HTTP.Client as L
26−import Network.HTTP.Client.MultipartFormData qualified as LM
24+import qualified Network.HTTP.Client.MultipartFormData as LM
27−import Network.HTTP.Req hiding (req)
25+import Network.HTTP.Req
28−import Network.HTTP.Req qualified as Req
29−import Network.HTTP.Types qualified as Y
26+import qualified Network.HTTP.Types as Y
30import Test.Hspec
27import Test.Hspec
31import Test.QuickCheck
28import Test.QuickCheck
32
29
30+#if !MIN_VERSION_base(4,13,0)
31+import Data.Semigroup ((<>))
32+#endif
33+
33spec :: Spec
34spec :: Spec
34spec = do
35spec = do
35− describe "exception throwing on non-2xx status codes" $
36+ describe "exception throwing on non-2xx status codes"
37+ $ it "throws indeed for non-2xx"
38+ $ req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty
39+ `shouldThrow` selector404
40+
41+ describe "exception throwing on non-2xx status codes (Req monad)"
36− it "throws indeed for non-2xx" $
42+ $ it "throws indeed for non-2xx"
43+ $ asIO . runReq defaultHttpConfig
44+ $ liftBaseWith
45+ $ \run ->
37− req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty
46+ run (req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty)
38 `shouldThrow` selector404
47 `shouldThrow` selector404
39
48
40− describe "exception throwing on non-2xx status codes (Req monad)" $
41− it "throws indeed for non-2xx" $
42− asIO . runReq defaultHttpConfig $
43− liftBaseWith $ \run ->
44− run (req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty)
45− `shouldThrow` selector404
46−
47− describe "response check via httpConfigCheckResponse" $
49+ describe "response check via httpConfigCheckResponse"
48− context "if it's set to always throw" $
50+ $ context "if it's set to always throw"
49− it "throws indeed" $
51+ $ it "throws indeed"
50− blindlyThrowing (req GET httpbin NoReqBody ignoreResponse mempty)
52+ $ blindlyThrowing (req GET httpbin NoReqBody ignoreResponse mempty)
51− `shouldThrow` anyException
53+ `shouldThrow` anyException
52−
54+
53− describe "isStatusCodeException" $
54− it "extracts non-2xx response" $
55− req GET (httpbin /: "foo") NoReqBody ignoreResponse mempty
56− `shouldThrow` selector404ByStatusCodeException
57−
58− describe "receiving user-agent header back" $
55+ describe "receiving user-agent header back"
59− it "works" $ do
56+ $ it "works"
57+ $ do
60 r <-
58 r <-
61 req
59 req
62 GET
60 GET
@@ -70,8 +68,9 @@ spec = do
70 responseStatusCode r `shouldBe` 200
68 responseStatusCode r `shouldBe` 200
71 responseStatusMessage r `shouldBe` "OK"
69 responseStatusMessage r `shouldBe` "OK"
72
70
73− describe "receiving request headers back" $
71+ describe "receiving request headers back"
74− it "works" $ do
72+ $ it "works"
73+ $ do
75 r <-
74 r <-
76 req
75 req
77 GET
76 GET
@@ -85,30 +84,34 @@ spec = do
85 .= object
84 .= object
86 [ "Accept-Encoding" .= ("gzip" :: Text),
85 [ "Accept-Encoding" .= ("gzip" :: Text),
87 "Foo" .= ("bar" :: Text),
86 "Foo" .= ("bar" :: Text),
88− "Baz" .= ("quux" :: Text)
87+ "Baz" .= ("quux" :: Text),
88+ "Host" .= ("httpbin.org" :: Text)
89 ]
89 ]
90 ]
90 ]
91 responseStatusCode r `shouldBe` 200
91 responseStatusCode r `shouldBe` 200
92 responseStatusMessage r `shouldBe` "OK"
92 responseStatusMessage r `shouldBe` "OK"
93
93
94− describe "receiving GET data back" $
94+ describe "receiving GET data back"
95− it "works" $ do
95+ $ it "works"
96+ $ do
96 r <- req GET (httpbin /: "get") NoReqBody jsonResponse mempty
97 r <- req GET (httpbin /: "get") NoReqBody jsonResponse mempty
97 (stripFunnyHeaders . stripOrigin) (responseBody r)
98 (stripFunnyHeaders . stripOrigin) (responseBody r)
98 `shouldBe` object
99 `shouldBe` object
99 [ "args" .= emptyObject,
100 [ "args" .= emptyObject,
100− "url" .= ("http://localhost:1234/get" :: Text),
101+ "url" .= ("https://httpbin.org/get" :: Text),
101 "headers"
102 "headers"
102 .= object
103 .= object
103− [ "Accept-Encoding" .= ("gzip" :: Text)
104+ [ "Accept-Encoding" .= ("gzip" :: Text),
105+ "Host" .= ("httpbin.org" :: Text)
104 ]
106 ]
105 ]
107 ]
106 responseHeader r "Content-Type" `shouldBe` return "application/json"
108 responseHeader r "Content-Type" `shouldBe` return "application/json"
107 responseStatusCode r `shouldBe` 200
109 responseStatusCode r `shouldBe` 200
108 responseStatusMessage r `shouldBe` "OK"
110 responseStatusMessage r `shouldBe` "OK"
109
111
110− describe "receiving POST JSON data back" $
112+ describe "receiving POST JSON data back"
111− it "works" $ do
113+ $ it "works"
114+ $ do
112 let text = "foo" :: Text
115 let text = "foo" :: Text
113 reflected = reflectJSON text
116 reflected = reflectJSON text
114 r <- req POST (httpbin /: "post") (ReqBodyJson text) jsonResponse mempty
117 r <- req POST (httpbin /: "post") (ReqBodyJson text) jsonResponse mempty
@@ -117,11 +120,12 @@ spec = do
117 [ "args" .= emptyObject,
120 [ "args" .= emptyObject,
118 "json" .= text,
121 "json" .= text,
119 "data" .= reflected,
122 "data" .= reflected,
120− "url" .= ("http://localhost:1234/post" :: Text),
123+ "url" .= ("https://httpbin.org/post" :: Text),
121 "headers"
124 "headers"
122 .= object
125 .= object
123 [ "Content-Type" .= ("application/json; charset=utf-8" :: Text),
126 [ "Content-Type" .= ("application/json; charset=utf-8" :: Text),
124 "Accept-Encoding" .= ("gzip" :: Text),
127 "Accept-Encoding" .= ("gzip" :: Text),
128+ "Host" .= ("httpbin.org" :: Text),
125 "Content-Length" .= show (T.length reflected)
129 "Content-Length" .= show (T.length reflected)
126 ],
130 ],
127 "files" .= emptyObject,
131 "files" .= emptyObject,
@@ -131,25 +135,27 @@ spec = do
131 responseStatusCode r `shouldBe` 200
135 responseStatusCode r `shouldBe` 200
132 responseStatusMessage r `shouldBe` "OK"
136 responseStatusMessage r `shouldBe` "OK"
133
137
134− describe "receiving POST data back (multipart form data)" $
138+ describe "receiving POST data back (multipart form data)"
135− it "works" $ do
139+ $ it "works"
140+ $ do
136 body <-
141 body <-
137 reqBodyMultipart
142 reqBodyMultipart
138 [ LM.partBS "foo" "foo data!",
143 [ LM.partBS "foo" "foo data!",
139 LM.partBS "bar" "bar data!"
144 LM.partBS "bar" "bar data!"
140 ]
145 ]
141 r <- req POST (httpbin /: "post") body jsonResponse mempty
146 r <- req POST (httpbin /: "post") body jsonResponse mempty
142− let contentType = fromJust (getRequestContentType body)
147+ let Just contentType = getRequestContentType body
143 (stripFunnyHeaders . stripOrigin) (responseBody r)
148 (stripFunnyHeaders . stripOrigin) (responseBody r)
144 `shouldBe` object
149 `shouldBe` object
145 [ "args" .= emptyObject,
150 [ "args" .= emptyObject,
146 "json" .= Null,
151 "json" .= Null,
147 "data" .= ("" :: Text),
152 "data" .= ("" :: Text),
148− "url" .= ("http://localhost:1234/post" :: Text),
153+ "url" .= ("https://httpbin.org/post" :: Text),
149 "headers"
154 "headers"
150 .= object
155 .= object
151 [ "Content-Type" .= T.decodeUtf8 contentType,
156 [ "Content-Type" .= T.decodeUtf8 contentType,
152 "Accept-Encoding" .= ("gzip" :: Text),
157 "Accept-Encoding" .= ("gzip" :: Text),
158+ "Host" .= ("httpbin.org" :: Text),
153 "Content-Length" .= ("242" :: Text)
159 "Content-Length" .= ("242" :: Text)
154 ],
160 ],
155 "files" .= emptyObject,
161 "files" .= emptyObject,
@@ -163,8 +169,9 @@ spec = do
163 responseStatusCode r `shouldBe` 200
169 responseStatusCode r `shouldBe` 200
164 responseStatusMessage r `shouldBe` "OK"
170 responseStatusMessage r `shouldBe` "OK"
165
171
166− describe "receiving PATCHed file back" $
172+ describe "receiving PATCHed file back"
167− it "works" $ do
173+ $ it "works"
174+ $ do
168 let file :: FilePath
175 let file :: FilePath
169 file = "httpbin-data/robots.txt"
176 file = "httpbin-data/robots.txt"
170 contents <- TIO.readFile file
177 contents <- TIO.readFile file
@@ -174,10 +181,11 @@ spec = do
174 [ "args" .= emptyObject,
181 [ "args" .= emptyObject,
175 "json" .= Null,
182 "json" .= Null,
176 "data" .= contents,
183 "data" .= contents,
177− "url" .= ("http://localhost:1234/patch" :: Text),
184+ "url" .= ("https://httpbin.org/patch" :: Text),
178 "headers"
185 "headers"
179 .= object
186 .= object
180 [ "Accept-Encoding" .= ("gzip" :: Text),
187 [ "Accept-Encoding" .= ("gzip" :: Text),
188+ "Host" .= ("httpbin.org" :: Text),
181 "Content-Length" .= show (T.length contents)
189 "Content-Length" .= show (T.length contents)
182 ],
190 ],
183 "files" .= emptyObject,
191 "files" .= emptyObject,
@@ -187,8 +195,9 @@ spec = do
187 responseStatusCode r `shouldBe` 200
195 responseStatusCode r `shouldBe` 200
188 responseStatusMessage r `shouldBe` "OK"
196 responseStatusMessage r `shouldBe` "OK"
189
197
190− describe "receiving PUT form URL-encoded data back" $
198+ describe "receiving PUT form URL-encoded data back"
191− it "works" $ do
199+ $ it "works"
200+ $ do
192 let params =
201 let params =
193 "foo" =: ("bar" :: Text)
202 "foo" =: ("bar" :: Text)
194 <> "baz" =: (5 :: Int)
203 <> "baz" =: (5 :: Int)
@@ -199,11 +208,12 @@ spec = do
199 [ "args" .= emptyObject,
208 [ "args" .= emptyObject,
200 "json" .= Null,
209 "json" .= Null,
201 "data" .= ("" :: Text),
210 "data" .= ("" :: Text),
202− "url" .= ("http://localhost:1234/put" :: Text),
211+ "url" .= ("https://httpbin.org/put" :: Text),
203 "headers"
212 "headers"
204 .= object
213 .= object
205 [ "Content-Type" .= ("application/x-www-form-urlencoded" :: Text),
214 [ "Content-Type" .= ("application/x-www-form-urlencoded" :: Text),
206 "Accept-Encoding" .= ("gzip" :: Text),
215 "Accept-Encoding" .= ("gzip" :: Text),
216+ "Host" .= ("httpbin.org" :: Text),
207 "Content-Length" .= ("18" :: Text)
217 "Content-Length" .= ("18" :: Text)
208 ],
218 ],
209 "files" .= emptyObject,
219 "files" .= emptyObject,
@@ -220,8 +230,9 @@ spec = do
220
230
221 -- TODO /delete
231 -- TODO /delete
222
232
223− describe "receiving UTF-8 encoded Unicode data" $
233+ describe "receiving UTF-8 encoded Unicode data"
224− it "works" $ do
234+ $ it "works"
235+ $ do
225 r <-
236 r <-
226 req
237 req
227 GET
238 GET
@@ -237,8 +248,9 @@ spec = do
237 -- TODO /gzip
248 -- TODO /gzip
238 -- TODO /deflate
249 -- TODO /deflate
239
250
240− describe "retrying" $
251+ describe "retrying"
241− it "retries as many times as specified" $ do
252+ $ it "retries as many times as specified"
253+ $ do
242 -- FIXME We no longer can count retries because all the functions
254 -- FIXME We no longer can count retries because all the functions
243 -- responsible for controlling retrying are pure now.
255 -- responsible for controlling retrying are pure now.
244 let status = 408 :: Int
256 let status = 408 :: Int
@@ -261,8 +273,9 @@ spec = do
261 -- TODO /response-headers
273 -- TODO /response-headers
262 -- TODO /redirect
274 -- TODO /redirect
263
275
264− describe "redirects" $
276+ describe "redirects"
265− it "follows redirects" $ do
277+ $ it "follows redirects"
278+ $ do
266 r <-
279 r <-
267 req
280 req
268 GET
281 GET
@@ -281,8 +294,9 @@ spec = do
281 let user, password :: Text
294 let user, password :: Text
282 user = "Scooby"
295 user = "Scooby"
283 password = "Doo"
296 password = "Doo"
284− context "when we do not send appropriate basic auth data" $
297+ context "when we do not send appropriate basic auth data"
285− it "fails with 401" $ do
298+ $ it "fails with 401"
299+ $ do
286 r <-
300 r <-
287 prepareForShit $
301 prepareForShit $
288 req
302 req
@@ -293,15 +307,16 @@ spec = do
293 mempty
307 mempty
294 responseStatusCode r `shouldBe` 401
308 responseStatusCode r `shouldBe` 401
295 responseStatusMessage r `shouldBe` "UNAUTHORIZED"
309 responseStatusMessage r `shouldBe` "UNAUTHORIZED"
296− context "when we provide appropriate basic auth data" $
310+ context "when we provide appropriate basic auth data"
297− it "succeeds" $ do
311+ $ it "succeeds"
312+ $ do
298 r <-
313 r <-
299 req
314 req
300 GET
315 GET
301 (httpbin /: "basic-auth" /~ user /~ password)
316 (httpbin /: "basic-auth" /~ user /~ password)
302 NoReqBody
317 NoReqBody
303 ignoreResponse
318 ignoreResponse
304− (basicAuthUnsafe (T.encodeUtf8 user) (T.encodeUtf8 password))
319+ (basicAuth (T.encodeUtf8 user) (T.encodeUtf8 password))
305 responseStatusCode r `shouldBe` 200
320 responseStatusCode r `shouldBe` 200
306 responseStatusMessage r `shouldBe` "OK"
321 responseStatusMessage r `shouldBe` "OK"
307
322
@@ -313,8 +328,9 @@ spec = do
313 -- TODO /range
328 -- TODO /range
314 -- TODO /html
329 -- TODO /html
315
330
316− describe "robots.txt" $
331+ describe "robots.txt"
317− it "works" $ do
332+ $ it "works"
333+ $ do
318 r <- req GET (httpbin /: "robots.txt") NoReqBody bsResponse mempty
334 r <- req GET (httpbin /: "robots.txt") NoReqBody bsResponse mempty
319 robots <- B.readFile "httpbin-data/robots.txt"
335 robots <- B.readFile "httpbin-data/robots.txt"
320 responseBody r `shouldBe` robots
336 responseBody r `shouldBe` robots
@@ -325,8 +341,9 @@ spec = do
325 -- TODO /cache
341 -- TODO /cache
326
342
327 describe "getting random bytes" $ do
343 describe "getting random bytes" $ do
328− it "works" $
344+ it "works"
329− property $ \n' -> do
345+ $ property
346+ $ \n' -> do
330 let n :: Word
347 let n :: Word
331 n = getSmall n'
348 n = getSmall n'
332 r <-
349 r <-
@@ -339,8 +356,9 @@ spec = do
339 responseBody r `shouldSatisfy` ((== n) . fromIntegral . BL.length)
356 responseBody r `shouldSatisfy` ((== n) . fromIntegral . BL.length)
340 responseStatusCode r `shouldBe` 200
357 responseStatusCode r `shouldBe` 200
341 responseStatusMessage r `shouldBe` "OK"
358 responseStatusMessage r `shouldBe` "OK"
342− context "when we try to interpret 1000 random bytes as JSON" $
359+ context "when we try to interpret 1000 random bytes as JSON"
343− it "throws correct exception" $ do
360+ $ it "throws correct exception"
361+ $ do
344 let selector :: HttpException -> Bool
362 let selector :: HttpException -> Bool
345 selector (JsonHttpException _) = True
363 selector (JsonHttpException _) = True
346 selector _ = False
364 selector _ = False
@@ -354,21 +372,22 @@ spec = do
354 mempty
372 mempty
355 `shouldThrow` selector
373 `shouldThrow` selector
356
374
357− describe "streaming random bytes" $
375+ describe "streaming random bytes"
358− it "works" $
376+ $ it "works"
359− property $ \n' -> do
377+ $ property
378+ $ \n' -> do
360− let n :: Word
379+ let n :: Word
361− n = getSmall n'
380+ n = getSmall n'
362− r <-
381+ r <-
363− req
382+ req
364− GET
383+ GET
365− (httpbin /: "stream-bytes" /~ n)
384+ (httpbin /: "stream-bytes" /~ n)
366− NoReqBody
385+ NoReqBody
367− bsResponse
386+ bsResponse
368− mempty
387+ mempty
369− responseBody r `shouldSatisfy` ((== n) . fromIntegral . B.length)
388+ responseBody r `shouldSatisfy` ((== n) . fromIntegral . B.length)
370− responseStatusCode r `shouldBe` 200
389+ responseStatusCode r `shouldBe` 200
371− responseStatusMessage r `shouldBe` "OK"
390+ responseStatusMessage r `shouldBe` "OK"
372
391
373-- TODO /links
392-- TODO /links
374-- TODO /image
393-- TODO /image
@@ -388,87 +407,52 @@ instance MonadHttp IO where
388----------------------------------------------------------------------------
407----------------------------------------------------------------------------
389-- Helpers
408-- Helpers
390
409
391−-- | Run a request with such settings that it does not signal errors.
410+-- | Run request with such settings that it does not signal error on adverse
411+-- response status codes.
392prepareForShit :: Req a -> IO a
412prepareForShit :: Req a -> IO a
393prepareForShit = runReq defaultHttpConfig {httpConfigCheckResponse = noNoise}
413prepareForShit = runReq defaultHttpConfig {httpConfigCheckResponse = noNoise}
394 where
414 where
395 noNoise _ _ _ = Nothing
415 noNoise _ _ _ = Nothing
396
416
397−-- | Run a request with such settings that it throws on any response.
417+-- | Run request with such settings that it throws on any response.
398blindlyThrowing :: Req a -> IO a
418blindlyThrowing :: Req a -> IO a
399blindlyThrowing = runReq defaultHttpConfig {httpConfigCheckResponse = doit}
419blindlyThrowing = runReq defaultHttpConfig {httpConfigCheckResponse = doit}
400 where
420 where
401 doit _ _ = error "Oops!"
421 doit _ _ = error "Oops!"
402
422
403-- | 'Url' representing <https://httpbin.org>.
423-- | 'Url' representing <https://httpbin.org>.
404−httpbin :: Url 'Http
424+httpbin :: Url 'Https
405−httpbin = http "localhost"
425+httpbin = https "httpbin.org"
406−
407−req ::
408− ( MonadHttp m,
409− HttpMethod method,
410− HttpBody body,
411− HttpResponse response,
412− HttpBodyAllowed (AllowsBody method) (ProvidesBody body)
413− ) =>
414− method ->
415− Url scheme ->
416− body ->
417− Proxy response ->
418− Option scheme ->
419− m response
420−req method url body responseProxy options =
421− Req.req method url body responseProxy (options <> defaultOptions)
422−
423−-- | Options to apply by default.
424−defaultOptions :: Option scheme
425−defaultOptions = port 1234
426
426
427-- | Remove “origin” field from JSON value. Origin may change, we don't want
427-- | Remove “origin” field from JSON value. Origin may change, we don't want
428-- to depend on that.
428-- to depend on that.
429stripOrigin :: Value -> Value
429stripOrigin :: Value -> Value
430−stripOrigin (Object m) = Object (Aeson.KeyMap.delete "origin" m)
430+stripOrigin (Object m) = Object (HM.delete "origin" m)
431stripOrigin value = value
431stripOrigin value = value
432
432
433-- | Remove funny headers that might break the tests.
433-- | Remove funny headers that might break the tests.
434stripFunnyHeaders :: Value -> Value
434stripFunnyHeaders :: Value -> Value
435−stripFunnyHeaders = \case
435+stripFunnyHeaders (Object m) =
436− Object m ->
436+ let f (Object p) = Object $ HM.filterWithKey (\k _ -> k `elem` hs) p
437− Object
438− ( runIdentity
439− ( Aeson.KeyMap.alterF
440− (pure . fmap stripFunnyHeaders')
441− "headers"
442− m
443− )
444− )
445− value -> value
446−
447−-- | Similar to 'stripFunnyHeaders', but acts directly on the argument
448−-- without trying to access its "headers" field.
449−stripFunnyHeaders' :: Value -> Value
450−stripFunnyHeaders' = \case
451− Object p ->
452− Object $
453− Aeson.KeyMap.filterWithKey
454− (\k _ -> k `elem` whitelistedHeaders)
455− p
456− value -> value
437+ f value = value
457− where
438+ hs =
458− whitelistedHeaders =
459− [ "Content-Type",
439+ [ "Content-Type",
460− "Accept-Encoding",
440+ "Accept-Encoding",
441+ "Host",
461− "Content-Length",
442+ "Content-Length",
462− "Foo",
443+ "Foo",
463− "Baz"
444+ "Baz"
464− ]
445+ ]
446+ in Object (HM.adjust f "headers" m)
447+stripFunnyHeaders value = value
465
448
466-- | This is a complete test case that makes use of <https://httpbin.org> to
449-- | This is a complete test case that makes use of <https://httpbin.org> to
467-- get various response status codes.
450-- get various response status codes.
468checkStatusCode :: Int -> SpecWith ()
451checkStatusCode :: Int -> SpecWith ()
469checkStatusCode code =
452checkStatusCode code =
470− describe ("receiving status code " ++ show code) $
453+ describe ("receiving status code " ++ show code)
471− it "works" $ do
454+ $ it "works"
455+ $ do
472 r <-
456 r <-
473 prepareForShit $
457 prepareForShit $
474 req
458 req
@@ -491,19 +475,12 @@ selector404
491 L.responseStatus response == Y.status404 && not (B.null chunk)
475 L.responseStatus response == Y.status404 && not (B.null chunk)
492selector404 _ = False
476selector404 _ = False
493
477
494−-- | Same as 'selector404' except that it uses 'isStatusCodeException'.
495−selector404ByStatusCodeException :: HttpException -> Bool
496−selector404ByStatusCodeException e =
497− case isStatusCodeException e of
498− Nothing -> False
499− Just r -> responseStatusCode r == 404
500−
501−-- | The empty JSON 'Object'.
478+-- | Empty JSON 'Object'.
502emptyObject :: Value
479emptyObject :: Value
503−emptyObject = Object Aeson.KeyMap.empty
480+emptyObject = Object HM.empty
504
481
505−-- | Get a rendered JSON value as 'Text'.
482+-- | Get rendered JSON value as 'Text'.
506−reflectJSON :: (ToJSON a) => a -> Text
483+reflectJSON :: ToJSON a => a -> Text
507reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
484reflectJSON = T.decodeUtf8 . BL.toStrict . A.encode
508
485
509-- | Clarify to the type checker that the inner computation is in the 'IO'
486-- | Clarify to the type checker that the inner computation is in the 'IO'
pure-tests/Network/HTTP/ReqSpec.hs+199−211
@@ -1,3 +1,4 @@
1+{-# LANGUAGE CPP #-}
1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
@@ -13,50 +14,53 @@
13
14
14module Network.HTTP.ReqSpec (spec) where
15module Network.HTTP.ReqSpec (spec) where
15
16
16−import Blaze.ByteString.Builder qualified as BB
17+import qualified Blaze.ByteString.Builder as BB
17import Control.Exception (throwIO)
18import Control.Exception (throwIO)
18import Control.Monad
19import Control.Monad
19import Control.Retry
20import Control.Retry
20import Data.Aeson (ToJSON (..))
21import Data.Aeson (ToJSON (..))
21−import Data.Aeson qualified as A
22+import qualified Data.Aeson as A
22import Data.ByteString (ByteString)
23import Data.ByteString (ByteString)
23−import Data.ByteString qualified as B
24+import qualified Data.ByteString as B
24−import Data.ByteString.Char8 qualified as B8
25+import qualified Data.ByteString.Char8 as B8
25−import Data.ByteString.Lazy qualified as BL
26+import qualified Data.ByteString.Lazy as BL
26−import Data.CaseInsensitive qualified as CI
27+import qualified Data.CaseInsensitive as CI
27import Data.Either (isRight)
28import Data.Either (isRight)
28−import Data.List.NonEmpty qualified as NE
29+import qualified Data.List.NonEmpty as NE
29import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
30import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
30import Data.Proxy
31import Data.Proxy
31import Data.Text (Text)
32import Data.Text (Text)
32−import Data.Text qualified as T
33+import qualified Data.Text as T
33−import Data.Text.Encoding qualified as T
34+import qualified Data.Text.Encoding as T
34import Data.Time
35import Data.Time
35import Data.Typeable (Typeable, eqT)
36import Data.Typeable (Typeable, eqT)
36−import GHC.Exts (IsList (..))
37import GHC.Generics
37import GHC.Generics
38−import Language.Haskell.TH qualified as TH
38+import qualified Language.Haskell.TH as TH
39−import Language.Haskell.TH.Quote qualified as TH
39+import qualified Language.Haskell.TH.Quote as TH
40−import Network.HTTP.Client qualified as L
40+import qualified Network.HTTP.Client as L
41import Network.HTTP.Req
41import Network.HTTP.Req
42−import Network.HTTP.Types qualified as Y
42+import qualified Network.HTTP.Types as Y
43−import Network.HTTP.Types.Header qualified as Y
43+import qualified Network.HTTP.Types.Header as Y
44import Test.Hspec
44import Test.Hspec
45import Test.Hspec.Core.Spec (SpecM)
45import Test.Hspec.Core.Spec (SpecM)
46import Test.QuickCheck
46import Test.QuickCheck
47import Text.URI (URI)
47import Text.URI (URI)
48−import Text.URI qualified as URI
48+import qualified Text.URI as URI
49−import Text.URI.QQ qualified as QQ
49+import qualified Text.URI.QQ as QQ
50−import Web.FormUrlEncoded qualified as F
50+
51+#if !MIN_VERSION_base(4,13,0)
52+import Data.Semigroup ((<>))
53+#endif
51
54
52spec :: Spec
55spec :: Spec
53spec = do
56spec = do
54− describe "config" $
57+ describe "config"
55− it "getHttpConfig has effect on resulting request" $
58+ $ it "getHttpConfig has effect on resulting request"
56− property $ \config -> do
59+ $ property
60+ $ \config -> do
57− request <- runReq config (req_ GET url NoReqBody mempty)
61+ request <- runReq config (req_ GET url NoReqBody mempty)
58− L.proxy request `shouldBe` httpConfigProxy config
62+ L.proxy request `shouldBe` httpConfigProxy config
59− L.redirectCount request `shouldBe` httpConfigRedirectCount config
63+ L.redirectCount request `shouldBe` httpConfigRedirectCount config
60
64
61 describe "methods" $ do
65 describe "methods" $ do
62 let mnth ::
66 let mnth ::
@@ -68,8 +72,9 @@ spec = do
68 SpecM () ()
72 SpecM () ()
69 mnth method = do
73 mnth method = do
70 let name = httpMethodName (Proxy :: Proxy method)
74 let name = httpMethodName (Proxy :: Proxy method)
71− describe (B8.unpack name) $
75+ describe (B8.unpack name)
72− it "affects name of HTTP method" $ do
76+ $ it "affects name of HTTP method"
77+ $ do
73 request <- req_ method url NoReqBody mempty
78 request <- req_ method url NoReqBody mempty
74 L.method request `shouldBe` name
79 L.method request `shouldBe` name
75 mnth GET
80 mnth GET
@@ -83,42 +88,48 @@ spec = do
83 mnth PATCH
88 mnth PATCH
84
89
85 describe "urls" $ do
90 describe "urls" $ do
86− describe "http" $
91+ describe "http"
87− it "sets all the params correctly" $
92+ $ it "sets all the params correctly"
93+ $ property
88− property $ \host -> do
94+ $ \host -> do
89− request <- req_ GET (http host) NoReqBody mempty
95+ request <- req_ GET (http host) NoReqBody mempty
90− L.secure request `shouldBe` False
96+ L.secure request `shouldBe` False
91− L.port request `shouldBe` 80
97+ L.port request `shouldBe` 80
92− L.host request `shouldBe` urlEncode host
98+ L.host request `shouldBe` urlEncode host
93− describe "https" $
99+ describe "https"
94− it "sets all the params correctly" $
100+ $ it "sets all the params correctly"
101+ $ property
95− property $ \host -> do
102+ $ \host -> do
96− request <- req_ GET (https host) NoReqBody mempty
103+ request <- req_ GET (https host) NoReqBody mempty
97− L.secure request `shouldBe` True
104+ L.secure request `shouldBe` True
98− L.port request `shouldBe` 443
105+ L.port request `shouldBe` 443
99− L.host request `shouldBe` urlEncode host
106+ L.host request `shouldBe` urlEncode host
100− describe "(/~)" $
107+ describe "(/~)"
101− it "attaches a path piece that is URL-encoded" $
108+ $ it "attaches a path piece that is URL-encoded"
109+ $ property
102− property $ \host pieces -> do
110+ $ \host pieces -> do
103− let url' = foldl (/~) (https host) pieces
111+ let url' = foldl (/~) (https host) pieces
104− request <- req_ GET url' NoReqBody mempty
112+ request <- req_ GET url' NoReqBody mempty
105− L.host request `shouldBe` urlEncode host
113+ L.host request `shouldBe` urlEncode host
106− L.path request `shouldBe` encodePathPieces pieces
114+ L.path request `shouldBe` encodePathPieces pieces
107− describe "(/:)" $
115+ describe "(/:)"
108− it "attaches a path piece that is URL-encoded" $
116+ $ it "attaches a path piece that is URL-encoded"
117+ $ property
109− property $ \host pieces -> do
118+ $ \host pieces -> do
110− let url' = foldl (/:) (https host) pieces
119+ let url' = foldl (/:) (https host) pieces
111− request <- req_ GET url' NoReqBody mempty
120+ request <- req_ GET url' NoReqBody mempty
112− L.host request `shouldBe` urlEncode host
121+ L.host request `shouldBe` urlEncode host
113− L.path request `shouldBe` encodePathPieces pieces
122+ L.path request `shouldBe` encodePathPieces pieces
114
123
115 describe "useHttpURI" $ do
124 describe "useHttpURI" $ do
116− it "does not recognize non-http schemes" $
125+ it "does not recognize non-http schemes"
117− property $ \uri ->
126+ $ property
127+ $ \uri ->
118 when (URI.uriScheme uri /= Just [QQ.scheme|http|]) $
128 when (URI.uriScheme uri /= Just [QQ.scheme|http|]) $
119 useHttpURI uri `shouldSatisfy` isNothing
129 useHttpURI uri `shouldSatisfy` isNothing
120− it "accepts correct URLs" $
130+ it "accepts correct URLs"
121− property $ \uri' -> do
131+ $ property
132+ $ \uri' -> do
122 unless (isRight (URI.uriAuthority uri')) discard
133 unless (isRight (URI.uriAuthority uri')) discard
123 let uri = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
134 let uri = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
124 (url', options) = fromJust (useHttpURI uri)
135 (url', options) = fromJust (useHttpURI uri)
@@ -130,12 +141,14 @@ spec = do
130 lookup "Authorization" (L.requestHeaders request)
141 lookup "Authorization" (L.requestHeaders request)
131 `shouldBe` uriBasicAuth uri
142 `shouldBe` uriBasicAuth uri
132 describe "useHttpsURI" $ do
143 describe "useHttpsURI" $ do
133− it "does not recognize non-https schemes" $
144+ it "does not recognize non-https schemes"
134− property $ \uri ->
145+ $ property
146+ $ \uri ->
135 when (URI.uriScheme uri /= Just [QQ.scheme|https|]) $
147 when (URI.uriScheme uri /= Just [QQ.scheme|https|]) $
136 useHttpsURI uri `shouldSatisfy` isNothing
148 useHttpsURI uri `shouldSatisfy` isNothing
137− it "parses correct URLs" $
149+ it "parses correct URLs"
138− property $ \uri' -> do
150+ $ property
151+ $ \uri' -> do
139 unless (isRight (URI.uriAuthority uri')) discard
152 unless (isRight (URI.uriAuthority uri')) discard
140 let uri = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
153 let uri = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
141 (url', options) = fromJust (useHttpsURI uri)
154 (url', options) = fromJust (useHttpsURI uri)
@@ -147,27 +160,27 @@ spec = do
147 lookup "Authorization" (L.requestHeaders request)
160 lookup "Authorization" (L.requestHeaders request)
148 `shouldBe` uriBasicAuth uri
161 `shouldBe` uriBasicAuth uri
149 describe "useURI" $ do
162 describe "useURI" $ do
150− it "does not recognize non-http and non-https schemes" $
163+ it "does not recognize non-http and non-https schemes"
151− property $ \uri ->
164+ $ property
165+ $ \uri ->
152 when
166 when
153 ( ( URI.uriScheme uri /= Just [QQ.scheme|http|]
167 ( ( URI.uriScheme uri /= Just [QQ.scheme|http|]
154 && (URI.uriScheme uri /= Just [QQ.scheme|https|])
168 && (URI.uriScheme uri /= Just [QQ.scheme|https|])
155 )
169 )
156 )
170 )
157 $ useURI uri `shouldSatisfy` isNothing
171 $ useURI uri `shouldSatisfy` isNothing
158− it "parses correct URLs" $
172+ it "parses correct URLs"
159− property $ \uri' -> do
173+ $ property
174+ $ \uri' -> do
160 unless (isRight (URI.uriAuthority uri')) discard
175 unless (isRight (URI.uriAuthority uri')) discard
161 let uriHttp = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
176 let uriHttp = uri' {URI.uriScheme = Just [QQ.scheme|http|]}
162 uriHttps = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
177 uriHttps = uri' {URI.uriScheme = Just [QQ.scheme|https|]}
163 requestHttp <-
178 requestHttp <-
164− case fromJust (useURI uriHttp) of
179+ let Left (url', options) = fromJust (useURI uriHttp)
165− Left (url', options) -> req_ GET url' NoReqBody options
180+ in req_ GET url' NoReqBody options
166− _ -> error "(useURI uriHttp) should have returned Left"
167 requestHttps <-
181 requestHttps <-
168− case fromJust (useURI uriHttps) of
182+ let Right (url', options) = fromJust (useURI uriHttps)
169− Right (url', options) -> req_ GET url' NoReqBody options
183+ in req_ GET url' NoReqBody options
170− _ -> error "(useURI uriHttps) should have returned Right"
171 L.host requestHttp `shouldBe` uriHost uriHttp
184 L.host requestHttp `shouldBe` uriHost uriHttp
172 L.host requestHttps `shouldBe` uriHost uriHttps
185 L.host requestHttps `shouldBe` uriHost uriHttps
173 L.port requestHttp `shouldBe` uriPort 80 uriHttp
186 L.port requestHttp `shouldBe` uriPort 80 uriHttp
@@ -181,83 +194,57 @@ spec = do
181 lookup "Authorization" (L.requestHeaders requestHttps)
194 lookup "Authorization" (L.requestHeaders requestHttps)
182 `shouldBe` uriBasicAuth uriHttps
195 `shouldBe` uriBasicAuth uriHttps
183
196
184− describe "renderUrl" $ do
185− context "http" $ do
186− context "empty path" $
187− it "renders correctly" $ do
188− let (uriHttp, _) = [urlQ|http://httpbin.org|]
189− renderUrl uriHttp `shouldBe` "http://httpbin.org"
190− context "non-empty path" $
191− it "renders correctly" $ do
192− let (uriHttp, _) = [urlQ|http://httpbin.org/here/we/go|]
193− renderUrl uriHttp `shouldBe` "http://httpbin.org/here/we/go"
194− context "http" $ do
195− context "empty path" $
196− it "renders correctly" $ do
197− let (uriHttp, _) = [urlQ|https://httpbin.org|]
198− renderUrl uriHttp `shouldBe` "https://httpbin.org"
199− context "non-empty path" $
200− it "renders correctly" $ do
201− let (uriHttp, _) = [urlQ|https://httpbin.org/here/we/go|]
202− renderUrl uriHttp `shouldBe` "https://httpbin.org/here/we/go"
203−
204 describe "bodies" $ do
197 describe "bodies" $ do
205− describe "NoReqBody" $
198+ describe "NoReqBody"
206− it "sets body to empty byte string" $ do
199+ $ it "sets body to empty byte string"
200+ $ do
207 request <- req_ POST url NoReqBody mempty
201 request <- req_ POST url NoReqBody mempty
208 case L.requestBody request of
202 case L.requestBody request of
209 L.RequestBodyBS x -> x `shouldBe` B.empty
203 L.RequestBodyBS x -> x `shouldBe` B.empty
210 _ -> expectationFailure "Wrong request body constructor."
204 _ -> expectationFailure "Wrong request body constructor."
211− describe "ReqBodyJson" $
205+ describe "ReqBodyJson"
212− it "sets body to correct lazy byte string" $
206+ $ it "sets body to correct lazy byte string"
207+ $ property
213− property $ \thing -> do
208+ $ \thing -> do
214− request <- req_ POST url (ReqBodyJson thing) mempty
209+ request <- req_ POST url (ReqBodyJson thing) mempty
215− case L.requestBody request of
210+ case L.requestBody request of
216− L.RequestBodyLBS x -> x `shouldBe` A.encode (thing :: Thing)
211+ L.RequestBodyLBS x -> x `shouldBe` A.encode (thing :: Thing)
217− _ -> expectationFailure "Wrong request body constructor."
212+ _ -> expectationFailure "Wrong request body constructor."
218− describe "ReqBodyBs" $
213+ describe "ReqBodyBs"
219− it "sets body to specified strict byte string" $
214+ $ it "sets body to specified strict byte string"
215+ $ property
220− property $ \bs -> do
216+ $ \bs -> do
221− request <- req_ POST url (ReqBodyBs bs) mempty
217+ request <- req_ POST url (ReqBodyBs bs) mempty
222− case L.requestBody request of
218+ case L.requestBody request of
223− L.RequestBodyBS x -> x `shouldBe` bs
219+ L.RequestBodyBS x -> x `shouldBe` bs
224− _ -> expectationFailure "Wrong request body constructor."
220+ _ -> expectationFailure "Wrong request body constructor."
225− describe "ReqBodyLbs" $
221+ describe "ReqBodyLbs"
226− it "sets body to specified lazy byte string" $
222+ $ it "sets body to specified lazy byte string"
223+ $ property
227− property $ \lbs -> do
224+ $ \lbs -> do
228− request <- req_ POST url (ReqBodyLbs lbs) mempty
225+ request <- req_ POST url (ReqBodyLbs lbs) mempty
229− case L.requestBody request of
226+ case L.requestBody request of
230− L.RequestBodyLBS x -> x `shouldBe` lbs
227+ L.RequestBodyLBS x -> x `shouldBe` lbs
231− _ -> expectationFailure "Wrong request body constructor."
228+ _ -> expectationFailure "Wrong request body constructor."
232− describe "ReqBodyUrlEnc" $
229+ describe "ReqBodyUrlEnc"
233− it "sets body to correct lazy byte string" $
230+ $ it "sets body to correct lazy byte string"
231+ $ property
234− property $ \params -> do
232+ $ \params -> do
235− request <- req_ POST url (ReqBodyUrlEnc (formUrlEnc params)) mempty
233+ request <- req_ POST url (ReqBodyUrlEnc (formUrlEnc params)) mempty
236− case L.requestBody request of
234+ case L.requestBody request of
237− L.RequestBodyLBS x -> x `shouldBe` renderQuery params
235+ L.RequestBodyLBS x -> x `shouldBe` renderQuery params
238− _ -> expectationFailure "Wrong request body constructor."
239−
240− describe "query params" $ do
241− describe "FormUrlEncodedParam" $ do
242− describe "formToQuery" $ do
243− it "should produce the same parameters as F.urlEncodeFormStable" $
244− property $ \form -> do
245− request <- req_ POST url (ReqBodyUrlEnc $ formToQuery form) mempty
246− case L.requestBody request of
247− L.RequestBodyLBS x -> x `shouldBe` F.urlEncodeFormStable form
248− _ -> expectationFailure "Wrong request body constructor"
236+ _ -> expectationFailure "Wrong request body constructor."
249− specParamToList (Proxy :: Proxy FormUrlEncodedParam)
250− describe "Option" $ do
251− specParamToList (Proxy :: Proxy (Option 'Http))
252
237
253 describe "optional parameters" $ do
238 describe "optional parameters" $ do
254 describe "header" $ do
239 describe "header" $ do
255− it "sets specified header value" $
240+ it "sets specified header value"
241+ $ property
256− property $ \name value -> do
242+ $ \name value -> do
257 request <- req_ GET url NoReqBody (header name value)
243 request <- req_ GET url NoReqBody (header name value)
258 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value
244 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value
259− it "left header wins" $
245+ it "left header wins"
246+ $ property
260− property $ \name value0 value1 -> do
247+ $ \name value0 value1 -> do
261 request <-
248 request <-
262 req_
249 req_
263 GET
250 GET
@@ -265,8 +252,9 @@ spec = do
265 NoReqBody
252 NoReqBody
266 (header name value0 <> header name value1)
253 (header name value0 <> header name value1)
267 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value0
254 lookup (CI.mk name) (L.requestHeaders request) `shouldBe` pure value0
268− it "overwrites headers set by other parts of the lib" $
255+ it "overwrites headers set by other parts of the lib"
269− property $ \value -> do
256+ $ property
257+ $ \value -> do
270 request <-
258 request <-
271 req_
259 req_
272 POST
260 POST
@@ -274,19 +262,26 @@ spec = do
274 (ReqBodyUrlEnc mempty)
262 (ReqBodyUrlEnc mempty)
275 (header "Content-Type" value)
263 (header "Content-Type" value)
276 lookup "Content-Type" (L.requestHeaders request) `shouldBe` pure value
264 lookup "Content-Type" (L.requestHeaders request) `shouldBe` pure value
277− describe "cookieJar" $
265+ describe "cookieJar"
278− it "cookie jar is set without modifications" $
266+ $ it "cookie jar is set without modifications"
279− property $ \cjar -> do
267+ $ property
268+ $ \cjar -> do
280− request <- req_ GET url NoReqBody (cookieJar cjar)
269+ request <- req_ GET url NoReqBody (cookieJar cjar)
270+#if MIN_VERSION_http_client(0,7,0)
281− L.cookieJar request `shouldSatisfy` (maybe False (L.equalCookieJar cjar))
271+ L.cookieJar request `shouldSatisfy` (maybe False (L.equalCookieJar cjar))
272+#else
273+ L.cookieJar request `shouldBe` Just cjar
274+#endif
282 describe "basicAuth" $ do
275 describe "basicAuth" $ do
283− it "sets Authorization header to correct value" $
276+ it "sets Authorization header to correct value"
277+ $ property
284− property $ \username password -> do
278+ $ \username password -> do
285 request <- req_ GET url NoReqBody (basicAuth username password)
279 request <- req_ GET url NoReqBody (basicAuth username password)
286 lookup "Authorization" (L.requestHeaders request)
280 lookup "Authorization" (L.requestHeaders request)
287 `shouldBe` Just (basicAuthHeader username password)
281 `shouldBe` Just (basicAuthHeader username password)
288− it "overwrites manual setting of header" $
282+ it "overwrites manual setting of header"
283+ $ property
289− property $ \username password value -> do
284+ $ \username password value -> do
290 request0 <-
285 request0 <-
291 req_
286 req_
292 GET
287 GET
@@ -302,8 +297,9 @@ spec = do
302 let result = Just (basicAuthHeader username password)
297 let result = Just (basicAuthHeader username password)
303 lookup "Authorization" (L.requestHeaders request0) `shouldBe` result
298 lookup "Authorization" (L.requestHeaders request0) `shouldBe` result
304 lookup "Authorization" (L.requestHeaders request1) `shouldBe` result
299 lookup "Authorization" (L.requestHeaders request1) `shouldBe` result
305− it "left auth option wins" $
300+ it "left auth option wins"
301+ $ property
306− property $ \username0 password0 username1 password1 -> do
302+ $ \username0 password0 username1 password1 -> do
307 request <-
303 request <-
308 req_
304 req_
309 GET
305 GET
@@ -313,13 +309,15 @@ spec = do
313 lookup "Authorization" (L.requestHeaders request)
309 lookup "Authorization" (L.requestHeaders request)
314 `shouldBe` Just (basicAuthHeader username0 password0)
310 `shouldBe` Just (basicAuthHeader username0 password0)
315 describe "oAuth2Bearer" $ do
311 describe "oAuth2Bearer" $ do
316− it "sets Authorization header to correct value" $
312+ it "sets Authorization header to correct value"
317− property $ \token -> do
313+ $ property
314+ $ \token -> do
318 request <- req_ GET url NoReqBody (oAuth2Bearer token)
315 request <- req_ GET url NoReqBody (oAuth2Bearer token)
319 lookup "Authorization" (L.requestHeaders request)
316 lookup "Authorization" (L.requestHeaders request)
320 `shouldBe` pure ("Bearer " <> token)
317 `shouldBe` pure ("Bearer " <> token)
321− it "overwrites manual setting of header" $
318+ it "overwrites manual setting of header"
319+ $ property
322− property $ \token value -> do
320+ $ \token value -> do
323 request0 <-
321 request0 <-
324 req_
322 req_
325 GET
323 GET
@@ -337,8 +335,9 @@ spec = do
337 `shouldBe` pure result
335 `shouldBe` pure result
338 lookup "Authorization" (L.requestHeaders request1)
336 lookup "Authorization" (L.requestHeaders request1)
339 `shouldBe` pure result
337 `shouldBe` pure result
340− it "left auth option wins" $
338+ it "left auth option wins"
339+ $ property
341− property $ \token0 token1 -> do
340+ $ \token0 token1 -> do
342 request <-
341 request <-
343 req_
342 req_
344 GET
343 GET
@@ -347,20 +346,23 @@ spec = do
347 (oAuth2Bearer token0 <> oAuth2Bearer token1)
346 (oAuth2Bearer token0 <> oAuth2Bearer token1)
348 lookup "Authorization" (L.requestHeaders request)
347 lookup "Authorization" (L.requestHeaders request)
349 `shouldBe` pure ("Bearer " <> token0)
348 `shouldBe` pure ("Bearer " <> token0)
350− describe "ProxyAuthorization" $
349+ describe "ProxyAuthorization"
351− it "sets Authorization header to correct value" $
350+ $ it "sets Authorization header to correct value"
351+ $ property
352− property $ \username password -> do
352+ $ \username password -> do
353− request <- req_ GET url NoReqBody (basicProxyAuth username password)
353+ request <- req_ GET url NoReqBody (basicProxyAuth username password)
354− lookup "Proxy-Authorization" (L.requestHeaders request)
354+ lookup "Proxy-Authorization" (L.requestHeaders request)
355− `shouldBe` pure (basicProxyAuthHeader username password)
355+ `shouldBe` pure (basicProxyAuthHeader username password)
356 describe "oAuth2Token" $ do
356 describe "oAuth2Token" $ do
357− it "sets Authorization header to correct value" $
357+ it "sets Authorization header to correct value"
358− property $ \token -> do
358+ $ property
359+ $ \token -> do
359 request <- req_ GET url NoReqBody (oAuth2Token token)
360 request <- req_ GET url NoReqBody (oAuth2Token token)
360 lookup "Authorization" (L.requestHeaders request)
361 lookup "Authorization" (L.requestHeaders request)
361 `shouldBe` pure ("token " <> token)
362 `shouldBe` pure ("token " <> token)
362− it "overwrites manual setting of header" $
363+ it "overwrites manual setting of header"
364+ $ property
363− property $ \token value -> do
365+ $ \token value -> do
364 request0 <-
366 request0 <-
365 req_
367 req_
366 GET
368 GET
@@ -378,8 +380,9 @@ spec = do
378 `shouldBe` pure result
380 `shouldBe` pure result
379 lookup "Authorization" (L.requestHeaders request1)
381 lookup "Authorization" (L.requestHeaders request1)
380 `shouldBe` pure result
382 `shouldBe` pure result
381− it "left auth option wins" $
383+ it "left auth option wins"
384+ $ property
382− property $ \token0 token1 -> do
385+ $ \token0 token1 -> do
383 request <-
386 request <-
384 req_
387 req_
385 GET
388 GET
@@ -388,24 +391,27 @@ spec = do
388 (oAuth2Token token0 <> oAuth2Token token1)
391 (oAuth2Token token0 <> oAuth2Token token1)
389 lookup "Authorization" (L.requestHeaders request)
392 lookup "Authorization" (L.requestHeaders request)
390 `shouldBe` pure ("token " <> token0)
393 `shouldBe` pure ("token " <> token0)
391− describe "port" $
394+ describe "port"
392− it "sets port overwriting the defaults" $
395+ $ it "sets port overwriting the defaults"
396+ $ property
393− property $ \n -> do
397+ $ \n -> do
394− request <- req_ GET url NoReqBody (port n)
398+ request <- req_ GET url NoReqBody (port n)
395− L.port request `shouldBe` n
399+ L.port request `shouldBe` n
396− describe "decompress" $
400+ describe "decompress"
397− it "sets decompress function overwriting the defaults" $
401+ $ it "sets decompress function overwriting the defaults"
402+ $ property
398− property $ \token -> do
403+ $ \token -> do
399− request <- req_ GET url NoReqBody (decompress (/= token))
404+ request <- req_ GET url NoReqBody (decompress (/= token))
400− L.decompress request token `shouldBe` False
405+ L.decompress request token `shouldBe` False
401 -- FIXME Can't really test responseTimeout right new because the
406 -- FIXME Can't really test responseTimeout right new because the
402 -- ResponseTimeout data type does not implement Eq and its constructors
407 -- ResponseTimeout data type does not implement Eq and its constructors
403 -- are also not exported. Sent a PR.
408 -- are also not exported. Sent a PR.
404− describe "httpVersion" $
409+ describe "httpVersion"
405− it "sets HTTP version overwriting the defaults" $
410+ $ it "sets HTTP version overwriting the defaults"
411+ $ property
406− property $ \major minor -> do
412+ $ \major minor -> do
407− request <- req_ GET url NoReqBody (httpVersion major minor)
413+ request <- req_ GET url NoReqBody (httpVersion major minor)
408− L.requestVersion request `shouldBe` Y.HttpVersion major minor
414+ L.requestVersion request `shouldBe` Y.HttpVersion major minor
409
415
410 describe "quasiquoter" $ do
416 describe "quasiquoter" $ do
411 it "works for valid urls" $
417 it "works for valid urls" $
@@ -413,12 +419,12 @@ spec = do
413 -- 'Https, Option _) type checks, so we can catch if the type of scheme
419 -- 'Https, Option _) type checks, so we can catch if the type of scheme
414 -- is unspecified.
420 -- is unspecified.
415 let testTypeOfQuoterResult ::
421 let testTypeOfQuoterResult ::
416− forall a s. (Typeable a) => (a, Option s) -> Bool
422+ forall a s. Typeable a => (a, Option s) -> Bool
417 testTypeOfQuoterResult _ = isJust $ eqT @a @(Url 'Https)
423 testTypeOfQuoterResult _ = isJust $ eqT @a @(Url 'Https)
418 in property $ testTypeOfQuoterResult [urlQ|https://example.org/|]
424 in property $ testTypeOfQuoterResult [urlQ|https://example.org/|]
419− it "doesn't work for invalid urls" $
425+ it "doesn't work for invalid urls"
420− property $
426+ $ property
421− TH.runQ (TH.quoteExp urlQ "not a url") `shouldThrow` anyIOException
427+ $ TH.runQ (TH.quoteExp urlQ "not a url") `shouldThrow` anyIOException
422
428
423----------------------------------------------------------------------------
429----------------------------------------------------------------------------
424-- Instances
430-- Instances
@@ -434,8 +440,6 @@ instance Arbitrary HttpConfig where
434 httpConfigCheckResponse _ _ _ = Nothing
440 httpConfigCheckResponse _ _ _ = Nothing
435 httpConfigRetryPolicy = retryPolicyDefault
441 httpConfigRetryPolicy = retryPolicyDefault
436 httpConfigRetryJudge _ _ = False
442 httpConfigRetryJudge _ _ = False
437− httpConfigRetryJudgeException _ _ = False
438− httpConfigBodyPreviewLength = 1024
439 return HttpConfig {..}
443 return HttpConfig {..}
440
444
441instance Show HttpConfig where
445instance Show HttpConfig where
@@ -503,9 +507,6 @@ instance Arbitrary Day where
503instance Arbitrary DiffTime where
507instance Arbitrary DiffTime where
504 arbitrary = secondsToDiffTime <$> arbitrary
508 arbitrary = secondsToDiffTime <$> arbitrary
505
509
506−instance Arbitrary F.Form where
507− arbitrary = (F.Form . fromList) <$> arbitrary
508−
509----------------------------------------------------------------------------
510----------------------------------------------------------------------------
510-- Helper types
511-- Helper types
511
512
@@ -573,14 +574,13 @@ uriPort def uri =
573 maybe def (fromIntegral) $
574 maybe def (fromIntegral) $
574 either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
575 either (const Nothing) Just (URI.uriAuthority uri) >>= URI.authPort
575
576
576−-- | Get the path from a 'URI'.
577+-- | Get path from 'URI'.
577uriPath :: URI -> ByteString
578uriPath :: URI -> ByteString
578uriPath uri = fromMaybe "" $ do
579uriPath uri = fromMaybe "" $ do
579− (trailingSlash, xs) <- URI.uriPath uri
580+ (_, xs) <- URI.uriPath uri
580− let pref = (encodePathPieces . fmap URI.unRText . NE.toList) xs
581+ (return . encodePathPieces . fmap URI.unRText . NE.toList) xs
581− return $ if trailingSlash then pref <> "/" else pref
582
582
583−-- | Get the query string from a 'URI'.
583+-- | Get query string from 'URI'.
584uriQuery :: URI -> ByteString
584uriQuery :: URI -> ByteString
585uriQuery uri = do
585uriQuery uri = do
586 let liftQueryParam = \case
586 let liftQueryParam = \case
@@ -618,15 +618,3 @@ basicProxyAuthHeader :: ByteString -> ByteString -> ByteString
618basicProxyAuthHeader username password =
618basicProxyAuthHeader username password =
619 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
619 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
620 L.applyBasicProxyAuth username password L.defaultRequest
620 L.applyBasicProxyAuth username password L.defaultRequest
621−
622−-- | Spec about 'paramToList' for the type @p@.
623−specParamToList :: (QueryParam p, Monoid p) => Proxy p -> Spec
624−specParamToList typeProxy = do
625− describe "paramToList" $ do
626− it "should reproduce the parameters given by queryParam" $
627− property $ \(QueryParams params) -> do
628− let queryParam0 =
629− (mconcat $ fmap (uncurry queryParam) params)
630− `asProxyTypeOf` typeProxy
631− got = queryParamToList queryParam0
632− got `shouldBe` params
req.cabal+70−68
@@ -1,15 +1,19 @@
1−cabal-version: 2.4
1+cabal-version: 1.18
2name: req
2name: req
3−version: 3.13.4
3+version: 3.3.0
4−license: BSD-3-Clause
4+license: BSD3
5license-file: LICENSE.md
5license-file: LICENSE.md
6maintainer: Mark Karpov <markkarpov92@gmail.com>
6maintainer: Mark Karpov <markkarpov92@gmail.com>
7author: Mark Karpov <markkarpov92@gmail.com>
7author: Mark Karpov <markkarpov92@gmail.com>
8−tested-with: ghc ==9.6.3 ghc ==9.8.2 ghc ==9.10.1
8+tested-with: ghc ==8.6.5 ghc ==8.8.3 ghc ==8.10.1
9homepage: https://github.com/mrkkrp/req
9homepage: https://github.com/mrkkrp/req
10bug-reports: https://github.com/mrkkrp/req/issues
10bug-reports: https://github.com/mrkkrp/req/issues
11−synopsis: HTTP client library
11+synopsis:
12+ Easy-to-use, type-safe, expandable, high-level HTTP client library
13+
12−description: HTTP client library.
14+description:
15+ Easy-to-use, type-safe, expandable, high-level HTTP client library.
16+
13category: Network, Web
17category: Network, Web
14build-type: Simple
18build-type: Simple
15data-files:
19data-files:
@@ -31,97 +35,95 @@ flag dev
31
35
32library
36library
33 exposed-modules: Network.HTTP.Req
37 exposed-modules: Network.HTTP.Req
34− default-language: GHC2021
38+ default-language: Haskell2010
35 build-depends:
39 build-depends:
36− aeson >=0.9 && <3,
40+ aeson >=0.9 && <1.6,
37− authenticate-oauth >=1.5 && <1.8,
41+ authenticate-oauth >=1.5 && <1.7,
38− base >=4.15 && <5,
42+ base >=4.12 && <5.0,
39 blaze-builder >=0.3 && <0.5,
43 blaze-builder >=0.3 && <0.5,
40− bytestring >=0.10.8 && <0.13,
44+ bytestring >=0.10.8 && <0.11,
41 case-insensitive >=0.2 && <1.3,
45 case-insensitive >=0.2 && <1.3,
42− containers >=0.5 && <0.8,
43− crypton-connection >=0.4.2 && <0.5,
46+ connection >=0.2.2 && <0.4,
44− data-default-class >=0.2.0.0,
45− exceptions >=0.6 && <0.11,
46− http-api-data >=0.2 && <0.7,
47+ http-api-data >=0.2 && <0.5,
47− http-client >=0.7.13.1 && <0.8,
48+ http-client >=0.5 && <0.8,
48 http-client-tls >=0.3.2 && <0.4,
49 http-client-tls >=0.3.2 && <0.4,
49− http-types >=0.8 && <10,
50+ http-types >=0.8 && <10.0,
50 modern-uri >=0.3 && <0.4,
51 modern-uri >=0.3 && <0.4,
51 monad-control >=1.0 && <1.1,
52 monad-control >=1.0 && <1.1,
52 mtl >=2.0 && <3.0,
53 mtl >=2.0 && <3.0,
53− retry >=0.8 && <0.10,
54+ retry >=0.8 && <0.9,
54− template-haskell >=2.19 && <2.23,
55+ template-haskell >=2.14 && <2.17,
55− text >=0.2 && <2.2,
56+ text >=0.2 && <1.3,
57+ time >=1.2 && <1.10,
56− transformers >=0.5.3.0 && <0.7,
58+ transformers >=0.4 && <0.6,
57− transformers-base,
59+ transformers-base -any
58− unliftio-core >=0.1.1 && <0.3
59
60
60 if flag(dev)
61 if flag(dev)
61− ghc-options:
62+ ghc-options: -O0 -Wall -Werror
62− -Wall -Werror -Wpartial-fields -Wunused-packages
63− -Wno-unused-imports
64
63
65 else
64 else
66 ghc-options: -O2 -Wall
65 ghc-options: -O2 -Wall
67
66
67+ if flag(dev)
68+ ghc-options:
69+ -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns
70+ -Wnoncanonical-monad-instances
71+
68test-suite pure-tests
72test-suite pure-tests
69− type: exitcode-stdio-1.0
73+ type: exitcode-stdio-1.0
70− main-is: Spec.hs
74+ main-is: Spec.hs
71− build-tool-depends: hspec-discover:hspec-discover >=2 && <3
75+ build-tools: hspec-discover >=2.0 && <3.0
72− hs-source-dirs: pure-tests
76+ hs-source-dirs: pure-tests
73− other-modules: Network.HTTP.ReqSpec
77+ other-modules: Network.HTTP.ReqSpec
74− default-language: GHC2021
78+ default-language: Haskell2010
75 build-depends:
79 build-depends:
76− QuickCheck >=2.7 && <3,
80+ QuickCheck >=2.7 && <3.0,
77− aeson >=0.9 && <3,
81+ aeson >=0.9 && <1.6,
78− base >=4.15 && <5.0,
82+ base >=4.12 && <5.0,
79 blaze-builder >=0.3 && <0.5,
83 blaze-builder >=0.3 && <0.5,
80− bytestring >=0.10.8 && <0.13,
84+ bytestring >=0.10.8 && <0.11,
81 case-insensitive >=0.2 && <1.3,
85 case-insensitive >=0.2 && <1.3,
82− hspec >=2.0 && <3,
86+ hspec >=2.0 && <3.0,
83− hspec-core >=2.0 && <3,
87+ hspec-core >=2.0 && <3.0,
84− http-api-data >=0.2 && <0.7,
85− http-client >=0.7 && <0.8,
88+ http-client >=0.5 && <0.8,
86− http-types >=0.8 && <10,
89+ http-types >=0.8 && <10.0,
87 modern-uri >=0.3 && <0.4,
90 modern-uri >=0.3 && <0.4,
91+ mtl >=2.0 && <3.0,
88− req,
92+ req -any,
89− retry >=0.8 && <0.10,
93+ retry >=0.8 && <0.9,
90− template-haskell >=2.19 && <2.23,
94+ template-haskell >=2.14 && <2.17,
91− text >=0.2 && <2.2,
95+ text >=0.2 && <1.3,
92− time >=1.2 && <1.15
96+ time >=1.2 && <1.10
93
97
94 if flag(dev)
98 if flag(dev)
95− ghc-options:
99+ ghc-options: -O0 -Wall -Werror
96− -Wall -Werror -Wredundant-constraints -Wpartial-fields
97− -Wunused-packages
98
100
99 else
101 else
100 ghc-options: -O2 -Wall
102 ghc-options: -O2 -Wall
101
103
102test-suite httpbin-tests
104test-suite httpbin-tests
103− type: exitcode-stdio-1.0
105+ type: exitcode-stdio-1.0
104− main-is: Spec.hs
106+ main-is: Spec.hs
105− build-tool-depends: hspec-discover:hspec-discover >=2 && <3
107+ build-tools: hspec-discover >=2.0 && <3.0
106− hs-source-dirs: httpbin-tests
108+ hs-source-dirs: httpbin-tests
107− other-modules: Network.HTTP.ReqSpec
109+ other-modules: Network.HTTP.ReqSpec
108− default-language: GHC2021
110+ default-language: Haskell2010
109 build-depends:
111 build-depends:
110− QuickCheck >=2.7 && <3,
112+ QuickCheck >=2.7 && <3.0,
111− aeson >=2 && <3,
113+ aeson >=0.9 && <1.6,
112− base >=4.15 && <5,
114+ base >=4.12 && <5.0,
113− bytestring >=0.10.8 && <0.13,
115+ bytestring >=0.10.8 && <0.11,
114 hspec >=2.0 && <3.0,
116 hspec >=2.0 && <3.0,
115− http-client >=0.7 && <0.8,
117+ http-client >=0.5 && <0.8,
116− http-types >=0.8 && <10,
118+ http-types >=0.8 && <10.0,
117 monad-control >=1.0 && <1.1,
119 monad-control >=1.0 && <1.1,
120+ mtl >=2.0 && <3.0,
118− req,
121+ req -any,
119− text >=0.2 && <2.2
122+ text >=0.2 && <1.3,
123+ unordered-containers >=0.2.5 && <0.2.11
120
124
121 if flag(dev)
125 if flag(dev)
122− ghc-options:
126+ ghc-options: -O0 -Wall -Werror
123− -Wall -Werror -Wredundant-constraints -Wpartial-fields
124− -Wunused-packages
125
127
126 else
128 else
127− buildable: False
129+ ghc-options: -O2 -Wall