git.iko.soy/typeable/servant-reflex
on master help sign in
merge master
commitf9228ead41e837c19b8835983c2decddb890d8e7 authorGreg Hale <imalsogreg@gmail.com> dateWed 29 Mar 2017, 16:39 (10 years ago) parent3f74358b13b6fe
5 files changed+73−51
README.md+54−5 deps/servant-snap+1−1 overrides-ghc.nix+12−12 overrides.nix+4−26 src/Servant/Common/Req.hs+2−7
README.md+54−5
@@ -2,7 +2,7 @@
22
33[![Build Status](https://travis-ci.org/imalsogreg/servant-reflex.svg?branch=master)](https://travis-ci.org/imalsogreg/servant-reflex)
44
5−## The problem `servant-reflex` solves
5+## `servant-reflex` lets you share your `servant` APIs with the frontend
66
77Keeping your frontend in sync with your API server can be difficult - when the API changes its input parameters or return type, XHR requests from the frontend will fail at runtime. If your API is defined by [servant](haskell-servant.readthedocs.io) combinators, you can use `servant-reflex` to share the API between the server and frontend.
88Syncronization between is checked at compile time, and rather than building XHR requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
@@ -33,6 +33,7 @@ type API = "getint" :> Get '[JSON] Int
3333 -- servant-reflex computes FRP functions for each API endpoint
3434 let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
3535 (Proxy :: Proxy m)
36+ (Proxy :: Proxy ())
3637 (constDyn (BasePath "/"))
3738```
3839
@@ -42,7 +43,7 @@ These client functions are computed from your API type. They manage serializatio
4243 -- No need to write these functions. servant-reflex creates them for you!
4344 getint :: MonadWidget t m
4445 => Event t () -- ^ Trigger the XHR Request
45− -> m (Event t (ReqResult Int)) -- ^ Consume the answer
46+ -> m (Event t (ReqResult () Int)) -- ^ Consume the answer
4647
4748 sayhi :: MonadWidget t m
4849 => Dynamic t (QParam Text)
@@ -53,15 +54,15 @@ These client functions are computed from your API type. They manage serializatio
5354 -- ^ Flag for capitalizing the response
5455 -> Event t ()
5556 -- ^ Trigger the XHR Request
56− -> m (Event t (ReqResult Text))
57+ -> m (Event t (ReqResult () Text))
5758
5859 doubleit :: MonadWidget t m
5960 => Dynamic t (Either Text Double)
6061 -> Event t ()
61− -> m (Event t (ReqResult Double))
62+ -> m (Event t (ReqResult () Double))
6263```
6364
64−`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:
65+`ReqResult tag 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:
6566
6667```haskell
6768 -- ... continued ...
@@ -136,3 +137,51 @@ dist/build/back/back -p 8001
136137And simply browse to `localhost:8001`
137138
138139**For a larger example of a project that shares types between backend and frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
140+
141+
142+## Tagging requests
143+
144+The input and the return type of a client function like `getDouble` are both event streams. The individual input events and responses occur at different times and aren't automatically paired up, but you can recover the relationship by tagging the requests.
145+
146+So far we have used an `Event t ()` to trigger sending a request. If we choose e.g. `Double` for the third `Proxy` argument to `client`, then `Event t Double` will be used to trigger requests, and each `ReqResult` will carry the tag of its request. Imagine we wanted to display not just the last "double" from `doubleIt`, but a whole table of valid inputs and their doubled responses:
147+
148+```haskell
149+ ...
150+ inp <- textInput def
151+
152+ -- Convert the raw text into an `Either Text Double`
153+ let inpNum = maybe (Left "No Parse") Right . readMaybe . T.unpack <$> value inp
154+ go <- button "Double It"
155+
156+ -- Call 'doubleIt' with triggers that coincide with good input parses
157+ rs <- doubleIt inpNum (fforMaybe (tagPromptlyDyn inpNum go) $ \case
158+ Left _ -> Nothing
159+ Right a -> Just a
160+ )
161+
162+ -- Accumulate good responses in a map
163+ doubleTable <- foldDyn (<>) mempty $ ffor rs $ \case
164+ ResponseSuccess tag v _ -> tag =: v
165+ _ -> mempty
166+
167+ el "table" $ do
168+ listWithKey doubleTable $ \k dv -> el "tr" $ do
169+ el "td" $ text (T.pack $ show k)
170+ el "td" $ dynText (T.pack . show <$> dv)
171+ ...
172+```
173+
174+## Simultaneous requests
175+
176+'Servant.Reflex.Multi' provides an alternative client-generation function called 'clientA' (client applicative). Choose a container type that has both `Applicative` and `Traversable` instances, and pass it to `clientA` through another 'Proxy'. Our `sayHi` client function will then have this type:
177+
178+```haskell
179+sayHi
180+ :: Dynamic t (f (QParam Text))
181+ -> Dynamic t (f [Text])
182+ -> Dynamic t (f Bool)
183+ -> Event t tag
184+ -> m (Event t (f (ReqResult tag Text)))
185+```
186+
187+The dynamic params are each wrapped in `f`. For every firing of the trigger event `tag`, all of these parameters will be combined according to `f`'s applicative instance (when `f` is `[]`, you will get all combinations of all parameters taken together as a request; when `f` is 'ZipList', the Nth elemens of each parameters list will be taken together as a request). Using this interface, you can trigger many XHR's from a single event occurence, and expect the responses to be structured the same way as the requests.
deps/servant-snap+1−1

Submodule changed.

overrides-ghc.nix+12−12
@@ -1,19 +1,19 @@
11{ reflex-platform, ... }:
22let
3− pkgs = import <nixpkgs> {};
3+ dc = reflex-platform.lib.dontCheck;
44 c2n = reflex-platform.cabal2nixResult;
55in reflex-platform.ghc.override {
66 overrides = self: super: {
7− servant-snap = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap) {});
8− snap = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap) {});
9− snap-server = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-server) {});
10− io-streams = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/io-streams) {});
11− io-streams-haproxy = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/io-streams-haproxy) {});
12− heist = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/heist) {});
13− xmlhtml = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/xmlhtml) {});
14− snap-core = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-core) {});
15− servant = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant/servant) {});
16− Glob = pkgs.haskell.lib.dontCheck super.Glob;
17− http-api-data = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/http-api-data) {});
7+ servant-snap = dc (self.callPackage (c2n deps/servant-snap) {});
8+ snap = dc (self.callPackage (c2n deps/servant-snap/deps/snap) {});
9+ snap-server = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-server) {});
10+ io-streams = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/io-streams) {});
11+ io-streams-haproxy = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/io-streams-haproxy) {});
12+ heist = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/heist) {});
13+ xmlhtml = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/xmlhtml) {});
14+ snap-core = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-core) {});
15+ servant = dc (self.callPackage (c2n deps/servant/servant) {});
16+ Glob = dc super.Glob;
17+ http-api-data = dc (self.callPackage (c2n deps/http-api-data) {});
1818 };
1919}
overrides.nix+4−26
@@ -1,35 +1,13 @@
11{ reflex-platform, ... }:
22let
33
4− nixpkgs = (import <nixpkgs> {});
5− dontCheck = nixpkgs.pkgs.haskell.lib.dontCheck;
6− cabal2nixResult = reflex-platform.cabal2nixResult;
4+ dc = reflex-platform.lib.dontCheck;
5+ c2n = reflex-platform.cabal2nixResult;
76
87in
98reflex-platform.ghcjs.override {
109 overrides = self: super: {
11− # servant = dontCheck (self.callPackage (cabal2nixResult deps/servant/servant) {});
12− # http-api-data = dontCheck (self.callPackage (cabal2nixResult deps/http-api-data) {});
13− # natural-transformation = self.callPackage (
14− # { mkDerivation, base, containers, fetchgit, quickcheck-instances
15− # , stdenv, tasty, tasty-quickcheck
16− # }:
17− # mkDerivation {
18− # pname = "natural-transformation";
19− # version = "0.4";
20− # src = fetchgit {
21− # url = "https://github.com/ku-fpg/natural-transformation";
22− # sha256 = "0xbnnxbf5ydszbhf7h5ra3mrqy4mcyqc3zb2k8bwm1zyqrz6v0fs";
23− # rev = "b1200c09dcafd034e32846413913b74735c8ba56";
24− # };
25− # libraryHaskellDepends = [ base ];
26− # testHaskellDepends = [
27− # base containers quickcheck-instances tasty tasty-quickcheck
28− # ];
29− # homepage = "https://github.com/ku-fpg/natural-transformation";
30− # description = "A natural transformation package";
31− # license = stdenv.lib.licenses.bsd3;
32− # }
33− # ) {};
10+ # servant = dc (self.callPackage (c2n deps/servant/servant) {});
11+ # http-api-data = dc (self.callPackage (c2n deps/http-api-data) {});
3412 };
3513}
src/Servant/Common/Req.hs+2−7
@@ -1,4 +1,5 @@
11{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveFunctor #-}
23{-# LANGUAGE FlexibleContexts #-}
34{-# LANGUAGE GADTs #-}
45{-# LANGUAGE OverloadedStrings #-}
@@ -58,6 +59,7 @@ data ReqResult tag a
5859 -- a non-successful response code
5960 | RequestFailure tag Text
6061 -- ^ A failure to construct the request tagged with 'tag' at trigger time
62+ deriving (Functor)
6163
6264
6365------------------------------------------------------------------------------
@@ -98,13 +100,6 @@ reqTag (ResponseSuccess tag _ _) = tag
98100reqTag (ResponseFailure tag _ _) = tag
99101reqTag (RequestFailure tag _ ) = tag
100102
101−------------------------------------------------------------------------------
102−instance Functor (ReqResult tag) where
103− fmap f (ResponseSuccess tag a xhr) = ResponseSuccess tag (f a) xhr
104− fmap _ (ResponseFailure tag r x) = ResponseFailure tag r x
105− fmap _ (RequestFailure tag r) = RequestFailure tag r
106−
107−
108103-------------------------------------------------------------------------------
109104-- | You must wrap the parameter of a QueryParam endpoint with 'QParam' to
110105-- indicate whether the parameter is valid and present, validly absent, or