git.iko.soy/typeable/servant
on master help sign in
tentative improvements
fbd9f3eAlp Mestanogullari
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
2
2
3−Someone asked on IRC about getting `Request` & `Response` of what
3+or simply put: _a practical introduction to `Servant.Client.Free`_.
4−`servant-client` uses, for testing purposes. My response: to ad-hoc extend
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
5−`servant-client` (as for tests), use `Servant.Client.Free`. This recipe is an
9+purposes), use `Servant.Client.Free`. This recipe shows how.
6−evidence.
7
10
8First the module header, but this time We'll comment the imports.
11First the module header, but this time We'll comment the imports.
9
12
@@ -48,9 +51,9 @@ api :: Proxy API
48api = Proxy
51api = Proxy
49```
52```
50
53
51−Next we implement a `main`. If passed `server` it will run `server`, if
54+Next we implement a `main`. If passed `server` it will run `server`, if passed
52−`client` a small `test` (to be defined next) will be run. This should be pretty
55+`client` it will run a small `test` (to be defined next) will be run. This should be
53−straigh-forward:
56+pretty straightforward:
54
57
55```haskell
58```haskell
56main :: IO ()
59main :: IO ()
@@ -70,20 +73,29 @@ main = do
70
73
71## Test
74## Test
72
75
73−In the actual test, we'll use a `Servant.Client.Free` client.
76+In the client part, we will use a `Servant.Client.Free` client.
74−Cecause we have a single endpoint API, we'll get a single client function:
77+Because we have a single endpoint API, we'll get a single client function,
78+running in the `Free ClientF` (free) monad:
75
79
76```haskell
80```haskell
77−fcli :: Int -> Free ClientF Int
81+getSquare :: Int -> Free ClientF Int
78−fcli = client api
82+getSquare = client api
79```
83```
80
84
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+
81−Next, we can write our small test. We'll pass a value to `fcli` and inspect
93+Next, we can write our small test. We'll pass a value to `getSquare` and inspect
82−the `Free` structure. First three possibilities are self-explanatory:
94+the `Free` structure. The first three possibilities are self-explanatory:
83
95
84```haskell
96```haskell
85test :: IO ()
97test :: IO ()
86−test = case fcli 42 of
98+test = case getSquare 42 of
87 Pure n ->
99 Pure n ->
88 putStrLn $ "ERROR: got pure result: " ++ show n
100 putStrLn $ "ERROR: got pure result: " ++ show n
89 Free (Throw err) ->
101 Free (Throw err) ->
@@ -136,8 +148,18 @@ and calling the continuation. We should get a `Pure` value.
136```
148```
137
149
138So that's it. Using `Free` we can evaluate servant clients step-by-step, and
150So 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
151+validate that the client functions or the HTTP client backend does what we expect
140−end an example of client run (prettified):
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:
141
163
142```
164```
143Making request: Request {
165Making request: Request {