git.iko.soy/typeable/servant
on master help sign in
tentative improvements
commitfbd9f3ec29b45f6b8f8f1a6a81afdabafbd85d22 authorAlp Mestanogullari <alpmestan@gmail.com> dateFri 6 Jul 2018, 00:36 (8 years ago) parent8dc323e
1 file changed+39−17
doc/cookbook/using-free-client/UsingFreeClient.lhs+39−17
doc/cookbook/using-free-client/UsingFreeClient.lhs+39−17
@@ -1,9 +1,12 @@
1−# Using Free Client (for tests)
1+# Inspecting, debugging, simulating clients and more
22
3−Someone asked on IRC about getting `Request` & `Response` of what
4−`servant-client` uses, for testing purposes. My response: to ad-hoc extend
5−`servant-client` (as for tests), use `Servant.Client.Free`. This recipe is an
6−evidence.
3+or simply put: _a practical introduction to `Servant.Client.Free`_.
4+By Oleg Grenrus
5+
6+Someone asked on IRC how one could access the intermediate Requests (resp. Responses)
7+produced (resp. received) by client functions derived using servant-client.
8+My response to such inquiries is: to extend `servant-client` in an ad-hoc way (e.g for testing or debugging
9+purposes), use `Servant.Client.Free`. This recipe shows how.
710
811First the module header, but this time We'll comment the imports.
912
@@ -48,9 +51,9 @@ api :: Proxy API
4851api = Proxy
4952```
5053
51−Next we implement a `main`. If passed `server` it will run `server`, if
52−`client` a small `test` (to be defined next) will be run. This should be pretty
53−straigh-forward:
54+Next we implement a `main`. If passed `server` it will run `server`, if passed
55+`client` it will run a small `test` (to be defined next) will be run. This should be
56+pretty straightforward:
5457
5558```haskell
5659main :: IO ()
@@ -70,20 +73,29 @@ main = do
7073
7174## Test
7275
73−In the actual test, we'll use a `Servant.Client.Free` client.
74−Cecause we have a single endpoint API, we'll get a single client function:
76+In the client part, we will use a `Servant.Client.Free` client.
77+Because we have a single endpoint API, we'll get a single client function,
78+running in the `Free ClientF` (free) monad:
7579
7680```haskell
77−fcli :: Int -> Free ClientF Int
78−fcli = client api
81+getSquare :: Int -> Free ClientF Int
82+getSquare = client api
7983```
8084
81−Next, we can write our small test. We'll pass a value to `fcli` and inspect
82−the `Free` structure. First three possibilities are self-explanatory:
85+Such clients are "client functions without a backend", so to speak,
86+or where the backend has been abstracted out. To be more precise, `ClientF` is a functor that
87+precisely represents the operations servant-client-core needs from an http client backend.
88+So if we are to emulate one or augment what such a backend does, it will be by interpreting
89+all those operations, the way we want to. This also means we get access to the requests and
90+responses and can do anything we want with them, right when they are produced or consumed,
91+respectively.
92+
93+Next, we can write our small test. We'll pass a value to `getSquare` and inspect
94+the `Free` structure. The first three possibilities are self-explanatory:
8395
8496```haskell
8597test :: IO ()
86−test = case fcli 42 of
98+test = case getSquare 42 of
8799 Pure n ->
88100 putStrLn $ "ERROR: got pure result: " ++ show n
89101 Free (Throw err) ->
@@ -136,8 +148,18 @@ and calling the continuation. We should get a `Pure` value.
136148```
137149
138150So that's it. Using `Free` we can evaluate servant clients step-by-step, and
139−validate that they or the backend does what we expect (think: debugger). At the
140−end an example of client run (prettified):
151+validate that the client functions or the HTTP client backend does what we expect
152+(e.g by printing requests/responses on the fly). In fact, using `Servant.Client.Free`
153+is a little simpler than defining a custom `RunClient` instance, especially
154+for those cases where it is handy to have the full sequence of client calls
155+and responses available for us to inspect, since `RunClient` only gives us
156+access to one `Request` or `Response` at a time.
157+
158+On the other hand, a "batch collection" of requests and/or responses can be achieved
159+with both free clients and a custom `RunClient` instance rather easily, for example
160+by using a `Writer [(Request, Response)]` monad.
161+
162+Here is an example of running our small `test` against a running server:
141163
142164```
143165Making request: Request {