packages feed

nri-http 0.1.1.0 → 0.2.0.0

raw patch · 6 files changed

+104/−75 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Http: Request :: Text -> [Header] -> Text -> Body -> Maybe Int -> Expect a -> Request a
+ Http: Request :: Text -> [Header] -> Text -> Body -> Maybe Int -> Expect x a -> Request x a
- Http: [body] :: Request a -> Body
+ Http: [body] :: Request x a -> Body
- Http: [expect] :: Request a -> Expect a
+ Http: [expect] :: Request x a -> Expect x a
- Http: [headers] :: Request a -> [Header]
+ Http: [headers] :: Request x a -> [Header]
- Http: [method] :: Request a -> Text
+ Http: [method] :: Request x a -> Text
- Http: [timeout] :: Request a -> Maybe Int
+ Http: [timeout] :: Request x a -> Maybe Int
- Http: [url] :: Request a -> Text
+ Http: [url] :: Request x a -> Text
- Http: data Expect a
+ Http: data Expect x a
- Http: data Request a
+ Http: data Request x a
- Http: expectBytesResponse :: (Response ByteString -> Result Error a) -> Expect a
+ Http: expectBytesResponse :: (Response ByteString -> Result x a) -> Expect x a
- Http: expectJson :: FromJSON a => Expect a
+ Http: expectJson :: FromJSON a => Expect Error a
- Http: expectText :: Expect Text
+ Http: expectText :: Expect Error Text
- Http: expectTextResponse :: (Response Text -> Result Error a) -> Expect a
+ Http: expectTextResponse :: (Response Text -> Result x a) -> Expect x a
- Http: expectWhatever :: Expect ()
+ Http: expectWhatever :: Expect Error ()
- Http: get :: Typeable a => Handler -> Text -> Expect a -> Task Error a
+ Http: get :: (Typeable x, Typeable a) => Handler -> Text -> Expect x a -> Task x a
- Http: post :: Typeable a => Handler -> Text -> Body -> Expect a -> Task Error a
+ Http: post :: (Typeable x, Typeable a) => Handler -> Text -> Body -> Expect x a -> Task x a
- Http: request :: Typeable expect => Handler -> Request expect -> Task Error expect
+ Http: request :: (Typeable x, Typeable expect) => Handler -> Request x expect -> Task x expect
- Http.Mock: getBytesBody :: Request expect -> ByteString
+ Http.Mock: getBytesBody :: Request Error expect -> ByteString
- Http.Mock: getHeader :: Text -> Request expect -> Maybe Text
+ Http.Mock: getHeader :: Text -> Request Error expect -> Maybe Text
- Http.Mock: getJsonBody :: FromJSON a => Request expect -> Result Text a
+ Http.Mock: getJsonBody :: FromJSON a => Request Error expect -> Result Text a
- Http.Mock: getTextBody :: Request expect -> Maybe Text
+ Http.Mock: getTextBody :: Request Error expect -> Maybe Text
- Http.Mock: mkStub :: Typeable expect => (Request expect -> Task Error (a, expect)) -> Stub a
+ Http.Mock: mkStub :: (Typeable e, Typeable expect) => (Request e expect -> Task e (a, expect)) -> Stub a

Files

