git.iko.soy/typeable/servant-reflex
on master help sign in
Merge pull request #29 from imalsogreg/trigger-tag

Trigger tag

commit8e52fe1ed8777df0fa037334702f70a31338882a authorGreg Hale <ImAlsoGreg@gmail.com> dateSat 4 Mar 2017, 19:32 (10 years ago) parent8a1e0176636bc2
8 files changed+906−284
.travis.yml+2−2 changelog.md+32 exec/Example.hs+77−26 overrides-ghc.nix−21 servant-reflex.cabal+2−1 src/Servant/Common/Req.hs+218−69 src/Servant/Reflex.hs+203−165 src/Servant/Reflex/Multi.hs+372
.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+77−26
@@ -2,29 +2,71 @@
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 Servant.API
16−import Servant.Reflex
1718import API
1819import Data.Proxy
1920import Text.Read (readMaybe)
2021import Reflex.Dom
22+------------------------------------------------------------------------------
23+import Servant.Reflex
24+import Servant.Reflex.Multi
25+
2126
2227api :: Proxy API
2328api = Proxy
2429
2530main :: IO ()
26−main = mainWidget run
27−
31+main = mainWidget $ do
32+ divClass "example-base" run
33+ divClass "example-multi" runMulti
34+
35+
36+runMulti :: forall t m. (SupportsServantReflex t m,
37+ DomBuilder t m,
38+ DomBuilderSpace m ~ GhcjsDomSpace,
39+ MonadFix m,
40+ PostBuild t m,
41+ MonadHold t m) => m ()
42+runMulti = do
43+ url <- baseUrlWidget
44+ el "br" blank
45+ let (_ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ ) =
46+ clientA api (Proxy :: Proxy m) (Proxy :: Proxy []) (Proxy :: Proxy Int) url
47+ num :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
48+ num2 :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
49+ -- dynText =<< holdDyn "waiting" (T.pack . show <$> num)
50+ -- numLabeled :: Event t (First Int, Double) <- zipListWithEvent (,) (First . Just <$> [0..]) num
51+ b <- button "Run dbl multi"
52+ reqCount <- count b
53+ r <- dbl ((\x y -> [x,y]) <$> num <*> num2) (tag (current reqCount) b)
54+ dynText =<< holdDyn "Waiting" (T.pack . show . fmapMaybe reqSuccess .snd <$> r)
55+ lastInd <- holdDyn Nothing (Just . fst <$> r)
56+ display lastInd
57+
58+ divClass "demo-group" $ do
59+ nms <- fmap (fmap T.words . value) $ divClass "" $ do
60+ text "Names"
61+ textInput def
62+ grts <- fmap (fmap T.words . value) $ divClass "" $ do
63+ text "Greetings"
64+ textInput def
65+ gust <- fmap (value) $ divClass "gusto-input" $ checkbox False def
66+ b <- button "Go"
67+ r' <- sayHi (fmap QParamSome <$> nms) (fmap (:[]) $ grts) (constDyn [True, False]) (1 <$ b)
68+ dynText =<< holdDyn "Waiting" (T.pack . show . fmap (fmapMaybe reqSuccess) <$> r')
69+ return ()
2870
2971run :: forall t m. (SupportsServantReflex t m,
3072 DomBuilder t m,
@@ -32,35 +74,39 @@ run :: forall t m. (SupportsServantReflex t m,
3274 MonadFix m,
3375 PostBuild t m,
3476 MonadHold t m) => m ()
35−run = do
77+run = mdo
3678
79+ reqCount <- count $ leftmost
80+ [() <$ unitBtn, () <$ intBtn, () <$ sayHiClicks, () <$ dblBtn, () <$ mpGo]
3781 -- Allow user to choose the url target for the request
3882 -- (alternatively we could just `let url = constDyn (BasePath "/")`)
3983 url <- baseUrlWidget
4084 el "br" (return ())
4185 dynText $ showBaseUrl <$> url
86+
4287 el "br" (return ())
4388
4489 -- Name the computed API client functions
4590 let (getUnit :<|> getInt :<|> sayhi :<|> dbl :<|> multi :<|> qna :<|> doRaw) =
4691 client api (Proxy :: Proxy m) url
4792
48− elClass "div" "demo-group" $ do
93+ (unitBtn, intBtn) <- elClass "div" "demo-group" $ do
4994 unitBtn <- divClass "unit-button" $ button "Get unit"
5095 intBtn <- divClass "int-button" $ button "Get int"
5196
52− unitResponse <- getUnit unitBtn
53− intResponse :: Event t (ReqResult Int) <- getInt intBtn
97+ unitResponse <- getUnit $ tag (current reqCount) unitBtn
98+ intResponse :: Event t (Int, ReqResult Int) <- getInt $ tag (current reqCount) intBtn
5499
55− score <- foldDyn (+) 0 (fmapMaybe reqSuccess intResponse)
100+ score <- foldDyn (+) 0 (fmapMaybe reqSuccess (snd <$> intResponse))
56101
57102 r <- holdDyn "Waiting" $ fmap showXhrResponse $
58− leftmost [fmapMaybe response unitResponse
59− ,fmapMaybe response intResponse
103+ leftmost [fmapMaybe response (snd <$> unitResponse)
104+ ,fmapMaybe response (snd <$> intResponse)
60105 ]
61106 divClass "unit-int-response" $ el "p" $ dynText r >> el "br" (return ()) >> text "Total: " >> display score
107+ return (unitBtn, intBtn)
62108
63− elClass "div" "demo-group" $ do
109+ sayHiClicks <- elClass "div" "demo-group" $ do
64110
65111 text "Name"
66112 el "br" $ return ()
@@ -78,36 +124,41 @@ run = do
78124 gusto <- fmap value $ divClass "gusto-input" $ checkbox False def
79125
80126 el "br" $ return ()
81− sayhiClicks :: Event t () <- divClass "hi-button" $ button "Say hi"
82− let triggers = leftmost [sayhiClicks, () <$ updated inp]
127+ sayHiClicks :: Event t () <- divClass "hi-button" $ button "Say hi"
128+ let triggers = leftmost [sayHiClicks, () <$ updated inp]
83129
84− resp <- sayhi checkedName greetings gusto triggers
130+ resp <- sayhi checkedName greetings gusto (tag (current reqCount) triggers)
85131 divClass "greeting-response" $ dynText =<<
86− holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess resp
87− , fmapMaybe reqFailure resp])
132+ holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess (snd <$> resp)
133+ , fmapMaybe reqFailure (snd <$> resp)])
134+ return sayHiClicks
88135
89− elClass "div" "demo-group" $ do
136+ dblBtn <- elClass "div" "demo-group" $ do
90137 text "A Double to double"
91138 el "br" $ return ()
92− dblinp <- fmap value $ divClass "double-input" $ textInput def
93− dblBtn <- divClass "double-button" $ button "Double it"
139+ dblinp <- fmap value $ divClass "double-input" $ textInput def
140+ (dblBtn) <- divClass "double-button" $ button "Double it"
94141 dblResp <- dbl (fmap (note "read failure" . readMaybe . T.unpack) $
95− dblinp) dblBtn
142+ dblinp) (tag (current reqCount) dblBtn)
96143 divClass "double-errors" $ dynText =<<
97− holdDyn "(no errors)" (fmapMaybe reqFailure dblResp)
144+ holdDyn "(no errors)" (fmapMaybe reqFailure (snd <$> dblResp))
98145 el "br" (return ())
99146 divClass "double-result" $ el "p" $ dynText =<<
100147 holdDyn "No number yet" (fmap tShow $
101− fmapMaybe reqSuccess dblResp)
148+ fmapMaybe reqSuccess (snd <$> dblResp))
149+ return dblBtn
102150
103− elClass "div" "demo-group" $ do
151+ mpGo <- elClass "div" "demo-group" $ do
104152 text "Multi-part path"
105153 b <- value <$> checkbox False def
106154 mpGo <- button "Test"
107− multiResp <- multi b mpGo
155+ multiResp <- multi b (tag (current reqCount) mpGo)
108156 dynText =<< holdDyn "No res yet" (fmap tShow $
109157 fmapMaybe reqSuccess $
110− multiResp)
158+ (snd <$> multiResp))
159+ return mpGo
160+
161+ return ()
111162
112163 el "br" $ return ()
113164
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+218−69
@@ -1,4 +1,3 @@
1−{-# LANGUAGE DeriveDataTypeable #-}
21{-# LANGUAGE CPP #-}
32{-# LANGUAGE FlexibleContexts #-}
43{-# LANGUAGE GADTs #-}
@@ -6,50 +5,83 @@
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
3046
31−data ReqResult a = ResponseSuccess a XhrResponse
32− | ResponseFailure Text XhrResponse
33− | RequestFailure Text
3447
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
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
3955
40−reqSuccess :: ReqResult a -> Maybe a
41−reqSuccess (ResponseSuccess x _) = Just x
42−reqSuccess _ = Nothing
4356
44−reqFailure :: ReqResult a -> Maybe Text
45−reqFailure (ResponseFailure s _) = Just s
46−reqFailure (RequestFailure s) = Just s
47−reqFailure _ = Nothing
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
4862
49−response :: ReqResult a -> Maybe XhrResponse
50−response (ResponseSuccess _ x) = Just x
51−response (ResponseFailure _ x) = Just x
52−response _ = Nothing
63+
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
70+
71+
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
78+
79+
80+------------------------------------------------------------------------------
81+instance Functor (ReqResult tag) where
82+ fmap f (ResponseSuccess tag a xhr) = ResponseSuccess tag (f a) xhr
83+ fmap _ (ResponseFailure tag r x) = ResponseFailure tag r x
84+ fmap _ (RequestFailure tag r) = RequestFailure tag r
5385
5486
5587-------------------------------------------------------------------------------
@@ -63,11 +95,13 @@ data QParam a = QParamSome a
6395 | QParamInvalid Text
6496 -- ^ Indication that your validation failed (the request isn't valid)
6597
98+
6699qParamToQueryPart :: ToHttpApiData a => QParam a -> Either Text (Maybe Text)
67100qParamToQueryPart (QParamSome a) = Right (Just $ toQueryParam a)
68101qParamToQueryPart QNone = Right Nothing
69102qParamToQueryPart (QParamInvalid e) = Left e
70103
104+
71105data QueryPart t = QueryPartParam (Dynamic t (Either Text (Maybe Text)))
72106 | QueryPartParams (Dynamic t [Text])
73107 | QueryPartFlag (Dynamic t Bool)
@@ -97,20 +131,13 @@ addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) ->
97131addHeader name val req = req { headers = (name, (fmap . fmap) (TE.decodeUtf8 . toHeader) val) : headers req }
98132
99133
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−
134+reqToReflexRequest
135+ :: forall t. Reflex t
136+ => Text
137+ -> Dynamic t BaseUrl
138+ -> Req t
139+ -> (Dynamic t (Either Text (XhrRequest XhrPayload)))
140+reqToReflexRequest reqMeth reqHost req =
114141 let t :: Dynamic t [Either Text Text]
115142 t = sequence $ reverse $ reqPathParts req
116143
@@ -143,8 +170,8 @@ performRequest reqMeth req reqHost trigger = do
143170 queryPartStrings' = fmap (sequence . catMaybes) $ sequence queryPartStrings :: Dynamic t (Either Text [Text])
144171 queryString :: Dynamic t (Either Text Text) =
145172 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
173+ xhrUrl = (liftA3 . liftA3) (\a p q -> a </> if T.null q then p else p <> "?" <> q)
174+ baseUrl urlPath queryString
148175 where
149176 (</>) :: Text -> Text -> Text
150177 x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
@@ -203,64 +230,186 @@ performRequest reqMeth req reqHost trigger = do
203230
204231 xhrReq = (liftA2 . liftA2) (\p opt -> XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
205232
206− let reqs = tagPromptlyDyn xhrReq trigger
207− okReqs = fmapMaybe (either (const Nothing) Just) reqs
208− badReqs = fmapMaybe (either Just (const Nothing)) reqs
233+ in xhrReq
234+
235+-- * performing requests
236+
237+displayHttpRequest :: Text -> Text
238+displayHttpRequest httpmethod = "HTTP " <> httpmethod <> " request"
239+
240+-- | This function actually performs the request.
241+performRequests :: forall t m f tag.(SupportsServantReflex t m, Traversable f)
242+ => Text
243+ -> Dynamic t (f (Req t))
244+ -> Dynamic t BaseUrl
245+ -> Event t tag
246+ -> m (Event t (tag, f (Either Text XhrResponse)))
247+performRequests reqMeth rs reqHost trigger = do
248+ -- let xhrReqs = sequence $ (\r -> reqToReflexRequest reqMeth r reqHost) <$> rs :: Dynamic t (f (Either Text (XhrRequest XhrPayload)))
249+ let xhrReqs = join $ (\(fxhr :: f (Req t)) -> sequence $ reqToReflexRequest reqMeth reqHost <$> fxhr) <$> rs
250+ let reqs = attachPromptlyDynWith (\fxhr t -> Compose (t, fxhr)) xhrReqs trigger
251+ resps <- performSomeRequestsAsync reqs
252+ return $ getCompose <$> resps
253+
254+-- | 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.
255+performSomeRequestsAsync
256+ :: (MonadIO (Performable m),
257+ HasWebView (Performable m),
258+ PerformEvent t m,
259+ TriggerEvent t m,
260+ Traversable f,
261+ IsXhrPayload a)
262+ => Event t (f (Either Text (XhrRequest a)))
263+ -> m (Event t (f (Either Text XhrResponse)))
264+performSomeRequestsAsync = performSomeRequestsAsync' newXMLHttpRequest . fmap return
265+
266+
267+------------------------------------------------------------------------------
268+-- | A modified version or Reflex.Dom.Xhr.performRequestsAsync
269+-- that accepts 'f (Either e (XhrRequestb))' events
270+performSomeRequestsAsync'
271+ :: (MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f)
272+ => (XhrRequest b -> (a -> IO ()) -> Performable m XMLHttpRequest)
273+ -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
274+performSomeRequestsAsync' newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
275+ rs <- hrs
276+ resps <- forM rs $ \r -> case r of
277+ Left e -> do
278+ resp <- liftIO $ newMVar (Left e)
279+ return resp
280+ Right r' -> do
281+ resp <- liftIO newEmptyMVar
282+ _ <- newXhr r' $ liftIO . putMVar resp . Right
283+ return resp
284+ _ <- liftIO $ forkIO $ cb =<< forM resps takeMVar
285+ return ()
286+
287+
288+
289+-- | This function actually performs the request.
290+performRequest :: forall t m tag .(SupportsServantReflex t m)
291+ => Text
292+ -> (Req t)
293+ -> Dynamic t BaseUrl
294+ -> Event t tag
295+ -> m (Event t (tag, XhrResponse), Event t (tag, Text))
296+performRequest reqMeth req reqHost trigger = do
297+
298+ let xhrReq = reqToReflexRequest reqMeth reqHost req
299+ let reqs = attachPromptlyDynWith (flip (,)) xhrReq trigger
300+ okReqs = fmapMaybe (\(t,e) -> either (const Nothing) (Just . (t,)) e) reqs
301+ badReqs = fmapMaybe (\(t,e) -> either (Just . (t,)) (const Nothing) e) reqs
209302
210− resps <- performRequestAsync okReqs
303+ resps <- performRequestsAsync okReqs
211304
212305 return (resps, badReqs)
213306
307+
214308type XhrPayload = T.Text
215309bytesToPayload :: BL.ByteString -> XhrPayload
216310bytesToPayload = TE.decodeUtf8 . BL.toStrict
217311
218312
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
313+-- performRequestNoBody :: forall t m tag.(SupportsServantReflex t m)
314+-- => Text
315+-- -> Req t
316+-- -> Dynamic t BaseUrl
317+-- -> Event t tag -> m (Event t (ReqResult tag NoContent))
318+-- performRequestNoBody reqMeth req reqHost trigger = do
319+-- (resp, badReq) <- performRequest reqMeth req reqHost trigger
320+-- let decodeResp = const $ Right NoContent
321+-- return $ leftmost [ fmap (evalResponse decodeResp) resp
322+-- , fmap (uncurry RequestFailure) badReq
323+-- ]
324+
325+
326+-- performRequestCT
327+-- :: (SupportsServantReflex t m,
328+-- MimeUnrender ct a)
329+-- => Proxy ct
330+-- -> Text
331+-- -> Req t
332+-- -> Dynamic t BaseUrl
333+-- -> Event t tag
334+-- -> m (Event t (ReqResult tag a))
335+-- performRequestCT ct reqMeth req reqHost trigger = do
336+-- (resp, badReq) <- performRequest reqMeth req reqHost trigger
337+-- let decodeResp x = first T.pack .
338+-- mimeUnrender ct .
339+-- BL.fromStrict .
340+-- TE.encodeUtf8 =<< note "No body text"
341+-- (_xhrResponse_responseText x)
342+
343+-- return $ leftmost [fmap (evalResponse decodeResp) resp
344+-- , fmap (uncurry RequestFailure) badReq
345+-- ]
346+
347+
348+performRequestsCT
349+ :: (SupportsServantReflex t m,
350+ MimeUnrender ct a, Traversable f)
351+ => Proxy ct
352+ -> Text
353+ -> Dynamic t (f (Req t))
354+ -> Dynamic t BaseUrl
355+ -> Event t tag
356+ -> m (Event t (f (ReqResult tag a)))
357+performRequestsCT ct reqMeth reqs reqHost trigger = do
358+ resps <- performRequests reqMeth reqs reqHost trigger
239359 let decodeResp x = first T.pack .
240360 mimeUnrender ct .
241361 BL.fromStrict .
242362 TE.encodeUtf8 =<< note "No body text"
243363 (_xhrResponse_responseText x)
244−
245− return $ leftmost [fmap (evalRequest decodeResp) resp,
246− fmap RequestFailure badReq]
364+ return $ fmap
365+ (\(t,rs) -> ffor rs $ \r -> case r of
366+ Left e -> RequestFailure t e
367+ Right g -> evalResponse decodeResp (t,g)
368+ )
369+ resps
370+
371+
372+performRequestsNoBody
373+ :: (SupportsServantReflex t m,
374+ Traversable f)
375+ => Text
376+ -> Dynamic t (f (Req t))
377+ -> Dynamic t BaseUrl
378+ -> Event t tag
379+ -> m (Event t (f (ReqResult tag NoContent)))
380+performRequestsNoBody reqMeth reqs reqHost trigger = do
381+ resps <- performRequests reqMeth reqs reqHost trigger
382+ let decodeResp = const $ Right NoContent
383+ return $ ffor resps $ \(tag,rs) -> ffor rs $ \r -> case r of
384+ Left e -> RequestFailure tag e
385+ Right g -> evalResponse decodeResp (tag,g)
247386
248387
249388------------------------------------------------------------------------------
250−evalRequest :: (XhrResponse -> Either Text a) -> XhrResponse -> ReqResult a
251−evalRequest decode xhr =
389+evalResponse
390+ :: (XhrResponse -> Either Text a)
391+ -> (tag, XhrResponse)
392+ -> ReqResult tag a
393+evalResponse decode (tag, xhr) =
252394 let okStatus = _xhrResponse_status xhr < 400
253395 errMsg = fromMaybe
254396 ("Empty response with error code " <>
255397 T.pack (show $ _xhrResponse_status xhr))
256398 (_xhrResponse_responseText xhr)
257399 respPayld = if okStatus
258− then either (flip ResponseFailure xhr)
259− (flip ResponseSuccess xhr) (decode xhr)
260− else ResponseFailure errMsg xhr
400+ then either
401+ (\e -> ResponseFailure tag e xhr)
402+ (\v -> ResponseSuccess tag v xhr)
403+ (decode xhr)
404+ else ResponseFailure tag errMsg xhr
261405 in respPayld
262406
263407
264408
409+
265410note :: e -> Maybe a -> Either e a
266411note e = maybe (Left e) Right
412+
413+fmapL :: (e -> e') -> Either e a -> Either e' a
414+fmapL _ (Right a) = Right a
415+fmapL f (Left e) = Left (f e)
src/Servant/Reflex.hs+203−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,58 @@
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+ qParams)
80+
6281
6382-- * Accessing APIs as a Client
6483
@@ -70,28 +89,31 @@ import Servant.Common.Req (QParam (..), QueryPart (..), Req,
7089-- > myApi :: Proxy MyApi
7190-- > myApi = Proxy
7291-- >
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)))
92+-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
93+-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
94+-- -> m (Event t (l, ReqResult Book)))
7695-- > (getAllBooks :<|> postNewBook) = client myApi host
7796-- > 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
97+client :: (HasClient t m layout tag)
98+ => Proxy layout -> Proxy m -> Proxy tag -> Dynamic t BaseUrl -> Client t m layout tag
99+client p q t baseurl = clientWithRoute p q t defReq baseurl
100+
81101
82102-- | This class lets us define how each API combinator
83103-- influences the creation of an HTTP request. It's mostly
84104-- 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
105+class HasClient t m layout (tag :: *) where
106+ type Client t m layout tag :: *
107+ clientWithRoute :: Proxy layout -> Proxy m -> Proxy tag -> Req t -> Dynamic t BaseUrl -> Client t m layout tag
88108
89109
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
110+instance (HasClient t m a tag, HasClient t m b tag) => HasClient t m (a :<|> b) tag where
111+ type Client t m (a :<|> b) tag = Client t m a tag :<|> Client t m b tag
112+
113+ clientWithRoute Proxy q pTag req baseurl =
114+ clientWithRoute (Proxy :: Proxy a) q pTag req baseurl :<|>
115+ clientWithRoute (Proxy :: Proxy b) q pTag req baseurl
116+
95117
96118-- Capture. Example:
97119-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
@@ -101,50 +123,54 @@ instance (HasClient t m a, HasClient t m b) => HasClient t m (a :<|> b) where
101123-- > getBook :: SupportsServantReflex t m
102124-- => Dynamic t BaseUrl
103125-- -> Dynamic t (Maybe Text)
104−-- -> Event t ()
105−-- -> m (Event t (Either XhrError (Text, Book)))
126+-- -> Event t l
127+-- -> m (Event t (l, ReqResult Book))
106128-- > 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
109129
110− type Client t m (Capture capture a :> sublayout) =
111− Dynamic t (Either Text a) -> Client t m sublayout
130+instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout tag)
131+ => HasClient t m (Capture capture a :> sublayout) tag where
112132
113− clientWithRoute Proxy q req baseurl val =
133+ type Client t m (Capture capture a :> sublayout) tag =
134+ Dynamic t (Either Text a) -> Client t m sublayout tag
135+
136+ clientWithRoute Proxy q t req baseurl val =
114137 clientWithRoute (Proxy :: Proxy sublayout)
115− q
138+ q t
116139 (prependToPathParts p req)
117140 baseurl
141+ where p = (fmap . fmap) (toUrlPiece) val
118142
119− where p = (fmap . fmap) toUrlPiece val
120143
121144-- VERB (Returning content) --
122145instance {-# OVERLAPPABLE #-}
123146 -- Note [Non-Empty Content Types]
124147 (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))
148+ ) => HasClient t m (Verb method status cts' a) tag where
149+ type Client t m (Verb method status cts' a) tag =
150+ Event t tag -> m (Event t (ReqResult tag a))
128151 -- TODO how to access input types here?
129152 -- ExceptT ServantError IO a
130− clientWithRoute Proxy _ req =
131− performRequestCT (Proxy :: Proxy ct) method req'
153+ clientWithRoute Proxy _ _ req baseurl trigs =
154+ -- performRequestCT (Proxy :: Proxy ct) method req' baseurl trigs
155+ fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity $ req') baseurl trigs
132156 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
133157 req' = req { reqMethod = method }
134158
159+
135160-- -- VERB (No content) --
136161instance {-# OVERLAPPING #-}
137162 (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))
163+ HasClient t m (Verb method status cts NoContent) tag where
164+ type Client t m (Verb method status cts NoContent) tag =
165+ Event t tag -> m (Event t (ReqResult tag NoContent))
141166 -- TODO: how to access input types here?
142167 -- ExceptT ServantError IO NoContent
143− clientWithRoute Proxy _ =
144− performRequestNoBody method
168+ clientWithRoute Proxy _ _ req baseurl =
169+ (fmap . fmap) runIdentity . performRequestsNoBody method (constDyn $ Identity req) baseurl
145170 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
146171
147−toHeaders :: BuildHeadersTo ls => ReqResult a -> ReqResult (Headers ls a)
172+
173+toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
148174toHeaders r =
149175 let toBS = E.encodeUtf8
150176 hdrs = maybe []
@@ -173,33 +199,35 @@ instance {-# OVERLAPPABLE #-}
173199 ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
174200 ReflectMethod method, cts' ~ (ct ': cts),
175201 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
202+ ) => HasClient t m (Verb method status cts' (Headers ls a)) tag where
203+ type Client t m (Verb method status cts' (Headers ls a)) tag =
204+ Event t tag -> m (Event t (ReqResult tag (Headers ls a)))
205+ clientWithRoute Proxy _ _ req baseurl trigs = do
180206 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
181− resp <- performRequestCT (Proxy :: Proxy ct) method req' baseurl
182− return $ (fmap . fmap) toHeaders resp
207+ resp <- fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity req') baseurl trigs
208+ return $ toHeaders <$> resp
183209 where req' = req { respHeaders =
184210 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
185211 }
186212
213+
187214-- HEADERS Verb (No content) --
188215instance {-# OVERLAPPABLE #-}
189216 ( BuildHeadersTo ls, BuildHeaderKeysTo ls, ReflectMethod method,
190217 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
218+ ) => HasClient t m (Verb method status cts (Headers ls NoContent)) tag where
219+ type Client t m (Verb method status cts (Headers ls NoContent)) tag
220+ = Event t tag -> m (Event t (ReqResult tag (Headers ls NoContent)))
221+ clientWithRoute Proxy _ _ req baseurl trigs = do
195222 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
196− resp <- performRequestNoBody method req' baseurl
197− return $ (fmap . fmap) toHeaders resp
223+ resp <- fmap runIdentity <$> performRequestsNoBody method (constDyn $ Identity req') baseurl trigs
224+ return $ toHeaders <$> resp
198225 where req' = req {respHeaders =
199226 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
200227 }
201228
202229
230+
203231-- HEADER
204232-- > newtype Referer = Referer { referrer :: Text }
205233-- > deriving (Eq, Show, Generic, FromText, ToHttpApiData)
@@ -214,28 +242,32 @@ instance {-# OVERLAPPABLE #-}
214242-- > -- then you can just use "viewRefer" to query that endpoint
215243-- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments
216244instance (KnownSymbol sym, ToHttpApiData a,
217− HasClient t m sublayout, SupportsServantReflex t m)
218− => HasClient t m (Header sym a :> sublayout) where
245+ HasClient t m sublayout tag, SupportsServantReflex t m)
246+ => HasClient t m (Header sym a :> sublayout) tag where
219247
220− type Client t m (Header sym a :> sublayout) =
221− Dynamic t (Either Text a) -> Client t m sublayout
248+ type Client t m (Header sym a :> sublayout) tag =
249+ Dynamic t (Either Text a) -> Client t m sublayout tag
222250
223− clientWithRoute Proxy q req baseurl eVal =
251+ clientWithRoute Proxy q t req baseurl eVal =
224252 clientWithRoute (Proxy :: Proxy sublayout)
225− q
253+ q t
226254 (Servant.Common.Req.addHeader hname eVal req)
227255 baseurl
228256 where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
229257
258+
259+
260+
230261-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
231262-- functions.
232−instance HasClient t m sublayout
233− => HasClient t m (HttpVersion :> sublayout) where
263+instance HasClient t m sublayout tag
264+ => HasClient t m (HttpVersion :> sublayout) tag where
234265
235− type Client t m (HttpVersion :> sublayout) =
236− Client t m sublayout
266+ type Client t m (HttpVersion :> sublayout) tag =
267+ Client t m sublayout tag
237268
238− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy sublayout)
269+ clientWithRoute Proxy q t =
270+ clientWithRoute (Proxy :: Proxy sublayout) q t
239271
240272
241273-- | If you use a 'QueryParam' in one of your endpoints in your API,
@@ -264,15 +296,15 @@ instance HasClient t m sublayout
264296-- > -- then you can just use "getBooksBy" to query that endpoint.
265297-- > -- 'getBooksBy Nothing' for all books
266298-- > -- '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
299+instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
300+ => HasClient t m (QueryParam sym a :> sublayout) tag where
269301
270− type Client t m (QueryParam sym a :> sublayout) =
271− Dynamic t (QParam a) -> Client t m sublayout
302+ type Client t m (QueryParam sym a :> sublayout) tag =
303+ Dynamic t (QParam a) -> Client t m sublayout tag
272304
273305 -- 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
306+ clientWithRoute Proxy q t req baseurl mparam =
307+ clientWithRoute (Proxy :: Proxy sublayout) q t
276308 (req {qParams = paramPair : qParams req}) baseurl
277309
278310 where pname = symbolVal (Proxy :: Proxy sym)
@@ -281,6 +313,8 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
281313 p prm = QueryPartParam $ fmap qParamToQueryPart prm -- (fmap . fmap) (unpack . toQueryParam) prm
282314 paramPair = (T.pack pname, p mparam)
283315
316+
317+
284318-- | If you use a 'QueryParams' in one of your endpoints in your API,
285319-- the corresponding querying function will automatically take
286320-- an additional argument, a list of values of the type specified
@@ -309,14 +343,14 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
309343-- > -- 'getBooksBy []' for all books
310344-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
311345-- > -- 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
346+instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
347+ => HasClient t m (QueryParams sym a :> sublayout) tag where
314348
315− type Client t m (QueryParams sym a :> sublayout) =
316− Dynamic t [a] -> Client t m sublayout
349+ type Client t m (QueryParams sym a :> sublayout) tag =
350+ Dynamic t [a] -> Client t m sublayout tag
317351
318− clientWithRoute Proxy q req baseurl paramlist =
319− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
352+ clientWithRoute Proxy q t req baseurl paramlist =
353+ clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl
320354
321355 where req' = req { qParams = (T.pack pname, params') : qParams req }
322356 pname = symbolVal (Proxy :: Proxy sym)
@@ -324,6 +358,7 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
324358 paramlist
325359
326360
361+
327362-- | If you use a 'QueryFlag' in one of your endpoints in your API,
328363-- the corresponding querying function will automatically take
329364-- an additional 'Bool' argument.
@@ -348,49 +383,42 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
348383-- > -- 'getBooksBy True' to only get _already published_ books
349384
350385-- TODO Bring back
351−instance (KnownSymbol sym, HasClient t m sublayout, Reflex t)
352− => HasClient t m (QueryFlag sym :> sublayout) where
386+instance (KnownSymbol sym, HasClient t m sublayout tag, Reflex t)
387+ => HasClient t m (QueryFlag sym :> sublayout) tag where
353388
354− type Client t m (QueryFlag sym :> sublayout) =
355− Dynamic t Bool -> Client t m sublayout
389+ type Client t m (QueryFlag sym :> sublayout) tag =
390+ Dynamic t Bool -> Client t m sublayout tag
356391
357− clientWithRoute Proxy q req baseurl flag =
358− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
392+ clientWithRoute Proxy q t req baseurl flag =
393+ clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl
359394
360395 where req' = req { qParams = thisPair : qParams req }
361396 thisPair = (T.pack pName, QueryPartFlag flag) :: (Text, QueryPart t)
362397 pName = symbolVal (Proxy :: Proxy sym)
363398
364399
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)
400+
401+-- | Send a raw 'XhrRequest ()' directly to 'baseurl'
402+instance SupportsServantReflex t m => HasClient t m Raw tag where
403+ type Client t m Raw tag = Dynamic t (Either Text (XhrRequest ()))
404+ -> Event t tag
405+ -> m (Event t (ReqResult tag ()))
406+
407+ clientWithRoute _ _ _ _ baseurl xhrs triggers = do
408+
409+ let xhrs' = liftA2 (\x path -> case x of
410+ Left e -> Left e
411+ Right jx -> Right $ jx { _xhrRequest_url = path <> _xhrRequest_url jx }
412+ ) xhrs (showBaseUrl <$> baseurl)
413+ xhrs'' = attachPromptlyDynWith (flip (,)) xhrs' triggers :: Event t (tag, Either Text (XhrRequest ()))
414+ badReq = fmapMaybe (\(t,x) -> either (Just . (t,)) (const Nothing) x) xhrs'' :: Event t (tag, Text)
415+ okReq = fmapMaybe (\(t,x) -> either (const Nothing) (Just . (t,)) x) xhrs'' :: Event t (tag, XhrRequest ())
416+
417+ resps <- performRequestsAsync okReq
418+ return $ leftmost [ uncurry RequestFailure <$> badReq
419+ , evalResponse (const $ Right ()) <$> resps
420+ ]
421+
394422
395423-- | If you use a 'ReqBody' in one of your endpoints in your API,
396424-- the corresponding querying function will automatically take
@@ -412,14 +440,14 @@ tattle = either Just (const Nothing)
412440-- > where host = BaseUrl Http "localhost" 8080
413441-- > -- then you can just use "addBook" to query that endpoint
414442
415−instance (MimeRender ct a, HasClient t m sublayout, Reflex t)
416− => HasClient t m (ReqBody (ct ': cts) a :> sublayout) where
443+instance (MimeRender ct a, HasClient t m sublayout tag, Reflex t)
444+ => HasClient t m (ReqBody (ct ': cts) a :> sublayout) tag where
417445
418− type Client t m (ReqBody (ct ': cts) a :> sublayout) =
419− Dynamic t (Either Text a) -> Client t m sublayout
446+ type Client t m (ReqBody (ct ': cts) a :> sublayout) tag =
447+ Dynamic t (Either Text a) -> Client t m sublayout tag
420448
421− clientWithRoute Proxy q req baseurl body =
422− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
449+ clientWithRoute Proxy q t req baseurl body =
450+ clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl
423451 where req' = req { reqBody = bodyBytesCT }
424452 ctProxy = Proxy :: Proxy ct
425453 ctString = T.pack $ show $ contentType ctProxy
@@ -427,39 +455,49 @@ instance (MimeRender ct a, HasClient t m sublayout, Reflex t)
427455 (\b -> (mimeRender ctProxy b, ctString))
428456 body
429457
458+
459+
430460-- | 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
461+instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient t m (path :> sublayout) tag where
462+ type Client t m (path :> sublayout) tag = Client t m sublayout tag
433463
434− clientWithRoute Proxy q req =
435− clientWithRoute (Proxy :: Proxy sublayout) q
436− (prependToPathParts (pure (Right $ T.pack p)) req)
464+ clientWithRoute Proxy q t req baseurl =
465+ clientWithRoute (Proxy :: Proxy sublayout) q t
466+ (prependToPathParts (pure (Right $ T.pack p)) req) baseurl
437467
438468 where p = symbolVal (Proxy :: Proxy path)
439469
440−instance HasClient t m api => HasClient t m (Vault :> api) where
441− type Client t m (Vault :> api) = Client t m api
442470
443− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
471+instance HasClient t m api tag => HasClient t m (Vault :> api) tag where
472+ type Client t m (Vault :> api) tag = Client t m api tag
473+
474+ clientWithRoute Proxy q t req baseurl =
475+ clientWithRoute (Proxy :: Proxy api) q t req baseurl
476+
477+
478+instance HasClient t m api tag => HasClient t m (RemoteHost :> api) tag where
479+ type Client t m (RemoteHost :> api) tag = Client t m api tag
480+
481+ clientWithRoute Proxy q t req baseurl =
482+ clientWithRoute (Proxy :: Proxy api) q t req baseurl
483+
444484
445−instance HasClient t m api => HasClient t m (RemoteHost :> api) where
446− type Client t m (RemoteHost :> api) = Client t m api
447485
448− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
486+instance HasClient t m api tag => HasClient t m (IsSecure :> api) tag where
487+ type Client t m (IsSecure :> api) tag = Client t m api tag
449488
450−instance HasClient t m api => HasClient t m (IsSecure :> api) where
451− type Client t m (IsSecure :> api) = Client t m api
489+ clientWithRoute Proxy q t req baseurl =
490+ clientWithRoute (Proxy :: Proxy api) q t req baseurl
452491
453− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
454492
455−instance (HasClient t m api, Reflex t)
456− => HasClient t m (BasicAuth realm usr :> api) where
493+instance (HasClient t m api tag, Reflex t)
494+ => HasClient t m (BasicAuth realm usr :> api) tag where
457495
458− type Client t m (BasicAuth realm usr :> api) = Dynamic t (Maybe BasicAuthData)
459− -> Client t m api
496+ type Client t m (BasicAuth realm usr :> api) tag = Dynamic t (Maybe BasicAuthData)
497+ -> Client t m api tag
460498
461− clientWithRoute Proxy q req baseurl authdata =
462− clientWithRoute (Proxy :: Proxy api) q req' baseurl
499+ clientWithRoute Proxy q t req baseurl authdata =
500+ clientWithRoute (Proxy :: Proxy api) q t req' baseurl
463501 where
464502 req' = req { authData = Just authdata }
465503
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