git.iko.soy/typeable/servant-reflex
on master help sign in
Merge branch 'master' of github.com:imalsogreg/servant-reflex
commit8244434dd7fa4a068479487abd1eedf36b6a94d1 authorGreg Hale <imalsogreg@gmail.com> dateTue 7 Mar 2017, 21:41 (10 years ago) parenta0a67c2aff9e9f
10 files changed+976−287
.gitignore+1 .travis.yml+2−2 changelog.md+32 exec/Example.hs+83−29 overrides-ghc.nix−21 servant-reflex.cabal+2−1 src/Servant/Common/Req.hs+224−69 src/Servant/Reflex.hs+204−165 src/Servant/Reflex/Multi.hs+372 stack.yaml+56
.gitignore+1
@@ -7,3 +7,4 @@ log
77*.stats
88out
99reflex-platform
10+/.stack-work/
.travis.yml+2−2
@@ -37,8 +37,8 @@ install:
3737 - travis_retry cabal update -v
3838 - git clone https://github.com/reflex-frp/reflex-platform
3939 - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
40− - reflex-platform/work-on ./overrides-ghc.nix ./testserver --command "cd testserver && cabal configure && cabal build"
4140 - reflex-platform/work-on ghc ./testdriver --command "cd testdriver && cabal configure && cabal build"
41+ - reflex-platform/work-on ./overrides-ghc.nix ./testserver --command "cd testserver && cabal configure && cabal build"
4242 - cd testserver && dist/build/back/back -p 8000 &
4343 - sleep 3
4444 - phantomjs --webdriver=127.0.0.1:4444 &
@@ -48,7 +48,7 @@ script:
4848 - git config --global user.email "travis-ci@example.com"
4949 - git config --global user.name "Travis-CI"
5050 - ./build.sh
51− - testdriver/dist/build/spec/spec
51+ # - testdriver/dist/build/spec/spec
5252
5353cache:
5454 directories:
changelog.mdnew file+32
@@ -0,0 +1,32 @@
1+0.3
2+---
3+
4+- **Addition of the 'Servant.Reflex.Multi' module** which allows the user
5+to choose some '(Applicative f, Traversable f) => f' for collecting
6+endpoint parameters. The traversable instance is used for passing
7+multiple 'XhrRequest's in response to a single event trigger. The
8+Applicative instance is used to combine parameters. For example,
9+'Capture "id" Name :> QueryParam "greeting" Text :> Get '[JSON] Text'
10+will generate a client function:
11+`Dynamic t (f Name) -> Dynamic t (f greeting) -> Event t tag ->
12+ m (Event t (f (ReqResult tag Text)))`
13+Calling that function with 'f' chosen to be list ('[]'), this call
14+`clientFun
15+(constDyn ["Greg", "Bernie"])
16+(constDyn ["Greetings", "Hey"])
17+trigger`
18+would produce four XhrRequests per 'trigger' event, one for each
19+combination of possible parameters, and one response event
20+would carry a list of the four responses.
21+A 'zip-wise' combination of the parameters can be chosen with
22+a different applicative wrapper: 'ZipList'
23+
24+- **Event tags**. A new type parameter lets users choose a payload
25+value to attach to outgoing requests at the point of triggering,
26+and which is embedded in the response, allowing individual
27+request-response pairs to be followed. The response's tag has been
28+added to each constructor `ReqResult tag a` type.
29+
30+- **Improved Unicode support** Thanks to Schell Scivally
31+(https://github.com/schell) for identifying and fixing a UTF-8
32+encoding bug!
exec/Example.hs+83−29
@@ -2,30 +2,75 @@
22{-# LANGUAGE GADTs #-}
33{-# LANGUAGE OverloadedStrings #-}
44{-# LANGUAGE RankNTypes #-}
5+{-# LANGUAGE RecursiveDo #-}
56{-# LANGUAGE ScopedTypeVariables #-}
67
78module Main where
89
10+------------------------------------------------------------------------------
911import Data.Bool
1012import Data.Maybe
1113import Control.Monad.Fix (MonadFix)
12−import Data.Monoid ((<>))
14+import Data.Monoid (First(..), (<>))
1315import Data.Text (Text)
1416import qualified Data.Text as T
1517import GHC.TypeLits
1618import Servant.API
17−import Servant.Reflex
1819import API
1920import Data.Proxy
2021import Text.Read (readMaybe)
2122import Reflex.Dom
23+------------------------------------------------------------------------------
24+import Servant.Reflex
25+import Servant.Reflex.Multi
26+
2227
2328api :: Proxy API
2429api = Proxy
2530
2631main :: IO ()
27−main = mainWidget run
28−
32+main = mainWidget $ do
33+ divClass "example-base" run
34+ divClass "example-multi" runMulti
35+
36+
37+runMulti :: forall t m. (SupportsServantReflex t m,
38+ DomBuilder t m,
39+ DomBuilderSpace m ~ GhcjsDomSpace,
40+ MonadFix m,
41+ PostBuild t m,
42+ MonadHold t m) => m ()
43+runMulti = do
44+ url <- baseUrlWidget
45+ el "br" blank
46+ let (_ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ ) =
47+ clientA api (Proxy :: Proxy m) (Proxy :: Proxy []) (Proxy :: Proxy Int) url
48+
49+ num :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
50+ num2 :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
51+
52+ b <- button "Run dbl multi"
53+ reqCount :: Dynamic t Int <- count b
54+ r <- dbl ((\x y -> [x,y]) <$> num <*> num2) (tag (current reqCount) b)
55+ dynText =<< holdDyn "Waiting" (T.pack . show . fmap reqSuccess <$> r)
56+
57+ lastInd <- holdDyn [] $ fmap reqTag <$> r
58+ display lastInd
59+
60+ divClass "demo-group" $ do
61+ nms <- fmap (fmap T.words . value) $ divClass "" $ do
62+ text "Names"
63+ textInput def
64+ grts <- fmap (fmap T.words . value) $ divClass "" $ do
65+ text "Greetings"
66+ textInput def
67+ gust <- fmap (value) $ divClass "gusto-input" $ checkbox False def
68+ b <- button "Go"
69+ r' <- sayHi (fmap QParamSome <$> nms) (fmap (:[]) $ grts) (constDyn [True, False]) (1 <$ b)
70+
71+ dynText =<< holdDyn "Waiting" (T.pack . show . catMaybes . fmap reqSuccess <$> r')
72+
73+ return ()
2974
3075run :: forall t m. (SupportsServantReflex t m,
3176 DomBuilder t m,
@@ -33,37 +78,41 @@ run :: forall t m. (SupportsServantReflex t m,
3378 MonadFix m,
3479 PostBuild t m,
3580 MonadHold t m) => m ()
36−run = do
81+run = mdo
3782
83+ reqCount <- count $ leftmost
84+ [() <$ unitBtn, () <$ intBtn, () <$ sayHiClicks, () <$ dblBtn, () <$ mpGo]
3885 -- Allow user to choose the url target for the request
3986 -- (alternatively we could just `let url = constDyn (BasePath "/")`)
4087 url <- baseUrlWidget
4188 el "br" (return ())
4289 dynText $ showBaseUrl <$> url
90+
4391 el "br" (return ())
4492
4593 -- Name the computed API client functions
4694 let (getUnit :<|> getInt :<|> sayhi :<|> dbl :<|> multi :<|> qna :<|> doRaw) =
47− client api (Proxy :: Proxy m) url
95+ client api (Proxy :: Proxy m) (Proxy :: Proxy Int) url
4896
49− c2 = client (Proxy :: Proxy ComprehensiveAPI) (Proxy :: Proxy m) url -- Just make sure this compiles for now
97+ c2 = client (Proxy :: Proxy ComprehensiveAPI) (Proxy :: Proxy m) (Proxy :: Proxy ()) url -- Just make sure this compiles for now
5098
51− elClass "div" "demo-group" $ do
99+ (unitBtn, intBtn) <- elClass "div" "demo-group" $ do
52100 unitBtn <- divClass "unit-button" $ button "Get unit"
53101 intBtn <- divClass "int-button" $ button "Get int"
54102
55− unitResponse <- getUnit unitBtn
56− intResponse :: Event t (ReqResult Int) <- getInt intBtn
103+ unitResponse <- getUnit $ tag (current reqCount) unitBtn
104+ intResponse :: Event t (ReqResult Int Int) <- getInt $ tag (current reqCount) intBtn
57105
58− score <- foldDyn (+) 0 (fmapMaybe reqSuccess intResponse)
106+ score <- foldDyn (+) 0 (fmapMaybe reqSuccess (intResponse))
59107
60108 r <- holdDyn "Waiting" $ fmap showXhrResponse $
61− leftmost [fmapMaybe response unitResponse
62− ,fmapMaybe response intResponse
109+ leftmost [fmapMaybe response (unitResponse)
110+ ,fmapMaybe response (intResponse)
63111 ]
64112 divClass "unit-int-response" $ el "p" $ dynText r >> el "br" (return ()) >> text "Total: " >> display score
113+ return (unitBtn, intBtn)
65114
66− elClass "div" "demo-group" $ do
115+ sayHiClicks <- elClass "div" "demo-group" $ do
67116
68117 text "Name"
69118 el "br" $ return ()
@@ -81,43 +130,48 @@ run = do
81130 gusto <- fmap value $ divClass "gusto-input" $ checkbox False def
82131
83132 el "br" $ return ()
84− sayhiClicks :: Event t () <- divClass "hi-button" $ button "Say hi"
85− let triggers = leftmost [sayhiClicks, () <$ updated inp]
133+ sayHiClicks :: Event t () <- divClass "hi-button" $ button "Say hi"
134+ let triggers = leftmost [sayHiClicks, () <$ updated inp]
86135
87− resp <- sayhi checkedName greetings gusto triggers
136+ resp <- sayhi checkedName greetings gusto (tag (current reqCount) triggers)
88137 divClass "greeting-response" $ dynText =<<
89− holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess resp
90− , fmapMaybe reqFailure resp])
138+ holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess (resp)
139+ , fmapMaybe reqFailure (resp)])
140+ return sayHiClicks
91141
92− elClass "div" "demo-group" $ do
142+ dblBtn <- elClass "div" "demo-group" $ do
93143 text "A Double to double"
94144 el "br" $ return ()
95− dblinp <- fmap value $ divClass "double-input" $ textInput def
96− dblBtn <- divClass "double-button" $ button "Double it"
145+ dblinp <- fmap value $ divClass "double-input" $ textInput def
146+ (dblBtn) <- divClass "double-button" $ button "Double it"
97147 dblResp <- dbl (fmap (note "read failure" . readMaybe . T.unpack) $
98− dblinp) dblBtn
148+ dblinp) (tag (current reqCount) dblBtn)
99149 divClass "double-errors" $ dynText =<<
100− holdDyn "(no errors)" (fmapMaybe reqFailure dblResp)
150+ holdDyn "(no errors)" (fmapMaybe reqFailure (dblResp))
101151 el "br" (return ())
102152 divClass "double-result" $ el "p" $ dynText =<<
103153 holdDyn "No number yet" (fmap tShow $
104− fmapMaybe reqSuccess dblResp)
154+ fmapMaybe reqSuccess (dblResp))
155+ return dblBtn
105156
106− elClass "div" "demo-group" $ do
157+ mpGo <- elClass "div" "demo-group" $ do
107158 text "Multi-part path"
108159 b <- value <$> checkbox False def
109160 mpGo <- button "Test"
110− multiResp <- multi b mpGo
161+ multiResp <- multi b (tag (current reqCount) mpGo)
111162 dynText =<< holdDyn "No res yet" (fmap tShow $
112163 fmapMaybe reqSuccess $
113− multiResp)
164+ (multiResp))
165+ return mpGo
166+
167+ return ()
114168
115169 el "br" $ return ()
116170
117171 elClass "div" "demo-group" $ do
118172 text "JSON Unicode encoding test"
119173 txt <- value <$> textInput def
120− ev <- button "Question"
174+ ev <- fmap (1 <$) $ button "Question"
121175 let dQ = Right . Question <$> traceDyn "will send: " txt
122176 rr <- qna dQ ev
123177 el "p" $
overrides-ghc.nix−21
@@ -14,27 +14,6 @@ in reflex-platform.ghc.override {
1414 snap-core = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-core) {});
1515 servant = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/servant/servant) {});
1616 Glob = pkgs.haskell.lib.dontCheck super.Glob;
17− natural-transformation = self.callPackage (
18− { mkDerivation, base, containers, fetchgit, quickcheck-instances
19− , stdenv, tasty, tasty-quickcheck
20− }:
21− mkDerivation {
22− pname = "natural-transformation";
23− version = "0.4";
24− src = fetchgit {
25− url = "https://github.com/ku-fpg/natural-transformation";
26− sha256 = "0xbnnxbf5ydszbhf7h5ra3mrqy4mcyqc3zb2k8bwm1zyqrz6v0fs";
27− rev = "b1200c09dcafd034e32846413913b74735c8ba56";
28− };
29− libraryHaskellDepends = [ base ];
30− testHaskellDepends = [
31− base containers quickcheck-instances tasty tasty-quickcheck
32− ];
33− homepage = "https://github.com/ku-fpg/natural-transformation";
34− description = "A natural transformation package";
35− license = stdenv.lib.licenses.bsd3;
36− }
37− ) {};
3817 http-api-data = pkgs.haskell.lib.dontCheck (self.callPackage (c2n deps/http-api-data) {});
3918 };
4019}
servant-reflex.cabal+2−1
@@ -1,5 +1,5 @@
11Name: servant-reflex
2−Version: 0.2.1
2+Version: 0.3
33Synopsis: Servant reflex API generator
44Description: Servant reflex API generator
55License: AllRightsReserved
@@ -14,6 +14,7 @@ Cabal-version: >=1.10
1414library
1515 exposed-modules:
1616 Servant.Reflex
17+ Servant.Reflex.Multi
1718
1819 other-modules:
1920 Servant.Common.BaseUrl
src/Servant/Common/Req.hs+224−69
@@ -1,4 +1,3 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
21{-# LANGUAGE CPP #-}
32{-# LANGUAGE FlexibleContexts #-}
43{-# LANGUAGE GADTs #-}
@@ -6,50 +5,89 @@
65{-# LANGUAGE LambdaCase #-}
76{-# LANGUAGE ScopedTypeVariables #-}
87{-# LANGUAGE TupleSections #-}
8+
99module Servant.Common.Req where
1010
1111-------------------------------------------------------------------------------
12+import Control.Concurrent
1213import Control.Applicative (liftA2, liftA3)
14+import Control.Monad (join)
15+import Control.Monad.IO.Class (MonadIO, liftIO)
1316import Data.Bifunctor (first)
1417import qualified Data.ByteString.Lazy.Char8 as BL
1518import qualified Data.Map as Map
1619import Data.Maybe (catMaybes, fromMaybe)
20+import Data.Functor.Compose
1721import Data.Monoid ((<>))
1822import Data.Proxy (Proxy(..))
1923import Data.Text (Text)
2024import qualified Data.Text as T
2125import qualified Data.Text.Encoding as TE
22−import Reflex.Dom
23−import Servant.Common.BaseUrl (BaseUrl, showBaseUrl, SupportsServantReflex)
26+import Data.Traversable (forM)
27+import Reflex.Dom hiding (tag)
28+import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
29+ SupportsServantReflex)
2430import Servant.API.ContentTypes (MimeUnrender(..), NoContent(..))
2531import Web.HttpApiData (ToHttpApiData(..))
2632-------------------------------------------------------------------------------
2733import Servant.API.BasicAuth
2834
2935
36+------------------------------------------------------------------------------
37+-- | The result of a request event
38+data ReqResult tag a
39+ = ResponseSuccess tag a XhrResponse
40+ -- ^ The succesfully decoded response from a request tagged with 'tag'
41+ | ResponseFailure tag Text XhrResponse
42+ -- ^ The failure response, which may have failed decoding or had
43+ -- a non-successful response code
44+ | RequestFailure tag Text
45+ -- ^ A failure to construct the request tagged with 'tag' at trigger time
46+
47+
48+------------------------------------------------------------------------------
49+-- | Simple filter/accessor for successful responses, when you want to
50+-- ignore the error case. For example:
51+-- >> goodResponses <- fmapMaybe reqSuccess <$> clientFun triggers
52+reqSuccess :: ReqResult tag a -> Maybe a
53+reqSuccess (ResponseSuccess _ x _) = Just x
54+reqSuccess _ = Nothing
55+
56+
57+------------------------------------------------------------------------------
58+-- | Simple filter/accessor like 'reqSuccess', but keeping the request tag
59+reqSuccess' :: ReqResult tag a -> Maybe (tag,a)
60+reqSuccess' (ResponseSuccess tag x _) = Just (tag,x)
61+reqSuccess' _ = Nothing
3062
31−data ReqResult a = ResponseSuccess a XhrResponse
32− | ResponseFailure Text XhrResponse
33− | RequestFailure Text
3463
35−instance Functor ReqResult where
36− fmap f (ResponseSuccess a xhr) = ResponseSuccess (f a) xhr
37− fmap _ (ResponseFailure r x) = ResponseFailure r x
38− fmap _ (RequestFailure r) = RequestFailure r
64+------------------------------------------------------------------------------
65+-- | Simple filter/accessor for any failure case
66+reqFailure :: ReqResult tag a -> Maybe Text
67+reqFailure (ResponseFailure _ s _) = Just s
68+reqFailure (RequestFailure _ s) = Just s
69+reqFailure _ = Nothing
3970
40−reqSuccess :: ReqResult a -> Maybe a
41−reqSuccess (ResponseSuccess x _) = Just x
42−reqSuccess _ = Nothing
4371
44−reqFailure :: ReqResult a -> Maybe Text
45−reqFailure (ResponseFailure s _) = Just s
46−reqFailure (RequestFailure s) = Just s
47−reqFailure _ = Nothing
72+------------------------------------------------------------------------------
73+-- | Simple filter/accessor for the raw XHR response
74+response :: ReqResult tag a -> Maybe XhrResponse
75+response (ResponseSuccess _ _ x) = Just x
76+response (ResponseFailure _ _ x) = Just x
77+response _ = Nothing
4878
49−response :: ReqResult a -> Maybe XhrResponse
50−response (ResponseSuccess _ x) = Just x
51−response (ResponseFailure _ x) = Just x
52−response _ = Nothing
79+------------------------------------------------------------------------------
80+-- | Retrieve response tag
81+reqTag :: ReqResult tag a -> tag
82+reqTag (ResponseSuccess tag _ _) = tag
83+reqTag (ResponseFailure tag _ _) = tag
84+reqTag (RequestFailure tag _ ) = tag
85+
86+------------------------------------------------------------------------------
87+instance Functor (ReqResult tag) where
88+ fmap f (ResponseSuccess tag a xhr) = ResponseSuccess tag (f a) xhr
89+ fmap _ (ResponseFailure tag r x) = ResponseFailure tag r x
90+ fmap _ (RequestFailure tag r) = RequestFailure tag r
5391
5492
5593-------------------------------------------------------------------------------
@@ -63,11 +101,13 @@ data QParam a = QParamSome a
63101 | QParamInvalid Text
64102 -- ^ Indication that your validation failed (the request isn't valid)
65103
104+
66105qParamToQueryPart :: ToHttpApiData a => QParam a -> Either Text (Maybe Text)
67106qParamToQueryPart (QParamSome a) = Right (Just $ toQueryParam a)
68107qParamToQueryPart QNone = Right Nothing
69108qParamToQueryPart (QParamInvalid e) = Left e
70109
110+
71111data QueryPart t = QueryPartParam (Dynamic t (Either Text (Maybe Text)))
72112 | QueryPartParams (Dynamic t [Text])
73113 | QueryPartFlag (Dynamic t Bool)
@@ -97,20 +137,13 @@ addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) ->
97137addHeader name val req = req { headers = (name, (fmap . fmap) (TE.decodeUtf8 . toHeader) val) : headers req }
98138
99139
100−-- * performing requests
101−
102−displayHttpRequest :: Text -> Text
103−displayHttpRequest httpmethod = "HTTP " <> httpmethod <> " request"
104−
105−-- | This function actually performs the request.
106−performRequest :: forall t m.(SupportsServantReflex t m)
107− => Text
108− -> Req t
109− -> Dynamic t BaseUrl
110− -> Event t ()
111− -> m (Event t XhrResponse, Event t Text)
112−performRequest reqMeth req reqHost trigger = do
113−
140+reqToReflexRequest
141+ :: forall t. Reflex t
142+ => Text
143+ -> Dynamic t BaseUrl
144+ -> Req t
145+ -> (Dynamic t (Either Text (XhrRequest XhrPayload)))
146+reqToReflexRequest reqMeth reqHost req =
114147 let t :: Dynamic t [Either Text Text]
115148 t = sequence $ reverse $ reqPathParts req
116149
@@ -143,8 +176,8 @@ performRequest reqMeth req reqHost trigger = do
143176 queryPartStrings' = fmap (sequence . catMaybes) $ sequence queryPartStrings :: Dynamic t (Either Text [Text])
144177 queryString :: Dynamic t (Either Text Text) =
145178 ffor queryPartStrings' $ \qs -> fmap (T.intercalate "&") qs
146−-- ffor queryPartStrings' $ \qs -> fmap (T.intercalate "&") (sequence qs)
147− xhrUrl = (liftA3 . liftA3) (\a p q -> a </> if T.null q then p else p <> "?" <> q) baseUrl urlPath queryString
179+ xhrUrl = (liftA3 . liftA3) (\a p q -> a </> if T.null q then p else p <> "?" <> q)
180+ baseUrl urlPath queryString
148181 where
149182 (</>) :: Text -> Text -> Text
150183 x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
@@ -203,64 +236,186 @@ performRequest reqMeth req reqHost trigger = do
203236
204237 xhrReq = (liftA2 . liftA2) (\p opt -> XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
205238
206− let reqs = tagPromptlyDyn xhrReq trigger
207− okReqs = fmapMaybe (either (const Nothing) Just) reqs
208− badReqs = fmapMaybe (either Just (const Nothing)) reqs
239+ in xhrReq
209240
210− resps <- performRequestAsync okReqs
241+-- * performing requests
242+
243+displayHttpRequest :: Text -> Text
244+displayHttpRequest httpmethod = "HTTP " <> httpmethod <> " request"
245+
246+-- | This function actually performs the request.
247+performRequests :: forall t m f tag.(SupportsServantReflex t m, Traversable f)
248+ => Text
249+ -> Dynamic t (f (Req t))
250+ -> Dynamic t BaseUrl
251+ -> Event t tag
252+ -> m (Event t (tag, f (Either Text XhrResponse)))
253+performRequests reqMeth rs reqHost trigger = do
254+ -- let xhrReqs = sequence $ (\r -> reqToReflexRequest reqMeth r reqHost) <$> rs :: Dynamic t (f (Either Text (XhrRequest XhrPayload)))
255+ let xhrReqs = join $ (\(fxhr :: f (Req t)) -> sequence $ reqToReflexRequest reqMeth reqHost <$> fxhr) <$> rs
256+ let reqs = attachPromptlyDynWith (\fxhr t -> Compose (t, fxhr)) xhrReqs trigger
257+ resps <- performSomeRequestsAsync reqs
258+ return $ getCompose <$> resps
259+
260+-- | Issues a collection of requests when the supplied Event fires. When ALL requests from a given firing complete, the results are collected and returned via the return Event.
261+performSomeRequestsAsync
262+ :: (MonadIO (Performable m),
263+ HasWebView (Performable m),
264+ PerformEvent t m,
265+ TriggerEvent t m,
266+ Traversable f,
267+ IsXhrPayload a)
268+ => Event t (f (Either Text (XhrRequest a)))
269+ -> m (Event t (f (Either Text XhrResponse)))
270+performSomeRequestsAsync = performSomeRequestsAsync' newXMLHttpRequest . fmap return
271+
272+
273+------------------------------------------------------------------------------
274+-- | A modified version or Reflex.Dom.Xhr.performRequestsAsync
275+-- that accepts 'f (Either e (XhrRequestb))' events
276+performSomeRequestsAsync'
277+ :: (MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f)
278+ => (XhrRequest b -> (a -> IO ()) -> Performable m XMLHttpRequest)
279+ -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
280+performSomeRequestsAsync' newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
281+ rs <- hrs
282+ resps <- forM rs $ \r -> case r of
283+ Left e -> do
284+ resp <- liftIO $ newMVar (Left e)
285+ return resp
286+ Right r' -> do
287+ resp <- liftIO newEmptyMVar
288+ _ <- newXhr r' $ liftIO . putMVar resp . Right
289+ return resp
290+ _ <- liftIO $ forkIO $ cb =<< forM resps takeMVar
291+ return ()
292+
293+
294+
295+-- | This function actually performs the request.
296+performRequest :: forall t m tag .(SupportsServantReflex t m)
297+ => Text
298+ -> (Req t)
299+ -> Dynamic t BaseUrl
300+ -> Event t tag
301+ -> m (Event t (tag, XhrResponse), Event t (tag, Text))
302+performRequest reqMeth req reqHost trigger = do
303+
304+ let xhrReq = reqToReflexRequest reqMeth reqHost req
305+ let reqs = attachPromptlyDynWith (flip (,)) xhrReq trigger
306+ okReqs = fmapMaybe (\(t,e) -> either (const Nothing) (Just . (t,)) e) reqs
307+ badReqs = fmapMaybe (\(t,e) -> either (Just . (t,)) (const Nothing) e) reqs
308+
309+ resps <- performRequestsAsync okReqs
211310
212311 return (resps, badReqs)
213312
313+
214314type XhrPayload = T.Text
215315bytesToPayload :: BL.ByteString -> XhrPayload
216316bytesToPayload = TE.decodeUtf8 . BL.toStrict
217317
218318
219−------------------------------------------------------------------------------
220−performRequestNoBody :: forall t m .(SupportsServantReflex t m)
221− => Text
222− -> Req t
223− -> Dynamic t BaseUrl
224− -> Event t () -> m (Event t (ReqResult NoContent))
225−performRequestNoBody reqMeth req reqHost trigger = do
226− (resp, badReq) <- performRequest reqMeth req reqHost trigger
227− let decodeResp = const $ Right NoContent
228− return $ leftmost [ fmap (evalRequest decodeResp) resp,
229− fmap RequestFailure badReq]
230−
231−
232−------------------------------------------------------------------------------
233−performRequestCT :: (SupportsServantReflex t m,
234− MimeUnrender ct a)
235− => Proxy ct -> Text -> Req t -> Dynamic t BaseUrl
236− -> Event t () -> m (Event t (ReqResult a))
237−performRequestCT ct reqMeth req reqHost trigger = do
238− (resp, badReq) <- performRequest reqMeth req reqHost trigger
319+-- performRequestNoBody :: forall t m tag.(SupportsServantReflex t m)
320+-- => Text
321+-- -> Req t
322+-- -> Dynamic t BaseUrl
323+-- -> Event t tag -> m (Event t (ReqResult tag NoContent))
324+-- performRequestNoBody reqMeth req reqHost trigger = do
325+-- (resp, badReq) <- performRequest reqMeth req reqHost trigger
326+-- let decodeResp = const $ Right NoContent
327+-- return $ leftmost [ fmap (evalResponse decodeResp) resp
328+-- , fmap (uncurry RequestFailure) badReq
329+-- ]
330+
331+
332+-- performRequestCT
333+-- :: (SupportsServantReflex t m,
334+-- MimeUnrender ct a)
335+-- => Proxy ct
336+-- -> Text
337+-- -> Req t
338+-- -> Dynamic t BaseUrl
339+-- -> Event t tag
340+-- -> m (Event t (ReqResult tag a))
341+-- performRequestCT ct reqMeth req reqHost trigger = do
342+-- (resp, badReq) <- performRequest reqMeth req reqHost trigger
343+-- let decodeResp x = first T.pack .
344+-- mimeUnrender ct .
345+-- BL.fromStrict .
346+-- TE.encodeUtf8 =<< note "No body text"
347+-- (_xhrResponse_responseText x)
348+
349+-- return $ leftmost [fmap (evalResponse decodeResp) resp
350+-- , fmap (uncurry RequestFailure) badReq
351+-- ]
352+
353+
354+performRequestsCT
355+ :: (SupportsServantReflex t m,
356+ MimeUnrender ct a, Traversable f)
357+ => Proxy ct
358+ -> Text
359+ -> Dynamic t (f (Req t))
360+ -> Dynamic t BaseUrl
361+ -> Event t tag
362+ -> m (Event t (f (ReqResult tag a)))
363+performRequestsCT ct reqMeth reqs reqHost trigger = do
364+ resps <- performRequests reqMeth reqs reqHost trigger
239365 let decodeResp x = first T.pack .
240366 mimeUnrender ct .
241367 BL.fromStrict .
242368 TE.encodeUtf8 =<< note "No body text"
243369 (_xhrResponse_responseText x)
244−
245− return $ leftmost [fmap (evalRequest decodeResp) resp,
246− fmap RequestFailure badReq]
370+ return $ fmap
371+ (\(t,rs) -> ffor rs $ \r -> case r of
372+ Left e -> RequestFailure t e
373+ Right g -> evalResponse decodeResp (t,g)
374+ )
375+ resps
376+
377+
378+performRequestsNoBody
379+ :: (SupportsServantReflex t m,
380+ Traversable f)
381+ => Text
382+ -> Dynamic t (f (Req t))
383+ -> Dynamic t BaseUrl
384+ -> Event t tag
385+ -> m (Event t (f (ReqResult tag NoContent)))
386+performRequestsNoBody reqMeth reqs reqHost trigger = do
387+ resps <- performRequests reqMeth reqs reqHost trigger
388+ let decodeResp = const $ Right NoContent
389+ return $ ffor resps $ \(tag,rs) -> ffor rs $ \r -> case r of
390+ Left e -> RequestFailure tag e
391+ Right g -> evalResponse decodeResp (tag,g)
247392
248393
249394------------------------------------------------------------------------------
250−evalRequest :: (XhrResponse -> Either Text a) -> XhrResponse -> ReqResult a
251−evalRequest decode xhr =
395+evalResponse
396+ :: (XhrResponse -> Either Text a)
397+ -> (tag, XhrResponse)
398+ -> ReqResult tag a
399+evalResponse decode (tag, xhr) =
252400 let okStatus = _xhrResponse_status xhr < 400
253401 errMsg = fromMaybe
254402 ("Empty response with error code " <>
255403 T.pack (show $ _xhrResponse_status xhr))
256404 (_xhrResponse_responseText xhr)
257405 respPayld = if okStatus
258− then either (flip ResponseFailure xhr)
259− (flip ResponseSuccess xhr) (decode xhr)
260− else ResponseFailure errMsg xhr
406+ then either
407+ (\e -> ResponseFailure tag e xhr)
408+ (\v -> ResponseSuccess tag v xhr)
409+ (decode xhr)
410+ else ResponseFailure tag errMsg xhr
261411 in respPayld
262412
263413
264414
415+
265416note :: e -> Maybe a -> Either e a
266417note e = maybe (Left e) Right
418+
419+fmapL :: (e -> e') -> Either e a -> Either e' a
420+fmapL _ (Right a) = Right a
421+fmapL f (Left e) = Left (f e)
src/Servant/Reflex.hs+204−165
@@ -1,8 +1,16 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
12{-# LANGUAGE CPP #-}
23{-# LANGUAGE DataKinds #-}
34{-# LANGUAGE FlexibleContexts #-}
45{-# LANGUAGE FlexibleInstances #-}
56{-# LANGUAGE InstanceSigs #-}
7+{-# LANGUAGE OverloadedStrings #-}
8+{-# LANGUAGE PolyKinds #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE TupleSections #-}
11+{-# LANGUAGE TypeFamilies #-}
12+{-# LANGUAGE TypeOperators #-}
13+{-# LANGUAGE UndecidableInstances #-}
614{-# LANGUAGE MultiParamTypeClasses #-}
715{-# LANGUAGE OverloadedStrings #-}
816{-# LANGUAGE PolyKinds #-}
@@ -18,47 +26,59 @@
1826-- API.
1927module Servant.Reflex
2028 ( client
29+ , BuildHeaderKeysTo(..)
30+ , toHeaders
2131 , HasClient(..)
2232 , module Servant.Common.Req
2333 , module Servant.Common.BaseUrl
2434 ) where
2535
26−-------------------------------------------------------------------------------
36+------------------------------------------------------------------------------
2737import Control.Applicative
28−import Data.CaseInsensitive (mk)
29−import qualified Data.Map as Map
30−import Data.Monoid ((<>))
31−import Data.Proxy (Proxy (..))
32−import qualified Data.Set as Set
33−import Data.Text (Text)
34−import qualified Data.Text as T
35−import qualified Data.Text.Encoding as E
36−import GHC.TypeLits (KnownSymbol, symbolVal)
37−import Reflex.Dom (Dynamic, Event, Reflex,
38− XhrRequest (..), XhrResponse (..),
39− XhrResponseHeaders (..), ffor,
40− fmapMaybe, leftmost,
41− performRequestAsync, tagPromptlyDyn)
42−import Servant.API ((:<|>) (..), (:>), BasicAuth,
43− BasicAuthData, BuildHeadersTo (..),
44− Capture, Header, Headers (..),
45− HttpVersion, IsSecure, MimeRender (..),
46− MimeUnrender, NoContent, QueryFlag,
47− QueryParam, QueryParams, Raw,
48− ReflectMethod (..), RemoteHost,
49− ReqBody, ToHttpApiData (..), Vault,
50− Verb, contentType)
51−import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
52− SupportsServantReflex, baseUrlWidget,
53− showBaseUrl)
54−import Servant.Common.Req (QParam (..), QueryPart (..), Req,
55− ReqResult (..), addHeader, authData,
56− defReq, performRequestCT,
57− performRequestNoBody,
58− prependToPathParts, qParamToQueryPart,
59− qParams, reqBody, reqFailure,
60− reqMethod, reqSuccess, respHeaders,
61− response)
38+import Data.Monoid ((<>))
39+import qualified Data.Set as Set
40+import qualified Data.Text.Encoding as E
41+import Data.CaseInsensitive (mk)
42+import Data.Functor.Identity
43+import Data.Proxy (Proxy (..))
44+import qualified Data.Map as Map
45+import Data.Text (Text)
46+import qualified Data.Text as T
47+import GHC.TypeLits (KnownSymbol, symbolVal)
48+import Servant.API ((:<|>)(..),(:>), BasicAuth,
49+ BasicAuthData, BuildHeadersTo(..),
50+ Capture, contentType, Header,
51+ Headers(..), HttpVersion, IsSecure,
52+ MimeRender(..), MimeUnrender,
53+ NoContent, QueryFlag, QueryParam,
54+ QueryParams, Raw, ReflectMethod(..),
55+ RemoteHost, ReqBody,
56+ ToHttpApiData(..), Vault, Verb)
57+
58+import Reflex.Dom (Dynamic, Event, Reflex,
59+ XhrRequest(..),
60+ XhrResponseHeaders(..),
61+ XhrResponse(..), attachPromptlyDynWith, constDyn, ffor, fmapMaybe,
62+ leftmost, performRequestsAsync,
63+ )
64+------------------------------------------------------------------------------
65+import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
66+ showBaseUrl,
67+ SupportsServantReflex)
68+import Servant.Common.Req (Req, ReqResult(..), QParam(..),
69+ QueryPart(..), addHeader, authData,
70+ defReq, evalResponse, prependToPathParts,
71+ -- performRequestCT,
72+ performRequestsCT,
73+ -- performRequestNoBody,
74+ performRequestsNoBody,
75+ performSomeRequestsAsync,
76+ qParamToQueryPart, reqBody,
77+ reqSuccess, reqFailure,
78+ reqMethod, respHeaders, response,
79+ reqTag,
80+ qParams)
81+
6282
6383-- * Accessing APIs as a Client
6484
@@ -70,28 +90,31 @@ import Servant.Common.Req (QParam (..), QueryPart (..), Req,
7090-- > myApi :: Proxy MyApi
7191-- > myApi = Proxy
7292-- >
73−-- > getAllBooks :: Event t () -> m (Event t (Either XhrError ((),[Book])))
74−-- > postNewBook :: Dynamic t (Maybe Book) -> Event t ()
75−-- -> m (Event t (Either XhrError (Book,Book)))
93+-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
94+-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
95+-- -> m (Event t (l, ReqResult Book)))
7696-- > (getAllBooks :<|> postNewBook) = client myApi host
7797-- > where host = constDyn $ BaseUrl Http "localhost" 8080
78−client :: (HasClient t m layout)
79− => Proxy layout -> Proxy m -> Dynamic t BaseUrl -> Client t m layout
80−client p q = clientWithRoute p q defReq
98+client :: (HasClient t m layout tag)
99+ => Proxy layout -> Proxy m -> Proxy tag -> Dynamic t BaseUrl -> Client t m layout tag
100+client p q t baseurl = clientWithRoute p q t defReq baseurl
101+
81102
82103-- | This class lets us define how each API combinator
83104-- influences the creation of an HTTP request. It's mostly
84105-- an internal class, you can just use 'client'.
85−class HasClient t m layout where
86− type Client t m layout :: *
87− clientWithRoute :: Proxy layout -> Proxy m -> Req t -> Dynamic t BaseUrl -> Client t m layout
106+class HasClient t m layout (tag :: *) where
107+ type Client t m layout tag :: *
108+ clientWithRoute :: Proxy layout -> Proxy m -> Proxy tag -> Req t -> Dynamic t BaseUrl -> Client t m layout tag
88109
89110
90−instance (HasClient t m a, HasClient t m b) => HasClient t m (a :<|> b) where
91− type Client t m (a :<|> b) = Client t m a :<|> Client t m b
92− clientWithRoute Proxy q req baseurl =
93− clientWithRoute (Proxy :: Proxy a) q req baseurl :<|>
94− clientWithRoute (Proxy :: Proxy b) q req baseurl
111+instance (HasClient t m a tag, HasClient t m b tag) => HasClient t m (a :<|> b) tag where
112+ type Client t m (a :<|> b) tag = Client t m a tag :<|> Client t m b tag
113+
114+ clientWithRoute Proxy q pTag req baseurl =
115+ clientWithRoute (Proxy :: Proxy a) q pTag req baseurl :<|>
116+ clientWithRoute (Proxy :: Proxy b) q pTag req baseurl
117+
95118
96119-- Capture. Example:
97120-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
@@ -101,50 +124,54 @@ instance (HasClient t m a, HasClient t m b) => HasClient t m (a :<|> b) where
101124-- > getBook :: SupportsServantReflex t m
102125-- => Dynamic t BaseUrl
103126-- -> Dynamic t (Maybe Text)
104−-- -> Event t ()
105−-- -> m (Event t (Either XhrError (Text, Book)))
127+-- -> Event t l
128+-- -> m (Event t (l, ReqResult Book))
106129-- > getBook = client myApi (constDyn host)
107−instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout)
108− => HasClient t m (Capture capture a :> sublayout) where
109130
110− type Client t m (Capture capture a :> sublayout) =
111− Dynamic t (Either Text a) -> Client t m sublayout
131+instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout tag)
132+ => HasClient t m (Capture capture a :> sublayout) tag where
112133
113− clientWithRoute Proxy q req baseurl val =
134+ type Client t m (Capture capture a :> sublayout) tag =
135+ Dynamic t (Either Text a) -> Client t m sublayout tag
136+
137+ clientWithRoute Proxy q t req baseurl val =
114138 clientWithRoute (Proxy :: Proxy sublayout)
115− q
139+ q t
116140 (prependToPathParts p req)
117141 baseurl
142+ where p = (fmap . fmap) (toUrlPiece) val
118143
119− where p = (fmap . fmap) toUrlPiece val
120144
121145-- VERB (Returning content) --
122146instance {-# OVERLAPPABLE #-}
123147 -- Note [Non-Empty Content Types]
124148 (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m
125− ) => HasClient t m (Verb method status cts' a) where
126− type Client t m (Verb method status cts' a) =
127− Event t () -> m (Event t (ReqResult a))
149+ ) => HasClient t m (Verb method status cts' a) tag where
150+ type Client t m (Verb method status cts' a) tag =
151+ Event t tag -> m (Event t (ReqResult tag a))
128152 -- TODO how to access input types here?
129153 -- ExceptT ServantError IO a
130− clientWithRoute Proxy _ req =
131− performRequestCT (Proxy :: Proxy ct) method req'
154+ clientWithRoute Proxy _ _ req baseurl trigs =
155+ -- performRequestCT (Proxy :: Proxy ct) method req' baseurl trigs
156+ fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity $ req') baseurl trigs
132157 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
133158 req' = req { reqMethod = method }
134159
160+
135161-- -- VERB (No content) --
136162instance {-# OVERLAPPING #-}
137163 (ReflectMethod method, SupportsServantReflex t m) =>
138− HasClient t m (Verb method status cts NoContent) where
139− type Client t m (Verb method status cts NoContent) =
140− Event t () -> m (Event t (ReqResult NoContent))
164+ HasClient t m (Verb method status cts NoContent) tag where
165+ type Client t m (Verb method status cts NoContent) tag =
166+ Event t tag -> m (Event t (ReqResult tag NoContent))
141167 -- TODO: how to access input types here?
142168 -- ExceptT ServantError IO NoContent
143− clientWithRoute Proxy _ =
144− performRequestNoBody method
169+ clientWithRoute Proxy _ _ req baseurl =
170+ (fmap . fmap) runIdentity . performRequestsNoBody method (constDyn $ Identity req) baseurl
145171 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
146172
147−toHeaders :: BuildHeadersTo ls => ReqResult a -> ReqResult (Headers ls a)
173+
174+toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
148175toHeaders r =
149176 let toBS = E.encodeUtf8
150177 hdrs = maybe []
@@ -173,33 +200,35 @@ instance {-# OVERLAPPABLE #-}
173200 ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
174201 ReflectMethod method, cts' ~ (ct ': cts),
175202 SupportsServantReflex t m
176− ) => HasClient t m (Verb method status cts' (Headers ls a)) where
177− type Client t m (Verb method status cts' (Headers ls a)) =
178− Event t () -> m (Event t (ReqResult (Headers ls a)))
179− clientWithRoute Proxy _ req baseurl = do
203+ ) => HasClient t m (Verb method status cts' (Headers ls a)) tag where
204+ type Client t m (Verb method status cts' (Headers ls a)) tag =
205+ Event t tag -> m (Event t (ReqResult tag (Headers ls a)))
206+ clientWithRoute Proxy _ _ req baseurl trigs = do
180207 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
181− resp <- performRequestCT (Proxy :: Proxy ct) method req' baseurl
182− return $ (fmap . fmap) toHeaders resp
208+ resp <- fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity req') baseurl trigs
209+ return $ toHeaders <$> resp
183210 where req' = req { respHeaders =
184211 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
185212 }
186213
214+
187215-- HEADERS Verb (No content) --
188216instance {-# OVERLAPPABLE #-}
189217 ( BuildHeadersTo ls, BuildHeaderKeysTo ls, ReflectMethod method,
190218 SupportsServantReflex t m
191− ) => HasClient t m (Verb method status cts (Headers ls NoContent)) where
192− type Client t m (Verb method status cts (Headers ls NoContent))
193− = Event t () -> m (Event t (ReqResult (Headers ls NoContent)))
194− clientWithRoute Proxy _ req baseurl = do
219+ ) => HasClient t m (Verb method status cts (Headers ls NoContent)) tag where
220+ type Client t m (Verb method status cts (Headers ls NoContent)) tag
221+ = Event t tag -> m (Event t (ReqResult tag (Headers ls NoContent)))
222+ clientWithRoute Proxy _ _ req baseurl trigs = do
195223 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
196− resp <- performRequestNoBody method req' baseurl
197− return $ (fmap . fmap) toHeaders resp
224+ resp <- fmap runIdentity <$> performRequestsNoBody method (constDyn $ Identity req') baseurl trigs
225+ return $ toHeaders <$> resp
198226 where req' = req {respHeaders =
199227 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
200228 }
201229
202230
231+
203232-- HEADER
204233-- > newtype Referer = Referer { referrer :: Text }
205234-- > deriving (Eq, Show, Generic, FromText, ToHttpApiData)
@@ -214,28 +243,32 @@ instance {-# OVERLAPPABLE #-}
214243-- > -- then you can just use "viewRefer" to query that endpoint
215244-- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments
216245instance (KnownSymbol sym, ToHttpApiData a,
217− HasClient t m sublayout, SupportsServantReflex t m)
218− => HasClient t m (Header sym a :> sublayout) where
246+ HasClient t m sublayout tag, SupportsServantReflex t m)
247+ => HasClient t m (Header sym a :> sublayout) tag where
219248
220− type Client t m (Header sym a :> sublayout) =
221− Dynamic t (Either Text a) -> Client t m sublayout
249+ type Client t m (Header sym a :> sublayout) tag =
250+ Dynamic t (Either Text a) -> Client t m sublayout tag
222251
223− clientWithRoute Proxy q req baseurl eVal =
252+ clientWithRoute Proxy q t req baseurl eVal =
224253 clientWithRoute (Proxy :: Proxy sublayout)
225− q
254+ q t
226255 (Servant.Common.Req.addHeader hname eVal req)
227256 baseurl
228257 where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
229258
259+
260+
261+
230262-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
231263-- functions.
232−instance HasClient t m sublayout
233− => HasClient t m (HttpVersion :> sublayout) where
264+instance HasClient t m sublayout tag
265+ => HasClient t m (HttpVersion :> sublayout) tag where
234266
235− type Client t m (HttpVersion :> sublayout) =
236− Client t m sublayout
267+ type Client t m (HttpVersion :> sublayout) tag =
268+ Client t m sublayout tag
237269
238− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy sublayout)
270+ clientWithRoute Proxy q t =
271+ clientWithRoute (Proxy :: Proxy sublayout) q t
239272
240273
241274-- | If you use a 'QueryParam' in one of your endpoints in your API,
@@ -264,15 +297,15 @@ instance HasClient t m sublayout
264297-- > -- then you can just use "getBooksBy" to query that endpoint.
265298-- > -- 'getBooksBy Nothing' for all books
266299-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
267−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
268− => HasClient t m (QueryParam sym a :> sublayout) where
300+instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
301+ => HasClient t m (QueryParam sym a :> sublayout) tag where
269302
270− type Client t m (QueryParam sym a :> sublayout) =
271− Dynamic t (QParam a) -> Client t m sublayout
303+ type Client t m (QueryParam sym a :> sublayout) tag =
304+ Dynamic t (QParam a) -> Client t m sublayout tag
272305
273306 -- if mparam = Nothing, we don't add it to the query string
274− clientWithRoute Proxy q req baseurl mparam =
275− clientWithRoute (Proxy :: Proxy sublayout) q
307+ clientWithRoute Proxy q t req baseurl mparam =
308+ clientWithRoute (Proxy :: Proxy sublayout) q t
276309 (req {qParams = paramPair : qParams req}) baseurl
277310
278311 where pname = symbolVal (Proxy :: Proxy sym)
@@ -281,6 +314,8 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
281314 p prm = QueryPartParam $ fmap qParamToQueryPart prm -- (fmap . fmap) (unpack . toQueryParam) prm
282315 paramPair = (T.pack pname, p mparam)
283316
317+
318+
284319-- | If you use a 'QueryParams' in one of your endpoints in your API,
285320-- the corresponding querying function will automatically take
286321-- an additional argument, a list of values of the type specified
@@ -309,14 +344,14 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
309344-- > -- 'getBooksBy []' for all books
310345-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
311346-- > -- to get all books by Asimov and Heinlein
312−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
313− => HasClient t m (QueryParams sym a :> sublayout) where
347+instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
348+ => HasClient t m (QueryParams sym a :> sublayout) tag where
314349
315− type Client t m (QueryParams sym a :> sublayout) =
316− Dynamic t [a] -> Client t m sublayout
350+ type Client t m (QueryParams sym a :> sublayout) tag =
351+ Dynamic t [a] -> Client t m sublayout tag
317352
318− clientWithRoute Proxy q req baseurl paramlist =
319− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
353+ clientWithRoute Proxy q t req baseurl paramlist =
354+ clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl
320355
321356 where req' = req { qParams = (T.pack pname, params') : qParams req }
322357 pname = symbolVal (Proxy :: Proxy sym)
@@ -324,6 +359,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
324359 paramlist
325360
326361
362+
327363-- | If you use a 'QueryFlag' in one of your endpoints in your API,
328364-- the corresponding querying function will automatically take
329365-- an additional 'Bool' argument.
@@ -348,49 +384,42 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
348384-- > -- 'getBooksBy True' to only get _already published_ books
349385
350386-- TODO Bring back
351−instance (KnownSymbol sym, HasClient t m sublayout, Reflex t)
352− => HasClient t m (QueryFlag sym :> sublayout) where
387+instance (KnownSymbol sym, HasClient t m sublayout tag, Reflex t)
388+ => HasClient t m (QueryFlag sym :> sublayout) tag where
353389
354− type Client t m (QueryFlag sym :> sublayout) =
355− Dynamic t Bool -> Client t m sublayout
390+ type Client t m (QueryFlag sym :> sublayout) tag =
391+ Dynamic t Bool -> Client t m sublayout tag
356392
357− clientWithRoute Proxy q req baseurl flag =
358− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
393+ clientWithRoute Proxy q t req baseurl flag =
394+ clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl
359395
360396 where req' = req { qParams = thisPair : qParams req }
361397 thisPair = (T.pack pName, QueryPartFlag flag) :: (Text, QueryPart t)
362398 pName = symbolVal (Proxy :: Proxy sym)
363399
364400
365−-- | Pick a 'Method' and specify where the server you want to query is. You get
366−-- back the full `Response`.
367−-- TODO redo
368−instance SupportsServantReflex t m => HasClient t m Raw where
369− type Client t m Raw = Dynamic t (Either Text (XhrRequest ()))
370− -> Event t ()
371− -> m (Event t (ReqResult ()))
372−
373− -- clientWithRoute :: Proxy Raw -> Proxy m -> Req -> BaseUrl -> Client t m Raw
374− clientWithRoute _ _ _ baseurl xhrs triggers = do
375− let xhrs' = liftA2 (\x path -> case x of
376− Left e -> Left e
377− Right jx -> Right $ jx {_xhrRequest_url = path
378− <> _xhrRequest_url jx})
379− xhrs
380− (showBaseUrl <$> baseurl)
381− reqs = tagPromptlyDyn xhrs' triggers
382− okReq = fmapMaybe hush reqs
383− badReq = fmapMaybe tattle reqs
384− resps <- performRequestAsync okReq
385− return $ leftmost [fmap (ResponseSuccess () ) resps
386− ,fmap RequestFailure badReq]
387−
388−
389−hush :: Either e a -> Maybe a
390−hush = either (const Nothing) Just
391−
392−tattle :: Either e a -> Maybe e
393−tattle = either Just (const Nothing)
401+
402+-- | Send a raw 'XhrRequest ()' directly to 'baseurl'
403+instance SupportsServantReflex t m => HasClient t m Raw tag where
404+ type Client t m Raw tag = Dynamic t (Either Text (XhrRequest ()))
405+ -> Event t tag
406+ -> m (Event t (ReqResult tag ()))
407+
408+ clientWithRoute _ _ _ _ baseurl xhrs triggers = do
409+
410+ let xhrs' = liftA2 (\x path -> case x of
411+ Left e -> Left e
412+ Right jx -> Right $ jx { _xhrRequest_url = path <> _xhrRequest_url jx }
413+ ) xhrs (showBaseUrl <$> baseurl)
414+ xhrs'' = attachPromptlyDynWith (flip (,)) xhrs' triggers :: Event t (tag, Either Text (XhrRequest ()))
415+ badReq = fmapMaybe (\(t,x) -> either (Just . (t,)) (const Nothing) x) xhrs'' :: Event t (tag, Text)
416+ okReq = fmapMaybe (\(t,x) -> either (const Nothing) (Just . (t,)) x) xhrs'' :: Event t (tag, XhrRequest ())
417+
418+ resps <- performRequestsAsync okReq
419+ return $ leftmost [ uncurry RequestFailure <$> badReq
420+ , evalResponse (const $ Right ()) <$> resps
421+ ]
422+
394423
395424-- | If you use a 'ReqBody' in one of your endpoints in your API,
396425-- the corresponding querying function will automatically take
@@ -412,14 +441,14 @@ tattle = either Just (const Nothing)
412441-- > where host = BaseUrl Http "localhost" 8080
413442-- > -- then you can just use "addBook" to query that endpoint
414443
415−instance (MimeRender ct a, HasClient t m sublayout, Reflex t)
416− => HasClient t m (ReqBody (ct ': cts) a :> sublayout) where
444+instance (MimeRender ct a, HasClient t m sublayout tag, Reflex t)
445+ => HasClient t m (ReqBody (ct ': cts) a :> sublayout) tag where
417446
418− type Client t m (ReqBody (ct ': cts) a :> sublayout) =
419− Dynamic t (Either Text a) -> Client t m sublayout
447+ type Client t m (ReqBody (ct ': cts) a :> sublayout) tag =
448+ Dynamic t (Either Text a) -> Client t m sublayout tag
420449
421− clientWithRoute Proxy q req baseurl body =
422− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
450+ clientWithRoute Proxy q t req baseurl body =
451+ clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl
423452 where req' = req { reqBody = bodyBytesCT }
424453 ctProxy = Proxy :: Proxy ct
425454 ctString = T.pack $ show $ contentType ctProxy
@@ -427,39 +456,49 @@ instance (MimeRender ct a, HasClient t m sublayout, Reflex t)
427456 (\b -> (mimeRender ctProxy b, ctString))
428457 body
429458
459+
460+
430461-- | Make the querying function append @path@ to the request path.
431−instance (KnownSymbol path, HasClient t m sublayout, Reflex t) => HasClient t m (path :> sublayout) where
432− type Client t m (path :> sublayout) = Client t m sublayout
462+instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient t m (path :> sublayout) tag where
463+ type Client t m (path :> sublayout) tag = Client t m sublayout tag
433464
434− clientWithRoute Proxy q req =
435− clientWithRoute (Proxy :: Proxy sublayout) q
436− (prependToPathParts (pure (Right $ T.pack p)) req)
465+ clientWithRoute Proxy q t req baseurl =
466+ clientWithRoute (Proxy :: Proxy sublayout) q t
467+ (prependToPathParts (pure (Right $ T.pack p)) req) baseurl
437468
438469 where p = symbolVal (Proxy :: Proxy path)
439470
440−instance HasClient t m api => HasClient t m (Vault :> api) where
441− type Client t m (Vault :> api) = Client t m api
442471
443− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
472+instance HasClient t m api tag => HasClient t m (Vault :> api) tag where
473+ type Client t m (Vault :> api) tag = Client t m api tag
474+
475+ clientWithRoute Proxy q t req baseurl =
476+ clientWithRoute (Proxy :: Proxy api) q t req baseurl
477+
478+
479+instance HasClient t m api tag => HasClient t m (RemoteHost :> api) tag where
480+ type Client t m (RemoteHost :> api) tag = Client t m api tag
481+
482+ clientWithRoute Proxy q t req baseurl =
483+ clientWithRoute (Proxy :: Proxy api) q t req baseurl
484+
444485
445−instance HasClient t m api => HasClient t m (RemoteHost :> api) where
446− type Client t m (RemoteHost :> api) = Client t m api
447486
448− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
487+instance HasClient t m api tag => HasClient t m (IsSecure :> api) tag where
488+ type Client t m (IsSecure :> api) tag = Client t m api tag
449489
450−instance HasClient t m api => HasClient t m (IsSecure :> api) where
451− type Client t m (IsSecure :> api) = Client t m api
490+ clientWithRoute Proxy q t req baseurl =
491+ clientWithRoute (Proxy :: Proxy api) q t req baseurl
452492
453− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
454493
455−instance (HasClient t m api, Reflex t)
456− => HasClient t m (BasicAuth realm usr :> api) where
494+instance (HasClient t m api tag, Reflex t)
495+ => HasClient t m (BasicAuth realm usr :> api) tag where
457496
458− type Client t m (BasicAuth realm usr :> api) = Dynamic t (Maybe BasicAuthData)
459− -> Client t m api
497+ type Client t m (BasicAuth realm usr :> api) tag = Dynamic t (Maybe BasicAuthData)
498+ -> Client t m api tag
460499
461− clientWithRoute Proxy q req baseurl authdata =
462− clientWithRoute (Proxy :: Proxy api) q req' baseurl
500+ clientWithRoute Proxy q t req baseurl authdata =
501+ clientWithRoute (Proxy :: Proxy api) q t req' baseurl
463502 where
464503 req' = req { authData = Just authdata }
465504
src/Servant/Reflex/Multi.hsnew file+372
@@ -0,0 +1,372 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE InstanceSigs #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE PolyKinds #-}
10+{-# LANGUAGE ScopedTypeVariables #-}
11+{-# LANGUAGE TupleSections #-}
12+{-# LANGUAGE TypeFamilies #-}
13+{-# LANGUAGE TypeOperators #-}
14+{-# LANGUAGE UndecidableInstances #-}
15+
16+module Servant.Reflex.Multi (
17+ -- * Compute servant client functions
18+ clientA
19+ , BaseUrl(..)
20+ , Scheme(..)
21+
22+ -- * Build QueryParam arguments
23+ , QParam(..)
24+
25+
26+ -- * Access response data
27+ , ReqResult(..)
28+ , reqSuccess
29+ , reqSuccess'
30+ , reqFailure
31+ , response
32+
33+ , HasClientMulti(..)
34+ ) where
35+
36+------------------------------------------------------------------------------
37+import Control.Applicative (liftA2)
38+import Data.Functor.Compose (Compose (..), getCompose)
39+import Data.Proxy (Proxy (..))
40+import qualified Data.Set as Set
41+import Data.Text (Text)
42+import qualified Data.Text as T
43+import qualified Data.Text.Encoding as E
44+import GHC.TypeLits (KnownSymbol, symbolVal)
45+import Servant.API ((:<|>) (..), (:>), BasicAuth,
46+ BasicAuthData, BuildHeadersTo (..),
47+ Capture, Header, Headers (..),
48+ HttpVersion, IsSecure, MimeRender (..),
49+ MimeUnrender, NoContent, QueryFlag,
50+ QueryParam, QueryParams, Raw,
51+ ReflectMethod (..), RemoteHost,
52+ ReqBody, ToHttpApiData (..), Vault,
53+ Verb, contentType)
54+
55+import Reflex.Dom (Dynamic, Event, Reflex,
56+ XhrRequest (..),
57+ XhrResponseHeaders (..),
58+ attachPromptlyDynWith, constDyn)
59+------------------------------------------------------------------------------
60+import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
61+ SupportsServantReflex)
62+import Servant.Common.Req (QParam (..), QueryPart (..), Req,
63+ ReqResult (..), addHeader, authData,
64+ defReq, performRequestsCT,
65+ performRequestsNoBody,
66+ performSomeRequestsAsync,
67+ prependToPathParts, qParamToQueryPart,
68+ qParams, reqBody, reqFailure,
69+ reqMethod, reqSuccess, reqSuccess',
70+ respHeaders, response)
71+import Servant.Reflex (BuildHeaderKeysTo (..), toHeaders)
72+
73+
74+------------------------------------------------------------------------------
75+clientA :: (HasClientMulti t m layout f tag, Applicative f, Reflex t)
76+ => Proxy layout -> Proxy m -> Proxy f -> Proxy tag
77+ -> Dynamic t BaseUrl -> ClientMulti t m layout f tag
78+clientA p q f tag baseurl =
79+ clientWithRouteMulti p q f tag (constDyn (pure defReq)) baseurl
80+
81+
82+------------------------------------------------------------------------------
83+class HasClientMulti t m layout f (tag :: *) where
84+ type ClientMulti t m layout f tag :: *
85+ clientWithRouteMulti :: Proxy layout -> Proxy m -> Proxy f -> Proxy tag
86+ -> Dynamic t (f (Req t)) -> Dynamic t BaseUrl
87+ -> ClientMulti t m layout f tag
88+
89+
90+------------------------------------------------------------------------------
91+instance (HasClientMulti t m a f tag, HasClientMulti t m b f tag) =>
92+ HasClientMulti t m (a :<|> b) f tag where
93+ type ClientMulti t m (a :<|> b) f tag = ClientMulti t m a f tag :<|>
94+ ClientMulti t m b f tag
95+ clientWithRouteMulti Proxy q f tag reqs baseurl =
96+ clientWithRouteMulti (Proxy :: Proxy a) q f tag reqs baseurl :<|>
97+ clientWithRouteMulti (Proxy :: Proxy b) q f tag reqs baseurl
98+
99+
100+------------------------------------------------------------------------------
101+instance (SupportsServantReflex t m,
102+ ToHttpApiData a,
103+ HasClientMulti t m sublayout f tag,
104+ Applicative f)
105+ => HasClientMulti t m (Capture capture a :> sublayout) f tag where
106+
107+ type ClientMulti t m (Capture capture a :> sublayout) f tag =
108+ f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f tag
109+
110+ clientWithRouteMulti _ q f tag reqs baseurl vals =
111+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl
112+ where
113+ reqs' = (prependToPathParts <$> ps <*>) <$> reqs
114+ ps = (fmap . fmap . fmap) toUrlPiece vals
115+
116+
117+------------------------------------------------------------------------------
118+-- VERB (Returning content) --
119+instance {-# OVERLAPPABLE #-}
120+ -- Note [Non-Empty Content Types]
121+ (MimeUnrender ct a,
122+ ReflectMethod method, cts' ~ (ct ': cts),
123+ SupportsServantReflex t m,
124+ Applicative f,
125+ Traversable f
126+ ) => HasClientMulti t m (Verb method status cts' a) f tag where
127+
128+ type ClientMulti t m (Verb method status cts' a) f tag =
129+ Event t tag -> m (Event t (f (ReqResult tag a)))
130+
131+ clientWithRouteMulti _ _ _ _ reqs baseurl =
132+ performRequestsCT (Proxy :: Proxy ct) method reqs' baseurl
133+ where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
134+ reqs' = fmap (\r -> r { reqMethod = method }) <$> reqs
135+
136+
137+------------------------------------------------------------------------------
138+-- -- VERB (No content) --
139+instance {-# OVERLAPPING #-}
140+ (ReflectMethod method, SupportsServantReflex t m, Traversable f) =>
141+ HasClientMulti t m (Verb method status cts NoContent) f tag where
142+ type ClientMulti t m (Verb method status cts NoContent) f tag =
143+ Event t tag -> m (Event t (f (ReqResult tag NoContent)))
144+ -- TODO: how to access input types here?
145+ -- ExceptT ServantError IO NoContent
146+ clientWithRouteMulti Proxy _ _ _ req baseurl =
147+ performRequestsNoBody method req baseurl
148+ where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
149+
150+
151+------------------------------------------------------------------------------
152+instance {-# OVERLAPPABLE #-}
153+ -- Note [Non-Empty Content Types]
154+ ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
155+ ReflectMethod method, cts' ~ (ct ': cts),
156+ SupportsServantReflex t m,
157+ Traversable f
158+ ) => HasClientMulti t m (Verb method status cts' (Headers ls a)) f tag where
159+ type ClientMulti t m (Verb method status cts' (Headers ls a)) f tag =
160+ Event t tag -> m (Event t (f (ReqResult tag (Headers ls a))))
161+ clientWithRouteMulti Proxy _ _ _ reqs baseurl triggers = do
162+ let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
163+ resp <- performRequestsCT (Proxy :: Proxy ct) method reqs' baseurl triggers :: m (Event t (f (ReqResult tag a)))
164+ return $ fmap toHeaders <$> resp
165+ where
166+ reqs' = fmap (\r ->
167+ r { respHeaders =
168+ OnlyHeaders (Set.fromList
169+ (buildHeaderKeysTo (Proxy :: Proxy ls)))
170+ }) <$> reqs
171+
172+
173+------------------------------------------------------------------------------
174+instance {-# OVERLAPPABLE #-}
175+ ( BuildHeadersTo ls,
176+ BuildHeaderKeysTo ls,
177+ ReflectMethod method,
178+ SupportsServantReflex t m,
179+ Traversable f
180+ ) => HasClientMulti t m (Verb method status
181+ cts (Headers ls NoContent)) f tag where
182+ type ClientMulti t m (Verb method status cts (Headers ls NoContent)) f tag
183+ = Event t tag -> m (Event t (f (ReqResult tag (Headers ls NoContent))))
184+ clientWithRouteMulti Proxy _ _ _ reqs baseurl triggers = do
185+ let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
186+ resp <- performRequestsNoBody method reqs' baseurl triggers
187+ return $ fmap toHeaders <$> resp
188+ where reqs' = fmap (\req ->
189+ req {respHeaders = OnlyHeaders (Set.fromList
190+ (buildHeaderKeysTo (Proxy :: Proxy ls)))
191+ }) <$> reqs
192+
193+
194+------------------------------------------------------------------------------
195+instance (KnownSymbol sym,
196+ ToHttpApiData a,
197+ HasClientMulti t m sublayout f tag,
198+ SupportsServantReflex t m,
199+ Traversable f,
200+ Applicative f)
201+ => HasClientMulti t m (Header sym a :> sublayout) f tag where
202+
203+ type ClientMulti t m (Header sym a :> sublayout) f tag =
204+ f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f tag
205+
206+ clientWithRouteMulti Proxy f q tag reqs baseurl eVals =
207+ clientWithRouteMulti (Proxy :: Proxy sublayout) f
208+ q tag
209+ reqs'
210+ baseurl
211+ where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
212+ reqs' = ((\eVal req -> Servant.Common.Req.addHeader hname eVal req)
213+ <$> eVals <*>) <$> reqs
214+
215+
216+------------------------------------------------------------------------------
217+instance HasClientMulti t m sublayout f tag
218+ => HasClientMulti t m (HttpVersion :> sublayout) f tag where
219+
220+ type ClientMulti t m (HttpVersion :> sublayout) f tag =
221+ ClientMulti t m sublayout f tag
222+
223+ clientWithRouteMulti Proxy q f tag =
224+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
225+
226+
227+------------------------------------------------------------------------------
228+instance (KnownSymbol sym,
229+ ToHttpApiData a,
230+ HasClientMulti t m sublayout f tag,
231+ Reflex t,
232+ Applicative f)
233+ => HasClientMulti t m (QueryParam sym a :> sublayout) f tag where
234+
235+ type ClientMulti t m (QueryParam sym a :> sublayout) f tag =
236+ Dynamic t (f (QParam a)) -> ClientMulti t m sublayout f tag
237+
238+ -- if mparam = Nothing, we don't add it to the query string
239+ -- TODO: Check the above comment
240+ clientWithRouteMulti Proxy q f tag reqs baseurl mparams =
241+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
242+ reqs' baseurl
243+
244+ where pname = symbolVal (Proxy :: Proxy sym)
245+ p prm = QueryPartParam $ fmap qParamToQueryPart prm
246+ paramPair mp = (T.pack pname, p mp)
247+ -- reqs' = (\params reqs -> (\req param -> req {qParams = paramPair param : qParams req}) <$> reqs <*> params)
248+ -- <$> mparams <*> reqs
249+ reqs' = liftA2 (\(pr :: QParam a) (r :: Req t) -> r { qParams = paramPair (constDyn pr) : qParams r })
250+ <$> mparams <*> reqs
251+
252+
253+instance (KnownSymbol sym,
254+ ToHttpApiData a,
255+ HasClientMulti t m sublayout f tag,
256+ Reflex t,
257+ Applicative f)
258+ => HasClientMulti t m (QueryParams sym a :> sublayout) f tag where
259+
260+ type ClientMulti t m (QueryParams sym a :> sublayout) f tag =
261+ Dynamic t (f [a]) -> ClientMulti t m sublayout f tag
262+
263+ clientWithRouteMulti Proxy q f tag reqs baseurl paramlists =
264+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl
265+
266+ where req' l r = r { qParams = (T.pack pname, params' (constDyn l)) : qParams r }
267+ pname = symbolVal (Proxy :: Proxy sym)
268+ params' l = QueryPartParams $ (fmap . fmap) (toQueryParam)
269+ l
270+ reqs' = liftA2 req' <$> paramlists <*> reqs
271+
272+
273+instance (KnownSymbol sym,
274+ HasClientMulti t m sublayout f tag,
275+ Reflex t,
276+ Applicative f)
277+ => HasClientMulti t m (QueryFlag sym :> sublayout) f tag where
278+
279+ type ClientMulti t m (QueryFlag sym :> sublayout) f tag =
280+ Dynamic t (f Bool) -> ClientMulti t m sublayout f tag
281+
282+ clientWithRouteMulti Proxy q f' tag reqs baseurl flags =
283+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f' tag reqs' baseurl
284+
285+ where req' f req = req { qParams = thisPair (constDyn f) : qParams req }
286+ thisPair f = (T.pack pName, QueryPartFlag f) :: (Text, QueryPart t)
287+ pName = symbolVal (Proxy :: Proxy sym)
288+ reqs' = liftA2 req' <$> flags <*> reqs
289+
290+
291+instance (SupportsServantReflex t m,
292+ Traversable f, Applicative f) => HasClientMulti t m Raw f tag where
293+ type ClientMulti t m Raw f tag = f (Dynamic t (Either Text (XhrRequest ())))
294+ -> Event t tag
295+ -> m (Event t (f (ReqResult tag ())))
296+
297+ clientWithRouteMulti _ _ _ _ _ _ rawReqs triggers = do
298+ let rawReqs' = sequence rawReqs :: Dynamic t (f (Either Text (XhrRequest ())))
299+ rawReqs'' = attachPromptlyDynWith (\fxhr t -> Compose (t, fxhr)) rawReqs' triggers
300+ resps <- fmap (fmap aux . sequenceA . getCompose) <$> performSomeRequestsAsync rawReqs''
301+ return resps
302+ where
303+ aux (tag, Right r) = ResponseSuccess tag () r
304+ aux (tag, Left e) = RequestFailure tag e
305+
306+
307+instance (MimeRender ct a,
308+ HasClientMulti t m sublayout f tag,
309+ Reflex t,
310+ Applicative f)
311+ => HasClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f tag where
312+
313+ type ClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f tag =
314+ Dynamic t (f (Either Text a)) -> ClientMulti t m sublayout f tag
315+
316+ clientWithRouteMulti Proxy q f tag reqs baseurl bodies =
317+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl
318+ where req' b r = r { reqBody = bodyBytesCT (constDyn b) }
319+ ctProxy = Proxy :: Proxy ct
320+ ctString = T.pack $ show $ contentType ctProxy
321+ bodyBytesCT b = Just $ (fmap . fmap)
322+ (\b' -> (mimeRender ctProxy b', ctString))
323+ b
324+ reqs' = liftA2 req' <$> bodies <*> reqs
325+
326+
327+instance (KnownSymbol path,
328+ HasClientMulti t m sublayout f tag,
329+ Reflex t,
330+ Functor f) => HasClientMulti t m (path :> sublayout) f tag where
331+ type ClientMulti t m (path :> sublayout) f tag = ClientMulti t m sublayout f tag
332+
333+ clientWithRouteMulti Proxy q f tag reqs baseurl =
334+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
335+ (fmap (prependToPathParts (pure (Right $ T.pack p))) <$> reqs)
336+ baseurl
337+
338+ where p = symbolVal (Proxy :: Proxy path)
339+
340+
341+instance HasClientMulti t m api f tag => HasClientMulti t m (Vault :> api) f tag where
342+ type ClientMulti t m (Vault :> api) f tag = ClientMulti t m api f tag
343+
344+ clientWithRouteMulti Proxy q f tag reqs baseurl =
345+ clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs baseurl
346+
347+
348+instance HasClientMulti t m api f tag => HasClientMulti t m (RemoteHost :> api) f tag where
349+ type ClientMulti t m (RemoteHost :> api) f tag = ClientMulti t m api f tag
350+
351+ clientWithRouteMulti Proxy q f tag reqs baseurl =
352+ clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs baseurl
353+
354+
355+instance HasClientMulti t m api f tag => HasClientMulti t m (IsSecure :> api) f tag where
356+ type ClientMulti t m (IsSecure :> api) f tag = ClientMulti t m api f tag
357+
358+ clientWithRouteMulti Proxy q f tag reqs baseurl =
359+ clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs baseurl
360+
361+
362+instance (HasClientMulti t m api f tag, Reflex t, Applicative f)
363+ => HasClientMulti t m (BasicAuth realm usr :> api) f tag where
364+
365+ type ClientMulti t m (BasicAuth realm usr :> api) f tag = Dynamic t (f (Maybe BasicAuthData))
366+ -> ClientMulti t m api f tag
367+
368+ clientWithRouteMulti Proxy q f tag reqs baseurl authdatas =
369+ clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs' baseurl
370+ where
371+ req' a r = r { authData = Just (constDyn a) }
372+ reqs' = liftA2 req' <$> authdatas <*> reqs
stack.yamlnew file+56
@@ -0,0 +1,56 @@
1+flags: {}
2+extra-package-dbs: []
3+packages:
4+- '.'
5+
6+- location:
7+ git: https://github.com/reflex-frp/reflex
8+ commit: d78ba4318c425ca9b942dc387d7c5c7ab2d2e095
9+ extra-dep: true
10+
11+- location:
12+ git: https://github.com/mokus0/dependent-sum.git
13+ commit: bbc8ad3a81ea7f2b07ce7d036efd56f2e0f7c6ff
14+ extra-dep: true
15+
16+- location:
17+ git: https://github.com/gtk2hs/gtk2hs.git
18+ commit: 21d3a12a82756d04066e5e4198b35097012ee65b
19+ subdirs:
20+ - cairo
21+ - gio
22+ - glib
23+ - gtk
24+ - pango
25+ - tools
26+ extra-dep: true
27+
28+- location:
29+ git: https://github.com/byorgey/hashtables.git
30+ commit: 47d9ed261a5aba66a13d3c4da0e391d338b57fc5
31+ extra-dep: true
32+
33+- location:
34+ git: https://github.com/afcady/reflex-dom.git
35+ commit: 39b5ff23f3ae95d77c909ab3ed4bc4bd9e61f600
36+ extra-dep: true
37+
38+- location:
39+ git: https://github.com/afcady/dependent-sum-template.git
40+ commit: 68fc1a90580ea8f20775491cf651eefea422212a
41+ extra-dep: true
42+
43+extra-deps:
44+- cpphs-1.20.4
45+- directory-1.2.7.1
46+- ghcjs-dom-0.2.4.0
47+- haskell-src-exts-1.17.1
48+- haskell-src-meta-0.6.0.14
49+- hlint-1.9.35
50+- prim-uniq-0.1.0.1
51+- primitive-0.6.2.0
52+- ref-tf-0.4.0.1
53+- vector-0.12.0.0
54+- webkitgtk3-javascriptcore-0.13.2.0
55+- zenc-0.1.1
56+resolver: lts-8.3