on master help sign in
Add formToQuery, queryParamToList, and FromForm instances
67be279Toshio Ito
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+
1## Req 3.10.0
8## Req 3.10.0
2
9
3* Add `MonadHttp` instances for `transformers` types.
10* Add `MonadHttp` instances for `transformers` types.
Network/HTTP/Req.hs+40
@@ -155,6 +155,7 @@ module Network.HTTP.Req
155 -- $query-parameters
155 -- $query-parameters
156 (=:),
156 (=:),
157 queryFlag,
157 queryFlag,
158+ formToQuery,
158 QueryParam (..),
159 QueryParam (..),
159
160
160 -- *** Headers
161 -- *** Headers
@@ -272,6 +273,8 @@ import Text.URI (URI)
272import qualified Text.URI as URI
273import qualified Text.URI as URI
273import qualified Text.URI.QQ as QQ
274import qualified Text.URI.QQ as QQ
274import qualified Web.Authenticate.OAuth as OAuth
275import qualified Web.Authenticate.OAuth as OAuth
276+import Web.FormUrlEncoded (FromForm (..), ToForm (..))
277+import qualified Web.FormUrlEncoded as Form
275import Web.HttpApiData (ToHttpApiData (..))
278import Web.HttpApiData (ToHttpApiData (..))
276
279
277----------------------------------------------------------------------------
280----------------------------------------------------------------------------
@@ -1303,6 +1306,13 @@ newtype FormUrlEncodedParam = FormUrlEncodedParam [(Text, Maybe Text)]
1303instance QueryParam FormUrlEncodedParam where
1306instance QueryParam FormUrlEncodedParam where
1304 queryParam name mvalue =
1307 queryParam name mvalue =
1305 FormUrlEncodedParam [(name, toQueryParam <$> mvalue)]
1308 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
1306
1316
1307-- | Multipart form data. Please consult the
1317-- | Multipart form data. Please consult the
1308-- "Network.HTTP.Client.MultipartFormData" module for how to construct
1318-- "Network.HTTP.Client.MultipartFormData" module for how to construct
@@ -1428,6 +1438,12 @@ instance Monoid (Option scheme) where
1428 mempty = Option mempty Nothing
1438 mempty = Option mempty Nothing
1429 mappend = (<>)
1439 mappend = (<>)
1430
1440
1441+-- | Use 'formToQuery'.
1442+--
1443+-- @since 3.11.0
1444+instance FromForm (Option scheme) where
1445+ fromForm = Right . formToQuery
1446+
1431-- | A helper to create an 'Option' that modifies only collection of query
1447-- | A helper to create an 'Option' that modifies only collection of query
1432-- parameters. This helper is not a part of the public API.
1448-- parameters. This helper is not a part of the public API.
1433withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
1449withQueryParams :: (Y.QueryText -> Y.QueryText) -> Option scheme
@@ -1482,6 +1498,24 @@ name =: value = queryParam name (pure value)
1482queryFlag :: QueryParam param => Text -> param
1498queryFlag :: QueryParam param => Text -> param
1483queryFlag name = queryParam name (Nothing :: Maybe ())
1499queryFlag name = queryParam name (Nothing :: Maybe ())
1484
1500
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+
1485-- | A type class for query-parameter-like things. The reason to have an
1519-- | A type class for query-parameter-like things. The reason to have an
1486-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
1520-- overloaded 'queryParam' is to be able to use it as an 'Option' and as a
1487-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
1521-- 'FormUrlEncodedParam' when constructing form URL encoded request bodies.
@@ -1493,9 +1527,15 @@ class QueryParam param where
1493 -- method, because they are easier to read.
1527 -- method, because they are easier to read.
1494 queryParam :: ToHttpApiData a => Text -> Maybe a -> param
1528 queryParam :: ToHttpApiData a => Text -> Maybe a -> param
1495
1529
1530+ -- | Get the query parameter names and values set by 'queryParam'.
1531+ --
1532+ -- @since 3.11.0
1533+ queryParamToList :: param -> [(Text, Maybe Text)]
1534+
1496instance QueryParam (Option scheme) where
1535instance QueryParam (Option scheme) where
1497 queryParam name mvalue =
1536 queryParam name mvalue =
1498 withQueryParams ((:) (name, toQueryParam <$> mvalue))
1537 withQueryParams ((:) (name, toQueryParam <$> mvalue))
1538+ queryParamToList (Option f _) = fst $ appEndo f ([], L.defaultRequest)
1499
1539
1500----------------------------------------------------------------------------
1540----------------------------------------------------------------------------
1501-- Request—Optional parameters—Headers
1541-- Request—Optional parameters—Headers
pure-tests/Network/HTTP/ReqSpec.hs+30
@@ -33,6 +33,7 @@ import qualified Data.Text as T
33import qualified Data.Text.Encoding as T
33import qualified Data.Text.Encoding as T
34import Data.Time
34import Data.Time
35import Data.Typeable (Typeable, eqT)
35import Data.Typeable (Typeable, eqT)
36+import GHC.Exts (IsList (..))
36import GHC.Generics
37import GHC.Generics
37import qualified Language.Haskell.TH as TH
38import qualified Language.Haskell.TH as TH
38import qualified Language.Haskell.TH.Quote as TH
39import qualified Language.Haskell.TH.Quote as TH
@@ -46,6 +47,7 @@ import Test.QuickCheck
46import Text.URI (URI)
47import Text.URI (URI)
47import qualified Text.URI as URI
48import qualified Text.URI as URI
48import qualified Text.URI.QQ as QQ
49import qualified Text.URI.QQ as QQ
50+import qualified Web.FormUrlEncoded as F
49
51
50spec :: Spec
52spec :: Spec
51spec = do
53spec = do
@@ -235,6 +237,19 @@ spec = do
235 L.RequestBodyLBS x -> x `shouldBe` renderQuery params
237 L.RequestBodyLBS x -> x `shouldBe` renderQuery params
236 _ -> expectationFailure "Wrong request body constructor."
238 _ -> expectationFailure "Wrong request body constructor."
237
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"
249+ specParamToList (Proxy :: Proxy FormUrlEncodedParam)
250+ describe "Option" $ do
251+ specParamToList (Proxy :: Proxy (Option 'Http))
252+
238 describe "optional parameters" $ do
253 describe "optional parameters" $ do
239 describe "header" $ do
254 describe "header" $ do
240 it "sets specified header value" $
255 it "sets specified header value" $
@@ -488,6 +503,9 @@ instance Arbitrary Day where
488instance Arbitrary DiffTime where
503instance Arbitrary DiffTime where
489 arbitrary = secondsToDiffTime <$> arbitrary
504 arbitrary = secondsToDiffTime <$> arbitrary
490
505
506+instance Arbitrary F.Form where
507+ arbitrary = (F.Form . fromList) <$> arbitrary
508+
491----------------------------------------------------------------------------
509----------------------------------------------------------------------------
492-- Helper types
510-- Helper types
493
511
@@ -600,3 +618,15 @@ basicProxyAuthHeader :: ByteString -> ByteString -> ByteString
600basicProxyAuthHeader username password =
618basicProxyAuthHeader username password =
601 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
619 fromJust . lookup Y.hProxyAuthorization . L.requestHeaders $
602 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+1
@@ -83,6 +83,7 @@ test-suite pure-tests
83 case-insensitive >=0.2 && <1.3,
83 case-insensitive >=0.2 && <1.3,
84 hspec >=2.0 && <3.0,
84 hspec >=2.0 && <3.0,
85 hspec-core >=2.0 && <3.0,
85 hspec-core >=2.0 && <3.0,
86+ http-api-data >=0.2 && <0.5,
86 http-client >=0.7 && <0.8,
87 http-client >=0.7 && <0.8,
87 http-types >=0.8 && <10.0,
88 http-types >=0.8 && <10.0,
88 modern-uri >=0.3 && <0.4,
89 modern-uri >=0.3 && <0.4,