Generate reflex client functions for querying a servant API
Go to file
2016-03-30 11:19:43 -04:00
deps Remove servant submodule 2016-03-26 01:38:07 -04:00
exec Add the comprehensive api 2016-03-12 17:56:13 +08:00
include add cpp 2016-02-08 21:09:34 -05:00
src/Servant Don't require FromHttpApiData instances for payload types 2016-03-30 11:19:43 -04:00
testserver Add the comprehensive api 2016-03-12 17:56:13 +08:00
.gitignore Fix type errors and example client and server 2016-02-18 17:56:26 -05:00
.gitmodules Remove servant submodule 2016-03-26 01:38:07 -04:00
build.sh Switch from try-reflex to most recent reflex-platform 2016-03-12 10:48:38 +08:00
default.nix Initial infrastructure, doesn't build 2016-02-05 12:55:57 -05:00
init-sandbox.sh Reorganize testserver, update all deps to 'improved-base' 2016-02-07 16:46:20 -05:00
LICENSE.md Add LICENSE to make cabal happy 2016-03-26 01:35:48 -04:00
overrides.nix Switch from try-reflex to most recent reflex-platform 2016-03-12 10:48:38 +08:00
README.md Don't require FromHttpApiData instances for payload types 2016-03-30 11:19:43 -04:00
servant-reflex.cabal Add LICENSE to make cabal happy 2016-03-26 01:35:48 -04:00
shell.sh Switch from try-reflex to most recent reflex-platform 2016-03-12 10:48:38 +08:00
toSite.sh Fix type errors and example client and server 2016-02-18 17:56:26 -05:00

Note: work in progress.

servant-reflex

This library lets you automatically derive reflex-dom clients that query each endpoint of a servant webservice.

Building the example

You will need a recent GHCJS installation, or use the reflex-platform. A snap server using 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:

git clone https://github.com/mightybyte/servant-reflex
cd servant-reflex
git submodule update --init --recursive
./init-sandbox.sh
cd testserver && cabal install --only-dep && cabal build && cd ..
cabal install --only-dep
cabal build
./toSite

The final script copies the ghcjs-generated files into the server's static directory.

To install with reflex-platform:

git clone https://github.com/mightybyte/servant-reflex
cd servant-reflex
git submodule update --init --recursive
./init-sandbox.sh
cd testserver && cabal install --only-dep && cabal build && cd ..
path/to/reflex-platform/work-on ghcjs ./.
cabal build
./toSite.sh

Example

We have a webservice API defined in a module where both the server (compiled with ghc) and the frontend (compiled with ghcjs) can see it:

type API = "getint"  :> Get '[JSON] Int
      :<|> "sayhi"   :> QueryParam  "username" String
                     :> QueryParams "greetings" String
                     :> QueryFlag   "gusto"
                     :> Get '[JSON] String
      :<|> "double" :> ReqBody '[JSON] Double
                    :> Post '[JSON] Double
      :<|> Raw

servant-reflex then computes functions that can query the API through an XhrRequest.


 runGUI :: forall t m.MonadWidget t m => do

  -- servant-reflex computes FRP functions for each API endpoint
  let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
                                                        (Proxy :: Proxy m)
                                                        (constDyn defaultUrl)

These client functions are computed from the API and manage serialization, XhrRequest generation, and deserialization for you. a parameters become Behavior t (Maybe a) values. You provide a trigger event and receive an Event t (Maybe r, XhrResponse), with responses from the API server (which you would write with servant-server).

   -- No need to write these functions. servant-reflex creates them for you!
   getint :: MonadWidget t m
          => Event t ()  -- ^ Trigger the XHR Request
          -> m (Event t (Maybe (Int, XhrResponse))) -- ^ Consume the answer

   sayhi :: MonadWidget t m
         => Behavior t (Maybe String) -- ^ One input parameter - the 'name'
         -> Behavior t [String]       -- ^ Another input - list of preferred greetings
         -> Behavior t Bool           -- ^ Flag for capitalizing the response
         -> Event t ()                -- ^ Trigger the XHR Request
         -> m (Event t (Maybe String, XhrResponse))

   doubleit :: MonadWidget t m
            => Behavior t (Maybe Double)
            -> Event t ()
            -> m (Event t (Maybe Double, XhrResponse))

Plug any of these functions into your reactive frontend to consume backend services without having to build XhrRequests by hand.

  elClass "div" "int-demo" $ do
    intButton  <- button "Get Int"
    serverInts <- fmap fst <$> getint intButton
    display =<< holdDyn (Just 0) serverInts

  elClass "div" "hello-demo" $ do
    nameText <- (current . value)               <$> textInput def
    greetings <- (fmap words . current . value) <$> textInput def
    withGusto <- checkbox def
    helloButton <- button "Say hi"
    hellos <- fmap fst <$> sayhi nameText greetings withGusto helloButton
    display =<< holdDyn Nothing hellos

  elClass "div" "demo-double" $ do
    inputDouble  <- (fmapMaybe readMaybe . current) <$> textInput def
    doubleButton <- button "Double it"
    outputDouble <- fmap fst <$> doubleit inputDouble doubleButton
    display =<< holdDyn Nothing outputDouble

For a great introduction to recative DOM building, see the README for the reflex-platform.

Input validation

The frontend's widgets are sometimes in a state where a valid XHR request can be generated, and sometimes not. When all of the input parameters (Behavior t (Maybe a)) are Just _, the trigger event will communicate with the server. When any of the inputs is Nothing, no XHR request will be made (the event will be silently dropped). In the future input parameters will be encoded as Behavior t (Either e a), and triggers that occur when a Request can't be generated will immediately return a Left e, which you could use to draw an error in the page.

Invalid responses

For convenience, successful XHR responses are decoded into a MimeUnrender ct a => Just a. The XHRResponse is also returned, which you can inspect to get more fine-grained information about the response.

TODOs

We're still working on thi