git.iko.soy/typeable/servant-reflex
on develop help sign in
165 lines 6.4 kB history raw
1Note: work in progress.
3# servant-reflex
5[![Build Status](https://travis-ci.org/imalsogreg/servant-reflex.svg?branch=dynamic)](https://travis-ci.org/imalsogreg/servant-reflex)
8This library lets you automatically derive [`reflex-dom`](https://github.com/reflex-frp/reflex-dom) clients that query each endpoint of a [`servant`](htps://github.com/haskell-servant/servant) webservice.
10## Building the library and test server
12### With `reflex-platform`:
14First build the library:
16```
17git clone https://github.com/imalsogreg/servant-reflex
18cd servant-reflex
19git submodule update --init --recursive
20./build.sh
21./toSite.sh
22```
24(the `toSite.sh` script copies the generated javascript to the server's static assets directory)
27Then build the test server (not in a nix-shell):
29```
30./init-sandbox.sh
31cd testserver && cabal install --only-dep && cabal build && cd ..
33```
36### Or, with a system-wide ghcjs
38You will need a recent [GHCJS installation](https://github.com/ghcjs/ghcjs), or use the [reflex-platform](https://github.com/reflex-frp/reflex-platform). A [snap](https://github.com/snapframework) server using [servant-snap](https://github.com/haskell-servant/servant-snap) is provided for serving the api. `Snap 1.0` is not yet on Hackage (and servant-snap is still experimental), but we bundle the sources as a git submodule. To install everything with cabal:
40```
41git clone https://github.com/imalsogreg/servant-reflex
42cd servant-reflex
43git submodule update --init --recursive
44./init-sandbox.sh
45cd testserver && cabal install --only-dep && cabal build && cd ..
46cabal install --only-dep
47cabal build
48./toSite
49```
51The `toSite.hs` script copies the ghcjs-generated files into the server's static directory.
53## Running the example site
55The server must be run from the directory where static assets live:
57```
58cd testserver
59dist/build/back/back -p 8001
60```
62And simply browse to `localhost:8001`
64## Example
66We have a webservice API defined in a module where both the server (compiled with ghc) and the frontend (compiled with ghcjs) can see it:
68```haskell
69type API = "getint" :> Get '[JSON] Int
70 :<|> "sayhi" :> QueryParam "username" String
71 :> QueryParams "greetings" String
72 :> QueryFlag "gusto"
73 :> Get '[JSON] String
74 :<|> "double" :> ReqBody '[JSON] Double
75 :> Post '[JSON] Double
76 :<|> Raw
77```
79`servant-reflex` then computes functions that can query the API through an `XhrRequest`.
81```haskell
83 runGUI :: forall t m.MonadWidget t m => do
85 -- servant-reflex computes FRP functions for each API endpoint
86 let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
87 (Proxy :: Proxy m)
88 (constDyn (BasePath "/"))
89```
91These client functions are computed from the API and manage serialization, XhrRequest generation, and deserialization for you. `a` parameters become `Dynamic t (Either String a)` values. You provide a trigger event and receive an `Event t (ReqResult a)`, with responses from the API server (which you would write with `servant-server`).
93```haskell
94 -- No need to write these functions. servant-reflex creates them for you!
95 getint :: MonadWidget t m
96 => Event t () -- ^ Trigger the XHR Request
97 -> m (Event t (ReqResult Int)) -- ^ Consume the answer
99 sayhi :: MonadWidget t m
100 => Dynamic t (Either String String)
101 -- ^ One input parameter - the 'name'
102 -> Dynamic t [String]
103 -- ^ Another input: list of preferred greetings
104 -> Dynamic t Bool
105 -- ^ Flag for capitalizing the response
106 -> Event t ()
107 -- ^ Trigger the XHR Request
108 -> m (Event t (ReqResult String))
110 doubleit :: MonadWidget t m
111 => Dynamic t (Either String Double)
112 -> Event t ()
113 -> m (Event t (ReqResult Double))
114```
116Plug any of these functions into your reflex frontend to consume backend servant services. *Isomorphic!*
118`ReqResult a` is defined in [`Servant.Common.Req`](https://github.com/imalsogreg/servant-reflex/blob/6d866e338edb9bf6fd8f8d5083ff0187b4d8c0d2/src/Servant/Common/Req.hs#L40-L42) and reports whether or not your request was sent (if validation fails, the request won't be sent), and how decoding of the response went. You can pattern match on these explicitly, but usually you'll want to use `fmapMaybe :: (a -> Maybe b) -> Event t a -> Event t b` and one of the elimination functions to filter the result type you care about, like this:
120```haskell
121 -- ... continued ...
122 res :: Event t (ReqResult Double) <- doubleIt xs triggers
123 let ys = fmapMaybe reqSuccess res
124 errs = fmapMaybe reqFailure res
126 -- Green <p> tag showing the last good result
127 elAttr "p" ("style" =: "color:green") $ do
128 text "Last good result: "
129 dynText =<< holdDyn "" (fmap show ys)
131 -- Red <p> tag showing the last error, cleared by a new good value
132 elAttr "p" ("style" =: "color:red") $
133 dynText =<< holdDyn "" (leftmost [errs, const "" <$> ys])
134```
136This example builds some input fields to enter API parameters, buttons to trigger the API calls, and text elements to show the results:
138```haskell
139 elClass "div" "int-demo" $ do
140 intButton <- button "Get Int"
141 serverInts <- fmapMaybe resSuccess <$> getint intButton
142 display =<< holdDyn (Just 0) serverInts
144 elClass "div" "hello-demo" $ do
145 nameText <- value <$> textInput def
146 greetings <- (fmap words . value) <$> textInput def
147 withGusto <- checkbox def
148 helloButton <- button "Say hi"
149 hellos <- fmapMaybe resResult <$> sayhi nameText greetings withGusto helloButton
150 display =<< holdDyn Nothing hellos
152 elClass "div" "demo-double" $ do
153 inputDouble <- (fmapMaybe readMaybe) <$> textInput def
154 doubleButton <- button "Double it"
155 outputDouble <- fmapMaybe resSuccess <$> doubleit inputDouble doubleButton
156 display =<< holdDyn Nothing outputDouble
157```
159For a great introduction to recative DOM building, see the [README](https://github.com/reflex-frp/reflex-platform) for the `reflex-platform`. For more information about servant, see their [documentation](http://haskell-servant.readthedocs.io/en/stable/). Thanks to the respective authors of these fabulous libraries.
161## TODOs
163 - Request Headers
164 - Tests
165 - Code cleanup