on master help sign in
Add formToQuery, queryParamToList, and FromForm instances
commit67be279a87e9a38bc9121747a78c5a04db12a1e6 authorToshio Ito <debug.ito@gmail.com> dateMon 28 Feb 2022, 13:52 (5 years ago) parente26e055
4 files changed+78
CHANGELOG.md+7 Network/HTTP/Req.hs+40 pure-tests/Network/HTTP/ReqSpec.hs+30 req.cabal+1
CHANGELOG.md+7
@@ -1,3 +1,10 @@
1+## Req 3.11.0
2+
3+* Add the `queryParamToList` method to the `QueryParam` type class.
4+* Add the `formToQuery` function. [Issue 126](https://github.com/mrkkrp/req/issues/126).
5+* Add `FromForm` instances (in the `Web.FormUrlEncoded` module) to the
6+ `Option` and `FormUrlEncodedParam` types.
7+
18## Req 3.10.0
29
310* Add `MonadHttp` instances for `transformers` types.
Network/HTTP/Req.hs+40
@@ -155,6 +155,7 @@ module Network.HTTP.Req
155155 -- $query-parameters
156156 (=:),
157157 queryFlag,
158+ formToQuery,
158159 QueryParam (..),
159160
160161 -- *** Headers
@@ -272,6 +273,8 @@ import Text.URI (URI)
272273import qualified Text.URI as URI
273274import qualified Text.URI.QQ as QQ
274275import qualified Web.Authenticate.OAuth as OAuth
276+import Web.FormUrlEncoded (FromForm (..), ToForm (..))
277+import qualified Web.FormUrlEncoded as Form
275278import Web.HttpApiData (ToHttpApiData (..))
276279
277280----------------------------------------------------------------------------
@@ -1303,6 +1306,13 @@ newtype FormUrlEncodedParam = FormUrlEncodedParam [(Text, Maybe Text)]
13031306instance QueryParam FormUrlEncodedParam where
13041307 queryParam name mvalue =
13051308 FormUrlEncodedParam [(name, toQueryParam <$> mvalue)]
1309+ queryParamToList (FormUrlEncodedParam p) = p
1310+
1311+-- | Use 'formToQuery'.
1312+--
1313+-- @since 3.11.0
1314+instance FromForm FormUrlEncodedParam where
1315+ fromForm = Right . formToQuery
13061316
13071317-- | Multipart form data. Please consult the
13081318-- "Network.HTTP.Client.MultipartFormData" module for how to construct
@@ -1428,6 +1438,12 @@ instance Monoid (Option scheme) where
14281438 mempty = Option mempty Nothing
14291439 mappend = (<>)
14301440
1441+-- | Use 'formToQuery'.
1442+--
1443+-- @since 3.11.0
1444+instance FromForm (Option scheme) where
1445+ fromForm = Right . formToQuery
1446+
14311447-- | A helper to create an 'Option' that modifies only collection of query
14321448-- parameters. This helper is not a part of the public API.
14331449withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
@@ -1482,6 +1498,24 @@ name =: value = queryParam name (pure value)
14821498queryFlag :: QueryParam param => Text -> param
14831499queryFlag name = queryParam name (Nothing :: Maybe ())
14841500
1501+-- | Construct query parameters from a 'ToForm' instance. This function
1502+-- produces the same query params as 'Form.urlEncodeAsFormStable'.
1503+--
1504+-- Note that 'Form.Form' doesn't have the concept of parameters with the
1505+-- empty value (i.e. what you can get by @key =: ""@). If the value is
1506+-- empty, it will be encoded as a valueless parameter (i.e. what you can get
1507+-- by @queryFlag key@).
1508+--
1509+-- @since 3.11.0
1510+formToQuery :: (QueryParam param, Monoid param, ToForm f) => f -> param
1511+formToQuery f = mconcat . fmap toParam . Form.toListStable $ toForm f
1512+ where
1513+ toParam (key, val) =
1514+ queryParam key $
1515+ if val == ""
1516+ then Nothing
1517+ else Just val
1518+
14851519-- | A type class for query-parameter-like things. The reason to have an
14861520-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
14871521-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
@@ -1493,9 +1527,15 @@ class QueryParam param where
14931527 -- method, because they are easier to read.
14941528 queryParam :: ToHttpApiData a => Text -> Maybe a -> param
14951529
1530+ -- | Get the query parameter names and values set by 'queryParam'.
1531+ --
1532+ -- @since 3.11.0
1533+ queryParamToList :: param -> [(Text, Maybe Text)]
1534+
14961535instance QueryParam (Option scheme) where
14971536 queryParam name mvalue =
14981537 withQueryParams ((:) (name, toQueryParam <$> mvalue))
1538+ queryParamToList (Option f _) = fst $ appEndo f ([], L.defaultRequest)
14991539
15001540----------------------------------------------------------------------------
15011541-- Request—Optional parameters—Headers
pure-tests/Network/HTTP/ReqSpec.hs+30
@@ -33,6 +33,7 @@ import qualified Data.Text as T
3333import qualified Data.Text.Encoding as T
3434import Data.Time
3535import Data.Typeable (Typeable, eqT)
36+import GHC.Exts (IsList (..))
3637import GHC.Generics
3738import qualified Language.Haskell.TH as TH
3839import qualified Language.Haskell.TH.Quote as TH
@@ -46,6 +47,7 @@ import Test.QuickCheck
4647import Text.URI (URI)
4748import qualified Text.URI as URI
4849import qualified Text.URI.QQ as QQ
50+import qualified Web.FormUrlEncoded as F
4951
5052spec :: Spec
5153spec = do
@@ -235,6 +237,19 @@ spec = do
235237 L.RequestBodyLBS x -> x `shouldBe` renderQuery params
236238 _ -> expectationFailure "Wrong request body constructor."
237239
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"
249+ specParamToList (Proxy :: Proxy FormUrlEncodedParam)
250+ describe "Option" $ do
251+ specParamToList (Proxy :: Proxy (Option 'Http))
252+
238253 describe "optional parameters" $ do
239254 describe "header" $ do
240255 it "sets specified header value" $
@@ -488,6 +503,9 @@ instance Arbitrary Day where
488503instance Arbitrary DiffTime where
489504 arbitrary = secondsToDiffTime <$> arbitrary
490505
506+instance Arbitrary F.Form where
507+ arbitrary = (F.Form . fromList) <$> arbitrary
508+
491509----------------------------------------------------------------------------
492510-- Helper types
493511
@@ -600,3 +618,15 @@ basicProxyAuthHeader :: ByteString -> ByteString -> ByteString
600618basicProxyAuthHeader username password =
601619 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
602620 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+1
@@ -83,6 +83,7 @@ test-suite pure-tests
8383 case-insensitive >=0.2 && <1.3,
8484 hspec >=2.0 && <3.0,
8585 hspec-core >=2.0 && <3.0,
86+ http-api-data >=0.2 && <0.5,
8687 http-client >=0.7 && <0.8,
8788 http-types >=0.8 && <10.0,
8889 modern-uri >=0.3 && <0.4,