git.iko.soy/typeable/servant-reflex
on fcfa875 help sign in
16 files changed+684−141
since splitsnapshots
unifiedsplit
.gitignore−1 .gitmodules+3−6 .travis.yml+2−3 build.sh+1−1 deps/reflex−1 deps/reflex-dom−1 deps/reflex-platform+1 exec/Example.hs+23−20 exec/TupleExample.hs+35 overrides-ghc.nix+1−9 overrides.nix−4 servant-reflex.cabal+5−2 src/Servant/Common/BaseUrl.hs+3−2 src/Servant/Common/Req.hs+221−14 src/Servant/Reflex.hs+144−77 src/Servant/Reflex/Tuple.hs+245
.gitignore−1
@@ -6,5 +6,4 @@ log
66*.webapp
77*.stats
88out
9−reflex-platform
109/.stack-work/
.gitmodules+3−6
@@ -1,9 +1,3 @@
1−[submodule "deps/reflex-dom"]
2− path = deps/reflex-dom
3− url = https://github.com/reflex-frp/reflex-dom.git
4−[submodule "deps/reflex"]
5− path = deps/reflex
6− url = https://github.com/reflex-frp/reflex.git
71[submodule "deps/http-api-data"]
82 path = deps/http-api-data
93 url = https://github.com/fizruk/http-api-data
@@ -13,3 +7,6 @@
137[submodule "deps/servant-snap"]
148 path = deps/servant-snap
159 url = https://github.com/haskell-servant/servant-snap
10+[submodule "deps/reflex-platform"]
11+ path = deps/reflex-platform
12+ url = https://github.com/reflex-frp/reflex-platform
.travis.yml+2−3
@@ -35,10 +35,9 @@ install:
3535 $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;
3636 fi
3737 - travis_retry cabal update -v
38− - git clone https://github.com/reflex-frp/reflex-platform
3938 - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
40− - reflex-platform/work-on ./overrides-ghc.nix ./testserver --command "cd testserver && cabal configure -fExample && cabal build"
41− - reflex-platform/work-on ghc ./testdriver --command "cd testdriver && cabal configure && cabal build"
39+ - deps/reflex-platform/work-on ./overrides-ghc.nix ./testserver --command "cd testserver && cabal configure -fExample && cabal build"
40+ - deps/reflex-platform/work-on ghc ./testdriver --command "cd testdriver && cabal configure && cabal build"
4241 - cd testserver && dist/build/back/back -p 8000 &
4342 - sleep 3
4443 - phantomjs --webdriver=127.0.0.1:4444 &
build.sh+1−1
@@ -1,3 +1,3 @@
11#!/usr/bin/env bash
22
3−reflex-platform/work-on ./overrides.nix ./. --run "cabal configure --ghcjs && cabal build && exec/toSite.sh"
3+deps/reflex-platform/work-on ./overrides.nix ./. --run "cabal configure --ghcjs && cabal build && exec/toSite.sh"
deps/reflexdeleted−1

Submodule changed.

deps/reflex-domdeleted−1

Submodule changed.

deps/reflex-platformnew file+1

Submodule changed.

