# Dormouse-Client Dormouse is an HTTP client that will help you REST. It was designed with the following objectives in mind: - HTTP requests and responses should be modelled by a simple, immutable Haskell Record. - Real HTTP calls should be made via an abstraction layer (`MonadDormouseClient`) so testing and mocking is painless. - Illegal requests should be unrepresentable, such as HTTP GET requests with a content body. - It should be possible to enforce a protocol (e.g. https) at the type level. - It should be possible to handle large request and response bodies via constant memory streaming. Example use: ```haskell {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} import Control.Monad.IO.Class import Data.Aeson.TH import Dormouse.Client import GHC.Generics (Generic) import Dormouse.Url.QQ data UserDetails = UserDetails { name :: String , nickname :: String , email :: String } deriving (Eq, Show, Generic) deriveJSON defaultOptions ''UserDetails data EchoedJson a = EchoedJson { echoedjson :: a } deriving (Eq, Show, Generic) deriveJSON defaultOptions {fieldLabelModifier = drop 6} ''EchoedJson main :: IO () main = do manager <- newManager tlsManagerSettings runDormouseClient (DormouseClientConfig { clientManager = manager }) $ do let userDetails = UserDetails { name = "James T. Kirk" , nickname = "Jim" , email = "james.t.kirk@starfleet.com" } req = accept json $ supplyBody json userDetails $ post [https|https://postman-echo.com/post|] response :: HttpResponse (EchoedJson UserDetails) <- expect req liftIO $ print response return () ``` ## Building requests ### GET requests Building a GET request is simple using a `Url` (Please see the [Dormouse-Uri](../dormouse-uri/README.md) documentation for more details of how to safely create and construct `Url`s). ```haskell postmanEchoGetUrl :: Url "http" postmanEchoGetUrl = [http|http://postman-echo.com/get?foo1=bar1&foo2=bar2/|] postmanEchoGetReq :: HttpRequest (Url "http") "GET" Empty EmptyPayload acceptTag postmanEchoGetReq = get postmanEchoGetUrl ``` It is often useful to tell Dormouse about the expected `Content-Type` of the response in advance so that the correct `Accept` headers can be sent: ```haskell postmanEchoGetReq' :: HttpRequest (Url "http") "GET" Empty EmptyPayload JsonPayload postmanEchoGetReq' = accept json $ get postmanEchoGetUrl ``` ### POST requests You can build POST requests in the same way ```haskell postmanEchoPostUrl :: Url "https" postmanEchoPostUrl = [https|https://postman-echo.com/post|] postmanEchoPostReq :: HttpRequest (Url "https") "POST" Empty EmptyPayload JsonPayload postmanEchoPostReq = accept json $ post postmanEchoPostUrl ``` ## Expecting a response Since we're expecting json, we also need data types and `FromJSON` instances to interpret the response with. Let's start with an example to handle the GET request. ```haskell {-# LANGUAGE DeriveGeneric #-} ``` ```haskell data Args = Args { foo1 :: String , foo2 :: String } deriving (Eq, Show, Generic) data PostmanEchoResponse = PostmanEchoResponse { args :: Args } deriving (Eq, Show, Generic) ``` Once the request has been built, you can send it and expect a response of a particular type in any `MonadDormouseClient m`. ```haskell sendPostmanEchoGetReq :: (MonadDormouseClient m, MonadThrow m) => m PostmanEchoResponse sendPostmanEchoGetReq = do (resp :: HttpResponse PostmanEchoResponse) <- expect postmanEchoGetReq' return $ responseBody resp ``` ## Running Dormouse Dormouse is not opinionated about how you run it. You can use a concrete type. ```haskell main :: IO () main = do manager <- newManager tlsManagerSettings postmanResponse <- runDormouseClient (DormouseClientConfig { clientManager = manager }) sendPostmanEchoGetReq print postmanResponse ``` You can integrate the `DormouseClientT` Monad Transformer into your transformer stack. ```haskell main :: IO () main = do manager <- newManager tlsManagerSettings postmanResponse <- runDormouseClientT (DormouseClientConfig { clientManager = manager }) sendPostmanEchoGetReq print postmanResponse ``` You can also integrate into your own Application monad using the `sendHttp` function from `Dormouse.Client.MonadIOImpl` and by providing an instance of `HasDormouseConfig` for your application environment. ```haskell data MyEnv = MyEnv { dormouseEnv :: DormouseClientConfig } instance HasDormouseClientConfigMyEnv where getDormouseClientConfig = dormouseEnv newtype AppM a = AppM { unAppM :: ReaderT Env IO a } deriving (Functor, Applicative, Monad, MonadReader Env, MonadIO, MonadThrow) instance MonadDormouseClient (AppM) where send = IOImpl.sendHttp runAppM :: Env -> AppM a -> IO a runAppM deps app = flip runReaderT deps $ unAppM app ```