# 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
```