on master help sign in
initial checkin
commit59156f977bbd4929b2083b5bc48aa914ca5a1421 authorGreg Hale <imalsogreg@gmail.com> dateSat 17 Mar 2018, 16:13 (9 years ago) parent2996dbc
15 files changed+153−1696
default.nix+16 exec/Example.hs−219 nix/reflex-platform.json+7 nix/reflex-platform.nix+15 nix/servant-src.nix+6 nix/servant.nix overrides-ghc.nix+6 servant-reflex.cabal+9−10 servant-reflex.nix+25 shell.nix+9 src/Servant/Client/Internal/ReflexClient.hs+51 src/Servant/Common/BaseUrl.hs−99 src/Servant/Common/Req.hs−409 src/Servant/Reflex.hs+9−571 src/Servant/Reflex/Multi.hs−388
default.nixnew file+16
@@ -0,0 +1,16 @@
1+{ reflex-platform ? import ./nix/reflex-platform.nix
2+, compiler ? "ghc"
3+} :
4+let
5+
6+ ghc = (reflex-platform.${compiler}.override {
7+ overrides = self: super: {
8+ servant = self.callCabal2nix "servant" (import ./nix/servant-src.nix + "/servant") {};
9+ servant-client-core = self.callCabal2nix "servant-client-core" (import ./nix/servant-src.nix + "/servant-client-core") {};
10+ };
11+ });
12+
13+ drv = ghc.callPackage ./servant-reflex.nix {};
14+
15+in
16+ drv
exec/Example.hsdeleted−219
@@ -1,219 +0,0 @@
1−{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE FlexibleContexts #-}
3−{-# LANGUAGE GADTs #-}
4−{-# LANGUAGE OverloadedStrings #-}
5−{-# LANGUAGE RankNTypes #-}
6−{-# LANGUAGE RecursiveDo #-}
7−{-# LANGUAGE ScopedTypeVariables #-}
8−
9−module Main where
10−
11−------------------------------------------------------------------------------
12−import Data.Bool
13−import Data.Maybe
14−import Control.Monad.Fix (MonadFix)
15−import Data.Monoid (First(..), (<>))
16−import Data.Text (Text)
17−import qualified Data.Text as T
18−import qualified Data.Text.Encoding as T
19−import GHC.TypeLits
20−import Servant.API
21−import API
22−import Data.Proxy
23−import Text.Read (readMaybe)
24−import Reflex.Dom hiding (run)
25−------------------------------------------------------------------------------
26−import Servant.Reflex
27−import Servant.Reflex.Multi
28−
29−
30−api :: Proxy API
31−api = Proxy
32−
33−main :: IO ()
34−main = mainWidget $ do
35− divClass "example-base" run
36− divClass "example-multi" runMulti
37−
38−
39−runMulti :: forall t m. (SupportsServantReflex t m,
40− DomBuilder t m,
41− DomBuilderSpace m ~ GhcjsDomSpace,
42− MonadFix m,
43− PostBuild t m,
44− MonadHold t m) => m ()
45−runMulti = do
46− url <- baseUrlWidget
47− el "br" blank
48− let (_ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ :<|> _ :<|> _ ) =
49− clientA api (Proxy :: Proxy m) (Proxy :: Proxy []) (Proxy :: Proxy Int) url
50−
51− num :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
52− num2 :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
53−
54− b <- button "Run dbl multi"
55− reqCount :: Dynamic t Int <- count b
56− r <- dbl ((\x y -> [x,y]) <$> num <*> num2) (tag (current reqCount) b)
57− dynText =<< holdDyn "Waiting" (T.pack . show . fmap reqSuccess <$> r)
58−
59− lastInd <- holdDyn [] $ fmap reqTag <$> r
60− display lastInd
61−
62− divClass "demo-group" $ do
63− nms <- fmap (fmap T.words . value) $ divClass "" $ do
64− text "Names"
65− textInput def
66− grts <- fmap (fmap T.words . value) $ divClass "" $ do
67− text "Greetings"
68− textInput def
69− gust <- fmap (value) $ divClass "gusto-input" $ checkbox False def
70− b <- button "Go"
71− r' <- sayHi (fmap QParamSome <$> nms) (fmap (:[]) $ grts)
72− (constDyn [True, False]) (1 <$ b)
73−
74− dynText =<< holdDyn "Waiting" (T.pack . show . catMaybes .
75− fmap reqSuccess <$> r')
76−
77−
78− return ()
79−
80−run :: forall t m. (SupportsServantReflex t m,
81− DomBuilder t m,
82− DomBuilderSpace m ~ GhcjsDomSpace,
83− MonadFix m,
84− PostBuild t m,
85− MonadHold t m) => m ()
86−run = mdo
87−
88− reqCount <- count $ leftmost
89− [() <$ unitBtn, () <$ intBtn, () <$ sayHiClicks, () <$ dblBtn, () <$ mpGo]
90− -- Allow user to choose the url target for the request
91− -- (alternatively we could just `let url = constDyn (BasePath "/")`)
92− url <- baseUrlWidget
93− el "br" (return ())
94− dynText $ showBaseUrl <$> url
95−
96− el "br" (return ())
97−
98− -- Name the computed API client functions
99− let tweakRequest = ClientOptions $ \r -> do
100− putStrLn ("Got req: " ++ show r)
101− return $ r & withCredentials .~ True
102− let (getUnit :<|> getInt :<|> sayhi :<|> dbl
103− :<|> multi :<|> qna :<|> secret :<|> doRaw) =
104− clientWithOpts api (Proxy :: Proxy m) (Proxy :: Proxy Int) url tweakRequest
105−
106− c2 = client (Proxy :: Proxy ComprehensiveAPI) (Proxy :: Proxy m) (Proxy :: Proxy ()) url -- Just make sure this compiles for now
107−
108− (unitBtn, intBtn) <- elClass "div" "demo-group" $ do
109− unitBtn <- divClass "unit-button" $ button "Get unit"
110− intBtn <- divClass "int-button" $ button "Get int"
111−
112− unitResponse <- getUnit $ tag (current reqCount) unitBtn
113− intResponse :: Event t (ReqResult Int Int) <- getInt $ tag (current reqCount) intBtn
114−
115− score <- foldDyn (+) 0 (fmapMaybe reqSuccess (intResponse))
116−
117− r <- holdDyn "Waiting" $ fmap showXhrResponse $
118− leftmost [fmapMaybe response (unitResponse)
119− ,fmapMaybe response (intResponse)
120− ]
121− divClass "unit-int-response" $ el "p" $ dynText r >> el "br" (return ()) >> text "Total: " >> display score
122− return (unitBtn, intBtn)
123−
124− sayHiClicks <- elClass "div" "demo-group" $ do
125−
126− text "Name"
127− el "br" $ return ()
128− inp :: Dynamic t Text <- fmap value $ divClass "name-input" $ (textInput def)
129− let checkedName = fmap (\i -> bool (QParamSome i) (QParamInvalid "Need a name") (T.null i)) inp
130− el "br" $ return ()
131−
132− text "Greetings (space-separated)"
133− el "br" $ return ()
134− greetings <- fmap (fmap T.words . value) $
135− divClass "greetings-input" $ (textInput def)
136−
137− el "br" $ return ()
138−
139− gusto <- fmap value $ divClass "gusto-input" $ checkbox False def
140−
141− el "br" $ return ()
142− sayHiClicks :: Event t () <- divClass "hi-button" $ button "Say hi"
143− let triggers = leftmost [sayHiClicks, () <$ updated inp]
144−
145− resp <- sayhi checkedName greetings gusto (tag (current reqCount) triggers)
146− divClass "greeting-response" $ dynText =<<
147− holdDyn "No hi yet" (leftmost [ fmapMaybe reqSuccess (resp)
148− , fmapMaybe reqFailure (resp)])
149− return sayHiClicks
150−
151− dblBtn <- elClass "div" "demo-group" $ do
152− text "A Double to double"
153− el "br" $ return ()
154− dblinp <- fmap value $ divClass "double-input" $ textInput def
155− (dblBtn) <- divClass "double-button" $ button "Double it"
156− dblResp <- dbl (fmap (note "read failure" . readMaybe . T.unpack) $
157− dblinp) (tag (current reqCount) dblBtn)
158− divClass "double-errors" $ dynText =<<
159− holdDyn "(no errors)" (fmapMaybe reqFailure (dblResp))
160− el "br" (return ())
161− divClass "double-result" $ el "p" $ dynText =<<
162− holdDyn "No number yet" (fmap tShow $
163− fmapMaybe reqSuccess (dblResp))
164− return dblBtn
165−
166− mpGo <- elClass "div" "demo-group" $ do
167− text "Multi-part path"
168− b <- value <$> checkbox False def
169− mpGo <- button "Test"
170− multiResp <- multi b (tag (current reqCount) mpGo)
171− dynText =<< holdDyn "No res yet" (fmap tShow $
172− fmapMaybe reqSuccess $
173− (multiResp))
174− return mpGo
175−
176− return ()
177−
178− el "br" $ return ()
179−
180− elClass "div" "demo-group" $ do
181− text "JSON Unicode encoding test"
182− txt <- value <$> textInput def
183− ev <- fmap (1 <$) $ button "Question"
184− let dQ = Right . Question <$> traceDyn "will send: " txt
185− rr <- qna dQ ev
186− el "p" $
187− dynText =<< holdDyn "No Answer" (unAnswer <$> fmapMaybe reqSuccess rr)
188−
189− divClass "demo-group" $ do
190− un <- fmap value $ text "Username" >> textArea def
191− pw <- fmap value $ text "Unhidden PW" >> textArea def
192− let ba :: Dynamic t (BasicAuthData ) = BasicAuthData
193− <$> fmap T.encodeUtf8 un
194− <*> fmap T.encodeUtf8 pw
195− b <- button "Get secret"
196− r <- secret (Just <$> ba) (0 <$ b)
197− res <- holdDyn Nothing (reqSuccess <$> r)
198− display res
199−
200−
201−showXhrResponse :: XhrResponse -> Text
202−showXhrResponse (XhrResponse stat stattxt rbmay rtmay respHeaders) =
203− T.unlines ["stat: " <> tShow stat
204− ,"stattxt: " <> tShow stattxt
205− ,"resp: " <> maybe "" showRB rbmay
206− ,"rtext: " <> tShow rtmay
207− ,"rHeaders: " <> tShow respHeaders]
208−
209−tShow :: Show a => a -> Text
210−tShow = T.pack . show
211−
212−showRB :: XhrResponseBody -> Text
213−showRB (XhrResponseBody_Default t) = tShow t
214−showRB (XhrResponseBody_Text t) = tShow t
215−showRB (XhrResponseBody_Blob t) = "<Blob>"
216−showRB (XhrResponseBody_ArrayBuffer t) = tShow t
217−
218−note :: e -> Maybe a -> Either e a
219−note e = maybe (Left e) Right
nix/reflex-platform.jsonnew file+7
@@ -0,0 +1,7 @@
1+{
2+ "url": "https://github.com/reflex-frp/reflex-platform",
3+ "rev": "f003577699ad5a47f8275dad4f05cdb15c4bcdf5",
4+ "date": "2018-03-09T20:45:25-05:00",
5+ "sha256": "1fwg9cfz6p6zrlk1j5648r9hc5s2m62cwwv036sc7byb3pdhlxdr",
6+ "fetchSubmodules": true
7+}
nix/reflex-platform.nixnew file+15
@@ -0,0 +1,15 @@
1+let
2+ initialNixpkgs = import <nixpkgs> {};
3+
4+ sources = rec {
5+ reflex-platform-info-pinned = initialNixpkgs.pkgs.lib.importJSON ./reflex-platform.json;
6+ reflex-platform = initialNixpkgs.pkgs.fetchFromGitHub {
7+ owner = "reflex-frp";
8+ repo = "reflex-platform";
9+ inherit (reflex-platform-info-pinned) rev sha256;
10+ };
11+ };
12+
13+ reflex-platform = import sources.reflex-platform {};
14+in
15+ reflex-platform
nix/servant-src.nixnew file+6
@@ -0,0 +1,6 @@
1+(import <nixpkgs> {}).fetchFromGitHub {
2+ owner = "haskell-servant";
3+ repo = "servant";
4+ rev = "4824fbd961b2fcecef25bd02dae171ab17fe1cac";
5+ sha256 = "1mdv91x5i9qfh6vs3w30vl4imjxycy6zyridp4d084k5blyfrspq";
6+}
nix/servant.nixnew file

No changes to the content.

overrides-ghc.nix+6
@@ -2,10 +2,16 @@
22let
33 c2n = reflex-platform.cabal2nixResult;
44 dc = reflex-platform.lib.dontCheck;
5+ mypkgs = (import <nixpkgs> {}).haskellPackages;
56in reflex-platform.ghc.override {
67 overrides = self: super: {
78 servant-snap = dc (self.callPackage (c2n deps/servant-snap) {});
89 heist = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/heist) {});
910 xmlhtml = dc (self.callPackage (c2n deps/servant-snap/deps/snap/deps/xmlhtml) {});
11+ servant = self.callHackage "servant" "0.12" {};
12+ servant-auth = self.callHackage "servant-auth" "0.3.0.1" {};
13+ servant-client-core = self.callHackage "servant-client-core" "0.12" {};
14+ # servant = self.callCabal2nix "servant" (import ./nix/servant-src.nix + "/servant") {};
15+ # servant-client-core = self.callCabal2nix "servant-client-core" (import ./nix/servant-src.nix + "/servant-client-core") {};
1016 };
1117}
servant-reflex.cabal+9−10
@@ -1,5 +1,5 @@
11Name: servant-reflex
2−Version: 0.3.3
2+Version: 0.4
33Synopsis: Servant reflex API generator
44Description: Servant reflex API generator
55License: BSD3
@@ -18,11 +18,7 @@ Flag Example
1818library
1919 exposed-modules:
2020 Servant.Reflex
21− Servant.Reflex.Multi
22−
23− other-modules:
24− Servant.Common.BaseUrl
25− Servant.Common.Req
21+ Servant.Client.Internal.ReflexClient
2622
2723 hs-source-dirs: src
2824 build-depends:
@@ -39,13 +35,16 @@ library
3935 mtl >= 2.2.1 && < 2.3,
4036 network-uri >= 2.6 && < 2.7,
4137 reflex >= 0.5 && < 0.6,
42− reflex-dom-core == 0.4 && < 0.5,
38+ reflex-dom-core >= 0.4 && < 0.5,
4339 safe >= 0.3.9 && < 0.4,
44− servant >= 0.8 && < 0.12,
45− servant-auth >= 0.2.1 && < 0.3,
40+ semigroupoids >= 5.2.1 && < 5.3,
41+ servant >= 0.8 && < 0.13,
42+ servant-auth >= 0.2.1 && < 0.4,
43+ servant-client-core >= 0.12 && < 0.13,
4644 string-conversions >= 0.4 && < 0.5,
4745 text >= 1.2 && < 1.3,
48− transformers >= 0.4 && < 0.6
46+ transformers >= 0.4 && < 0.6,
47+ transformers-base >= 0.4 && < 0.5
4948
5049 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
5150
servant-reflex.nixnew file+25
@@ -0,0 +1,25 @@
1+{ mkDerivation, aeson, base, bytestring, case-insensitive
2+, containers, data-default, exceptions, ghcjs-dom, http-api-data
3+, http-media, jsaddle, mtl, network-uri, reflex, reflex-dom
4+, reflex-dom-core, safe, scientific, servant, servant-auth
5+, servant-client-core, stdenv, string-conversions, text
6+, transformers
7+}:
8+mkDerivation {
9+ pname = "servant-reflex";
10+ version = "0.3.3";
11+ src = ./.;
12+ isLibrary = true;
13+ isExecutable = true;
14+ libraryHaskellDepends = [
15+ base bytestring case-insensitive containers data-default exceptions
16+ ghcjs-dom http-api-data http-media jsaddle mtl network-uri reflex
17+ reflex-dom-core safe servant servant-auth servant-client-core
18+ string-conversions text transformers
19+ ];
20+ executableHaskellDepends = [
21+ aeson base reflex reflex-dom scientific servant text
22+ ];
23+ description = "Servant reflex API generator";
24+ license = stdenv.lib.licenses.bsd3;
25+}
shell.nixnew file+9
@@ -0,0 +1,9 @@
1+{ nixpkgs ? import <nixpkgs> {}
2+, compiler ? "ghc"
3+} :
4+let
5+ inherit (nixpkgs) pkgs;
6+ reflex-platform = import ./nix/reflex-platform.nix;
7+ drv = import ./. { inherit reflex-platform compiler; };
8+in
9+ if pkgs.lib.inNixShell then drv.env else drv
src/Servant/Client/Internal/ReflexClient.hsnew file+51
@@ -0,0 +1,51 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE DeriveGeneric #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8+{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE TypeFamilies #-}
11+
12+module Servant.Client.Internal.ReflexClient where
13+
14+import Control.Monad.Base (MonadBase(..))
15+import Control.Monad.Catch (MonadCatch, MonadThrow)
16+import Control.Monad.IO.Class (MonadIO (..))
17+import Control.Monad.Error.Class (MonadError(..))
18+import Control.Monad.Except (ExceptT (..), runExceptT )
19+import Control.Monad.Reader(MonadReader, ReaderT (..))
20+import Data.Functor.Alt (Alt(..))
21+-- import Control.Monad.Trans.Control
22+import GHC.Generics
23+import Servant.Client.Core
24+import Reflex
25+
26+
27+data ClientEnv = ClientEnv
28+ { baseUrl :: BaseUrl }
29+
30+newtype ClientM t a = ClientM { unClientM :: ReaderT ClientEnv (ExceptT ServantError IO) (Event t a) }
31+ deriving ( Functor, Applicative, Monad, MonadIO, Generic
32+ , MonadReader ClientEnv, MonadError ServantError, MonadThrow
33+ , MonadCatch )
34+
35+-- instance MonadBase IO ClientM where
36+-- liftBase = ClientM . liftBase
37+
38+-- instance MonadBaseControl IO ClientM where
39+-- type StM ClientM a = Either ServantError a
40+-- liftBaseWith f = ClientM (liftBaseWith (\g -> f (g . unClientM)))
41+-- restoreM st = ClientM (restoreM st)
42+
43+instance Alt (ClientM t) where
44+ a <!> b = a `catchError` \_ -> b
45+
46+instance ClientLike (ClientM t a) (ClientM t a) where
47+ mkClient = id
48+
49+runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)
50+runClientM cm env = runExceptT $ flip runReaderT env $ unClientM cm
51+
src/Servant/Common/BaseUrl.hsdeleted−99
@@ -1,99 +0,0 @@
1−{-# LANGUAGE ConstraintKinds #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE DeriveGeneric #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE GADTs #-}
6−{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE ViewPatterns #-}
8−{-# LANGUAGE RankNTypes #-}
9−{-# LANGUAGE ScopedTypeVariables #-}
10−module Servant.Common.BaseUrl (
11− -- * types
12− BaseUrl (..)
13− , Scheme (..)
14− -- * functions
15− , baseUrlWidget
16− , showBaseUrl
17−
18− -- * constraints
19− , SupportsServantReflex
20−) where
21−
22−import Control.Monad (join)
23−-- import Control.Monad.IO.Class (MonadIO)
24−import Control.Monad.Fix (MonadFix)
25−import Data.Monoid ((<>))
26−import Data.Text (Text)
27−import qualified Data.Text as T
28−import GHC.Generics
29−import Language.Javascript.JSaddle.Monad (MonadJSM)
30−import Reflex
31−import Reflex.Dom.Core
32−import Text.Read
33−
34−
35−type SupportsServantReflex t m = (Reflex t, TriggerEvent t m, PerformEvent t m, HasWebView (Performable m), MonadJSM (Performable m))
36−
37−-- | URI scheme to use
38−data Scheme =
39− Http -- ^ http://
40− | Https -- ^ https://
41− deriving (Show, Read, Eq, Ord, Generic)
42−
43−-- | Simple data type to represent the target of HTTP requests
44−-- for servant's automatically-generated clients.
45−data BaseUrl = BaseFullUrl Scheme Text Int Text
46− | BasePath Text
47− deriving (Ord, Read, Show, Generic)
48−
49−
50−instance Eq BaseUrl where
51− BasePath s == BasePath s' = s == s'
52− BaseFullUrl a b c path == BaseFullUrl a' b' c' path'
53− = a == a' && b == b' && c == c' && s path == s path'
54− where s x = if T.isPrefixOf "/" x then T.tail x else x
55− _ == _ = False
56−
57−showBaseUrl :: BaseUrl -> Text
58−showBaseUrl (BasePath s) = s
59−showBaseUrl (BaseFullUrl urlscheme host port path) =
60− schemeString <> "//" <> host <> (portString </> path)
61− where
62− a </> b = if "/" `T.isPrefixOf` b || T.null b then a <> b else a <> "/" <> b
63− schemeString = case urlscheme of
64− Http -> "http:"
65− Https -> "https:"
66− portString = case (urlscheme, port) of
67− (Http, 80) -> ""
68− (Https, 443) -> ""
69− _ -> ":" <> T.pack (show port)
70−
71−baseUrlWidget :: forall t m .(SupportsServantReflex t m,
72− DomBuilderSpace m ~ GhcjsDomSpace,
73− MonadFix m,
74− PostBuild t m,
75− MonadHold t m,
76− DomBuilder t m)
77− => m (Dynamic t BaseUrl)
78−baseUrlWidget = elClass "div" "base-url" $ do
79− urlWidget <- dropdown (0 :: Int) (constDyn $ 0 =: "BasePath" <> 1 =: "BaseUrlFull") def
80− let bUrlWidget = ffor (value urlWidget) $ \i -> case i of
81− 0 -> pathWidget
82− 1 -> fullUrlWidget
83− _ -> error "Surprising value"
84− join <$> widgetHold pathWidget (updated bUrlWidget)
85− where pathWidget :: m (Dynamic t BaseUrl)
86− pathWidget = do
87− text "Url base path"
88− t <- textInput (def {_textInputConfig_attributes =
89− constDyn ("placeholder" =: "/a/b")})
90− return $ BasePath <$> value t
91− fullUrlWidget :: m (Dynamic t BaseUrl)
92− fullUrlWidget = do
93− schm <- dropdown Https (constDyn $ Https =: "https" <> Http =: "http") def
94− srv <- textInput def {_textInputConfig_attributes = constDyn $ "placeholder" =: "example.com"}
95− text ":"
96− prt <- textInput def { _textInputConfig_attributes = constDyn $ "placeholder" =: "80"}
97− port :: Dynamic t Int <- holdDyn 80 (fmapMaybe (readMaybe . T.unpack) $ updated (value prt))
98− path <- textInput def { _textInputConfig_attributes = constDyn $ "placeholder" =: "a/b" }
99− return $ BaseFullUrl <$> value schm <*> value srv <*> port <*> value path
src/Servant/Common/Req.hsdeleted−409
@@ -1,409 +0,0 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE GADTs #-}
5−{-# LANGUAGE OverloadedStrings #-}
6−{-# LANGUAGE LambdaCase #-}
7−{-# LANGUAGE RankNTypes #-}
8−{-# LANGUAGE ScopedTypeVariables #-}
9−{-# LANGUAGE TupleSections #-}
10−
11−module Servant.Common.Req where
12−
13−-------------------------------------------------------------------------------
14−import Control.Applicative (liftA2, liftA3)
15−import Control.Concurrent
16−import Control.Monad (join)
17−import Control.Monad.IO.Class (MonadIO, liftIO)
18−import Data.Bifunctor (first)
19−import qualified Data.ByteString.Builder as Builder
20−import qualified Data.ByteString.Lazy.Char8 as BL
21−import qualified Data.Map as Map
22−import Data.Maybe (catMaybes, fromMaybe)
23−import Data.Functor.Compose
24−import Data.Monoid ((<>))
25−import Data.Proxy (Proxy(..))
26−import Data.Text (Text)
27−import qualified Data.Text as T
28−import qualified Data.Text.Encoding as TE
29−import Data.Traversable (forM)
30−import Language.Javascript.JSaddle.Monad (JSM, MonadJSM, liftJSM)
31−import qualified Network.URI as N
32−import Reflex.Dom.Core hiding (tag)
33−import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
34− SupportsServantReflex)
35−import Servant.API.ContentTypes (MimeUnrender(..), NoContent(..))
36−import Web.HttpApiData (ToHttpApiData(..))
37−-------------------------------------------------------------------------------
38−import Servant.API.BasicAuth
39−
40−
41−------------------------------------------------------------------------------
42−-- | The result of a request event
43−data ReqResult tag a
44− = ResponseSuccess tag a XhrResponse
45− -- ^ The succesfully decoded response from a request tagged with 'tag'
46− | ResponseFailure tag Text XhrResponse
47− -- ^ The failure response, which may have failed decoding or had
48− -- a non-successful response code
49− | RequestFailure tag Text
50− -- ^ A failure to construct the request tagged with 'tag' at trigger time
51− deriving (Functor)
52−
53−data ClientOptions = ClientOptions
54− { optsRequestFixup :: forall a. Show a => XhrRequest a -> JSM (XhrRequest a)
55− -- ^ Aribtrarily modify requests just before they are sent.
56− -- Warning: This escape hatch opens the possibility for your
57− -- requests to diverge from what the server expects, when the
58− -- server is also derived from a servant API
59− }
60−
61−defaultClientOptions :: ClientOptions
62−defaultClientOptions = ClientOptions { optsRequestFixup = return }
63−
64−-- withCredentials :: Lens' (XhrRequest a) Bool
65−withCredentials :: (Show a, Functor f) => (Bool -> f Bool) -> XhrRequest a -> f (XhrRequest a)
66−withCredentials inj r@(XhrRequest _ _ cfg) =
67− let cfg' = (\b -> cfg { _xhrRequestConfig_withCredentials = b}) <$>
68− inj (_xhrRequestConfig_withCredentials cfg)
69− in (\c' -> r {_xhrRequest_config = c' }) <$> cfg'
70−
71−------------------------------------------------------------------------------
72−-- | Simple filter/accessor for successful responses, when you want to
73−-- ignore the error case. For example:
74−-- >> goodResponses <- fmapMaybe reqSuccess <$> clientFun triggers
75−reqSuccess :: ReqResult tag a -> Maybe a
76−reqSuccess (ResponseSuccess _ x _) = Just x
77−reqSuccess _ = Nothing
78−
79−
80−------------------------------------------------------------------------------
81−-- | Simple filter/accessor like 'reqSuccess', but keeping the request tag
82−reqSuccess' :: ReqResult tag a -> Maybe (tag,a)
83−reqSuccess' (ResponseSuccess tag x _) = Just (tag,x)
84−reqSuccess' _ = Nothing
85−
86−
87−------------------------------------------------------------------------------
88−-- | Simple filter/accessor for any failure case
89−reqFailure :: ReqResult tag a -> Maybe Text
90−reqFailure (ResponseFailure _ s _) = Just s
91−reqFailure (RequestFailure _ s) = Just s
92−reqFailure _ = Nothing
93−
94−
95−------------------------------------------------------------------------------
96−-- | Simple filter/accessor for the raw XHR response
97−response :: ReqResult tag a -> Maybe XhrResponse
98−response (ResponseSuccess _ _ x) = Just x
99−response (ResponseFailure _ _ x) = Just x
100−response _ = Nothing
101−
102−------------------------------------------------------------------------------
103−-- | Retrieve response tag
104−reqTag :: ReqResult tag a -> tag
105−reqTag (ResponseSuccess tag _ _) = tag
106−reqTag (ResponseFailure tag _ _) = tag
107−reqTag (RequestFailure tag _ ) = tag
108−
109−-------------------------------------------------------------------------------
110−-- | You must wrap the parameter of a QueryParam endpoint with 'QParam' to
111−-- indicate whether the parameter is valid and present, validly absent, or
112−-- invalid
113−data QParam a = QParamSome a
114− -- ^ A valid query parameter
115− | QNone
116− -- ^ Indication that the parameter is intentionally absent (the request is valid)
117− | QParamInvalid Text
118− -- ^ Indication that your validation failed (the request isn't valid)
119−
120−
121−qParamToQueryPart :: ToHttpApiData a => QParam a -> Either Text (Maybe Text)
122−qParamToQueryPart (QParamSome a) = Right (Just $ toQueryParam a)
123−qParamToQueryPart QNone = Right Nothing
124−qParamToQueryPart (QParamInvalid e) = Left e
125−
126−
127−data QueryPart t = QueryPartParam (Dynamic t (Either Text (Maybe Text)))
128− | QueryPartParams (Dynamic t [Text])
129− | QueryPartFlag (Dynamic t Bool)
130−
131−
132−-------------------------------------------------------------------------------
133−-- The data structure used to build up request information while traversing
134−-- the shape of a servant API
135−data Req t = Req
136− { reqMethod :: Text
137− , reqPathParts :: [Dynamic t (Either Text Text)]
138− , qParams :: [(Text, QueryPart t)]
139− , reqBody :: Maybe (Dynamic t (Either Text (BL.ByteString, Text)))
140− , headers :: [(Text, Dynamic t (Either Text Text))]
141− , respHeaders :: XhrResponseHeaders
142− , authData :: Maybe (Dynamic t (Maybe BasicAuthData))
143− }
144−
145−defReq :: Req t
146−defReq = Req "GET" [] [] Nothing [] def Nothing
147−
148−prependToPathParts :: Dynamic t (Either Text Text) -> Req t -> Req t
149−prependToPathParts p req =
150− req { reqPathParts = p : reqPathParts req }
151−
152−addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) -> Req t -> Req t
153−addHeader name val req = req { headers = (name, (fmap . fmap) (TE.decodeUtf8 . toHeader) val) : headers req }
154−
155−
156−reqToReflexRequest
157− :: forall t. Reflex t
158− => Text
159− -> Dynamic t BaseUrl
160− -> Req t
161− -> Dynamic t (Either Text (XhrRequest XhrPayload))
162−reqToReflexRequest reqMeth reqHost req =
163− let t :: Dynamic t [Either Text Text]
164− t = sequence $ reverse $ reqPathParts req
165−
166− baseUrl :: Dynamic t (Either Text Text)
167− baseUrl = Right . showBaseUrl <$> reqHost
168−
169− urlParts :: Dynamic t (Either Text [Text])
170− urlParts = fmap sequence t
171−
172− urlPath :: Dynamic t (Either Text Text)
173− urlPath = (fmap.fmap)
174− (T.intercalate "/" . fmap escape)
175− urlParts
176−
177− queryPartString :: (Text, QueryPart t) -> Dynamic t (Maybe (Either Text Text))
178− queryPartString (pName, qp) = case qp of
179− QueryPartParam p -> ffor p $ \case
180− Left e -> Just (Left e)
181− Right (Just a) -> Just (Right $ pName <> "=" <> escape a)
182− Right Nothing -> Nothing
183− QueryPartParams ps -> ffor ps $ \pStrings ->
184− if null pStrings
185− then Nothing
186− else Just . Right
187− . T.intercalate "&"
188− $ fmap (\p -> pName <> "=" <> escape p) pStrings
189− QueryPartFlag fl -> ffor fl $ \case
190− True -> Just $ Right pName
191− False -> Nothing
192−
193−
194− queryPartStrings :: [Dynamic t (Maybe (Either Text Text))]
195− queryPartStrings = map queryPartString (qParams req)
196− queryPartStrings' = fmap (sequence . catMaybes) $ sequence queryPartStrings :: Dynamic t (Either Text [Text])
197− queryString :: Dynamic t (Either Text Text) =
198− ffor queryPartStrings' $ \qs -> fmap (T.intercalate "&") qs
199− xhrUrl = (liftA3 . liftA3) (\a p q -> a </> if T.null q then p else p <> "?" <> q)
200− baseUrl urlPath queryString
201− where
202− (</>) :: Text -> Text -> Text
203− x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
204− | otherwise = x <> "/" <> y
205−
206−
207− xhrHeaders :: Dynamic t (Either Text [(Text, Text)])
208− xhrHeaders = (fmap sequence . sequence . fmap f . headers) req
209− where
210− f = \(headerName, dynam) ->
211− fmap (fmap (\rightVal -> (headerName, rightVal))) dynam
212−
213− mkConfigBody :: Either Text [(Text,Text)]
214− -> (Either Text (BL.ByteString, Text))
215− -> Either Text (XhrRequestConfig XhrPayload)
216− mkConfigBody ehs rb = case (ehs, rb) of
217− (_, Left e) -> Left e
218− (Left e, _) -> Left e
219− (Right hs, Right (bBytes, bCT)) ->
220− Right $ XhrRequestConfig
221− { _xhrRequestConfig_sendData = bytesToPayload bBytes
222− , _xhrRequestConfig_headers =
223− Map.insert "Content-Type" bCT (Map.fromList hs)
224− , _xhrRequestConfig_user = Nothing
225− , _xhrRequestConfig_password = Nothing
226− , _xhrRequestConfig_responseType = Nothing
227− , _xhrRequestConfig_withCredentials = False
228− , _xhrRequestConfig_responseHeaders = def
229− }
230−
231− xhrOpts :: Dynamic t (Either Text (XhrRequestConfig XhrPayload))
232− xhrOpts = case reqBody req of
233− Nothing -> ffor xhrHeaders $ \case
234− Left e -> Left e
235− Right hs -> Right $ def { _xhrRequestConfig_headers = Map.fromList hs
236− , _xhrRequestConfig_user = Nothing
237− , _xhrRequestConfig_password = Nothing
238− , _xhrRequestConfig_responseType = Nothing
239− , _xhrRequestConfig_sendData = ""
240− , _xhrRequestConfig_withCredentials = False
241− }
242− Just rBody -> liftA2 mkConfigBody xhrHeaders rBody
243−
244− mkAuth :: Maybe BasicAuthData -> Either Text (XhrRequestConfig x) -> Either Text (XhrRequestConfig x)
245− mkAuth _ (Left e) = Left e
246− mkAuth Nothing r = r
247− mkAuth (Just (BasicAuthData u p)) (Right config) = Right $ config
248− { _xhrRequestConfig_user = Just $ TE.decodeUtf8 u
249− , _xhrRequestConfig_password = Just $ TE.decodeUtf8 p}
250−
251− addAuth :: Dynamic t (Either Text (XhrRequestConfig x))
252− -> Dynamic t (Either Text (XhrRequestConfig x))
253− addAuth xhr = case authData req of
254− Nothing -> xhr
255− Just auth -> liftA2 mkAuth auth xhr
256−
257− xhrReq = (liftA2 . liftA2) (\p opt -> XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
258−
259− in xhrReq
260−
261−-- * performing requests
262−
263−displayHttpRequest :: Text -> Text
264−displayHttpRequest httpmethod = "HTTP " <> httpmethod <> " request"
265−
266−-- | This function performs the request
267−performRequests :: forall t m f tag.(SupportsServantReflex t m, Traversable f)
268− => Text
269− -> Dynamic t (f (Req t))
270− -> Dynamic t BaseUrl
271− -> ClientOptions
272− -> Event t tag
273− -> m (Event t (tag, f (Either Text XhrResponse)))
274−performRequests reqMeth rs reqHost opts trigger = do
275− let xhrReqs =
276− join $ (\(fxhr :: f (Req t)) -> sequence $
277− reqToReflexRequest reqMeth reqHost <$> fxhr) <$> rs
278−
279− -- xhrReqs = fmap snd <$> xhrReqsAndDebugs
280− reqs = attachPromptlyDynWith
281− (\fxhr t -> Compose (t, fxhr)) xhrReqs trigger
282−
283− resps <- performSomeRequestsAsync opts reqs
284− return $ getCompose <$> resps
285−
286−-- | Issues a collection of requests when the supplied Event fires.
287−-- When ALL requests from a given firing complete, the results are
288−-- collected and returned via the return Event.
289−performSomeRequestsAsync
290− :: (MonadIO (Performable m),
291− MonadJSM (Performable m),
292− HasWebView (Performable m),
293− PerformEvent t m,
294− TriggerEvent t m,
295− Traversable f,
296− IsXhrPayload a,
297− Show a
298− )
299− => ClientOptions
300− -> Event t (f (Either Text (XhrRequest a)))
301− -> m (Event t (f (Either Text XhrResponse)))
302−performSomeRequestsAsync opts =
303− performSomeRequestsAsync' opts newXMLHttpRequest . fmap return
304−
305−
306−------------------------------------------------------------------------------
307−-- | A modified version or Reflex.Dom.Xhr.performRequestsAsync
308−-- that accepts 'f (Either e (XhrRequestb))' events
309−performSomeRequestsAsync'
310− :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f, Show b)
311− => ClientOptions
312− -> (XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
313− -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
314−performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
315− rs <- hrs
316− resps <- forM rs $ \r -> case r of
317− Left e -> do
318− resp <- liftIO $ newMVar (Left e)
319− return resp
320− Right r' -> do
321− resp <- liftIO newEmptyMVar
322− r'' <- liftJSM $ (optsRequestFixup opts) r'
323− _ <- newXhr r'' $ liftIO . putMVar resp . Right
324− return resp
325− _ <- liftIO $ forkIO $ cb =<< forM resps takeMVar
326− return ()
327−
328−
329−
330−type XhrPayload = T.Text
331−bytesToPayload :: BL.ByteString -> XhrPayload
332−bytesToPayload = TE.decodeUtf8 . BL.toStrict
333−
334−
335−performRequestsCT
336− :: (SupportsServantReflex t m,
337− MimeUnrender ct a, Traversable f)
338− => Proxy ct
339− -> Text
340− -> Dynamic t (f (Req t))
341− -> Dynamic t BaseUrl
342− -> ClientOptions
343− -> Event t tag
344− -> m (Event t (f (ReqResult tag a)))
345−performRequestsCT ct reqMeth reqs reqHost opts trigger = do
346− resps <- performRequests reqMeth reqs reqHost opts trigger
347− let decodeResp x = first T.pack .
348− mimeUnrender ct .
349− BL.fromStrict .
350− TE.encodeUtf8 =<< note "No body text"
351− (_xhrResponse_responseText x)
352− return $ fmap
353− (\(t,rs) -> ffor rs $ \r -> case r of
354− Left e -> RequestFailure t e
355− Right g -> evalResponse decodeResp (t,g)
356− )
357− resps
358−
359−
360−performRequestsNoBody
361− :: (SupportsServantReflex t m,
362− Traversable f)
363− => Text
364− -> Dynamic t (f (Req t))
365− -> Dynamic t BaseUrl
366− -> ClientOptions
367− -> Event t tag
368− -> m (Event t (f (ReqResult tag NoContent)))
369−performRequestsNoBody reqMeth reqs reqHost opts trigger = do
370− resps <- performRequests reqMeth reqs reqHost opts trigger
371− let decodeResp = const $ Right NoContent
372− return $ ffor resps $ \(tag,rs) -> ffor rs $ \r -> case r of
373− Left e -> RequestFailure tag e
374− Right g -> evalResponse decodeResp (tag,g)
375−
376−
377−------------------------------------------------------------------------------
378−evalResponse
379− :: (XhrResponse -> Either Text a)
380− -> (tag, XhrResponse)
381− -> ReqResult tag a
382−evalResponse decode (tag, xhr) =
383− let status = _xhrResponse_status xhr
384− okStatus = status >= 200 && status < 400
385− errMsg = fromMaybe
386− ("Empty response with error code " <>
387− T.pack (show $ _xhrResponse_status xhr))
388− (_xhrResponse_responseText xhr)
389− respPayld = if okStatus
390− then either
391− (\e -> ResponseFailure tag e xhr)
392− (\v -> ResponseSuccess tag v xhr)
393− (decode xhr)
394− else ResponseFailure tag errMsg xhr
395− in respPayld
396−
397−
398−note :: e -> Maybe a -> Either e a
399−note e = maybe (Left e) Right
400−
401−fmapL :: (e -> e') -> Either e a -> Either e' a
402−fmapL _ (Right a) = Right a
403−fmapL f (Left e) = Left (f e)
404−
405−builderToText :: Builder.Builder -> T.Text
406−builderToText = TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString
407−
408−escape :: T.Text -> T.Text
409−escape = T.pack . N.escapeURIString (not . N.isReserved) . T.unpack . TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString . toEncodedUrlPiece
src/Servant/Reflex.hs+9−571
@@ -1,573 +1,11 @@
1−{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE CPP #-}
3−{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7−{-# LANGUAGE InstanceSigs #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9−{-# LANGUAGE OverloadedStrings #-}
10−{-# LANGUAGE PolyKinds #-}
11−{-# LANGUAGE ScopedTypeVariables #-}
12−{-# LANGUAGE TupleSections #-}
13−{-# LANGUAGE TypeFamilies #-}
14−{-# LANGUAGE TypeOperators #-}
15−{-# LANGUAGE UndecidableInstances #-}
1+module Servant.Reflex (
2+ a
3+ , ClientEnv (..)
4+ , HasClient (..)
5+) where
166
17−#if MIN_VERSION_base(4,9,0)
18−{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
19−#endif
20−
21−-- #include "overlapping-compat.h"
22−-- | This module provides 'client' which can automatically generate
23−-- querying functions for each endpoint just from the type representing your
24−-- API.
25−module Servant.Reflex
26− ( client
27− , clientWithOpts
28− , clientWithRoute
29− , BuildHeaderKeysTo(..)
30− , toHeaders
31− , HasClient
32− , Client
33− , module Servant.Common.Req
34− , module Servant.Common.BaseUrl
35− ) where
36−
37−------------------------------------------------------------------------------
38−import Control.Applicative
39−import Data.Monoid ((<>))
40−import qualified Data.Set as Set
41−import qualified Data.Text.Encoding as E
42−import Data.CaseInsensitive (mk)
43−import Data.Functor.Identity
44−import Data.Proxy (Proxy (..))
45−import qualified Data.Map as Map
46−import Data.Text (Text)
47−import qualified Data.Text as T
48−import GHC.Exts (Constraint)
49−import GHC.TypeLits (KnownSymbol, symbolVal)
50−import Servant.API ((:<|>)(..),(:>), BasicAuth,
51− BasicAuthData, BuildHeadersTo(..),
52− Capture, contentType, Header,
53− Headers(..), HttpVersion, IsSecure,
54− MimeRender(..), MimeUnrender,
55− NoContent, QueryFlag, QueryParam,
56− QueryParams, Raw, ReflectMethod(..),
57− RemoteHost, ReqBody,
58− ToHttpApiData(..), Vault, Verb)
59−import qualified Servant.Auth as Auth
60−
61−import Reflex.Dom.Core (Dynamic, Event, Reflex,
62− XhrRequest(..),
63− XhrResponseHeaders(..),
64− XhrResponse(..), attachPromptlyDynWith, constDyn, ffor, fmapMaybe,
65− leftmost, performRequestsAsync,
66− )
67−------------------------------------------------------------------------------
68−import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
69− showBaseUrl,
70− SupportsServantReflex)
71−import Servant.Common.Req (ClientOptions(..),
72− defaultClientOptions,
73− Req, ReqResult(..), QParam(..),
74− QueryPart(..), addHeader, authData,
75− defReq, evalResponse, prependToPathParts,
76− -- performRequestCT,
77− performRequestsCT,
78− -- performRequestNoBody,
79− performRequestsNoBody,
80− performSomeRequestsAsync,
81− qParamToQueryPart, reqBody,
82− reqSuccess, reqFailure,
83− reqMethod, respHeaders,
84− response,
85− reqTag,
86− qParams, withCredentials)
87−
88−
89−-- * Accessing APIs as a Client
90−
91−-- | 'client' allows you to produce operations to query an API from a client.
92−--
93−-- > type MyApi = "books" :> Get '[JSON] [Book] -- GET /books
94−-- > :<|> "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book -- POST /books
95−-- >
96−-- > myApi :: Proxy MyApi
97−-- > myApi = Proxy
98−-- >
99−-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
100−-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
101−-- -> m (Event t (l, ReqResult Book)))
102−-- > (getAllBooks :<|> postNewBook) = client myApi host
103−-- > where host = constDyn $ BaseUrl Http "localhost" 8080
104−client
105− :: (HasClient t m layout tag)
106− => Proxy layout
107− -> Proxy m
108− -> Proxy tag
109− -> Dynamic t BaseUrl
110− -> Client t m layout tag
111−client p q t baseurl = clientWithRoute p q t defReq baseurl defaultClientOptions
112−
113−clientWithOpts
114− :: (HasClient t m layout tag)
115− => Proxy layout
116− -> Proxy m
117− -> Proxy tag
118− -> Dynamic t BaseUrl
119− -> ClientOptions
120− -> Client t m layout tag
121−clientWithOpts p q t baseurl = clientWithRoute p q t defReq baseurl
122−
123−
124−-- | This class lets us define how each API combinator
125−-- influences the creation of an HTTP request. It's mostly
126−-- an internal class, you can just use 'client'.
127−class HasClient t m layout (tag :: *) where
128− type Client t m layout tag :: *
129− clientWithRoute :: Proxy layout -> Proxy m -> Proxy tag -> Req t -> Dynamic t BaseUrl -> ClientOptions -> Client t m layout tag
130−
131−
132−instance (HasClient t m a tag, HasClient t m b tag) => HasClient t m (a :<|> b) tag where
133− type Client t m (a :<|> b) tag = Client t m a tag :<|> Client t m b tag
134−
135− clientWithRoute Proxy q pTag req baseurl opts =
136− clientWithRoute (Proxy :: Proxy a) q pTag req baseurl opts :<|>
137− clientWithRoute (Proxy :: Proxy b) q pTag req baseurl opts
138−
139−
140−-- Capture. Example:
141−-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
142−-- >
143−-- > myApi :: Proxy MyApi = Proxy
144−-- >
145−-- > getBook :: SupportsServantReflex t m
146−-- => Dynamic t BaseUrl
147−-- -> Dynamic t (Maybe Text)
148−-- -> Event t l
149−-- -> m (Event t (l, ReqResult Book))
150−-- > getBook = client myApi (constDyn host)
151−
152−instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout tag)
153− => HasClient t m (Capture capture a :> sublayout) tag where
154−
155− type Client t m (Capture capture a :> sublayout) tag =
156− Dynamic t (Either Text a) -> Client t m sublayout tag
157−
158− clientWithRoute Proxy q t req baseurl opts val =
159− clientWithRoute (Proxy :: Proxy sublayout)
160− q t
161− (prependToPathParts p req)
162− baseurl opts
163− where p = (fmap . fmap) (toUrlPiece) val
164−
165−
166−-- VERB (Returning content) --
167−instance {-# OVERLAPPABLE #-}
168− -- Note [Non-Empty Content Types]
169− (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m
170− ) => HasClient t m (Verb method status cts' a) tag where
171− type Client t m (Verb method status cts' a) tag =
172− Event t tag -> m (Event t (ReqResult tag a))
173− -- TODO how to access input types here?
174− -- ExceptT ServantError IO a
175− clientWithRoute Proxy _ _ req baseurl opts trigs =
176− fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity $ req') baseurl opts trigs
177− where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
178− req' = req { reqMethod = method }
179−
180−
181−-- -- VERB (No content) --
182−instance {-# OVERLAPPING #-}
183− (ReflectMethod method, SupportsServantReflex t m) =>
184− HasClient t m (Verb method status cts NoContent) tag where
185− type Client t m (Verb method status cts NoContent) tag =
186− Event t tag -> m (Event t (ReqResult tag NoContent))
187− -- TODO: how to access input types here?
188− -- ExceptT ServantError IO NoContent
189− clientWithRoute Proxy _ _ req baseurl opts =
190− (fmap . fmap) runIdentity . performRequestsNoBody method (constDyn $ Identity req) baseurl opts
191− where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
192−
193−
194−toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
195−toHeaders r =
196− let toBS = E.encodeUtf8
197− hdrs = maybe []
198− (\xhr -> fmap (\(h,v) -> (mk (toBS h), toBS v))
199− (Map.toList $ _xhrResponse_headers xhr))
200− (response r)
201− in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
202−
203−class BuildHeaderKeysTo hs where
204− buildHeaderKeysTo :: Proxy hs -> [T.Text]
205−
206−instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
207− where buildHeaderKeysTo _ = []
208−
209−instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
210− => BuildHeaderKeysTo ((Header h v) ': xs) where
211− buildHeaderKeysTo _ = T.pack (symbolVal (Proxy :: Proxy h)) : buildHeaderKeysTo (Proxy :: Proxy xs)
212−
213−-- HEADERS Verb (Content) --
214−-- Headers combinator not treated in fully general case,
215−-- in order to deny instances for (Headers ls (Capture "id" Int)),
216−-- a combinator that wouldn't make sense
217−-- TODO Overlapping??
218−instance {-# OVERLAPPABLE #-}
219− -- Note [Non-Empty Content Types]
220− ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
221− ReflectMethod method, cts' ~ (ct ': cts),
222− SupportsServantReflex t m
223− ) => HasClient t m (Verb method status cts' (Headers ls a)) tag where
224− type Client t m (Verb method status cts' (Headers ls a)) tag =
225− Event t tag -> m (Event t (ReqResult tag (Headers ls a)))
226− clientWithRoute Proxy _ _ req baseurl opts trigs = do
227− let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
228− resp <- fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity req') baseurl opts trigs
229− return $ toHeaders <$> resp
230− where req' = req { respHeaders =
231− OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
232− }
233−
234−
235−-- HEADERS Verb (No content) --
236−instance {-# OVERLAPPABLE #-}
237− ( BuildHeadersTo ls, BuildHeaderKeysTo ls, ReflectMethod method,
238− SupportsServantReflex t m
239− ) => HasClient t m (Verb method status cts (Headers ls NoContent)) tag where
240− type Client t m (Verb method status cts (Headers ls NoContent)) tag
241− = Event t tag -> m (Event t (ReqResult tag (Headers ls NoContent)))
242− clientWithRoute Proxy _ _ req baseurl opts trigs = do
243− let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
244− resp <- fmap runIdentity <$> performRequestsNoBody method (constDyn $ Identity req') baseurl opts trigs
245− return $ toHeaders <$> resp
246− where req' = req {respHeaders =
247− OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
248− }
249−
250−
251−
252−-- HEADER
253−-- > newtype Referer = Referer { referrer :: Text }
254−-- > deriving (Eq, Show, Generic, FromText, ToHttpApiData)
255−-- >
256−-- > -- GET /view-my-referer
257−-- > type MyApi = "view-my-referer" :> Header "Referer" Referer :> Get '[JSON] Referer
258−-- >
259−-- >
260−-- > viewReferer :: Maybe Referer -> ExceptT String IO Book
261−-- > viewReferer = client myApi host
262−-- > where host = BaseUrl Http "localhost" 8080
263−-- > -- then you can just use "viewRefer" to query that endpoint
264−-- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments
265−instance (KnownSymbol sym, ToHttpApiData a,
266− HasClient t m sublayout tag, SupportsServantReflex t m)
267− => HasClient t m (Header sym a :> sublayout) tag where
268−
269− type Client t m (Header sym a :> sublayout) tag =
270− Dynamic t (Either Text a) -> Client t m sublayout tag
271−
272− clientWithRoute Proxy q t req baseurl opts eVal =
273− clientWithRoute (Proxy :: Proxy sublayout)
274− q t
275− (Servant.Common.Req.addHeader hname eVal req)
276− baseurl opts
277− where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
278−
279−
280−
281−
282−-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
283−-- functions.
284−instance HasClient t m sublayout tag
285− => HasClient t m (HttpVersion :> sublayout) tag where
286−
287− type Client t m (HttpVersion :> sublayout) tag =
288− Client t m sublayout tag
289−
290− clientWithRoute Proxy q t =
291− clientWithRoute (Proxy :: Proxy sublayout) q t
292−
293−
294−-- | If you use a 'QueryParam' in one of your endpoints in your API,
295−-- the corresponding querying function will automatically take
296−-- an additional argument of the type specified by your 'QueryParam',
297−-- enclosed in Maybe.
298−--
299−-- If you give Nothing, nothing will be added to the query string.
300−--
301−-- If you give a non-'Nothing' value, this function will take care
302−-- of inserting a textual representation of this value in the query string.
303−--
304−-- You can control how values for your type are turned into
305−-- text by specifying a 'ToHttpApiData' instance for your type.
306−--
307−-- Example:
308−--
309−-- > type MyApi = "books" :> QueryParam "author" Text :> Get '[JSON] [Book]
310−-- >
311−-- > myApi :: Proxy MyApi
312−-- > myApi = Proxy
313−-- >
314−-- > getBooksBy :: Maybe Text -> ExceptT String IO [Book]
315−-- > getBooksBy = client myApi host
316−-- > where host = BaseUrl Http "localhost" 8080
317−-- > -- then you can just use "getBooksBy" to query that endpoint.
318−-- > -- 'getBooksBy Nothing' for all books
319−-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
320−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
321− => HasClient t m (QueryParam sym a :> sublayout) tag where
322−
323− type Client t m (QueryParam sym a :> sublayout) tag =
324− Dynamic t (QParam a) -> Client t m sublayout tag
325−
326− -- if mparam = Nothing, we don't add it to the query string
327− clientWithRoute Proxy q t req baseurl opts mparam =
328− clientWithRoute (Proxy :: Proxy sublayout) q t
329− (req {qParams = paramPair : qParams req}) baseurl opts
330−
331− where pname = symbolVal (Proxy :: Proxy sym)
332− --p prm = QueryPartParam $ (fmap . fmap) (toQueryParam) prm
333− --paramPair = (T.pack pname, p mparam)
334− p prm = QueryPartParam $ fmap qParamToQueryPart prm -- (fmap . fmap) (unpack . toQueryParam) prm
335− paramPair = (T.pack pname, p mparam)
336−
337−
338−
339−-- | If you use a 'QueryParams' in one of your endpoints in your API,
340−-- the corresponding querying function will automatically take
341−-- an additional argument, a list of values of the type specified
342−-- by your 'QueryParams'.
343−--
344−-- If you give an empty list, nothing will be added to the query string.
345−--
346−-- Otherwise, this function will take care
347−-- of inserting a textual representation of your values in the query string,
348−-- under the same query string parameter name.
349−--
350−-- You can control how values for your type are turned into
351−-- text by specifying a 'ToHttpApiData' instance for your type.
352−--
353−-- Example:
354−--
355−-- > type MyApi = "books" :> QueryParams "authors" Text :> Get '[JSON] [Book]
356−-- >
357−-- > myApi :: Proxy MyApi
358−-- > myApi = Proxy
359−-- >
360−-- > getBooksBy :: [Text] -> ExceptT String IO [Book]
361−-- > getBooksBy = client myApi host
362−-- > where host = BaseUrl Http "localhost" 8080
363−-- > -- then you can just use "getBooksBy" to query that endpoint.
364−-- > -- 'getBooksBy []' for all books
365−-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
366−-- > -- to get all books by Asimov and Heinlein
367−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
368− => HasClient t m (QueryParams sym a :> sublayout) tag where
369−
370− type Client t m (QueryParams sym a :> sublayout) tag =
371− Dynamic t [a] -> Client t m sublayout tag
372−
373− clientWithRoute Proxy q t req baseurl opts paramlist =
374− clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl opts
375−
376− where req' = req { qParams = (T.pack pname, params') : qParams req }
377− pname = symbolVal (Proxy :: Proxy sym)
378− params' = QueryPartParams $ (fmap . fmap) toQueryParam
379− paramlist
380−
381−
382−
383−-- | If you use a 'QueryFlag' in one of your endpoints in your API,
384−-- the corresponding querying function will automatically take
385−-- an additional 'Bool' argument.
386−--
387−-- If you give 'False', nothing will be added to the query string.
388−--
389−-- Otherwise, this function will insert a value-less query string
390−-- parameter under the name associated to your 'QueryFlag'.
391−--
392−-- Example:
393−--
394−-- > type MyApi = "books" :> QueryFlag "published" :> Get '[JSON] [Book]
395−-- >
396−-- > myApi :: Proxy MyApi
397−-- > myApi = Proxy
398−-- >
399−-- > getBooks :: Bool -> ExceptT String IO [Book]
400−-- > getBooks = client myApi host
401−-- > where host = BaseUrl Http "localhost" 8080
402−-- > -- then you can just use "getBooks" to query that endpoint.
403−-- > -- 'getBooksBy False' for all books
404−-- > -- 'getBooksBy True' to only get _already published_ books
405−
406−-- TODO Bring back
407−instance (KnownSymbol sym, HasClient t m sublayout tag, Reflex t)
408− => HasClient t m (QueryFlag sym :> sublayout) tag where
409−
410− type Client t m (QueryFlag sym :> sublayout) tag =
411− Dynamic t Bool -> Client t m sublayout tag
412−
413− clientWithRoute Proxy q t req baseurl opts flag =
414− clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl opts
415−
416− where req' = req { qParams = thisPair : qParams req }
417− thisPair = (T.pack pName, QueryPartFlag flag) :: (Text, QueryPart t)
418− pName = symbolVal (Proxy :: Proxy sym)
419−
420−
421−
422−-- | Send a raw 'XhrRequest ()' directly to 'baseurl'
423−instance SupportsServantReflex t m => HasClient t m Raw tag where
424− type Client t m Raw tag = Dynamic t (Either Text (XhrRequest ()))
425− -> Event t tag
426− -> m (Event t (ReqResult tag ()))
427−
428− clientWithRoute _ _ _ _ baseurl _ xhrs triggers = do
429−
430− let xhrs' = liftA2 (\x path -> case x of
431− Left e -> Left e
432− Right jx -> Right $ jx { _xhrRequest_url = path <> _xhrRequest_url jx }
433− ) xhrs (showBaseUrl <$> baseurl)
434− xhrs'' = attachPromptlyDynWith (flip (,)) xhrs' triggers :: Event t (tag, Either Text (XhrRequest ()))
435− badReq = fmapMaybe (\(t,x) -> either (Just . (t,)) (const Nothing) x) xhrs'' :: Event t (tag, Text)
436− okReq = fmapMaybe (\(t,x) -> either (const Nothing) (Just . (t,)) x) xhrs'' :: Event t (tag, XhrRequest ())
437−
438− resps <- performRequestsAsync okReq
439− return $ leftmost [ uncurry RequestFailure <$> badReq
440− , evalResponse (const $ Right ()) <$> resps
441− ]
442−
443−
444−-- | If you use a 'ReqBody' in one of your endpoints in your API,
445−-- the corresponding querying function will automatically take
446−-- an additional argument of the type specified by your 'ReqBody'.
447−-- That function will take care of encoding this argument as JSON and
448−-- of using it as the request body.
449−--
450−-- All you need is for your type to have a 'ToJSON' instance.
451−--
452−-- Example:
453−--
454−-- > type MyApi = "books" :> ReqBody '[JSON] Book :> Post '[JSON] Book
455−-- >
456−-- > myApi :: Proxy MyApi
457−-- > myApi = Proxy
458−-- >
459−-- > addBook :: Book -> ExceptT String IO Book
460−-- > addBook = client myApi host
461−-- > where host = BaseUrl Http "localhost" 8080
462−-- > -- then you can just use "addBook" to query that endpoint
463−
464−instance (MimeRender ct a, HasClient t m sublayout tag, Reflex t)
465− => HasClient t m (ReqBody (ct ': cts) a :> sublayout) tag where
466−
467− type Client t m (ReqBody (ct ': cts) a :> sublayout) tag =
468− Dynamic t (Either Text a) -> Client t m sublayout tag
469−
470− clientWithRoute Proxy q t req baseurl opts body =
471− clientWithRoute (Proxy :: Proxy sublayout) q t req' baseurl opts
472− where req' = req { reqBody = bodyBytesCT }
473− ctProxy = Proxy :: Proxy ct
474− ctString = T.pack $ show $ contentType ctProxy
475− bodyBytesCT = Just $ (fmap . fmap)
476− (\b -> (mimeRender ctProxy b, ctString))
477− body
478−
479−
480−
481−-- | Make the querying function append @path@ to the request path.
482−instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient t m (path :> sublayout) tag where
483− type Client t m (path :> sublayout) tag = Client t m sublayout tag
484−
485− clientWithRoute Proxy q t req baseurl opts =
486− clientWithRoute (Proxy :: Proxy sublayout) q t
487− (prependToPathParts (pure (Right $ T.pack p)) req) baseurl opts
488−
489− where p = symbolVal (Proxy :: Proxy path)
490−
491−
492−instance HasClient t m api tag => HasClient t m (Vault :> api) tag where
493− type Client t m (Vault :> api) tag = Client t m api tag
494−
495− clientWithRoute Proxy q t req baseurl =
496− clientWithRoute (Proxy :: Proxy api) q t req baseurl
497−
498−
499−instance HasClient t m api tag => HasClient t m (RemoteHost :> api) tag where
500− type Client t m (RemoteHost :> api) tag = Client t m api tag
501−
502− clientWithRoute Proxy q t req baseurl =
503− clientWithRoute (Proxy :: Proxy api) q t req baseurl
504−
505−
506−
507−instance HasClient t m api tag => HasClient t m (IsSecure :> api) tag where
508− type Client t m (IsSecure :> api) tag = Client t m api tag
509−
510− clientWithRoute Proxy q t req baseurl =
511− clientWithRoute (Proxy :: Proxy api) q t req baseurl
512−
513−
514−instance (HasClient t m api tag, Reflex t)
515− => HasClient t m (BasicAuth realm usr :> api) tag where
516−
517− type Client t m (BasicAuth realm usr :> api) tag = Dynamic t (Maybe BasicAuthData)
518− -> Client t m api tag
519−
520− clientWithRoute Proxy q t req baseurl opts authdata =
521− clientWithRoute (Proxy :: Proxy api) q t req' baseurl opts
522− where
523− req' = req { authData = Just authdata }
524−
525−-- instance HasClient t m subapi =>
526−-- HasClient t m (WithNamedConfig name config subapi) where
527−
528−-- type Client t m (WithNamedConfig name config subapi) = Client t m subapi
529−-- clientWithRoute Proxy q = clientWithRoute (Proxy :: Proxy subapi) q
530−
531−
532−{- Note [Non-Empty Content Types]
533−~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
534−Rather than have
535−
536− instance (..., cts' ~ (ct ': cts)) => ... cts' ...
537−
538−It may seem to make more sense to have:
539−
540− instance (...) => ... (ct ': cts) ...
541−
542−But this means that if another instance exists that does *not* require
543−non-empty lists, but is otherwise more specific, no instance will be overall
544−more specific. This in turn generally means adding yet another instance (one
545−for empty and one for non-empty lists).
546−-}
547−
548−
549−-- SUPPORT FOR servant-auth --
550−
551−-- For JavaScript clients we should be sending/storing JSON web tokens in a
552−-- way that is inaccessible to JavaScript.
553−--
554−-- For @servant-auth@ this is done with HTTP-only cookies. In a Reflex-DOM
555−-- app this means the @servant-auth@ client should only verify that the API
556−-- supports Cookie-based authentication but do nothing with the token
557−-- directly.
558−
559−-- @HasCookieAuth auths@ is nominally a redundant constraint, but ensures
560−-- we're not trying to rely on cookies when the API does not use them.
561−instance (HasCookieAuth auths, HasClient t m api tag) => HasClient t m (Auth.Auth auths a :> api) tag where
562−
563− type Client t m (Auth.Auth auths a :> api) tag = Client t m api tag
564− clientWithRoute Proxy = clientWithRoute (Proxy :: Proxy api)
565−
566−
567−type family HasCookieAuth xs :: Constraint where
568− HasCookieAuth (Auth.Cookie ': xs) = ()
569− HasCookieAuth (x ': xs) = HasCookieAuth xs
570− HasCookieAuth '[] = CookieAuthNotEnabled
571−
572−class CookieAuthNotEnabled
7+import Servant.Client.Core.Reexport
8+import Servant.Client.Internal.ReflexClient
5739
10+a :: Int
11+a = 3
src/Servant/Reflex/Multi.hsdeleted−388
@@ -1,388 +0,0 @@
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− , clientWithOptsA
20− , BaseUrl(..)
21− , Scheme(..)
22−
23− -- * Build QueryParam arguments
24− , QParam(..)
25−
26− -- * Access response data
27− , withCredentials
28−
29− -- * Access response data
30− , ReqResult(..)
31− , reqSuccess
32− , reqSuccess'
33− , reqFailure
34− , response
35−
36− , HasClientMulti(..)
37− ) where
38−
39−------------------------------------------------------------------------------
40−import Control.Applicative (liftA2)
41−import Data.Functor.Compose (Compose (..), getCompose)
42−import Data.Proxy (Proxy (..))
43−import qualified Data.Set as Set
44−import Data.Text (Text)
45−import qualified Data.Text as T
46−import qualified Data.Text.Encoding as E
47−import GHC.TypeLits (KnownSymbol, symbolVal)
48−import Servant.API ((:<|>) (..), (:>), BasicAuth,
49− BasicAuthData, BuildHeadersTo (..),
50− Capture, Header, Headers (..),
51− HttpVersion, IsSecure, MimeRender (..),
52− MimeUnrender, NoContent, QueryFlag,
53− QueryParam, QueryParams, Raw,
54− ReflectMethod (..), RemoteHost,
55− ReqBody, ToHttpApiData (..), Vault,
56− Verb, contentType)
57−
58−import Reflex.Dom.Core (Dynamic, Event, Reflex,
59− XhrRequest (..),
60− XhrResponseHeaders (..),
61− attachPromptlyDynWith, constDyn)
62−------------------------------------------------------------------------------
63−import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
64− SupportsServantReflex)
65−import Servant.Common.Req (ClientOptions,
66− QParam (..), QueryPart (..), Req,
67− ReqResult (..), addHeader, authData,
68− defReq,
69− defaultClientOptions,
70− performRequestsCT,
71− performRequestsNoBody,
72− performSomeRequestsAsync,
73− prependToPathParts, qParamToQueryPart,
74− qParams, reqBody, reqFailure,
75− reqMethod, reqSuccess, reqSuccess',
76− respHeaders, response, withCredentials)
77−import Servant.Reflex (BuildHeaderKeysTo (..), toHeaders)
78−
79−
80−------------------------------------------------------------------------------
81−clientA :: (HasClientMulti t m layout f tag, Applicative f, Reflex t)
82− => Proxy layout -> Proxy m -> Proxy f -> Proxy tag
83− -> Dynamic t BaseUrl -> ClientMulti t m layout f tag
84−clientA p q f tag baseurl =
85− clientWithRouteMulti p q f tag (constDyn (pure defReq)) baseurl
86− defaultClientOptions
87−
88−
89−-- | A version of @client@ that sets the withCredentials flag
90−-- on requests. Use this function for clients of CORS API's
91−clientWithOptsA :: (HasClientMulti t m layout f tag, Applicative f, Reflex t)
92− => Proxy layout -> Proxy m -> Proxy f -> Proxy tag
93− -> Dynamic t BaseUrl -> ClientOptions -> ClientMulti t m layout f tag
94−clientWithOptsA p q f tag baseurl opts =
95− clientWithRouteMulti p q f tag
96− (constDyn (pure defReq)) baseurl opts
97−
98−------------------------------------------------------------------------------
99−class HasClientMulti t m layout f (tag :: *) where
100− type ClientMulti t m layout f tag :: *
101− clientWithRouteMulti :: Proxy layout -> Proxy m -> Proxy f -> Proxy tag
102− -> Dynamic t (f (Req t)) -> Dynamic t BaseUrl
103− -> ClientOptions -> ClientMulti t m layout f tag
104−
105−
106−------------------------------------------------------------------------------
107−instance (HasClientMulti t m a f tag, HasClientMulti t m b f tag) =>
108− HasClientMulti t m (a :<|> b) f tag where
109− type ClientMulti t m (a :<|> b) f tag = ClientMulti t m a f tag :<|>
110− ClientMulti t m b f tag
111− clientWithRouteMulti Proxy q f tag reqs baseurl opts =
112− clientWithRouteMulti (Proxy :: Proxy a) q f tag reqs baseurl opts :<|>
113− clientWithRouteMulti (Proxy :: Proxy b) q f tag reqs baseurl opts
114−
115−
116−------------------------------------------------------------------------------
117−instance (SupportsServantReflex t m,
118− ToHttpApiData a,
119− HasClientMulti t m sublayout f tag,
120− Applicative f)
121− => HasClientMulti t m (Capture capture a :> sublayout) f tag where
122−
123− type ClientMulti t m (Capture capture a :> sublayout) f tag =
124− f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f tag
125−
126− clientWithRouteMulti _ q f tag reqs baseurl opts vals =
127− clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl opts
128− where
129− reqs' = (prependToPathParts <$> ps <*>) <$> reqs
130− ps = (fmap . fmap . fmap) toUrlPiece vals
131−
132−
133−------------------------------------------------------------------------------
134−-- VERB (Returning content) --
135−instance {-# OVERLAPPABLE #-}
136− -- Note [Non-Empty Content Types]
137− (MimeUnrender ct a,
138− ReflectMethod method, cts' ~ (ct ': cts),
139− SupportsServantReflex t m,
140− Applicative f,
141− Traversable f
142− ) => HasClientMulti t m (Verb method status cts' a) f tag where
143−
144− type ClientMulti t m (Verb method status cts' a) f tag =
145− Event t tag -> m (Event t (f (ReqResult tag a)))
146−
147− clientWithRouteMulti _ _ _ _ reqs baseurl opts =
148− performRequestsCT (Proxy :: Proxy ct) method reqs' baseurl opts
149− where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
150− reqs' = fmap (\r -> r { reqMethod = method }) <$> reqs
151−
152−
153−------------------------------------------------------------------------------
154−-- -- VERB (No content) --
155−instance {-# OVERLAPPING #-}
156− (ReflectMethod method, SupportsServantReflex t m, Traversable f) =>
157− HasClientMulti t m (Verb method status cts NoContent) f tag where
158− type ClientMulti t m (Verb method status cts NoContent) f tag =
159− Event t tag -> m (Event t (f (ReqResult tag NoContent)))
160− -- TODO: how to access input types here?
161− -- ExceptT ServantError IO NoContent
162− clientWithRouteMulti Proxy _ _ _ req baseurl opts =
163− performRequestsNoBody method req baseurl opts
164− where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
165−
166−
167−------------------------------------------------------------------------------
168−instance {-# OVERLAPPABLE #-}
169− -- Note [Non-Empty Content Types]
170− ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
171− ReflectMethod method, cts' ~ (ct ': cts),
172− SupportsServantReflex t m,
173− Traversable f
174− ) => HasClientMulti t m (Verb method status cts' (Headers ls a)) f tag where
175− type ClientMulti t m (Verb method status cts' (Headers ls a)) f tag =
176− Event t tag -> m (Event t (f (ReqResult tag (Headers ls a))))
177− clientWithRouteMulti Proxy _ _ _ reqs baseurl opts triggers = do
178− let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
179− resp <- performRequestsCT (Proxy :: Proxy ct) method reqs' baseurl opts triggers :: m (Event t (f (ReqResult tag a)))
180− return $ fmap toHeaders <$> resp
181− where
182− reqs' = fmap (\r ->
183− r { respHeaders =
184− OnlyHeaders (Set.fromList
185− (buildHeaderKeysTo (Proxy :: Proxy ls)))
186− }) <$> reqs
187−
188−
189−------------------------------------------------------------------------------
190−instance {-# OVERLAPPABLE #-}
191− ( BuildHeadersTo ls,
192− BuildHeaderKeysTo ls,
193− ReflectMethod method,
194− SupportsServantReflex t m,
195− Traversable f
196− ) => HasClientMulti t m (Verb method status
197− cts (Headers ls NoContent)) f tag where
198− type ClientMulti t m (Verb method status cts (Headers ls NoContent)) f tag
199− = Event t tag -> m (Event t (f (ReqResult tag (Headers ls NoContent))))
200− clientWithRouteMulti Proxy _ _ _ reqs baseurl opts triggers = do
201− let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
202− resp <- performRequestsNoBody method reqs' baseurl opts triggers
203− return $ fmap toHeaders <$> resp
204− where reqs' = fmap (\req ->
205− req {respHeaders = OnlyHeaders (Set.fromList
206− (buildHeaderKeysTo (Proxy :: Proxy ls)))
207− }) <$> reqs
208−
209−
210−------------------------------------------------------------------------------
211−instance (KnownSymbol sym,
212− ToHttpApiData a,
213− HasClientMulti t m sublayout f tag,
214− SupportsServantReflex t m,
215− Traversable f,
216− Applicative f)
217− => HasClientMulti t m (Header sym a :> sublayout) f tag where
218−
219− type ClientMulti t m (Header sym a :> sublayout) f tag =
220− f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f tag
221−
222− clientWithRouteMulti Proxy f q tag reqs baseurl opts eVals =
223− clientWithRouteMulti (Proxy :: Proxy sublayout) f
224− q tag
225− reqs'
226− baseurl opts
227− where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
228− reqs' = ((\eVal req -> Servant.Common.Req.addHeader hname eVal req)
229− <$> eVals <*>) <$> reqs
230−
231−
232−------------------------------------------------------------------------------
233−instance HasClientMulti t m sublayout f tag
234− => HasClientMulti t m (HttpVersion :> sublayout) f tag where
235−
236− type ClientMulti t m (HttpVersion :> sublayout) f tag =
237− ClientMulti t m sublayout f tag
238−
239− clientWithRouteMulti Proxy q f tag =
240− clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
241−
242−
243−------------------------------------------------------------------------------
244−instance (KnownSymbol sym,
245− ToHttpApiData a,
246− HasClientMulti t m sublayout f tag,
247− Reflex t,
248− Applicative f)
249− => HasClientMulti t m (QueryParam sym a :> sublayout) f tag where
250−
251− type ClientMulti t m (QueryParam sym a :> sublayout) f tag =
252− Dynamic t (f (QParam a)) -> ClientMulti t m sublayout f tag
253−
254− -- if mparam = Nothing, we don't add it to the query string
255− -- TODO: Check the above comment
256− clientWithRouteMulti Proxy q f tag reqs baseurl opts mparams =
257− clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
258− reqs' baseurl opts
259−
260− where pname = symbolVal (Proxy :: Proxy sym)
261− p prm = QueryPartParam $ fmap qParamToQueryPart prm
262− paramPair mp = (T.pack pname, p mp)
263− -- reqs' = (\params reqs -> (\req param -> req {qParams = paramPair param : qParams req}) <$> reqs <*> params)
264− -- <$> mparams <*> reqs
265− reqs' = liftA2 (\(pr :: QParam a) (r :: Req t) -> r { qParams = paramPair (constDyn pr) : qParams r })
266− <$> mparams <*> reqs
267−
268−
269−instance (KnownSymbol sym,
270− ToHttpApiData a,
271− HasClientMulti t m sublayout f tag,
272− Reflex t,
273− Applicative f)
274− => HasClientMulti t m (QueryParams sym a :> sublayout) f tag where
275−
276− type ClientMulti t m (QueryParams sym a :> sublayout) f tag =
277− Dynamic t (f [a]) -> ClientMulti t m sublayout f tag
278−
279− clientWithRouteMulti Proxy q f tag reqs baseurl opts paramlists =
280− clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl opts
281−
282− where req' l r = r { qParams = (T.pack pname, params' (constDyn l)) : qParams r }
283− pname = symbolVal (Proxy :: Proxy sym)
284− params' l = QueryPartParams $ (fmap . fmap) (toQueryParam)
285− l
286− reqs' = liftA2 req' <$> paramlists <*> reqs
287−
288−
289−instance (KnownSymbol sym,
290− HasClientMulti t m sublayout f tag,
291− Reflex t,
292− Applicative f)
293− => HasClientMulti t m (QueryFlag sym :> sublayout) f tag where
294−
295− type ClientMulti t m (QueryFlag sym :> sublayout) f tag =
296− Dynamic t (f Bool) -> ClientMulti t m sublayout f tag
297−
298− clientWithRouteMulti Proxy q f' tag reqs baseurl opts flags =
299− clientWithRouteMulti (Proxy :: Proxy sublayout) q f' tag reqs' baseurl opts
300−
301− where req' f req = req { qParams = thisPair (constDyn f) : qParams req }
302− thisPair f = (T.pack pName, QueryPartFlag f) :: (Text, QueryPart t)
303− pName = symbolVal (Proxy :: Proxy sym)
304− reqs' = liftA2 req' <$> flags <*> reqs
305−
306−
307−instance (SupportsServantReflex t m,
308− Traversable f, Applicative f) => HasClientMulti t m Raw f tag where
309− type ClientMulti t m Raw f tag = f (Dynamic t (Either Text (XhrRequest ())))
310− -> Event t tag
311− -> m (Event t (f (ReqResult tag ())))
312−
313− clientWithRouteMulti _ _ _ _ _ _ opts rawReqs triggers = do
314− let rawReqs' = sequence rawReqs :: Dynamic t (f (Either Text (XhrRequest ())))
315− rawReqs'' = attachPromptlyDynWith (\fxhr t -> Compose (t, fxhr)) rawReqs' triggers
316− resps <- fmap (fmap aux . sequenceA . getCompose) <$> performSomeRequestsAsync opts rawReqs''
317− return resps
318− where
319− aux (tag, Right r) = ResponseSuccess tag () r
320− aux (tag, Left e) = RequestFailure tag e
321−
322−
323−instance (MimeRender ct a,
324− HasClientMulti t m sublayout f tag,
325− Reflex t,
326− Applicative f)
327− => HasClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f tag where
328−
329− type ClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f tag =
330− Dynamic t (f (Either Text a)) -> ClientMulti t m sublayout f tag
331−
332− clientWithRouteMulti Proxy q f tag reqs baseurl opts bodies =
333− clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl opts
334− where req' b r = r { reqBody = bodyBytesCT (constDyn b) }
335− ctProxy = Proxy :: Proxy ct
336− ctString = T.pack $ show $ contentType ctProxy
337− bodyBytesCT b = Just $ (fmap . fmap)
338− (\b' -> (mimeRender ctProxy b', ctString))
339− b
340− reqs' = liftA2 req' <$> bodies <*> reqs
341−
342−
343−instance (KnownSymbol path,
344− HasClientMulti t m sublayout f tag,
345− Reflex t,
346− Functor f) => HasClientMulti t m (path :> sublayout) f tag where
347− type ClientMulti t m (path :> sublayout) f tag = ClientMulti t m sublayout f tag
348−
349− clientWithRouteMulti Proxy q f tag reqs baseurl =
350− clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
351− (fmap (prependToPathParts (pure (Right $ T.pack p))) <$> reqs)
352− baseurl
353−
354− where p = symbolVal (Proxy :: Proxy path)
355−
356−
357−instance HasClientMulti t m api f tag => HasClientMulti t m (Vault :> api) f tag where
358− type ClientMulti t m (Vault :> api) f tag = ClientMulti t m api f tag
359−
360− clientWithRouteMulti Proxy q f tag reqs baseurl =
361− clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs baseurl
362−
363−
364−instance HasClientMulti t m api f tag => HasClientMulti t m (RemoteHost :> api) f tag where
365− type ClientMulti t m (RemoteHost :> api) f tag = ClientMulti t m api f tag
366−
367− clientWithRouteMulti Proxy q f tag reqs baseurl =
368− clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs baseurl
369−
370−
371−instance HasClientMulti t m api f tag => HasClientMulti t m (IsSecure :> api) f tag where
372− type ClientMulti t m (IsSecure :> api) f tag = ClientMulti t m api f tag
373−
374− clientWithRouteMulti Proxy q f tag reqs baseurl =
375− clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs baseurl
376−
377−
378−instance (HasClientMulti t m api f tag, Reflex t, Applicative f)
379− => HasClientMulti t m (BasicAuth realm usr :> api) f tag where
380−
381− type ClientMulti t m (BasicAuth realm usr :> api) f tag = Dynamic t (f (Maybe BasicAuthData))
382− -> ClientMulti t m api f tag
383−
384− clientWithRouteMulti Proxy q f tag reqs baseurl opts authdatas =
385− clientWithRouteMulti (Proxy :: Proxy api) q f tag reqs' baseurl opts
386− where
387− req' a r = r { authData = Just (constDyn a) }
388− reqs' = liftA2 req' <$> authdatas <*> reqs