exec/Example.hs+23−20
@@ -19,10 +19,11 @@ import Servant.API
1919import API
2020import Data.Proxy
2121import Text.Read (readMaybe)
22−import Reflex.Dom
22+import Reflex.Dom hiding (run)
2323------------------------------------------------------------------------------
2424import Servant.Reflex
2525import Servant.Reflex.Multi
26+import qualified TupleExample
2627
2728
2829api :: Proxy API
@@ -30,8 +31,10 @@ api = Proxy
3031
3132main :: IO ()
3233main = mainWidget $ do
34+ el "h1" $ text "Wowzers!"
3335 divClass "example-base" run
3436 divClass "example-multi" runMulti
37+ divClass "example-tuple" TupleExample.run
3538
3639
3740runMulti :: forall t m. (SupportsServantReflex t m,
@@ -64,9 +67,9 @@ runMulti = do
6467 grts <- fmap (fmap T.words . value) $ divClass "" $ do
6568 text "Greetings"
6669 textInput def
67− gust <- fmap (value) $ divClass "gusto-input" $ checkbox False def
70+ gust <- fmap value $ divClass "gusto-input" $ checkbox False def
6871 b <- button "Go"
69− r' <- sayHi (fmap QParamSome <$> nms) (fmap (:[]) $ grts) (constDyn [True, False]) (1 <$ b)
72+ r' <- sayHi (fmap QParamSome <$> nms) (fmap (:[]) grts) (constDyn [True, False]) (1 <$ b)
7073
7174 dynText =<< holdDyn "Waiting" (T.pack . show . catMaybes . fmap reqSuccess <$> r')
7275
@@ -103,11 +106,11 @@ run = mdo
103106 unitResponse <- getUnit $ tag (current reqCount) unitBtn
104107 intResponse :: Event t (ReqResult Int Int) <- getInt $ tag (current reqCount) intBtn
105108
106− score <- foldDyn (+) 0 (fmapMaybe reqSuccess (intResponse))
109+ score <- foldDyn (+) 0 (fmapMaybe reqSuccess intResponse)
107110
108− r <- holdDyn "Waiting" $ fmap showXhrResponse $
109− leftmost [fmapMaybe response (unitResponse)
110− ,fmapMaybe response (intResponse)
111+ r <- holdDyn "Waiting" $ showXhrResponse <$>
112+ leftmost [fmapMaybe response unitResponse
113+ ,fmapMaybe response intResponse
111114 ]
112115 divClass "unit-int-response" $ el "p" $ dynText r >> el "br" (return ()) >> text "Total: " >> display score
113116 return (unitBtn, intBtn)
@@ -116,14 +119,14 @@ run = mdo
116119
117120 text "Name"
118121 el "br" $ return ()
119− inp :: Dynamic t Text <- fmap value $ divClass "name-input" $ (textInput def)
122+ inp :: Dynamic t Text <- fmap value $ divClass "name-input" $ textInput def
120123 let checkedName = fmap (\i -> bool (QParamSome i) (QParamInvalid "Need a name") (T.null i)) inp
121124 el "br" $ return ()
122125
123126 text "Greetings (space-separated)"
124127 el "br" $ return ()
125128 greetings <- fmap (fmap T.words . value) $
126− divClass "greetings-input" $ (textInput def)
129+ divClass "greetings-input" $ textInput def
127130
128131 el "br" $ return ()
129132
@@ -135,23 +138,23 @@ run = mdo
135138
136139 resp <- sayhi checkedName greetings gusto (tag (current reqCount) triggers)
137140 divClass "greeting-response" $ dynText =<<
138− holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess (resp)
139− , fmapMaybe reqFailure (resp)])
141+ holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess resp
142+ , fmapMaybe reqFailure resp])
140143 return sayHiClicks
141144
142145 dblBtn <- elClass "div" "demo-group" $ do
143146 text "A Double to double"
144147 el "br" $ return ()
145148 dblinp <- fmap value $ divClass "double-input" $ textInput def
146− (dblBtn) <- divClass "double-button" $ button "Double it"
147− dblResp <- dbl (fmap (note "read failure" . readMaybe . T.unpack) $
149+ dblBtn <- divClass "double-button" $ button "Double it"
150+ dblResp <- dbl ((note "read failure" . readMaybe . T.unpack) <$>
148151 dblinp) (tag (current reqCount) dblBtn)
149152 divClass "double-errors" $ dynText =<<
150− holdDyn "(no errors)" (fmapMaybe reqFailure (dblResp))
153+ holdDyn "(no errors)" (fmapMaybe reqFailure dblResp)
151154 el "br" (return ())
152155 divClass "double-result" $ el "p" $ dynText =<<
153− holdDyn "No number yet" (fmap tShow $
154− fmapMaybe reqSuccess (dblResp))
156+ holdDyn "No number yet" (tShow <$>
157+ fmapMaybe reqSuccess dblResp)
155158 return dblBtn
156159
157160 mpGo <- elClass "div" "demo-group" $ do
@@ -159,9 +162,9 @@ run = mdo
159162 b <- value <$> checkbox False def
160163 mpGo <- button "Test"
161164 multiResp <- multi b (tag (current reqCount) mpGo)
162− dynText =<< holdDyn "No res yet" (fmap tShow $
163− fmapMaybe reqSuccess $
164− (multiResp))
165+ dynText =<< holdDyn "No res yet" (tShow <$>
166+ fmapMaybe reqSuccess
167+ multiResp)
165168 return mpGo
166169
167170 return ()
@@ -171,7 +174,7 @@ run = mdo
171174 elClass "div" "demo-group" $ do
172175 text "JSON Unicode encoding test"
173176 txt <- value <$> textInput def
174− ev <- fmap (1 <$) $ button "Question"
177+ ev <- (1 <$) <$> button "Question"
175178 let dQ = Right . Question <$> traceDyn "will send: " txt
176179 rr <- qna dQ ev
177180 el "p" $
exec/TupleExample.hsnew file+35
@@ -0,0 +1,35 @@
1+{-# LANGUAGE RankNTypes #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE TypeOperators #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+{-# LANGUAGE TypeApplications #-}
8+
9+module TupleExample where
10+
11+import Data.Aeson
12+import Data.Proxy
13+import Data.Text
14+import GHC.TypeLits
15+import Reflex.Dom
16+import Servant.API
17+import Servant.Reflex.Tuple
18+
19+type TestAPI = Get '[JSON] Int
20+ -- :<|> Post '[JSON] Int
21+ :<|> Capture "name" Text :> Post '[JSON] Text
22+
23+
24+run :: forall t m. (SupportsServantReflex t m, MonadWidget t m) => m ()
25+run = do
26+ let (getInt :<|> greet) = client (Proxy @TestAPI) (Proxy @m) (BasePath "/")
27+ -- let getInt = client (Proxy @TestAPI) (Proxy @m) (BasePath "/")
28+ b <- button "Go for it"
29+ g <- ("greg" <$) <$> button "Hello"
30+ -- g <- button "Hello"
31+ b' <- getInt b
32+ display =<< holdDyn (Left "Waiting") b'
33+ g' <- greet g
34+ display =<< holdDyn (Left "Waiting") g'
35+
overrides-ghc.nix+1−9
@@ -1,19 +1,11 @@
11{ reflex-platform, ... }:
22let
3− dc = reflex-platform.lib.dontCheck;
43 c2n = reflex-platform.cabal2nixResult;
4+ dc = reflex-platform.lib.dontCheck;
55in reflex-platform.ghc.override {
66 overrides = self: super: {
77 servant-snap = dc (self.callPackage (c2n deps/servant-snap) {});
8− snap = dc (self.callPackage (c2n deps/servant-snap/deps/snap) {});
9− snap-server = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-server) {});
10− io-streams = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/io-streams) {});
11− io-streams-haproxy = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/io-streams-haproxy) {});
128 heist = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/heist) {});
139 xmlhtml = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/xmlhtml) {});
14− snap-core = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/snap-core) {});
15− servant = dc (self.callPackage (c2n deps/servant/servant) {});
16− Glob = dc super.Glob;
17− http-api-data = dc (self.callPackage (c2n deps/http-api-data) {});
1810 };
1911}
overrides.nix−4
@@ -1,13 +1,9 @@
11{ reflex-platform, ... }:
22let
3−
43 dc = reflex-platform.lib.dontCheck;
54 c2n = reflex-platform.cabal2nixResult;
6−
75in
86reflex-platform.ghcjs.override {
97 overrides = self: super: {
10− # servant = dc (self.callPackage (c2n deps/servant/servant) {});
11− # http-api-data = dc (self.callPackage (c2n deps/http-api-data) {});
128 };
139}
servant-reflex.cabal+5−2
@@ -1,5 +1,5 @@
11Name: servant-reflex
2−Version: 0.3
2+Version: 0.3.1
33Synopsis: Servant reflex API generator
44Description: Servant reflex API generator
55License: AllRightsReserved
@@ -19,6 +19,7 @@ library
1919 exposed-modules:
2020 Servant.Reflex
2121 Servant.Reflex.Multi
22+ Servant.Reflex.Tuple
2223
2324 other-modules:
2425 Servant.Common.BaseUrl
@@ -32,9 +33,10 @@ library
3233 containers >= 0.5.6 && < 0.6,
3334 data-default >= 0.5 && < 0.8,
3435 exceptions >= 0.8 && < 0.9,
35− ghcjs-dom >= 0.2 && < 0.3,
36+ ghcjs-dom >= 0.2 && < 0.10,
3637 http-api-data >= 0.2 && < 0.4,
3738 http-media >= 0.6 && < 0.7,
39+ jsaddle >= 0.8 && < 0.10,
3840 mtl >= 2.2.1 && < 2.3,
3941 network-uri >= 2.6 && < 2.7,
4042 reflex >= 0.5 && < 0.6,
@@ -58,4 +60,5 @@ executable example
5860 default-language: Haskell2010
5961 main-is: Example.hs
6062 other-modules: API
63+ TupleExample
6164 hs-source-dirs: exec
src/Servant/Common/BaseUrl.hs+3−2
@@ -20,18 +20,19 @@ module Servant.Common.BaseUrl (
2020) where
2121
2222import Control.Monad (join)
23−import Control.Monad.IO.Class (MonadIO)
23+-- import Control.Monad.IO.Class (MonadIO)
2424import Control.Monad.Fix (MonadFix)
2525import Data.Monoid ((<>))
2626import Data.Text (Text)
2727import qualified Data.Text as T
2828import GHC.Generics
29+import Language.Javascript.JSaddle.Monad (MonadJSM)
2930import Reflex
3031import Reflex.Dom
3132import Text.Read
3233
3334
34−type SupportsServantReflex t m = (Reflex t, TriggerEvent t m, PerformEvent t m, HasWebView (Performable m), MonadIO (Performable m))
35+type SupportsServantReflex t m = (Reflex t, TriggerEvent t m, PerformEvent t m, HasWebView (Performable m), MonadJSM (Performable m))
3536
3637-- | URI scheme to use
3738data Scheme =
src/Servant/Common/Req.hs+221−14
@@ -25,7 +25,9 @@ import Data.Text (Text)
2525import qualified Data.Text as T
2626import qualified Data.Text.Encoding as TE
2727import Data.Traversable (forM)
28+import Language.Javascript.JSaddle.Monad (JSM, MonadJSM, liftJSM)
2829import Reflex.Dom hiding (tag)
30+import Reflex.Dom.Xhr (newXMLHttpRequest)
2931import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
3032 SupportsServantReflex)
3133import Servant.API.ContentTypes (MimeUnrender(..), NoContent(..))
@@ -107,6 +109,10 @@ data QueryPart t = QueryPartParam (Dynamic t (Either Text (Maybe Text)))
107109 | QueryPartParams (Dynamic t [Text])
108110 | QueryPartFlag (Dynamic t Bool)
109111
112+data QueryPartIO = QueryPartParamIO (Maybe Text)
113+ | QueryPartParamsIO [Text]
114+ | QueryPartFlagIO Bool
115+
110116
111117-------------------------------------------------------------------------------
112118-- The data structure used to build up request information while traversing
@@ -121,23 +127,45 @@ data Req t = Req
121127 , authData :: Maybe (Dynamic t (Maybe BasicAuthData))
122128 }
123129
130+data ReqIO = ReqIO
131+ { reqMethodIO :: Text
132+ , reqPathPartsIO :: [Text]
133+ , qParamsIO :: [(Text, QueryPartIO)]
134+ , reqBodyIO :: Maybe (BL.ByteString, Text)
135+ , headersIO :: [(Text, Text)]
136+ , respHeadersIO :: XhrResponseHeaders
137+ , authDataIO :: Maybe BasicAuthData
138+ }
139+
124140defReq :: Req t
125141defReq = Req "GET" [] [] Nothing [] def Nothing
126142
143+defReqIO :: ReqIO
144+defReqIO = ReqIO "GET" [] [] Nothing [] def Nothing
145+
127146prependToPathParts :: Dynamic t (Either Text Text) -> Req t -> Req t
128147prependToPathParts p req =
129148 req { reqPathParts = p : reqPathParts req }
130149
150+
131151addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) -> Req t -> Req t
132152addHeader name val req = req { headers = (name, (fmap . fmap) (TE.decodeUtf8 . toHeader) val) : headers req }
133153
154+prependToPathPartsIO :: Text -> ReqIO -> ReqIO
155+prependToPathPartsIO p req =
156+ req { reqPathPartsIO = p : reqPathPartsIO req }
157+
158+addHeaderIO :: (ToHttpApiData a) => Text -> a -> ReqIO -> ReqIO
159+addHeaderIO name val req = req { headersIO = (name, (TE.decodeUtf8 . toHeader) val) : headersIO req }
160+
161+
134162
135163reqToReflexRequest
136164 :: forall t. Reflex t
137165 => Text
138166 -> Dynamic t BaseUrl
139167 -> Req t
140− -> (Dynamic t (Either Text (XhrRequest XhrPayload)))
168+ -> Dynamic t (Either Text (XhrRequest XhrPayload))
141169reqToReflexRequest reqMeth reqHost req =
142170 let t :: Dynamic t [Either Text Text]
143171 t = sequence $ reverse $ reqPathParts req
@@ -168,7 +196,9 @@ reqToReflexRequest reqMeth reqHost req =
168196
169197 queryPartStrings :: [Dynamic t (Maybe (Either Text Text))]
170198 queryPartStrings = map queryPartString (qParams req)
171− queryPartStrings' = fmap (sequence . catMaybes) $ sequence queryPartStrings :: Dynamic t (Either Text [Text])
199+
200+ queryPartStrings' :: Dynamic t (Either Text [Text])
201+ queryPartStrings' = sequence . catMaybes <$> sequence queryPartStrings :: Dynamic t (Either Text [Text])
172202 queryString :: Dynamic t (Either Text Text) =
173203 ffor queryPartStrings' $ \qs -> fmap (T.intercalate "&") qs
174204 xhrUrl = (liftA3 . liftA3) (\a p q -> a </> if T.null q then p else p <> "?" <> q)
@@ -182,17 +212,17 @@ reqToReflexRequest reqMeth reqHost req =
182212 xhrHeaders :: Dynamic t (Either Text [(Text, Text)])
183213 xhrHeaders = (fmap sequence . sequence . fmap f . headers) req
184214 where
185− f = \(headerName, dynam) ->
215+ f (headerName, dynam) =
186216 fmap (fmap (\rightVal -> (headerName, rightVal))) dynam
187217
188218 mkConfigBody :: Either Text [(Text,Text)]
189− -> (Either Text (BL.ByteString, Text))
219+ -> Either Text (BL.ByteString, Text)
190220 -> Either Text (XhrRequestConfig XhrPayload)
191221 mkConfigBody ehs rb = case (ehs, rb) of
192222 (_, Left e) -> Left e
193223 (Left e, _) -> Left e
194224 (Right hs, Right (bBytes, bCT)) ->
195− Right $ XhrRequestConfig
225+ Right XhrRequestConfig
196226 { _xhrRequestConfig_sendData = bytesToPayload bBytes
197227 , _xhrRequestConfig_headers =
198228 Map.insert "Content-Type" bCT (Map.fromList hs)
@@ -229,10 +259,103 @@ reqToReflexRequest reqMeth reqHost req =
229259 Nothing -> xhr
230260 Just auth -> liftA2 mkAuth auth xhr
231261
232− xhrReq = (liftA2 . liftA2) (\p opt -> XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
262+ xhrReq = (liftA2 . liftA2) (XhrRequest reqMeth) xhrUrl (addAuth xhrOpts)
233263
234264 in xhrReq
235265
266+reqToReflexRequestIO
267+ :: Text
268+ -> BaseUrl
269+ -> ReqIO
270+ -> XhrRequest XhrPayload
271+reqToReflexRequestIO reqMeth reqHost req =
272+ let t :: [Text]
273+ t = reverse $ reqPathPartsIO req
274+
275+ baseUrl :: Text
276+ baseUrl = showBaseUrl reqHost
277+
278+ urlParts :: [Text]
279+ urlParts = t
280+
281+ urlPath :: Text
282+ urlPath = T.intercalate "/" urlParts
283+
284+ queryPartString :: (Text, QueryPartIO) -> Maybe Text
285+ queryPartString (pName, qp) = case qp of
286+ QueryPartParamIO p -> case p of
287+ Just a -> Just (pName <> "=" <> a)
288+ _ -> Nothing
289+ QueryPartParamsIO ps ->
290+ if null ps
291+ then Nothing
292+ else Just (T.intercalate "&" (fmap (\p -> pName <> "=" <> p) ps))
293+ QueryPartFlagIO fl -> if fl
294+ then Just pName
295+ else Nothing
296+
297+
298+ queryPartStrings :: [Maybe Text]
299+ queryPartStrings = map queryPartString (qParamsIO req)
300+
301+ queryPartStrings' :: [Text]
302+ queryPartStrings' = catMaybes queryPartStrings
303+
304+ queryString :: Text = T.intercalate "&" queryPartStrings'
305+
306+ xhrUrl = (\a p q -> a </> if T.null q then p else p <> "?" <> q)
307+ baseUrl urlPath queryString
308+ where
309+ (</>) :: Text -> Text -> Text
310+ x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
311+ | otherwise = x <> "/" <> y
312+
313+
314+ xhrHeaders :: [(Text, Text)]
315+ xhrHeaders = headersIO req
316+
317+ mkConfigBody :: [(Text,Text)]
318+ -> (BL.ByteString, Text)
319+ -> XhrRequestConfig XhrPayload
320+ mkConfigBody hs (bBytes, bCT) =
321+ XhrRequestConfig
322+ { _xhrRequestConfig_sendData = bytesToPayload bBytes
323+ , _xhrRequestConfig_headers =
324+ Map.insert "Content-Type" bCT (Map.fromList hs)
325+ , _xhrRequestConfig_user = Nothing
326+ , _xhrRequestConfig_password = Nothing
327+ , _xhrRequestConfig_responseType = Nothing
328+ , _xhrRequestConfig_withCredentials = False
329+ , _xhrRequestConfig_responseHeaders = def
330+ }
331+
332+ xhrOpts :: XhrRequestConfig XhrPayload
333+ xhrOpts = case reqBodyIO req of
334+ Nothing -> def { _xhrRequestConfig_headers = Map.fromList xhrHeaders
335+ , _xhrRequestConfig_user = Nothing
336+ , _xhrRequestConfig_password = Nothing
337+ , _xhrRequestConfig_responseType = Nothing
338+ , _xhrRequestConfig_sendData = ""
339+ , _xhrRequestConfig_withCredentials = False
340+ }
341+ Just rBody -> mkConfigBody xhrHeaders rBody
342+
343+ mkAuth :: BasicAuthData -> XhrRequestConfig x -> XhrRequestConfig x
344+ mkAuth (BasicAuthData u p) config = config
345+ { _xhrRequestConfig_user = Just $ TE.decodeUtf8 u
346+ , _xhrRequestConfig_password = Just $ TE.decodeUtf8 p}
347+
348+ addAuth :: XhrRequestConfig x
349+ -> XhrRequestConfig x
350+ addAuth xhr = case authDataIO req of
351+ Nothing -> xhr
352+ Just auth -> mkAuth auth xhr
353+
354+ xhrReq = (XhrRequest reqMeth) xhrUrl (addAuth xhrOpts)
355+
356+ in xhrReq
357+
358+
236359-- * performing requests
237360
238361displayHttpRequest :: Text -> Text
@@ -252,9 +375,25 @@ performRequests reqMeth rs reqHost trigger = do
252375 resps <- performSomeRequestsAsync reqs
253376 return $ getCompose <$> resps
254377
378+
379+performRequestsIO :: forall t m f tag.(SupportsServantReflex t m, Traversable f)
380+ => Text
381+ -> Event t (f ReqIO)
382+ -> BaseUrl
383+ -> m (Event t (f XhrResponse))
384+performRequestsIO reqMeth rs reqHost = do
385+ -- let xhrReqs = sequence $ (\r -> reqToReflexRequest reqMeth r reqHost) <$> rs :: Dynamic t (f (Either Text (XhrRequest XhrPayload)))
386+ -- let xhrReqs = join $ (\(fxhr :: f (Req t)) -> sequence $ reqToReflexRequest reqMeth reqHost <$> fxhr) <$> rs
387+ let xhrReqs :: (Event t (f (XhrRequest XhrPayload))) = fmap (reqToReflexRequestIO reqMeth reqHost) <$> rs
388+ -- let reqs = attachPromptlyDynWith (\fxhr t -> fxhr) xhrReqs trigger
389+ resps <- performRequestsAsync xhrReqs
390+ return $ resps
391+
392+
255393-- | 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.
256394performSomeRequestsAsync
257− :: (MonadIO (Performable m),
395+ :: (MonadJSM (Performable m),
396+ MonadJSM (Performable m),
258397 HasWebView (Performable m),
259398 PerformEvent t m,
260399 TriggerEvent t m,
@@ -267,17 +406,16 @@ performSomeRequestsAsync = performSomeRequestsAsync' newXMLHttpRequest . fmap re
267406
268407------------------------------------------------------------------------------
269408-- | A modified version or Reflex.Dom.Xhr.performRequestsAsync
270−-- that accepts 'f (Either e (XhrRequestb))' events
409+-- that accepts 'f (Either e (XhrRequest))' events
271410performSomeRequestsAsync'
272− :: (MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f)
273− => (XhrRequest b -> (a -> IO ()) -> Performable m XMLHttpRequest)
411+ :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f)
412+ => (XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
274413 -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
275414performSomeRequestsAsync' newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
276415 rs <- hrs
277416 resps <- forM rs $ \r -> case r of
278− Left e -> do
279− resp <- liftIO $ newMVar (Left e)
280− return resp
417+ Left e -> liftIO $ newMVar (Left e)
418+
281419 Right r' -> do
282420 resp <- liftIO newEmptyMVar
283421 _ <- newXhr r' $ liftIO . putMVar resp . Right
@@ -290,7 +428,7 @@ performSomeRequestsAsync' newXhr req = performEventAsync $ ffor req $ \hrs cb ->
290428-- | This function actually performs the request.
291429performRequest :: forall t m tag .(SupportsServantReflex t m)
292430 => Text
293− -> (Req t)
431+ -> Req t
294432 -> Dynamic t BaseUrl
295433 -> Event t tag
296434 -> m (Event t (tag, XhrResponse), Event t (tag, Text))
@@ -306,6 +444,32 @@ performRequest reqMeth req reqHost trigger = do
306444 return (resps, badReqs)
307445
308446
447+-- -- | This function actually performs the request.
448+-- performRequestIO :: forall t m tag. (SupportsServantReflex t m)
449+-- => Text
450+-- -> ReqIO
451+-- -> BaseUrl
452+-- -> tag
453+-- -> m (Either (tag, XhrResponse) (tag, Text))
454+-- performRequestIO reqMeth req reqHost trigger = do
455+
456+-- let xhrReq = reqToReflexRequestIO reqMeth reqHost req
457+-- let reqs = (trigger ,xhrReq )
458+
459+-- -- resps <- performRequestsAsync okReqs
460+
461+-- -- let x :: _ = newXMLHttpRequestWithError
462+
463+-- -- return (resps, badReqs)
464+-- undefined
465+
466+-- doThing :: (IsXhrPayload a, MonadJSM m, HasJSContext m) => XhrRequest a -> m XhrResponse
467+-- doThing req = do
468+-- v <- liftIO newEmptyMVar
469+-- r <- newXMLHttpRequest req (liftIO . putMVar v)
470+-- liftIO $ takeMVar v
471+
472+
309473type XhrPayload = T.Text
310474bytesToPayload :: BL.ByteString -> XhrPayload
311475bytesToPayload = TE.decodeUtf8 . BL.toStrict
@@ -370,6 +534,30 @@ performRequestsCT ct reqMeth reqs reqHost trigger = do
370534 resps
371535
372536
537+performRequestsCTIO
538+ :: (SupportsServantReflex t m,
539+ MimeUnrender ct a, Traversable f)
540+ => Proxy ct
541+ -> Text
542+ -> Event t (f ReqIO)
543+ -> BaseUrl
544+ -> m (Event t (f (Either Text a)))
545+performRequestsCTIO ct reqMeth reqs reqHost = do
546+ resps <- performRequestsIO reqMeth reqs reqHost
547+ let decodeResp x = first T.pack .
548+ mimeUnrender ct .
549+ BL.fromStrict .
550+ TE.encodeUtf8 =<< note "No body text"
551+ (_xhrResponse_responseText x)
552+ return $ fmap (fmap decodeResp) resps
553+ -- return $ fmap
554+ -- (\(t,rs) -> ffor rs $ \r -> case r of
555+ -- Left e -> RequestFailure t e
556+ -- Right g -> evalResponse decodeResp (t,g)
557+ -- )
558+ -- resps
559+
560+
373561performRequestsNoBody
374562 :: (SupportsServantReflex t m,
375563 Traversable f)
@@ -405,6 +593,25 @@ evalResponse decode (tag, xhr) =
405593 else ResponseFailure tag errMsg xhr
406594 in respPayld
407595
596+-- ------------------------------------------------------------------------------
597+-- evalResponseIO
598+-- :: (XhrResponse -> Either Text a)
599+-- -> (XhrResponse)
600+-- -> ReqResult tag a
601+-- evalResponseIO decode (tag, xhr) =
602+-- let okStatus = _xhrResponse_status xhr < 400
603+-- errMsg = fromMaybe
604+-- ("Empty response with error code " <>
605+-- T.pack (show $ _xhrResponse_status xhr))
606+-- (_xhrResponse_responseText xhr)
607+-- respPayld = if okStatus
608+-- then either
609+-- (\e -> ResponseFailure tag e xhr)
610+-- (\v -> ResponseSuccess tag v xhr)
611+-- (decode xhr)
612+-- else ResponseFailure tag errMsg xhr
613+-- in respPayld
614+
408615
409616
410617
src/Servant/Reflex.hs+144−77
@@ -1,24 +1,19 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE InstanceSigs #-}
7−{-# LANGUAGE OverloadedStrings #-}
8−{-# LANGUAGE PolyKinds #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
10−{-# LANGUAGE TupleSections #-}
11−{-# LANGUAGE TypeFamilies #-}
12−{-# LANGUAGE TypeOperators #-}
13−{-# LANGUAGE UndecidableInstances #-}
14−{-# LANGUAGE MultiParamTypeClasses #-}
15−{-# LANGUAGE OverloadedStrings #-}
16−{-# LANGUAGE PolyKinds #-}
17−{-# LANGUAGE ScopedTypeVariables #-}
18−{-# LANGUAGE TupleSections #-}
19−{-# LANGUAGE TypeFamilies #-}
20−{-# LANGUAGE TypeOperators #-}
21−{-# LANGUAGE UndecidableInstances #-}
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE FunctionalDependencies #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
12+{-# LANGUAGE ScopedTypeVariables #-}
13+{-# LANGUAGE TupleSections #-}
14+{-# LANGUAGE TypeFamilies #-}
15+{-# LANGUAGE TypeOperators #-}
16+{-# LANGUAGE UndecidableInstances #-}
2217
2318-- #include "overlapping-compat.h"
2419-- | This module provides 'client' which can automatically generate
@@ -35,49 +30,47 @@ module Servant.Reflex
3530
3631------------------------------------------------------------------------------
3732import Control.Applicative
38−import Data.Monoid ((<>))
39−import qualified Data.Set as Set
40−import qualified Data.Text.Encoding as E
41−import Data.CaseInsensitive (mk)
33+import Data.CaseInsensitive (mk)
4234import 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− )
35+import qualified Data.Map as Map
36+import Data.Monoid ((<>))
37+import Data.Proxy (Proxy (..))
38+import qualified Data.Set as Set
39+import Data.Text (Text)
40+import qualified Data.Text as T
41+import qualified Data.Text.Encoding as E
42+import GHC.TypeLits
43+import Servant.API ((:<|>) (..), (:>), BasicAuth,
44+ BasicAuthData, BuildHeadersTo (..),
45+ Capture, Header, Headers (..),
46+ HttpVersion, IsSecure, MimeRender (..),
47+ MimeUnrender, NoContent, QueryFlag,
48+ QueryParam, QueryParams, Raw,
49+ ReflectMethod (..), RemoteHost,
50+ ReqBody, ToHttpApiData (..), Vault,
51+ Verb, contentType)
52+
53+import Reflex.Dom (Dynamic, Event, Reflex,
54+ XhrRequest (..), XhrResponse (..),
55+ XhrResponseHeaders (..),
56+ attachPromptlyDynWith, constDyn, ffor,
57+ fmapMaybe, leftmost,
58+ performRequestsAsync)
6459------------------------------------------------------------------------------
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)
60+import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
61+ SupportsServantReflex, baseUrlWidget,
62+ showBaseUrl)
63+import Servant.Common.Req (QParam (..), QueryPart (..), Req,
64+ ReqIO(..), ReqResult (..), addHeader, addHeaderIO,
65+ authData, defReq, evalResponse,
66+ performRequestsCT,
67+ performRequestsCTIO,
68+ performRequestsNoBody,
69+ performSomeRequestsAsync,
70+ prependToPathParts, prependToPathPartsIO, qParamToQueryPart,
71+ qParams, reqBody, reqFailure,
72+ reqMethod, reqSuccess, reqTag,
73+ respHeaders, response)
8174
8275
8376-- * Accessing APIs as a Client
@@ -97,24 +90,67 @@ import Servant.Common.Req (Req, ReqResult(..), QParam(..),
9790-- > where host = constDyn $ BaseUrl Http "localhost" 8080
9891client :: (HasClient t m layout tag)
9992 => 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
93+client p q t = clientWithRoute p q t defReq
10194
10295
10396-- | This class lets us define how each API combinator
10497-- influences the creation of an HTTP request. It's mostly
10598-- an internal class, you can just use 'client'.
106−class HasClient t m layout (tag :: *) where
99+class HasClient t m layout (tag :: *) | m -> t where
107100 type Client t m layout tag :: *
108− clientWithRoute :: Proxy layout -> Proxy m -> Proxy tag -> Req t -> Dynamic t BaseUrl -> Client t m layout tag
101+ type ClientIO m layout tag :: *
102+ type Inp m layout tag :: k
103+ clientWithRoute
104+ :: Proxy layout
105+ -> Proxy m
106+ -> Proxy tag
107+ -> Req t
108+ -> Dynamic t BaseUrl
109+ -> Client t m layout tag
110+ clientWithRouteIO
111+ :: Proxy layout
112+ -> Proxy m
113+ -> Proxy tag
114+ -> ReqIO
115+ -> BaseUrl
116+ -> ClientIO m layout tag
117+
118+data HList :: [*] -> * where
119+ HNil :: HList '[]
120+ HCons :: a -> HList xs -> HList (a ': xs)
121+
122+
123+type family ToTuple xs :: *
124+
125+type instance ToTuple (HList '[]) = ()
126+type instance ToTuple (HList (a ': '[])) = a
127+type instance ToTuple (HList (a ': b ': '[])) = (a,b)
128+type instance ToTuple (HList (a ': b ': c ': '[])) = (a,b,c)
129+type instance ToTuple (HList (a ': b ': c ': d ': '[])) = (a,b,c,d)
130+type instance ToTuple (HList (a ': b ': c ': d ': e ': '[])) = (a,b,c,d,e)
131+type instance ToTuple (HList (a ': b ': c ': d ': e ': f ': '[])) = (a,b,c,d,e,f)
132+
133+type instance ToTuple (a :> ()) = a
134+type instance ToTuple (a :> b :> ()) = (a,b)
135+type instance ToTuple (a :> b :> c :> ()) = (a,b,c)
136+type instance ToTuple (a :> b :> c :> d :> ()) = (a,b,c,d)
137+type instance ToTuple (a :> b :> c :> d :> e :> ()) = (a,b,c,d,e)
138+type instance ToTuple (a :> b :> c :> d :> e :> f :> ()) = (a,b,c,d,e,f)
109139
110140
111141instance (HasClient t m a tag, HasClient t m b tag) => HasClient t m (a :<|> b) tag where
112142 type Client t m (a :<|> b) tag = Client t m a tag :<|> Client t m b tag
143+ type Inp m (a :<|> b) tag = Inp m a tag :<|> Inp m b tag
144+ type ClientIO m (a :<|> b) tag = ClientIO m a tag :<|> ClientIO m b tag
113145
114146 clientWithRoute Proxy q pTag req baseurl =
115147 clientWithRoute (Proxy :: Proxy a) q pTag req baseurl :<|>
116148 clientWithRoute (Proxy :: Proxy b) q pTag req baseurl
117149
150+ clientWithRouteIO Proxy q pTag req baseurl =
151+ clientWithRouteIO (Proxy :: Proxy a) q pTag req baseurl :<|>
152+ clientWithRouteIO (Proxy :: Proxy b) q pTag req baseurl
153+
118154
119155-- Capture. Example:
120156-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
@@ -134,12 +170,26 @@ instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout ta
134170 type Client t m (Capture capture a :> sublayout) tag =
135171 Dynamic t (Either Text a) -> Client t m sublayout tag
136172
173+ -- type ClientIO m (Capture capture a :> sublayout) tag =
174+ -- ToTuple (Inp m (Capture capture a :> sublayout) -> ClientIO m sublayout tag
175+
176+ -- type Inp m (Capture capture a :> sublayout) tag =
177+ -- a :> Inp m sublayout tag
178+
137179 clientWithRoute Proxy q t req baseurl val =
138180 clientWithRoute (Proxy :: Proxy sublayout)
139181 q t
140182 (prependToPathParts p req)
141183 baseurl
142− where p = (fmap . fmap) (toUrlPiece) val
184+ where p = (fmap . fmap) toUrlPiece val
185+
186+ -- clientWithRouteIO Proxy q t req baseurl val =
187+ -- clientWithRouteIO (Proxy :: Proxy sublayout)
188+ -- q t
189+ -- req
190+ -- -- (prependToPathPartsIO p req)
191+ -- baseurl
192+ -- where p = toUrlPiece val
143193
144194
145195-- VERB (Returning content) --
@@ -147,16 +197,26 @@ instance {-# OVERLAPPABLE #-}
147197 -- Note [Non-Empty Content Types]
148198 (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m
149199 ) => HasClient t m (Verb method status cts' a) tag where
200+
150201 type Client t m (Verb method status cts' a) tag =
151202 Event t tag -> m (Event t (ReqResult tag a))
203+
204+ type ClientIO m (Verb method status cts' a) tag =
205+ tag -> m (ReqResult tag a)
206+
152207 -- TODO how to access input types here?
153208 -- ExceptT ServantError IO a
154209 clientWithRoute Proxy _ _ req baseurl trigs =
155210 -- performRequestCT (Proxy :: Proxy ct) method req' baseurl trigs
156− fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity $ req') baseurl trigs
211+ fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity req') baseurl trigs
157212 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
158213 req' = req { reqMethod = method }
159214
215+ -- clientWithRouteIO Proxy _ _ req baseurl tag =
216+ -- performRequestsCTIO (Proxy :: Proxy ct) method (constDyn $ Identity req') baseurl tag
217+ -- where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
218+ -- req' = req { reqMethodIO = method }
219+
160220
161221-- -- VERB (No content) --
162222instance {-# OVERLAPPING #-}
@@ -256,6 +316,13 @@ instance (KnownSymbol sym, ToHttpApiData a,
256316 baseurl
257317 where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
258318
319+ -- clientWithRouteIO Proxy q t req baseurl eVal =
320+ -- clientWithRouteIO (Proxy :: Proxy sublayout)
321+ -- q t
322+ -- (Servant.Common.Req.addHeaderIO hname eVal req)
323+ -- baseurl
324+ -- where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
325+
259326
260327
261328
@@ -267,8 +334,8 @@ instance HasClient t m sublayout tag
267334 type Client t m (HttpVersion :> sublayout) tag =
268335 Client t m sublayout tag
269336
270− clientWithRoute Proxy q t =
271− clientWithRoute (Proxy :: Proxy sublayout) q t
337+ clientWithRoute Proxy =
338+ clientWithRoute (Proxy :: Proxy sublayout)
272339
273340
274341-- | If you use a 'QueryParam' in one of your endpoints in your API,
@@ -462,9 +529,9 @@ instance (MimeRender ct a, HasClient t m sublayout tag, Reflex t)
462529instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient t m (path :> sublayout) tag where
463530 type Client t m (path :> sublayout) tag = Client t m sublayout tag
464531
465− clientWithRoute Proxy q t req baseurl =
532+ clientWithRoute Proxy q t req =
466533 clientWithRoute (Proxy :: Proxy sublayout) q t
467− (prependToPathParts (pure (Right $ T.pack p)) req) baseurl
534+ (prependToPathParts (pure (Right $ T.pack p)) req)
468535
469536 where p = symbolVal (Proxy :: Proxy path)
470537
@@ -472,23 +539,23 @@ instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient
472539instance HasClient t m api tag => HasClient t m (Vault :> api) tag where
473540 type Client t m (Vault :> api) tag = Client t m api tag
474541
475− clientWithRoute Proxy q t req baseurl =
476− clientWithRoute (Proxy :: Proxy api) q t req baseurl
542+ clientWithRoute Proxy =
543+ clientWithRoute (Proxy :: Proxy api)
477544
478545
479546instance HasClient t m api tag => HasClient t m (RemoteHost :> api) tag where
480547 type Client t m (RemoteHost :> api) tag = Client t m api tag
481548
482− clientWithRoute Proxy q t req baseurl =
483− clientWithRoute (Proxy :: Proxy api) q t req baseurl
549+ clientWithRoute Proxy =
550+ clientWithRoute (Proxy :: Proxy api)
484551
485552
486553
487554instance HasClient t m api tag => HasClient t m (IsSecure :> api) tag where
488555 type Client t m (IsSecure :> api) tag = Client t m api tag
489556
490− clientWithRoute Proxy q t req baseurl =
491− clientWithRoute (Proxy :: Proxy api) q t req baseurl
557+ clientWithRoute Proxy =
558+ clientWithRoute (Proxy :: Proxy api)
492559
493560
494561instance (HasClient t m api tag, Reflex t)
src/Servant/Reflex/Tuple.hsnew file+245
@@ -0,0 +1,245 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE FunctionalDependencies #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
12+{-# LANGUAGE ScopedTypeVariables #-}
13+{-# LANGUAGE TupleSections #-}
14+{-# LANGUAGE TypeFamilies #-}
15+{-# LANGUAGE TypeOperators #-}
16+{-# LANGUAGE UndecidableInstances #-}
17+
18+-- #include "overlapping-compat.h"
19+-- | This module provides 'client' which can automatically generate
20+-- querying functions for each endpoint just from the type representing your
21+-- API.
22+module Servant.Reflex.Tuple
23+ ( client
24+ , BuildHeaderKeysTo(..)
25+ , toHeaders
26+ , HasClient(..)
27+ , module Servant.Common.Req
28+ , module Servant.Common.BaseUrl
29+ ) where
30+
31+------------------------------------------------------------------------------
32+import Control.Applicative
33+import Data.CaseInsensitive (mk)
34+import Data.Functor.Identity
35+import qualified Data.Map as Map
36+import Data.Monoid ((<>))
37+import Data.Proxy (Proxy (..))
38+import qualified Data.Set as Set
39+import Data.Text (Text)
40+import qualified Data.Text as T
41+import qualified Data.Text.Encoding as E
42+import GHC.TypeLits (KnownSymbol, symbolVal)
43+import Servant.API ((:<|>) (..), (:>), BasicAuth,
44+ BasicAuthData, BuildHeadersTo (..),
45+ Capture, Header, Headers (..),
46+ HttpVersion, IsSecure, MimeRender (..),
47+ MimeUnrender, NoContent, QueryFlag,
48+ QueryParam, QueryParams, Raw,
49+ ReflectMethod (..), RemoteHost,
50+ ReqBody, ToHttpApiData (..), Vault,
51+ Verb, contentType)
52+
53+import Reflex.Dom (Dynamic, Event, Reflex,
54+ XhrRequest (..), XhrResponse (..),
55+ XhrResponseHeaders (..),
56+ attachPromptlyDynWith, constDyn, ffor,
57+ fmapMaybe, leftmost,
58+ performRequestsAsync)
59+------------------------------------------------------------------------------
60+import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
61+ SupportsServantReflex, baseUrlWidget,
62+ showBaseUrl)
63+import Servant.Common.Req (QParam (..), QueryPart (..), Req,
64+ ReqIO, ReqResult (..), addHeaderIO,
65+ authData, defReqIO, evalResponse,
66+ performRequestsCT,
67+ performRequestsNoBody,
68+ performSomeRequestsAsync,
69+ prependToPathPartsIO, qParamToQueryPart,
70+ qParams, reqBody, reqFailure,
71+ reqMethodIO, reqSuccess, reqTag,
72+ respHeadersIO, response)
73+
74+type family ToTuple a :: *
75+
76+type instance ToTuple () = ()
77+type instance ToTuple (a,()) = a
78+type instance ToTuple (a,(b,())) = (a,b)
79+type instance ToTuple (a,(b,(c,()))) = (a,b,c)
80+type instance ToTuple (a,(b,(c,(d,())))) = (a,b,c)
81+type instance ToTuple (a,(b,(c,(d,(e,()))))) = (a,b,c,d,e)
82+
83+client :: (HasClient layout t m, HasInp layout) => Proxy layout -> Proxy m -> BaseUrl -> Client layout t m
84+client = undefined
85+
86+class HasInp layout where
87+ type Inp layout :: *
88+ type Out layout :: *
89+ inpToReq :: Proxy layout -> Inp layout -> ReqIO
90+
91+class HasClient layout (t :: *) (m :: * -> *) where
92+ type Client layout t m :: *
93+
94+instance (HasClient a t m,
95+ HasClient b t m,
96+ HasInp a,
97+ HasInp b
98+ ) => HasClient (a :<|> b) t m where
99+ type Client (a :<|> b) t m = Client a t m :<|> Client b t m
100+
101+instance (ToHttpApiData a, HasInp sublayout) => HasInp (Capture capture a :> sublayout) where
102+ type Inp (Capture capture a :> sublayout) = (a, Inp sublayout)
103+ type Out (Capture capture a :> sublayout) = Out sublayout
104+ inpToReq _ (a, otherInp) = prependToPathPartsIO (toUrlPiece a) $ inpToReq (Proxy :: Proxy sublayout) otherInp
105+
106+instance (HasInp (Capture capture a :> sublayout)) => HasClient (Capture capture a :> sublayout) t m where
107+ type Client (Capture capture a :> sublayout) t m =
108+ Event t (ToTuple (Inp (Capture capture a :> sublayout))) ->
109+ m (Event t (Out (Capture capture a :> sublayout)))
110+
111+
112+
113+instance {-# OVERLAPPABLE #-}
114+ (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
115+ ) => HasInp (Verb method status cts' a) where
116+ type Inp (Verb method status cts' a) = ()
117+ type Out (Verb method status cts' a) = Either Text a
118+ inpToReq _ _ = defReqIO
119+
120+instance {-# OVERLAPPABLE #-}
121+ (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts)
122+ ) => HasClient (Verb method status cts' a) t m where
123+ type Client (Verb method status cts' a) t m = Event t (ToTuple (Inp (Verb method status cts' a))) ->
124+ m (Event t (Out (Verb method status cts' a)))
125+
126+
127+instance {-# OVERLAPPING #-}
128+ (ReflectMethod method) => HasInp (Verb method status cts NoContent) where
129+ type Inp (Verb method status cts NoContent) = ()
130+ type Out (Verb method status cts NoContent) = Either Text NoContent
131+
132+
133+instance {-# OVERLAPPING #-}
134+ (ReflectMethod method, SupportsServantReflex t m)
135+ => HasClient (Verb method status cts NoContent) t m where
136+ type Client (Verb method status cts NoContent) t m =
137+ Event t (ToTuple (Inp (Verb method status cts NoContent))) ->
138+ m (Event t (Out (Verb method status cts NoContent)))
139+
140+
141+instance {-# OVERLAPPABLE #-}
142+ (MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
143+ ReflectMethod method, cts' ~ (ct ': cts)
144+ ) => HasInp (Verb method status cts' (Headers ls a)) where
145+ type Inp (Verb method status cts' (Headers ls a)) = ()
146+ type Out (Verb method status cts' (Headers ls a)) = Either Text (Headers ls a)
147+ inpToReq _ _ = defReqIO { respHeadersIO =
148+ OnlyHeaders (Set.fromList
149+ (buildHeaderKeysTo
150+ (Proxy :: Proxy ls)))
151+ }
152+
153+
154+------------------------------------------------------------------------------
155+instance {-# OVERLAPPABLE #-}
156+ HasInp (Verb method status cts' (Headers ls a))
157+ => HasClient (Verb method status cts' (Headers ls a)) t m where
158+ type Client (Verb method status cts' (Headers ls a)) t m =
159+ Event t (ToTuple (Inp (Verb method status cts' (Headers ls a)))) ->
160+ m (Event t (Out (Verb method status cts' (Headers ls a))))
161+
162+
163+------------------------------------------------------------------------------
164+instance {-# OVERLAPPING #-}
165+ (BuildHeadersTo ls,
166+ BuildHeaderKeysTo ls
167+ ) => HasInp (Verb method status cts' (Headers ls NoContent)) where
168+ type Inp (Verb method status cts' (Headers ls NoContent)) = ()
169+ type Out (Verb method status cts' (Headers ls NoContent)) =
170+ Either Text (Headers ls NoContent)
171+ inpToReq _ _ = defReqIO { respHeadersIO =
172+ OnlyHeaders (Set.fromList
173+ (buildHeaderKeysTo
174+ (Proxy :: Proxy ls)))
175+ }
176+
177+
178+------------------------------------------------------------------------------
179+instance {-# OVERLAPPING #-}
180+ (BuildHeadersTo ls,
181+ BuildHeaderKeysTo ls,
182+ SupportsServantReflex t m
183+ ) => HasClient (Verb method status cts' (Headers ls NoContent)) t m where
184+ type Client (Verb method status cts' (Headers ls NoContent)) t m =
185+ Event t (ToTuple (Inp (Verb method status cts' (Headers ls NoContent)))) ->
186+ m (Event t (Out (Verb method status cts' (Headers ls NoContent))))
187+
188+
189+------------------------------------------------------------------------------
190+toHeaders
191+ :: BuildHeadersTo ls
192+ => ReqResult tag a
193+ -> ReqResult tag (Headers ls a)
194+toHeaders r =
195+ let toBS = E.encodeUtf8
196+ hdrs = maybe []
197+ (\xhr -> fmap (\(h,v) -> (mk (toBS h), toBS v))
198+ (Map.toList $ _xhrResponse_headers xhr))
199+ (response r)
200+ in ffor r $ \a -> Headers {getResponse = a
201+ ,getHeadersHList = buildHeadersTo hdrs
202+ }
203+
204+
205+------------------------------------------------------------------------------
206+class BuildHeaderKeysTo hs where
207+ buildHeaderKeysTo :: Proxy hs -> [T.Text]
208+
209+
210+------------------------------------------------------------------------------
211+instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
212+ where buildHeaderKeysTo _ = []
213+
214+
215+------------------------------------------------------------------------------
216+instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
217+ => BuildHeaderKeysTo ((Header h v) ': xs) where
218+ buildHeaderKeysTo _ =
219+ T.pack (symbolVal (Proxy :: Proxy h)) :
220+ buildHeaderKeysTo (Proxy :: Proxy xs)
221+
222+
223+------------------------------------------------------------------------------
224+instance (KnownSymbol sym,
225+ ToHttpApiData a,
226+ HasInp sublayout
227+ ) => HasInp (Header sym a :> sublayout) where
228+ type Inp (Header sym a :> sublayout) = (Maybe a, Inp sublayout)
229+ type Out (Header sym a :> sublayout) = Out sublayout
230+ inpToReq _ (h, vs) =
231+ (addHeaderIO hname h) (inpToReq (Proxy :: Proxy sublayout) vs)
232+ where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
233+
234+
235+------------------------------------------------------------------------------
236+instance (KnownSymbol sym,
237+ ToHttpApiData a,
238+ HasClient sublayout t m
239+ ) => HasClient (Header sym a :> sublayout) t m where
240+ type Client (Header sym a :> sublayout) t m =
241+ Event t (ToTuple (Inp (Header sym a :> sublayout)))
242+ -> m (Event t (Out (Header sym a :> sublayout)))
243+
244+
245+------------------------------------------------------------------------------