packages feed

request 0.2.3.0 → 0.3.0.0

raw patch · 4 files changed

+132/−39 lines, 4 files

Files

README.md view
@@ -16,9 +16,40 @@ import Network.HTTP.Request  resp <- get "https://api.leancloud.cn/1.1/date"-print $ requestStatus resp+print $ responseStatus resp ``` +## Record Dot Syntax Support++This library supports modern Haskell record dot syntax. To use it, enable these language extensions:++```haskell+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}+```++### Creating Records with Dot Syntax++```haskell+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-}++import Network.HTTP.Request+import qualified Data.ByteString as BS++-- Create request using record dot syntax+let req = Request { method = GET, url = "https://api.leancloud.cn/1.1/date", headers = [], body = Nothing }++-- Response with ByteString body+responseBS <- send req :: IO (Response BS.ByteString)+print responseBS.status        -- 200+print responseBS.body          -- ByteString response++-- Response with String body+responseStr <- send req :: IO (Response String)+print responseStr.body         -- String response+```+ ## Core API  Request's API has three core concepts: `Request` record type, `Response` record type, `send` function.@@ -28,11 +59,11 @@ `Request` is all about the information you will send to the target URL.  ```haskell-data Request = Request-  { requestMethod  :: Method-  , requestUrl     :: String-  , requestHeaders :: Headers-  , requestBody    :: Maybe Data.ByteString.ByteString+data Request a = Request+  { method  :: Method+  , url     :: String+  , headers :: Headers+  , body    :: Maybe a   } deriving (Show) ``` @@ -41,7 +72,7 @@ Once you have constructed your own `Request` record, you can call the `send` function to send it to the server. The `send` function's type is:  ```haskell-send :: Request -> IO Response+send :: (IsString a) => Request a -> IO (Response a) ```  ### Response@@ -49,17 +80,26 @@ `Response` is what you got from the server URL.  ```haskell-data Response = Response-  { responseStatus  :: Int-  , responseHeaders :: Headers-  , responseBody    :: Data.ByteString.ByteString+data Response a = Response+  { status  :: Int+  , headers :: Headers+  , body    :: a   } deriving (Show) ``` +The response body type `a` can be any type that implements the `IsString` constraint, allowing flexible handling of response data.++### Backward Compatibility++For users who prefer not to use the language extensions, you can still:++- Create requests using positional arguments: `Request GET "url" [] Nothing`+- Use prefixed accessor functions: `requestStatus response`, `requestHeaders response`, etc.+ ### Example  ```haskell-:set -XOverloadedStrings+{-# LANGUAGE OverloadedStrings #-}  import Network.HTTP.Request @@ -67,8 +107,8 @@ let req = Request GET "https://api.leancloud.cn/1.1/date" [] Nothing -- Send it. res <- send req--- access the fields on Response.-print $ requestStatus resp+-- Access the fields on Response.+print $ responseStatus res ```  ## Shortcuts@@ -76,21 +116,21 @@ As you expected, there are some shortcuts for the most used scenarios.  ```haskell-get :: String -> IO Response+get :: String -> IO (Response BS.ByteString) get url =-  send $ Request GET url [] Nothing+  send $ Request { method = GET, url = url, headers = [], body = Nothing } -delete :: String -> IO Response+delete :: String -> IO (Response BS.ByteString) delete url =-  send $ Request DELETE url [] Nothing+  send $ Request { method = DELETE, url = url, headers = [], body = Nothing } -post :: (String, Maybe Data.ByteString.ByteString) -> IO Response+post :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) post (url, body) =-  send $ Request POST url [] body+  send $ Request { method = POST, url = url, headers = [], body = body } -put :: (String, Maybe Data.ByteString.ByteString) -> IO Response+put :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) put (url, body) =-  send $ Request PUT url [] body+  send $ Request { method = PUT, url = url, headers = [], body = body } ```  These shortcuts' definitions are simple and direct. You are encouraged to add your own if the built-in does not match your use cases, like add custom headers in every request.
request.cabal view
@@ -1,5 +1,5 @@ name:                request-version:             0.2.3.0+version:             0.3.0.0 -- synopsis: description:         "HTTP client for haskell, inpired by requests and http-dispatch." homepage:            https://github.com/aisk/request#readme@@ -35,4 +35,5 @@   build-depends:       base                      , request                      , hspec+                     , bytestring         >= 0.10.12 && < 0.13   default-language:    Haskell2010
src/Network/HTTP/Request.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}  module Network.HTTP.Request@@ -11,6 +13,13 @@     post,     put,     send,+    requestMethod,+    requestUrl,+    requestHeaders,+    requestBody,+    responseStatus,+    responseHeaders,+    responseBody,   ) where @@ -51,13 +60,26 @@   show (Method method) = method  data Request a = Request-  { requestMethod :: Method,-    requestUrl :: String,-    requestHeaders :: Headers,-    requestBody :: Maybe a+  { method :: Method,+    url :: String,+    headers :: Headers,+    body :: Maybe a   }   deriving (Show) +-- Compatibility accessor functions+requestMethod :: Request a -> Method+requestMethod req = req.method++requestUrl :: Request a -> String+requestUrl req = req.url++requestHeaders :: Request a -> Headers+requestHeaders req = req.headers++requestBody :: Request a -> Maybe a+requestBody req = req.body+ toLowlevelRequest :: (S.IsString a) => Request a -> IO LowLevelClient.Request toLowlevelRequest req = do   initReq <- LowLevelClient.parseRequest $ requestUrl req@@ -67,17 +89,27 @@         LowLevelClient.requestHeaders = map (\(k, v) -> (CI.mk k, v)) $ requestHeaders req       } -data Response = Response-  { responseStatus :: Int,-    responseHeaders :: Headers,-    responseBody :: BS.ByteString+data Response a = Response+  { status :: Int,+    headers :: Headers,+    body :: a   }   deriving (Show) -fromLowLevelRequest :: LowLevelClient.Response LBS.ByteString -> Response+-- Compatibility accessor functions for Response+responseStatus :: Response a -> Int+responseStatus res = res.status++responseHeaders :: Response a -> Headers+responseHeaders res = res.headers++responseBody :: Response a -> a+responseBody res = res.body++fromLowLevelRequest :: (S.IsString a) => LowLevelClient.Response LBS.ByteString -> Response a fromLowLevelRequest res =   let status = LowLevelStatus.statusCode . LowLevelClient.responseStatus $ res-      body = LBS.toStrict $ LowLevelClient.responseBody res+      body = S.fromString . C.unpack . LBS.toStrict $ LowLevelClient.responseBody res       headers = LowLevelClient.responseHeaders res    in Response         status@@ -90,29 +122,29 @@         )         body -send :: (S.IsString a) => Request a -> IO Response+send :: (S.IsString a) => Request a -> IO (Response a) send req = do   manager <- LowLevelTLSClient.getGlobalManager   llreq <- toLowlevelRequest req   llres <- LowLevelClient.httpLbs llreq manager   return $ fromLowLevelRequest llres -get :: String -> IO Response+get :: String -> IO (Response BS.ByteString) get url =   send $ Request GET url [] Nothing -delete :: String -> IO Response+delete :: String -> IO (Response BS.ByteString) delete url =   send $ Request DELETE url [] Nothing -post :: (String, Maybe BS.ByteString) -> IO Response+post :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) post (url, body) =   send $ Request POST url [] body -put :: (String, Maybe BS.ByteString) -> IO Response+put :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) put (url, body) =   send $ Request PUT url [] body -patch :: (String, Maybe BS.ByteString) -> IO Response+patch :: (String, Maybe BS.ByteString) -> IO (Response BS.ByteString) patch (url, body) =   send $ Request PATCH url [] body
test/Spec.hs view
@@ -1,9 +1,12 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-}  module Main where  import Network.HTTP.Request import Test.Hspec+import qualified Data.ByteString as BS  main :: IO () main = hspec $ do@@ -27,3 +30,20 @@     it "should patch to httpbin.org/patch and return 200 OK" $ do       response <- patch ("https://postman-echo.com/patch", Just "Hello!")       responseStatus response `shouldBe` 200++    it "should use dot record syntax to create and access request/response" $ do+      let req = Request { method = GET, url = "http://example.com", headers = [("User-Agent", "Haskell-Request")], body = Nothing }+      response <- send req+      response.status `shouldBe` 200+      response.headers `shouldSatisfy` (not . null)++    it "should access response body with different IsString types" $ do+      -- Test with ByteString body+      let req1 = Request { method = GET, url = "http://example.com", headers = [], body = Nothing }+      response1 <- send req1 :: IO (Response BS.ByteString)+      BS.length response1.body `shouldSatisfy` (> 0)++      -- Test with String body+      let req2 = Request { method = GET, url = "http://example.com", headers = [], body = Nothing }+      response2 <- send req2 :: IO (Response String)+      not (null response2.body) `shouldBe` True