nri-http.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           nri-http-version:        0.1.1.0+version:        0.2.0.0 synopsis:       Make Elm style HTTP requests description:    Please see the README at <https://github.com/NoRedInk/haskell-libraries/tree/trunk/nri-http#readme>. category:       Web
src/Http.hs view
@@ -108,7 +108,7 @@ -- QUICKS  -- | Create a @GET@ request.-get :: (Dynamic.Typeable a) => Handler -> Text -> Expect a -> Task Error a+get :: (Dynamic.Typeable x, Dynamic.Typeable a) => Handler -> Text -> Expect x a -> Task x a get handler' url expect =   request     handler'@@ -122,7 +122,7 @@       }  -- | Create a @POST@ request.-post :: (Dynamic.Typeable a) => Handler -> Text -> Body -> Expect a -> Task Error a+post :: (Dynamic.Typeable x, Dynamic.Typeable a) => Handler -> Text -> Body -> Expect x a -> Task x a post handler' url body expect =   request     handler'@@ -187,13 +187,15 @@  -- | Create a custom request. request ::-  (Dynamic.Typeable expect) =>+  ( Dynamic.Typeable x,+    Dynamic.Typeable expect+  ) =>   Handler ->-  Internal.Request expect ->-  Task Error expect+  Internal.Request x expect ->+  Task x expect request Internal.Handler {Internal.handlerRequest} settings = handlerRequest settings -_request :: Platform.DoAnythingHandler -> HTTP.Manager -> Internal.Request expect -> Task Error expect+_request :: Platform.DoAnythingHandler -> HTTP.Manager -> Internal.Request x expect -> Task x expect _request doAnythingHandler manager settings = do   requestManager <- prepareManagerForRequest manager   Platform.doAnything doAnythingHandler <| do@@ -222,7 +224,7 @@         HTTP.httpLbs finalRequest requestManager     pure <| handleResponse (Internal.expect settings) response -handleResponse :: Expect a -> Either HTTP.HttpException (HTTP.Response Data.ByteString.Lazy.ByteString) -> Result Error a+handleResponse :: Expect x a -> Either HTTP.HttpException (HTTP.Response Data.ByteString.Lazy.ByteString) -> Result x a handleResponse expect response =   case response of     Right okResponse ->@@ -247,8 +249,12 @@           exception             |> exceptionToResponse identity             |> mkResult-        _ ->+        Internal.ExpectJson ->           Err (exceptionToError exception)+        Internal.ExpectText ->+          Err (exceptionToError exception)+        Internal.ExpectWhatever ->+          Err (exceptionToError exception)  exceptionToError :: HTTP.HttpException -> Error exceptionToError exception =@@ -319,27 +325,27 @@  -- | -- Expect the response body to be JSON.-expectJson :: Aeson.FromJSON a => Expect a+expectJson :: Aeson.FromJSON a => Expect Error a expectJson = Internal.ExpectJson  -- | -- Expect the response body to be a `Text`.-expectText :: Expect Text+expectText :: Expect Error Text expectText = Internal.ExpectText  -- | -- Expect the response body to be whatever. It does not matter. Ignore it!-expectWhatever :: Expect ()+expectWhatever :: Expect Error () expectWhatever = Internal.ExpectWhatever  -- | -- Expect a `Response` with a `Text` body.-expectTextResponse :: (Internal.Response Text -> Result Error a) -> Expect a+expectTextResponse :: (Internal.Response Text -> Result x a) -> Expect x a expectTextResponse = Internal.ExpectTextResponse  -- | -- Expect a `Response` with a `ByteString` body-expectBytesResponse :: (Internal.Response ByteString -> Result Error a) -> Expect a+expectBytesResponse :: (Internal.Response ByteString -> Result x a) -> Expect x a expectBytesResponse = Internal.ExpectBytesResponse  -- |
src/Http/Internal.hs view
@@ -17,13 +17,13 @@  -- | A handler for making HTTP requests. data Handler = Handler-  { handlerRequest :: forall expect. Dynamic.Typeable expect => Request expect -> Task Error expect,-    handlerWithThirdParty :: forall a e. (HTTP.Manager -> Task e a) -> Task e a,+  { handlerRequest :: forall e expect. (Dynamic.Typeable expect, Dynamic.Typeable e) => Request e expect -> Task e expect,+    handlerWithThirdParty :: forall e a. (HTTP.Manager -> Task e a) -> Task e a,     handlerWithThirdPartyIO :: forall a. Platform.LogHandler -> (HTTP.Manager -> IO a) -> IO a   }  -- | A custom request.-data Request a = Request+data Request x a = Request   { -- | The request method, like @"GET"@ or @"PUT"@.     method :: Text,     -- | A list of request headers.@@ -35,7 +35,7 @@     -- | The amount of microseconds you're willing to wait before giving up.     timeout :: Maybe Int,     -- | The type of response you expect back from the request.-    expect :: Expect a+    expect :: Expect x a   }  -- | An HTTP header for configuration requests.@@ -50,12 +50,12 @@  -- | -- Logic for interpreting a response body.-data Expect a where-  ExpectJson :: Aeson.FromJSON a => Expect a-  ExpectText :: Expect Text-  ExpectWhatever :: Expect ()-  ExpectTextResponse :: (Response Text -> Result Error a) -> Expect a-  ExpectBytesResponse :: (Response Data.ByteString.ByteString -> Result Error a) -> Expect a+data Expect x a where+  ExpectJson :: Aeson.FromJSON a => Expect Error a+  ExpectText :: Expect Error Text+  ExpectWhatever :: Expect Error ()+  ExpectTextResponse :: (Response Text -> Result x a) -> Expect x a+  ExpectBytesResponse :: (Response Data.ByteString.ByteString -> Result x a) -> Expect x a  -- | A 'Request' can fail in a couple of ways: --
src/Http/Mock.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-} @@ -35,14 +36,14 @@ -- different kinds of http requests, you'll want one of these per request type. data Stub a where   Stub ::-    Dynamic.Typeable expect =>-    (Internal.Request expect -> Task Internal.Error (a, expect)) ->+    (Dynamic.Typeable e, Dynamic.Typeable expect) =>+    (Internal.Request e expect -> Task e (a, expect)) ->     Stub a  -- | Create a 'Stub'. mkStub ::-  Dynamic.Typeable expect =>-  (Internal.Request expect -> Task Internal.Error (a, expect)) ->+  (Dynamic.Typeable e, Dynamic.Typeable expect) =>+  (Internal.Request e expect -> Task e (a, expect)) ->   Stub a mkStub = Stub @@ -80,16 +81,7 @@   let mockHandler =         Internal.Handler           ( \req -> do-              (log, res) <--                tryRespond-                  responders-                  ( Internal.NetworkError-                      ( "Http request was made with expected return type "-                          ++ printType req-                          ++ ", but I don't how to create a mock response of this type. Please add a `mkStub` entry for this type in the test."-                      )-                  )-                  req+              (log, res) <- tryRespond responders req               Data.IORef.modifyIORef' logRef (\prev -> log : prev)                 |> map Ok                 |> Platform.doAnything doAnything@@ -105,7 +97,7 @@ -- submitted inside a 'stub' function. -- -- This will return 'Nothing' if the body cannot be parsed as UTF8 text.-getTextBody :: Internal.Request expect -> Maybe Text+getTextBody :: Internal.Request Internal.Error expect -> Maybe Text getTextBody req =   Data.Text.Encoding.decodeUtf8' (getBytesBody req)     |> eitherToMaybe@@ -114,7 +106,7 @@ -- submitted inside a 'stub' function. -- -- This will return an error if parsing the JSON body fails.-getJsonBody :: Aeson.FromJSON a => Internal.Request expect -> Result Text a+getJsonBody :: Aeson.FromJSON a => Internal.Request Internal.Error expect -> Result Text a getJsonBody req =   case Aeson.eitherDecodeStrict (getBytesBody req) of     Prelude.Left err -> Err (Text.fromList err)@@ -122,7 +114,7 @@  -- | Read the body of the request as bytes. Useful to check what data got -- submitted inside a 'stub' function.-getBytesBody :: Internal.Request expect -> ByteString+getBytesBody :: Internal.Request Internal.Error expect -> ByteString getBytesBody req =   Internal.body req     |> Internal.bodyContents@@ -133,7 +125,7 @@ -- -- This will return 'Nothing' if no header with that name was set on the -- request.-getHeader :: Text -> Internal.Request expect -> Maybe Text+getHeader :: Text -> Internal.Request Internal.Error expect -> Maybe Text getHeader name req =   Internal.headers req     |> List.map Internal.unHeader@@ -148,17 +140,36 @@  tryRespond ::   ( Dynamic.Typeable expect,-    Dynamic.Typeable a+    Dynamic.Typeable a,+    Dynamic.Typeable e   ) =>   List (Stub a) ->-  Internal.Error ->-  Internal.Request expect ->-  Task Internal.Error (a, expect)-tryRespond [] err _ = Task.fail err-tryRespond (Stub respond : rest) err req =+  Internal.Request e expect ->+  Task e (a, expect)+tryRespond [] req =+  let msg =+        "Http request was made with expected return type "+          ++ printType req+          ++ ", but I don't how to create a mock response of this type. Please add a `mkStub` entry for this type in the test."+      handleCustomResponse :: (Internal.Response s -> Result e expect) -> Task e (a, expect)+      handleCustomResponse f = case f (Internal.NetworkError_ msg) of+        Err err -> Task.fail err+        Ok _ -> Debug.todo "Since we manually craft the Response as an Error, this case will not run."+   in case Internal.expect req of+        Internal.ExpectJson ->+          Task.fail (Internal.NetworkError msg)+        Internal.ExpectText ->+          Task.fail (Internal.NetworkError msg)+        Internal.ExpectWhatever ->+          Task.fail (Internal.NetworkError msg)+        Internal.ExpectTextResponse f ->+          handleCustomResponse f+        Internal.ExpectBytesResponse f ->+          handleCustomResponse f+tryRespond (Stub respond : rest) req =   Dynamic.dynApply (Dynamic.toDyn respond) (Dynamic.toDyn req)     |> Maybe.andThen Dynamic.fromDynamic-    |> Maybe.withDefault (tryRespond rest err req)+    |> Maybe.withDefault (tryRespond rest req)  printType :: Dynamic.Typeable expect => proxy expect -> Text printType expect =
test/Main.hs view
@@ -58,7 +58,7 @@           (constant "12" Status.ok200)           ( \http url -> do               err <--                Http.get http url (Http.expectJson :: Http.Expect Text)+                Http.get http url (Http.expectJson :: Http.Expect Http.Error Text)                   |> Expect.fails               err                 |> Expect.equal (Http.BadBody "Error in $: parsing Text failed, expected String, but encountered Number")@@ -114,7 +114,7 @@       test "Http.Mock.stub" <| \_ -> do         urlsAccessed <-           Http.Mock.stub-            [Http.Mock.mkStub (\req -> Task.succeed (Http.url req, "Response!" :: Text))]+            [Http.Mock.mkStub (\req -> Task.succeed (Http.url req, "Response!") :: Task Http.Error (Text, Text))]             ( \http ->                 Expect.succeeds <| do                   _ <- Http.get http "example.com/one" Http.expectText@@ -127,33 +127,45 @@         withServer           (constant "Some text" Status.ok200)           ( \http url -> do-            res <--              Http.get http url (Http.expectTextResponse Ok)-                |> Expect.succeeds-            case res of-              Http.GoodStatus_ metadata body -> do-                Expect.equal 200 (Http.metadataStatusCode metadata)-                Expect.equal "OK" (Http.metadataStatusText metadata)-                Expect.equal "Some text" body-              other ->-                Expect.fail <| "Unexpected response: " ++ (Text.fromList <| Prelude.show other)+              res <-+                Http.get http url (Http.expectTextResponse Ok)+                  |> Expect.succeeds+              case res of+                Http.GoodStatus_ metadata body -> do+                  Expect.equal 200 (Http.metadataStatusCode metadata)+                  Expect.equal "OK" (Http.metadataStatusText metadata)+                  Expect.equal "Some text" body+                other ->+                  Expect.fail <| "Unexpected response: " ++ (Text.fromList <| Prelude.show other)           ),       test "Using expectBytesResponse, we can read the body when the request is not successful" <| \() ->         withServer           (constant "This is a bad request" Status.badRequest400)           ( \http url -> do-            res <--              Http.get http url (Http.expectBytesResponse Ok)-                |> Expect.succeeds-            case res of-              Http.BadStatus_ metadata body -> do-                Expect.equal 400 (Http.metadataStatusCode metadata)-                Expect.equal "Bad Request" (Http.metadataStatusText metadata)-                Expect.equal "This is a bad request" body-              other ->-                Expect.fail <| "Unexpected response: " ++ (Text.fromList <| Prelude.show other)+              res <-+                Http.get http url (Http.expectBytesResponse Ok)+                  |> Expect.succeeds+              case res of+                Http.BadStatus_ metadata body -> do+                  Expect.equal 400 (Http.metadataStatusCode metadata)+                  Expect.equal "Bad Request" (Http.metadataStatusText metadata)+                  Expect.equal "This is a bad request" body+                other ->+                  Expect.fail <| "Unexpected response: " ++ (Text.fromList <| Prelude.show other)+          ),+      test "We can use a custom error type" <| \() ->+        withServer+          (constant "This is a bad request" Status.badRequest400)+          ( \http url -> do+              res <-+                Http.get http url (Http.expectTextResponse (\_ -> Err CustomResponseError)) |> Expect.fails+              case res of+                CustomResponseError ->+                  Expect.pass           )     ]++data CustomResponseError = CustomResponseError  -- # Wai applications to test against -- WAI NOT?
test/golden-results/expected-http-span view
@@ -9,9 +9,9 @@             { srcLocPackage = "main"             , srcLocModule = "Main"             , srcLocFile = "test/Main.hs"-            , srcLocStartLine = 221+            , srcLocStartLine = 233             , srcLocStartCol = 7-            , srcLocEndLine = 225+            , srcLocEndLine = 237             , srcLocEndCol = 40             }         )@@ -31,9 +31,9 @@                     { srcLocPackage = "main"                     , srcLocModule = "Http"                     , srcLocFile = "src/Http.hs"-                    , srcLocStartLine = 428+                    , srcLocStartLine = 434                     , srcLocStartCol = 11-                    , srcLocEndLine = 440+                    , srcLocEndLine = 446                     , srcLocEndCol = 14                     }                 )