git.iko.soy/typeable/servant-reflex
on 5ef8cd9 help sign in
20 files changed+1535−34
since splitsnapshots
unifiedsplit
cabal.project+1 default.nix+4−2 exec/API.hs+4−1 exec/Example.hs+156−16 nix.conf+3 nix/reflex-platform.nix+2−2 nix/servant-reflex.nix+24 nix/testdriver.nix+3 release.nix+27 servant-reflex.cabal+10−2 src/Servant/Reflex/Examples.hs+60 src/Servant/Reflex/Internal/TupleRequest.hs+473 src/Servant/Reflex/TupleRequest.hs+568 src/Servant/Reflex/TupleRequestMulti.hs+153 testdriver/default.nix+16 testdriver/shell.nix+1 testserver/Main.hs+12−8 testserver/default.nix+14 testserver/shell.nix+1 testserver/testserver.cabal+3−3
cabal.projectnew file+1
@@ -0,0 +1 @@
1+packages: ./servant-reflex.cabal
default.nix+4−2
@@ -1,5 +1,5 @@
11{ mkDerivation, aeson, base, bytestring, case-insensitive
2−, containers, data-default, exceptions, ghcjs-dom, http-api-data
2+, containers, data-default, exceptions, generic-lens, ghcjs-dom, http-api-data
33, http-media, jsaddle, mtl, network-uri, reflex, reflex-dom-core
44, safe, scientific, servant, servant-auth, stdenv
55, string-conversions, text, transformers
@@ -11,13 +11,15 @@ mkDerivation {
1111 (path: type:
1212 baseNameOf path != "result"
1313 && baseNameOf path != "nix"
14+ && baseNameOf path != "testdriver"
15+ && baseNameOf path != "testserver"
1416 ) ./.;
1517 configureFlags = [ "-fexample" ];
1618 isLibrary = true;
1719 isExecutable = true;
1820 libraryHaskellDepends = [
1921 base bytestring case-insensitive containers data-default exceptions
20− ghcjs-dom http-api-data http-media jsaddle mtl network-uri reflex
22+ generic-lens ghcjs-dom http-api-data http-media jsaddle mtl network-uri reflex
2123 reflex-dom-core safe servant servant-auth string-conversions text
2224 transformers
2325 ];
exec/API.hs+4−1
@@ -31,6 +31,7 @@ instance FromJSON Answer where
3131-- | API spec for server, client, and docs
3232type API = "getunit" :> Get '[JSON] ()
3333 :<|> "getint" :> Get '[JSON] Int
34+ :<|> "dblint" :> Capture "anint" Int :> Get '[JSON] Int
3435 :<|> "sayhi" :> QueryParam "username" Text
3536 :> QueryParams "greetings" Text
3637 :> QueryFlag "gusto"
@@ -39,7 +40,9 @@ type API = "getunit" :> Get '[JSON] ()
3940 :> Post '[JSON] Double
4041 :<|> "a" :> "b" :> QueryFlag "gusto" :> Get '[JSON] Text
4142 :<|> "qna" :> ReqBody '[JSON] Question
42− :> Post '[JSON] Answer
43+ :> Header "qnum" Int
44+ :> Header "is-favorite" Bool
45+ :> Post '[JSON] (Headers '[Header "anum" Int, Header "was-favorite" Bool] Answer)
4346 :<|> "secret" :> BasicAuth "realm" () :> Get '[JSON] Int
4447 :<|> Raw
4548
exec/Example.hs+156−16
@@ -5,6 +5,8 @@
55{-# LANGUAGE RankNTypes #-}
66{-# LANGUAGE RecursiveDo #-}
77{-# LANGUAGE ScopedTypeVariables #-}
8+{-# LANGUAGE TypeApplications #-}
9+{-# LANGUAGE TypeOperators #-}
810
911module Main where
1012
@@ -25,16 +27,153 @@ import Reflex.Dom.Core hiding (run)
2527------------------------------------------------------------------------------
2628import Servant.Reflex
2729import Servant.Reflex.Multi
30+import qualified Servant.Reflex.RequestAPI as SR
2831
2932
3033api :: Proxy API
3134api = Proxy
3235
36+type MyAPI =
37+ "getunit" :> Get '[JSON] ()
38+ :<|> "getint" :> Get '[JSON] Int
39+ :<|> "dblint" :> Capture "anint" Int :> Get '[JSON] Int
40+ :<|> "sayhi" :> QueryParam "username" Text
41+ :> QueryParams "greetings" Text
42+ :> QueryFlag "gusto"
43+ :> Get '[JSON] Text
44+ :<|> "double" :> ReqBody '[JSON] Double
45+ :> Post '[JSON] Double
46+ :<|> "a" :> "b" :> QueryFlag "gusto" :> Get '[JSON] Text
47+ :<|> "qna" :> ReqBody '[JSON] Question
48+ :> Header "qnum" Int
49+ :> Header "is-favorite" Bool
50+ :> Post '[JSON] (Headers '[Header "anum" Int, Header "was-favorite" Bool] Answer)
51+ :<|> "secret" :> BasicAuth "realm" () :> Get '[JSON] Int
52+
3353main :: IO ()
3454main = mainWidget $ do
55+ divClass "example-tuple" runTuple
3556 divClass "example-base" run
3657 divClass "example-multi" runMulti
3758
59+hiWidget :: (SupportsServantReflex t m,
60+ DomBuilder t m,
61+ DomBuilderSpace m ~ GhcjsDomSpace,
62+ PostBuild t m) => m (Dynamic t (Maybe Text),
63+ Dynamic t [Text],
64+ Dynamic t Bool,
65+ Event t ())
66+hiWidget = do
67+ text "Name"
68+ el "br" $ return ()
69+ inp :: Dynamic t Text <- fmap value $ divClass "name-input" $ (textInput def)
70+ let checkedName = fmap (\i -> bool (Just i) Nothing (T.null i)) inp
71+ el "br" $ return ()
72+
73+ text "Greetings (space-separated)"
74+ el "br" $ return ()
75+ greetings <- fmap (fmap T.words . value) $
76+ divClass "greetings-input" $ (textInput def)
77+
78+ el "br" $ return ()
79+
80+ gusto <- fmap value $ divClass "gusto-input" $ checkbox False def
81+
82+ el "br" $ return ()
83+ sayHiClicks :: Event t () <- divClass "hi-button" $ button "Say hi"
84+ return (checkedName, greetings, gusto, sayHiClicks)
85+
86+
87+readWidget :: (SupportsServantReflex t m,
88+ DomBuilder t m,
89+ DomBuilderSpace m ~ GhcjsDomSpace,
90+ PostBuild t m,
91+ Read a
92+ ) => m (Dynamic t (Maybe a))
93+readWidget = fmap (readMaybe . T.unpack) . value <$> textInput def
94+
95+readWidget' :: (SupportsServantReflex t m,
96+ DomBuilder t m,
97+ DomBuilderSpace m ~ GhcjsDomSpace,
98+ PostBuild t m,
99+ Read a
100+ ) => a -> m (Dynamic t a)
101+readWidget' d = fmap (fromMaybe d . readMaybe . T.unpack) . value <$> textInput def
102+
103+basicAuthWidget :: (SupportsServantReflex t m,
104+ DomBuilder t m,
105+ DomBuilderSpace m ~ GhcjsDomSpace,
106+ PostBuild t m
107+ ) => m (Dynamic t BasicAuthData)
108+basicAuthWidget = do
109+ un <- fmap value $ text "Username" >> textArea def
110+ pw <- fmap value $ text "Unhidden PW" >> textArea def
111+ return $ BasicAuthData <$> fmap T.encodeUtf8 un <*> fmap T.encodeUtf8 pw
112+
113+
114+runTuple :: forall t m. (SupportsServantReflex t m,
115+ DomBuilder t m,
116+ DomBuilderSpace m ~ GhcjsDomSpace,
117+ MonadFix m,
118+ PostBuild t m,
119+ MonadHold t m) => m ()
120+runTuple = do
121+ el "h1" (text "Tuple API")
122+ url <- baseUrlWidget
123+
124+ let (getUnit :<|> getInt :<|> dblInt :<|> sayHi :<|> dbl :<|> ab :<|> question :<|> secret :<|> _) =
125+ SR.clientR (Proxy @API) (Proxy @m) (Proxy @t) (Proxy @[]) url
126+
127+ u <- divClass "demo-group" $ do
128+ unitBtn <- button "get unit"
129+ getUnit (fmap (:[]) unitBtn)
130+
131+ (giR, diR) <- divClass "demo-group" $ do
132+ int <- readWidget
133+ getIntBtn <- button "get int"
134+ dblIntBtn <- button "double int"
135+ gi <- getInt (fmap (:[]) getIntBtn)
136+ di <- dblInt (fmap (:[]) (fmapMaybe id $ tagPromptlyDyn int dblIntBtn))
137+ return (gi, di)
138+
139+ hiResp <- divClass "demo-group" $ do
140+ (nm, greets, gusto, hiClicks) <- hiWidget
141+ let hiReqs = tagPromptlyDyn ((,,) <$> nm <*> greets <*> gusto) hiClicks
142+ sayHi (fmap (:[]) hiReqs)
143+
144+ dblResp <- divClass "demo-group" $ do
145+ dblInput <- readWidget
146+ dblBtn <- button "double double"
147+ dbl $ (fmap (:[])) $ fmapMaybe id $ tagPromptlyDyn dblInput dblBtn
148+
149+ twoPathResp <- divClass "demo-group" $ do
150+ twoPathInp <- text "Two Path" >> checkbox False def
151+ ab (fmap (:[]) $ updated (value twoPathInp))
152+
153+ qnaR <- divClass "demo-group" $ do
154+ q <- fmap Question . value <$> textInput def
155+ i <- readWidget' 0
156+ f <- readWidget' False
157+ question (fmap (:[]) (updated $ (,,) <$> q <*> i <*> f))
158+
159+ secretR <- divClass "demo-group" $ do
160+ ba <- basicAuthWidget
161+ sBtn <- button "Get secret"
162+ secret (fmap pure $ tagPromptlyDyn ba sBtn)
163+
164+ divClass "demo-group" $ do
165+ el "div" $ text "responses"
166+ dynText =<< holdDyn "No responses yet"
167+ (leftmost [ tshow <$> u , tshow <$> giR
168+ , tshow <$> diR
169+ , tshow <$> hiResp , tshow <$> dblResp
170+ , tshow <$> twoPathResp
171+ , T.concat . fmap (either tshow (\x -> tshow (tshow (getHeaders x), tshow (getResponse x)))) <$> qnaR
172+ , tshow <$> secretR
173+ ])
174+
175+ return ()
176+
38177
39178runMulti :: forall t m. (SupportsServantReflex t m,
40179 DomBuilder t m,
@@ -45,7 +184,7 @@ runMulti :: forall t m. (SupportsServantReflex t m,
45184runMulti = do
46185 url <- baseUrlWidget
47186 el "br" blank
48− let (_ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ :<|> _ :<|> _ ) =
187+ let (_ :<|> _ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ :<|> _ :<|> _ ) =
49188 clientA api (Proxy :: Proxy m) (Proxy :: Proxy []) (Proxy :: Proxy Int) url
50189
51190 num :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
@@ -98,7 +237,7 @@ run = mdo
98237 -- Name the computed API client functions
99238 let tweakRequest = ClientOptions $ \r -> do
100239 return $ r & withCredentials .~ True
101− let (getUnit :<|> getInt :<|> sayhi :<|> dbl
240+ let (getUnit :<|> getInt :<|> dblInt :<|> sayhi :<|> dbl
102241 :<|> multi :<|> qna :<|> secret :<|> doRaw) =
103242 clientWithOpts api (Proxy :: Proxy m) (Proxy :: Proxy Int) url tweakRequest
104243
@@ -158,7 +297,7 @@ run = mdo
158297 holdDyn "(no errors)" (fmapMaybe reqFailure (dblResp))
159298 el "br" (return ())
160299 divClass "double-result" $ el "p" $ dynText =<<
161− holdDyn "No number yet" (fmap tShow $
300+ holdDyn "No number yet" (fmap tshow $
162301 fmapMaybe reqSuccess (dblResp))
163302 return dblBtn
164303
@@ -167,7 +306,7 @@ run = mdo
167306 b <- value <$> checkbox False def
168307 mpGo <- button "Test"
169308 multiResp <- multi b (tag (current reqCount) mpGo)
170− dynText =<< holdDyn "No res yet" (fmap tShow $
309+ dynText =<< holdDyn "No res yet" (fmap tshow $
171310 fmapMaybe reqSuccess $
172311 (multiResp))
173312 return mpGo
@@ -181,14 +320,15 @@ run = mdo
181320 txt <- value <$> textInput def
182321 ev <- fmap (1 <$) $ button "Question"
183322 let dQ = Right . Question <$> traceDyn "will send: " txt
184− rr <- qna dQ ev
323+ -- rr <- qna dQ ev _ _
185324 el "p" $
186− dynText =<< holdDyn "No Answer" (unAnswer <$> fmapMaybe reqSuccess rr)
325+ return ()
326+ -- dynText =<< holdDyn "No Answer" (unAnswer <$> fmapMaybe reqSuccess rr)
187327
188328 divClass "demo-group" $ do
189329 un <- fmap value $ text "Username" >> textArea def
190330 pw <- fmap value $ text "Unhidden PW" >> textArea def
191− let ba :: Dynamic t (BasicAuthData ) = BasicAuthData
331+ let ba :: Dynamic t BasicAuthData = BasicAuthData
192332 <$> fmap T.encodeUtf8 un
193333 <*> fmap T.encodeUtf8 pw
194334 b <- button "Get secret"
@@ -199,20 +339,20 @@ run = mdo
199339
200340showXhrResponse :: XhrResponse -> Text
201341showXhrResponse (XhrResponse stat stattxt rbmay rtmay respHeaders) =
202− T.unlines ["stat: " <> tShow stat
203− ,"stattxt: " <> tShow stattxt
342+ T.unlines ["stat: " <> tshow stat
343+ ,"stattxt: " <> tshow stattxt
204344 ,"resp: " <> maybe "" showRB rbmay
205− ,"rtext: " <> tShow rtmay
206− ,"rHeaders: " <> tShow respHeaders]
345+ ,"rtext: " <> tshow rtmay
346+ ,"rHeaders: " <> tshow respHeaders]
207347
208−tShow :: Show a => a -> Text
209−tShow = T.pack . show
348+tshow :: Show a => a -> Text
349+tshow = T.pack . show
210350
211351showRB :: XhrResponseBody -> Text
212−showRB (XhrResponseBody_Default t) = tShow t
213−showRB (XhrResponseBody_Text t) = tShow t
352+showRB (XhrResponseBody_Default t) = tshow t
353+showRB (XhrResponseBody_Text t) = tshow t
214354showRB (XhrResponseBody_Blob t) = "<Blob>"
215−showRB (XhrResponseBody_ArrayBuffer t) = tShow t
355+showRB (XhrResponseBody_ArrayBuffer t) = tshow t
216356
217357note :: e -> Maybe a -> Either e a
218358note e = maybe (Left e) Right
nix.confnew file+3
@@ -0,0 +1,3 @@
1+substituters = https://cache.nixos.org https://cache.nixos.org https://nixcache.reflex-frp.org
2+trusted-substituters = https://cache.nixos.org https://cache.nixos.org https://nixcache.reflex-frp.org
3+trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
nix/reflex-platform.nix+2−2
@@ -1,7 +1,7 @@
11let
2− rev = "a15d3a2411e7ca7d4ee4853b57c72fe83faee272";
2+ rev = "2df9a830193f551ff222ac6b0c739c622578ddc0";
33in import (builtins.fetchTarball
44 {
55 url = "https://github.com/reflex-frp/reflex-platform/archive/${rev}.tar.gz";
6− sha256 = "1dsvw0lah7761vndip1hqal4fjpjv84ravinnfhy83jgfav5ivna";
6+ sha256 = "17084xqhixk79av0gnjgwkhdigf9zwkd4axb6qp89dyyffy5s3hi";
77 }) {}
nix/servant-reflex.nixnew file+24
@@ -0,0 +1,24 @@
1+{ pkgs ? import <nixpkgs> {}, reflexPlatform, compiler ? "ghcjs" }:
2+
3+let
4+
5+ # generic-lens-src = pkgs.fetchFromGitHub {
6+ # owner = "kcsongor";
7+ # repo = "generic-lens";
8+ # rev = "6787a4e85d09ad9fcfe5baae53fea2cccbae5976";
9+ # sha256 = "1bvjwxm4ik2zcx9s57ibi376y255vm5mawx12s1sjcn09mv1vvdh";
10+ # };
11+
12+ ghcjsPackages = reflexPlatform.${compiler}.override {
13+ overrides = self: super: {
14+ servant = super.servant;
15+ # generic-lens = pkgs.haskell.lib.dontCheck (self.callCabal2nix "generic-lens" generic-lens-src {
16+ # inspection-testing = null;
17+ # });
18+ wai = super.wai;
19+ servant-reflex = pkgs.haskell.lib.appendConfigureFlag
20+ (self.callPackage ../. {}) "-fExample";
21+ };
22+ };
23+
24+in ghcjsPackages.servant-reflex
nix/testdriver.nixnew file+3
@@ -0,0 +1,3 @@
1+{ reflexPlatform }:
2+
3+reflexPlatform.ghc.callPackage ../testdriver/default.nix {}
release.nixnew file+27
@@ -0,0 +1,27 @@
1+{ pkgs ? import <nixpkgs> {}, compiler ? "ghcjs" }:
2+
3+let
4+ reflexPlatform =
5+ import ./nix/reflex-platform.nix {
6+ fetchFromGitHub = pkgs.fetchFromGitHub;
7+ };
8+
9+in rec {
10+
11+ servant-reflex = import ./nix/servant-reflex.nix {
12+ reflexPlatform = reflexPlatform;
13+ compiler = compiler;
14+ };
15+ testserver = import ./nix/testserver.nix
16+ { reflexPlatform = reflexPlatform;
17+ servant-reflex = servant-reflex ;
18+ };
19+ testdriver = import ./nix/testdriver.nix
20+ { reflexPlatform = reflexPlatform;
21+ };
22+ testresults = import ./nix/testresults.nix
23+ { inherit reflexPlatform testserver testdriver;
24+ inherit (pkgs) curl;
25+ phantomjs = pkgs.phantomjs2;
26+ };
27+ }
servant-reflex.cabal+10−2
@@ -17,10 +17,16 @@ Flag Example
1717
1818library
1919 exposed-modules:
20− Servant.Common.BaseUrl
21− Servant.Common.Req
2220 Servant.Reflex
21+ Servant.Reflex.Examples
2322 Servant.Reflex.Multi
23+ Servant.Reflex.Internal.TupleRequest
24+ Servant.Reflex.TupleRequest
25+ Servant.Reflex.TupleRequestMulti
26+
27+ other-modules:
28+ Servant.Common.BaseUrl
29+ Servant.Common.Req
2430
2531 hs-source-dirs: src
2632 build-depends:
@@ -29,11 +35,13 @@ library
2935 case-insensitive >= 1.2.0.4 && < 1.3,
3036 containers >= 0.5.6 && < 0.7,
3137 data-default >= 0.5 && < 0.8,
38+ generic-lens,
3239 exceptions >= 0.8 && < 0.11,
3340 ghcjs-dom >= 0.2 && < 0.10,
3441 http-api-data >= 0.3.6 && < 0.5,
3542 http-media >= 0.6 && < 0.9,
3643 jsaddle >= 0.8 && < 0.10,
44+ lens,
3745 mtl >= 2.2.1 && < 2.3,
3846 network-uri >= 2.6 && < 2.7,
3947 reflex >= 0.5 && < 0.7,
src/Servant/Reflex/Examples.hsnew file+60
@@ -0,0 +1,60 @@
1+-- |
2+
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE KindSignatures #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE RankNTypes #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TypeApplications #-}
10+{-# LANGUAGE TypeOperators #-}
11+
12+
13+module Servant.Reflex.Examples where
14+
15+import Control.Monad.Identity (Identity(..)) -- Temporary
16+import Data.Proxy (Proxy (..))
17+import Reflex (Event, MonadHold, PostBuild,
18+ holdDyn, leftmost)
19+import Reflex.Dom.Core (DomBuilder, display)
20+import Reflex.Dom.Widget (button)
21+import Servant.API ((:<|>) (..), (:>) (..))
22+import qualified Servant.API as API
23+import Servant.Common.BaseUrl (SupportsServantReflex)
24+
25+import qualified Servant.Reflex.TupleRequest as TupleRequest
26+import qualified Servant.Reflex.TupleRequestMulti as TupleRequestMulti
27+import qualified Servant.Test.ComprehensiveAPI as Servant
28+
29+
30+type MyAPI =
31+
32+ API.Header "hi" Int
33+ :> API.Capture "hello" Int
34+ :> API.Capture "hello2" Bool
35+ :> API.Get '[API.JSON] (API.Headers '[API.Header "hi" Int] Int)
36+
37+ :<|> API.Get '[API.JSON] Bool
38+
39+type MyAPI2 = API.Raw
40+
41+
42+m :: forall t m.(SupportsServantReflex t m, MonadHold t m, PostBuild t m, DomBuilder t m) => m ()
43+m = do
44+ let (a :<|> b) = TupleRequest.clientR (Proxy @MyAPI) (Proxy :: Proxy m) (Proxy :: Proxy t) undefined
45+ btn <- button "Hello"
46+ resA <- a ((2, 5, True) <$ btn)
47+ resB <- b (() <$ btn)
48+ display =<< holdDyn "waiting" (leftmost [show <$> resB ])
49+
50+ let rawR = TupleRequest.clientR (Proxy @MyAPI2) (Proxy @m) (Proxy @t) undefined
51+
52+ let (aMulti1 :<|> bMulti1) =
53+ TupleRequestMulti.clientR
54+ (Proxy @MyAPI)
55+ (Proxy :: Proxy m)
56+ (Proxy :: Proxy t)
57+ (Proxy :: Proxy ((,) Int))
58+ undefined
59+ return ()
60+
src/Servant/Reflex/Internal/TupleRequest.hsnew file+473
@@ -0,0 +1,473 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
12+{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE TupleSections #-}
15+{-# LANGUAGE TypeApplications #-}
16+{-# LANGUAGE TypeFamilies #-}
17+{-# LANGUAGE TypeOperators #-}
18+{-# LANGUAGE UndecidableInstances #-}
19+
20+module Servant.Reflex.Internal.TupleRequest where
21+
22+import Control.Arrow (first, second)
23+import Data.CaseInsensitive (mk)
24+import Control.Lens (over,set)
25+import qualified Data.ByteString.Lazy as BL
26+import qualified Data.Default as Default
27+import qualified Data.List as L
28+import qualified Data.Map as M
29+import qualified Data.Set as Set
30+import Data.Generics.Product
31+import Data.Proxy (Proxy(..))
32+import GHC.TypeLits (KnownSymbol, symbolVal)
33+import Data.Maybe (catMaybes)
34+import Data.Text (Text)
35+import qualified Data.Text as T
36+import qualified Data.Text.Encoding as E
37+import GHC.Generics (Generic)
38+import Reflex.Dom.Xhr (XhrRequest (..), XhrRequestConfig (..),
39+ XhrResponseHeaders (..),
40+ XhrResponseBody(..),
41+ XhrResponseType (..), XhrResponse(..))
42+import Servant.API hiding (HList(..))
43+import Servant.API ((:<|>), BasicAuthData, Raw,
44+ basicAuthPassword, basicAuthUsername)
45+
46+import Servant.Reflex (BuildHeaderKeysTo (..),
47+ toHeaders)
48+import Servant.Common.BaseUrl (SupportsServantReflex, BaseUrl (..), showBaseUrl)
49+import Servant.Common.Req (escape)
50+
51+
52+
53+-- | A class for generating endpoint-level API client functions
54+-- We need two separate type classes because the needs of endpoints
55+-- and top-level clients are incompatible
56+--
57+-- Endpoints need to accumulate a list of their input types from
58+-- the API definition (the @EInputs@ associated type) when building
59+-- up a function from a sequence of @:>@ combinators. But for
60+-- sequences of @:<|>@ combinators, such a listing doesn't make
61+-- sense. So, we use one class for endpoint-level recursion and
62+-- another for collecting endpoints into the top-level API
63+class EndpointR t m api f where
64+
65+ type EInputs t m api f :: [*]
66+ type EOutput t m api f :: *
67+
68+ mkReq :: HList (EInputs t m api f) -> ReqR
69+ getResp :: XhrResponse -> EOutput t m api f
70+
71+data HList xs where
72+ HNil :: HList '[]
73+ HCons :: x -> HList xs -> HList (x ': xs)
74+
75+
76+class ToTuple xs where
77+ type Tuple xs :: *
78+ toTuple :: xs -> Tuple xs
79+ fromTuple :: Tuple xs -> xs
80+
81+data Sealed a
82+data SealedRaw
83+
84+type family Seal a where
85+ Seal (a :<|> b) = Seal a :<|> Seal b
86+ Seal Raw = SealedRaw
87+ Seal a = Sealed a
88+
89+instance ToTuple (HList '[]) where
90+ type Tuple (HList '[]) = ()
91+ toTuple _ = ()
92+ fromTuple _ = HNil
93+
94+instance ToTuple (HList '[a]) where
95+ type Tuple (HList '[a]) = a
96+ toTuple (HCons a HNil) = a
97+ fromTuple a = HCons a HNil
98+
99+instance ToTuple (HList '[a,b]) where
100+ type Tuple (HList '[a,b]) = (a,b)
101+ toTuple (HCons a (HCons b HNil)) = (a,b)
102+ fromTuple (a,b) = HCons a (HCons b HNil)
103+
104+instance ToTuple (HList '[a,b,c]) where
105+ type Tuple (HList '[a,b,c]) = (a,b,c)
106+ toTuple (HCons a (HCons b (HCons c HNil))) = (a,b,c)
107+ fromTuple (a,b,c) = HCons a (HCons b (HCons c HNil))
108+
109+
110+data ReqR = ReqR
111+ { reqRMethod :: T.Text
112+ , reqRPathParts :: [T.Text]
113+ , reqRParams :: [(T.Text, QueryPart)]
114+ , reqRBody :: Maybe (BL.ByteString, T.Text)
115+ , reqRHeaders :: [(T.Text, T.Text)]
116+ , reqRRespHeaders :: XhrResponseHeaders
117+ , reqRAuthData :: Maybe BasicAuthData
118+ } deriving (Generic)
119+
120+instance Show ReqR where
121+ show (ReqR m pp ps b hs hs' a) = concat $ L.intersperse "\n" [
122+ "ReqR {"
123+ , ("reqRMethod = " <> show m)
124+ , ("reqRPathParts = " <> show pp)
125+ , ("reqRParams = " <> show ps)
126+ , ("reqRBody = " <> show b)
127+ , ("reqRHeaders = " <> show hs)
128+ , ("reqRRespHeaders = " <> show hs')
129+ , ("reqRAuthData = " <> maybe "Nothing" (const "Just <<redacted>>") a)
130+ ]
131+
132+emptyReqR :: T.Text -> ReqR
133+emptyReqR method = ReqR method [] [] Nothing [] (OnlyHeaders mempty) Nothing
134+
135+data QueryPart = QueryPartParam (Maybe Text)
136+ | QueryPartParams [Text]
137+ | QueryPartFlag Bool
138+ deriving (Show)
139+
140+mkXhrRequest :: BaseUrl -> ReqR -> XhrRequest T.Text
141+mkXhrRequest reqHost req =
142+ let path = T.intercalate "/" $ reqRPathParts req
143+
144+ queryPartString :: (Text, QueryPart) -> Maybe Text
145+ queryPartString (pName, qp) = case qp of
146+
147+ QueryPartParam Nothing -> Nothing
148+ QueryPartParam (Just a) -> Just (pName <> "=" <> escape a)
149+
150+ QueryPartParams ps -> Just . T.intercalate "&"
151+ $ fmap (\p -> pName <> "=" <> escape p)
152+ ps
153+
154+ QueryPartFlag True -> Just pName
155+ QueryPartFlag False -> Nothing
156+
157+ (</>) :: Text -> Text -> Text
158+ x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
159+ | otherwise = x <> "/" <> y
160+
161+ queryString = T.intercalate "&" . catMaybes . map queryPartString $ reqRParams req
162+ url = showBaseUrl reqHost </> path <> if T.null queryString then "" else (T.cons '?' queryString)
163+
164+ (headers, body) =
165+ let headers0 = M.fromList $ reqRHeaders req
166+ in case reqRBody req of
167+ Nothing -> (headers0, "")
168+ Just (rb,ct) -> (M.insert "Content-Type" ct headers0,
169+ E.decodeUtf8 $ BL.toStrict rb)
170+
171+ xhrReqConfig = Default.def
172+ { _xhrRequestConfig_headers = headers
173+ , _xhrRequestConfig_user = E.decodeUtf8 . basicAuthUsername <$> reqRAuthData req
174+ , _xhrRequestConfig_password = E.decodeUtf8 . basicAuthPassword <$> reqRAuthData req
175+ , _xhrRequestConfig_responseType = Just XhrResponseType_ArrayBuffer
176+ , _xhrRequestConfig_sendData = body
177+ , _xhrRequestConfig_responseHeaders = reqRRespHeaders req
178+ , _xhrRequestConfig_withCredentials = False
179+ }
180+ xhrReq = XhrRequest (reqRMethod req) url xhrReqConfig
181+ in xhrReq
182+
183+
184+instance {-# OVERLAPPING #-}
185+ ( MimeUnrender ct a
186+ , ReflectMethod method
187+ , cts' ~ (ct ': cts)
188+ , SupportsServantReflex t m
189+ -- , Applicative f
190+ )
191+ => EndpointR t m (Verb method status cts' a) f where
192+ type EInputs t m (Verb method status cts' a) f = '[]
193+ type EOutput t m (Verb method status cts' a) f = Either String a
194+
195+ mkReq _ = emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
196+ getResp resp = case _xhrResponse_response resp of
197+ Just (XhrResponseBody_ArrayBuffer x) -> mimeUnrender (Proxy @ct) (BL.fromStrict x)
198+
199+instance {-# OVERLAPPING #-}
200+ ( ReflectMethod method
201+ , SupportsServantReflex t m
202+ -- , Applicative f
203+ )
204+ => EndpointR t m (Verb method status cts' NoContent) f where
205+ type EInputs t m (Verb method status cts' NoContent) f = '[]
206+ type EOutput t m (Verb method status cts' NoContent) f = Either String NoContent
207+
208+ mkReq _ = emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
209+ getResp resp = Right NoContent -- TODO: Should EOutput be `NoContent` rather than Either... ?
210+
211+
212+instance {-# OVERLAPPING #-}
213+ ( MimeUnrender ct a
214+ , BuildHeadersTo hs
215+ , ReflectMethod method, cts' ~ (ct ': cts)
216+ , SupportsServantReflex t m
217+ -- , Applicative f
218+ , BuildHeaderKeysTo hs
219+ )
220+ => EndpointR t m (Verb method status cts' (Headers hs a)) f where
221+ type EInputs t m (Verb method status cts' (Headers hs a)) f = '[]
222+ type EOutput t m (Verb method status cts' (Headers hs a)) f = Either String (Headers hs a)
223+
224+ mkReq _ = set (field @"reqRRespHeaders") (OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy @hs)))) $ emptyReqR $ E.decodeUtf8 $ reflectMethod (Proxy @method)
225+ getResp resp = case _xhrResponse_response resp of
226+ Just (XhrResponseBody_ArrayBuffer x) ->
227+ let hs = buildHeadersTo
228+ . fmap (first (mk . E.encodeUtf8) . second E.encodeUtf8)
229+ . M.toList
230+ $ _xhrResponse_headers resp
231+ in fmap (flip Headers (hs)) $ mimeUnrender (Proxy @ct) (BL.fromStrict x)
232+
233+
234+instance {-# OVERLAPPING #-}
235+ (MimeUnrender ct a
236+ , BuildHeadersTo hs
237+ , BuildHeaderKeysTo hs
238+ , ReflectMethod method
239+ , SupportsServantReflex t m
240+ -- , Applicative f
241+ )
242+ => EndpointR t m (Verb method status cts' (Headers hs NoContent)) f where
243+ type EInputs t m (Verb method status cts' (Headers hs NoContent)) f = '[]
244+ type EOutput t m (Verb method status cts' (Headers hs NoContent)) f = Either String (Headers hs NoContent)
245+
246+ mkReq _ = set (field @"reqRRespHeaders") (OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy @hs)))) $ emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
247+ getResp resp = case _xhrResponse_response resp of
248+ Just (XhrResponseBody_ArrayBuffer x) ->
249+ let hs = buildHeadersTo
250+ . fmap (first (mk . E.encodeUtf8) . second E.encodeUtf8)
251+ . M.toList
252+ $ _xhrResponse_headers resp
253+ in Right (Headers NoContent hs)
254+
255+
256+instance
257+ ( SupportsServantReflex t m
258+ , ToHttpApiData a
259+ , EndpointR t m sublayout f
260+ -- , Applicative f
261+ )
262+ => EndpointR t m (Capture capture a :> sublayout) f where
263+
264+ type EInputs t m (Capture capture a :> sublayout) f = a ': EInputs t m sublayout f
265+ type EOutput t m (Capture capture a :> sublayout) f = EOutput t m sublayout f
266+
267+ mkReq (HCons x xs) = (\a -> over (field @"reqRPathParts") (toUrlPiece a:)) x $
268+ mkReq @t @m @sublayout @f xs
269+
270+ getResp = getResp @t @m @sublayout @f
271+
272+instance
273+ ( SupportsServantReflex t m
274+ , ToHttpApiData a
275+ , EndpointR t m sublayout f
276+ , Applicative f
277+ )
278+ => EndpointR t m (CaptureAll capture a :> sublayout) f where
279+
280+ type EInputs t m (CaptureAll capture a :> sublayout) f = [a] ': EInputs t m sublayout f
281+ type EOutput t m (CaptureAll capture a :> sublayout) f = EOutput t m sublayout f
282+
283+ mkReq (HCons x xs) = (\a -> over (field @"reqRPathParts") (fmap toUrlPiece a <>)) x $
284+ mkReq @t @m @sublayout @f xs
285+
286+ getResp = getResp @t @m @sublayout @f
287+
288+
289+instance
290+ (SupportsServantReflex t m
291+ , KnownSymbol sym
292+ , EndpointR t m sublayout f
293+ -- , Applicative f
294+ )
295+ => EndpointR t m (sym :> sublayout) f where
296+
297+ type EInputs t m (sym :> sublayout) f = EInputs t m sublayout f
298+ type EOutput t m (sym :> sublayout) f = EOutput t m sublayout f
299+
300+ mkReq r = over (field @"reqRPathParts") (T.pack (symbolVal (Proxy @sym)) :) $ mkReq @t @m @sublayout @f r
301+
302+ getResp = getResp @t @m @sublayout @f
303+
304+instance
305+ ( SupportsServantReflex t m
306+ , ToHttpApiData a
307+ , EndpointR t m sublayout f
308+ , KnownSymbol sym
309+ -- , Applicative f
310+ )
311+ => EndpointR t m (QueryParam sym a :> sublayout) f where
312+
313+ type EInputs t m (QueryParam sym a :> sublayout) f = Maybe a ': EInputs t m sublayout f
314+ type EOutput t m (QueryParam sym a :> sublayout) f = EOutput t m sublayout f
315+
316+ mkReq (HCons x xs) = over (field @"reqRParams") ((T.pack $ symbolVal (Proxy @sym),
317+ QueryPartParam $ fmap (toQueryParam) x) : ) $
318+ mkReq @t @m @sublayout @f xs
319+
320+
321+ getResp = getResp @t @m @sublayout @f
322+
323+instance
324+ ( SupportsServantReflex t m
325+ , ToHttpApiData a
326+ , EndpointR t m sublayout f
327+ , KnownSymbol sym
328+ -- , Applicative f
329+ )
330+ => EndpointR t m (QueryParams sym a :> sublayout) f where
331+
332+ type EInputs t m (QueryParams sym a :> sublayout) f = [a] ': EInputs t m sublayout f
333+ type EOutput t m (QueryParams sym a :> sublayout) f = EOutput t m sublayout f
334+
335+ mkReq (HCons x xs) =
336+ let newParams = (T.pack (symbolVal (Proxy @sym)), QueryPartParams (toQueryParam <$> x))
337+ in over (field @"reqRParams") (newParams :) $ mkReq @t @m @sublayout @f xs
338+
339+ getResp = getResp @t @m @sublayout @f
340+
341+instance
342+ ( SupportsServantReflex t m
343+ , EndpointR t m sublayout f
344+ , KnownSymbol sym
345+ -- , Applicative f
346+ )
347+ => EndpointR t m (QueryFlag sym :> sublayout) f where
348+
349+ type EInputs t m (QueryFlag sym :> sublayout) f = Bool ': EInputs t m sublayout f
350+ type EOutput t m (QueryFlag sym :> sublayout) f = EOutput t m sublayout f
351+
352+ mkReq (HCons x xs) =
353+ let newParam = (T.pack (symbolVal (Proxy @sym)), QueryPartFlag x)
354+ in over (field @"reqRParams") (newParam :) $ mkReq @t @m @sublayout @f xs
355+
356+ getResp = getResp @t @m @sublayout @f
357+
358+instance
359+ ( SupportsServantReflex t m
360+ , EndpointR t m sublayout f
361+ , MimeRender ct a
362+ -- , Applicative f
363+ )
364+ => EndpointR t m (ReqBody (ct ': cts) a :> sublayout) f where
365+
366+
367+ type EInputs t m (ReqBody (ct ': cts) a :> sublayout) f = a ': EInputs t m sublayout f
368+ type EOutput t m (ReqBody (ct ': cts) a :> sublayout) f = EOutput t m sublayout f
369+
370+ mkReq (HCons x xs) =
371+ set (field @"reqRBody") (Just (mimeRender (Proxy @ct) x, T.pack (show (contentType (Proxy @ct))))) $ mkReq @t @m @sublayout @f xs
372+
373+ getResp = getResp @t @m @sublayout @f
374+
375+
376+instance
377+ ( SupportsServantReflex t m
378+ , EndpointR t m sublayout f
379+ , KnownSymbol sym
380+ , ToHttpApiData a
381+ -- , Applicative f
382+ )
383+ => EndpointR t m (Header sym a :> sublayout) f where
384+
385+
386+ type EInputs t m (Header sym a :> sublayout) f = a ': EInputs t m sublayout f
387+ type EOutput t m (Header sym a :> sublayout) f = EOutput t m sublayout f
388+
389+ mkReq (HCons x xs) =
390+ over (field @"reqRHeaders")
391+ -- ((symbolVal (Proxy @sym), (mimeRender (Proxy @a) x)) :)
392+ ((T.pack $ symbolVal (Proxy @sym), E.decodeUtf8 $ toHeader x) :)
393+ $ mkReq @t @m @sublayout @f xs
394+
395+ getResp = getResp @t @m @sublayout @f
396+
397+#if MIN_VERSION_servant(0,13,0)
398+-- TODO: Untested
399+instance (SupportsServantReflex t m,
400+ EndpointR t m sublayout f,
401+ KnownSymbol sym,
402+ ToHttpApiData a,
403+ Applicative f
404+ ) => EndpointR t m (Summary desc :> sublayout) f where
405+
406+ type EInputs t m (Summary desc :> sublayout) f = EInputs t m sublayout f
407+ type EOutput t m (Summary desc :> sublayout) f = EOutput t m sublayout f
408+
409+ mkReq = mkReq @t @m @sublayout @f
410+
411+ getResp = getResp @t @m @sublayout @f
412+#endif
413+
414+
415+#if MIN_VERSION_servant(0,13,0)
416+-- TODO: Untested
417+instance (SupportsServantReflex t m,
418+ EndpointR t m sublayout f,
419+ KnownSymbol sym,
420+ ToHttpApiData a,
421+ Applicative f
422+ ) => EndpointR t m (Description desc :> sublayout) f where
423+
424+ type EInputs t m (Description desc :> sublayout) f = EInputs t m sublayout f
425+ type EOutput t m (Description desc :> sublayout) f = EOutput t m sublayout f
426+
427+ mkReq = mkReq @t @m @sublayout @f
428+
429+ getResp = getResp @t @m @sublayout @f
430+#endif
431+
432+instance (SupportsServantReflex t m,
433+ EndpointR t m sublayout f,
434+ KnownSymbol sym,
435+ ToHttpApiData a,
436+ Applicative f
437+ ) => EndpointR t m (IsSecure :> sublayout) f where
438+
439+
440+ type EInputs t m (IsSecure :> sublayout) f = EInputs t m sublayout f
441+ type EOutput t m (IsSecure :> sublayout) f = EOutput t m sublayout f
442+
443+ mkReq = mkReq @t @m @sublayout @f
444+
445+ getResp = getResp @t @m @sublayout @f
446+
447+instance (SupportsServantReflex t m,
448+ EndpointR t m sublayout f,
449+ KnownSymbol sym,
450+ ToHttpApiData a,
451+ Applicative f
452+ ) => EndpointR t m (Vault :> sublayout) f where
453+
454+
455+ type EInputs t m (Vault :> sublayout) f = EInputs t m sublayout f
456+ type EOutput t m (Vault :> sublayout) f = EOutput t m sublayout f
457+
458+ mkReq = mkReq @t @m @sublayout @f
459+
460+ getResp = getResp @t @m @sublayout @f
461+
462+instance (SupportsServantReflex t m,
463+ EndpointR t m sublayout f,
464+ Applicative f
465+ ) => EndpointR t m (BasicAuth realm usr :> sublayout) f where
466+
467+ type EInputs t m (BasicAuth realm usr :> sublayout) f = BasicAuthData ': EInputs t m sublayout f
468+ type EOutput t m (BasicAuth realm usr :> sublayout) f = EOutput t m sublayout f
469+
470+ mkReq (HCons x xs) =
471+ set (field @"reqRAuthData") (Just x) $ mkReq @t @m @sublayout @f xs
472+
473+ getResp = getResp @t @m @sublayout @f
src/Servant/Reflex/TupleRequest.hsnew file+568
@@ -0,0 +1,568 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
12+{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE TupleSections #-}
15+{-# LANGUAGE TypeApplications #-}
16+{-# LANGUAGE TypeFamilies #-}
17+{-# LANGUAGE TypeOperators #-}
18+{-# LANGUAGE UndecidableInstances #-}
19+
20+module Servant.Reflex.TupleRequest where
21+
22+import Control.Arrow (first, second)
23+import Control.Lens
24+import qualified Data.ByteString.Lazy as BL
25+import Data.CaseInsensitive (mk)
26+import qualified Data.Default as Default
27+import Data.Generics.Product
28+import qualified Data.List as L
29+import qualified Data.Map as M
30+import Data.Maybe (catMaybes)
31+import Data.Proxy
32+import Data.Semigroup ((<>))
33+import qualified Data.Set as Set
34+import Data.Text (Text)
35+import qualified Data.Text as T
36+import qualified Data.Text.Encoding as E
37+import GHC.Generics
38+import GHC.TypeLits (KnownSymbol, symbolVal)
39+import Language.Javascript.JSaddle (MonadJSM)
40+import Reflex hiding (HList (..))
41+import Servant.API hiding (HList (..))
42+import Servant.Common.BaseUrl
43+import Servant.Common.Req hiding (QueryPart (..))
44+
45+import Reflex.Dom.Xhr hiding (HList (..))
46+
47+import Servant.Reflex (BuildHeaderKeysTo (..),
48+ toHeaders)
49+import Servant.Reflex.Internal.TupleRequest
50+
51+{-
52+TODO:
53+ - [x] :<|>
54+ - [x] Verb content
55+ - [x] Verb nocontent
56+ - [x] Verb (Headers content)
57+ - [x] Verb (Headers nocontent)
58+ - [ ] Stream
59+ - [X] Capture :> ...
60+ - [ ] Capture' :> ...
61+ - [x] CaptureAll :> ...
62+ - [x] QueryParam :> ...
63+ - [x] QueryParams :> ...
64+ - [x] QueryFlag :> ...
65+ - [x] ReqBody :> ...
66+ - [ ] Header' :> ...
67+ - [x] Header :> ...
68+ - [x] Summary :> ...
69+ - [x] Description :> ...
70+ - [x] path :> ...
71+ - [ ] RemoteHost :> ...
72+ - [x] IsSecure :> ...
73+ - [x] Vault :> ...
74+ - [ ] WithNamedContext :> ...
75+ - [ ] AuthenticatedRequest :> ...
76+ - [x] BasicAuth :> ...
77+ - [ ] EmptyClient :> ...
78+ - [ ] EmptyClient
79+ - [ ] HttpVersion :> ...
80+-}
81+
82+clientR
83+ :: forall t m layout
84+ .( HasClientR t m (Seal layout)
85+ , Reflex t
86+ )
87+ => Proxy layout
88+ -> Proxy (m :: * -> *)
89+ -> Proxy t
90+ -> Dynamic t BaseUrl
91+ -> ClientR t m (Seal layout)
92+clientR _ mP tP url =
93+ clientRWithRoute (Proxy :: Proxy (Seal layout)) mP undefined url undefined
94+ -- empty -^ dynOpts -^
95+
96+-- | A class for generating top-level the API client function
97+class HasClientR t (m :: * -> *) api where
98+ type ClientR t m api :: *
99+ clientRWithRoute
100+ :: Proxy api
101+ -> Proxy m
102+ -> Dynamic t (Req t)
103+ -> Dynamic t BaseUrl
104+ -> ClientOptions
105+ -> ClientR t m api
106+
107+
108+
109+-- -- | A class for generating endpoint-level API client functions
110+-- -- We need two separate type classes because the needs of endpoints
111+-- -- and top-level clients are incompatible
112+-- --
113+-- -- Endpoints need to accumulate a list of their input types from
114+-- -- the API definition (the @EInputs@ associated type) when building
115+-- -- up a function from a sequence of @:>@ combinators. But for
116+-- -- sequences of @:<|>@ combinators, such a listing doesn't make
117+-- -- sense. So, we use one class for endpoint-level recursion and
118+-- -- another for collecting endpoints into the top-level API
119+-- class EndpointR t m api where
120+
121+-- type EInputs t m api :: [*]
122+-- type EOutput t m api :: *
123+
124+-- mkReq :: HList (EInputs t m api) -> ReqR
125+-- getResp :: XhrResponse -> EOutput t m api
126+
127+
128+------------------------------------------------------------------------------
129+-- | :<|> APIs
130+instance {-# OVERLAPPING #-}
131+ (HasClientR t m (layoutLeft),
132+ HasClientR t m (layoutRight)
133+ ) => HasClientR t m (layoutLeft :<|> layoutRight) where
134+ type ClientR t m (layoutLeft :<|> layoutRight) =
135+ ClientR t m layoutLeft :<|> ClientR t m layoutRight
136+ clientRWithRoute _ mP _ url opts =
137+ clientRWithRoute (Proxy @layoutLeft) mP undefined url opts
138+ :<|>
139+ clientRWithRoute (Proxy @layoutRight) mP undefined url opts
140+
141+#if MIN_VERSION_servant (0,13,0)
142+-- TODO: Untested
143+instance HasClientR t m EmptyAPI where
144+ type ClientR t m EmptyAPI = EmptyAPI
145+ clientRWithRoute _ _ _ _ _ = EmptyAPI
146+#endif
147+
148+
149+
150+-- | Turn a single Endpoint into a client, by turning the input HList
151+-- into a tuple
152+instance {-# OVERLAPPING #-}
153+ forall t m layout
154+ .( EndpointR t m layout Identity
155+ , ToTuple (HList (EInputs t m layout Identity))
156+ , Monad m
157+ , SupportsServantReflex t m
158+ )
159+ => HasClientR t (m :: * -> *) (Sealed layout) where
160+ type ClientR t m (Sealed layout) =
161+ Event t (Tuple (HList (EInputs t m layout Identity)))
162+ -> m (Event t (EOutput t m layout Identity))
163+ clientRWithRoute _ mP dReq url opts (reqs :: Event t (Tuple (HList (EInputs t m layout Identity)))) = do
164+ let
165+ reqTuples :: Event t (HList (EInputs t m layout Identity))
166+ reqTuples = (fromTuple @(HList (EInputs t m layout Identity))) <$> reqs
167+
168+ -- TODO: replace these 2 lines w/ the next 2. The trace is for library debugging
169+ reqRs :: Event t (ReqR)
170+ reqRs = (mkReq @t @m @layout @Identity) <$> reqTuples
171+
172+ reqs' :: Event t (XhrRequest T.Text)
173+ reqs' = ffor (attachPromptlyDyn url reqRs) $ \(bUrl, rs) -> mkXhrRequest bUrl $ rs
174+ -- reqs' = ffor (attachPromptlyDyn url reqTuples) $ \(bUrl, inputs) ->
175+ -- mkXhrRequest bUrl . mkReq @t @m @layout @f <$> inputs
176+ resps <- performRequestAsync $ reqs'
177+ -- return $ _ resps
178+ return ((getResp @t @m @layout @Identity) <$> resps)
179+
180+
181+instance (SupportsServantReflex t m) => HasClientR t m SealedRaw where
182+ type ClientR t m SealedRaw = Event t (XhrRequest T.Text) -> m (Event t XhrResponse)
183+ clientRWithRoute _ mP dReq url opts reqs = performRequestAsync reqs
184+
185+
186+-- instance {-# OVERLAPPING #-}(MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m
187+-- ) => EndpointR t m (Verb method status cts' a) where
188+-- type EInputs t m (Verb method status cts' a) = '[]
189+-- type EOutput t m (Verb method status cts' a) = Either String a
190+
191+-- mkReq _ = emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
192+-- getResp resp = case _xhrResponse_response resp of
193+-- Just (XhrResponseBody_ArrayBuffer x) -> mimeUnrender (Proxy @ct) (BL.fromStrict x)
194+
195+-- instance {-# OVERLAPPING #-}(ReflectMethod method, SupportsServantReflex t m
196+-- ) => EndpointR t m (Verb method status cts' NoContent) where
197+-- type EInputs t m (Verb method status cts' NoContent) = '[]
198+-- type EOutput t m (Verb method status cts' NoContent) = Either String NoContent
199+
200+-- mkReq _ = emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
201+-- getResp resp = Right NoContent -- TODO: Should EOutput be `NoContent` rather than Either... ?
202+
203+
204+-- instance {-# OVERLAPPING #-}
205+-- (MimeUnrender ct a,
206+-- BuildHeadersTo hs,
207+-- ReflectMethod method, cts' ~ (ct ': cts),
208+-- SupportsServantReflex t m,
209+-- Applicative f,
210+-- BuildHeaderKeysTo hs
211+-- ) => EndpointR t m (Verb method status cts' (Headers hs a)) f where
212+-- type EInputs t m (Verb method status cts' (Headers hs a)) f = '[]
213+-- type EOutput t m (Verb method status cts' (Headers hs a)) f = Either String (Headers hs a)
214+
215+-- mkReq _ = set (field @"reqRRespHeaders") (OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy @hs)))) $ emptyReqR $ E.decodeUtf8 $ reflectMethod (Proxy @method)
216+-- getResp resp = case _xhrResponse_response resp of
217+-- Just (XhrResponseBody_ArrayBuffer x) ->
218+-- let hs = buildHeadersTo
219+-- . fmap (first (mk . E.encodeUtf8) . second E.encodeUtf8)
220+-- . M.toList
221+-- $ _xhrResponse_headers resp
222+-- in fmap (flip Headers (hs)) $ mimeUnrender (Proxy @ct) (BL.fromStrict x)
223+
224+
225+-- instance {-# OVERLAPPING #-}(MimeUnrender ct a,
226+-- BuildHeadersTo hs,
227+-- BuildHeaderKeysTo hs,
228+-- ReflectMethod method,
229+-- SupportsServantReflex t m,
230+-- Applicative f
231+-- ) => EndpointR t m (Verb method status cts' (Headers hs NoContent)) f where
232+-- type EInputs t m (Verb method status cts' (Headers hs NoContent)) f = '[]
233+-- type EOutput t m (Verb method status cts' (Headers hs NoContent)) f = Either String (Headers hs NoContent)
234+
235+-- mkReq _ = set (field @"reqRRespHeaders") (OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy @hs)))) $ emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
236+-- getResp resp = case _xhrResponse_response resp of
237+-- Just (XhrResponseBody_ArrayBuffer x) ->
238+-- let hs = buildHeadersTo
239+-- . fmap (first (mk . E.encodeUtf8) . second E.encodeUtf8)
240+-- . M.toList
241+-- $ _xhrResponse_headers resp
242+-- in Right (Headers NoContent hs)
243+
244+
245+-- instance (SupportsServantReflex t m,
246+-- ToHttpApiData a,
247+-- EndpointR t m sublayout f,
248+-- Applicative f
249+-- ) => EndpointR t m (Capture capture a :> sublayout) f where
250+
251+-- type EInputs t m (Capture capture a :> sublayout) f = a ': EInputs t m sublayout f
252+-- type EOutput t m (Capture capture a :> sublayout) f = EOutput t m sublayout f
253+
254+-- mkReq (HCons x xs) = (\a -> over (field @"reqRPathParts") (toUrlPiece a:)) x $
255+-- mkReq @t @m @sublayout @f xs
256+
257+-- getResp = getResp @t @m @sublayout @f
258+
259+-- instance (SupportsServantReflex t m,
260+-- ToHttpApiData a,
261+-- EndpointR t m sublayout f,
262+-- Applicative f
263+-- ) => EndpointR t m (CaptureAll capture a :> sublayout) f where
264+
265+-- type EInputs t m (CaptureAll capture a :> sublayout) f = [a] ': EInputs t m sublayout f
266+-- type EOutput t m (CaptureAll capture a :> sublayout) f = EOutput t m sublayout f
267+
268+-- mkReq (HCons x xs) = (\a -> over (field @"reqRPathParts") (fmap toUrlPiece a <>)) x $
269+-- mkReq @t @m @sublayout @f xs
270+
271+-- getResp = getResp @t @m @sublayout @f
272+
273+
274+-- instance (SupportsServantReflex t m,
275+-- KnownSymbol sym,
276+-- EndpointR t m sublayout f,
277+-- Applicative f
278+-- ) => EndpointR t m (sym :> sublayout) f where
279+
280+-- type EInputs t m (sym :> sublayout) f = EInputs t m sublayout f
281+-- type EOutput t m (sym :> sublayout) f = EOutput t m sublayout f
282+
283+-- mkReq r = over (field @"reqRPathParts") (T.pack (symbolVal (Proxy @sym)) :) $ mkReq @t @m @sublayout @f r
284+
285+-- getResp = getResp @t @m @sublayout @f
286+
287+-- instance (SupportsServantReflex t m,
288+-- ToHttpApiData a,
289+-- EndpointR t m sublayout f,
290+-- KnownSymbol sym,
291+-- Applicative f
292+-- ) => EndpointR t m (QueryParam sym a :> sublayout) f where
293+
294+-- type EInputs t m (QueryParam sym a :> sublayout) f = Maybe a ': EInputs t m sublayout f
295+-- type EOutput t m (QueryParam sym a :> sublayout) f = EOutput t m sublayout f
296+
297+-- mkReq (HCons x xs) = over (field @"reqRParams") ((T.pack $ symbolVal (Proxy @sym),
298+-- QueryPartParam $ fmap (toQueryParam) x) : ) $
299+-- mkReq @t @m @sublayout @f xs
300+
301+
302+-- getResp = getResp @t @m @sublayout @f
303+
304+-- instance (SupportsServantReflex t m,
305+-- ToHttpApiData a,
306+-- EndpointR t m sublayout f,
307+-- KnownSymbol sym,
308+-- Applicative f
309+-- ) => EndpointR t m (QueryParams sym a :> sublayout) f where
310+
311+-- type EInputs t m (QueryParams sym a :> sublayout) f = [a] ': EInputs t m sublayout f
312+-- type EOutput t m (QueryParams sym a :> sublayout) f = EOutput t m sublayout f
313+
314+-- mkReq (HCons x xs) =
315+-- let newParams = (T.pack (symbolVal (Proxy @sym)), QueryPartParams (toQueryParam <$> x))
316+-- in over (field @"reqRParams") (newParams :) $ mkReq @t @m @sublayout @f xs
317+
318+-- getResp = getResp @t @m @sublayout @f
319+
320+-- instance (SupportsServantReflex t m,
321+-- EndpointR t m sublayout f,
322+-- KnownSymbol sym,
323+-- Applicative f
324+-- ) => EndpointR t m (QueryFlag sym :> sublayout) f where
325+
326+-- type EInputs t m (QueryFlag sym :> sublayout) f = Bool ': EInputs t m sublayout f
327+-- type EOutput t m (QueryFlag sym :> sublayout) f = EOutput t m sublayout f
328+
329+-- mkReq (HCons x xs) =
330+-- let newParam = (T.pack (symbolVal (Proxy @sym)), QueryPartFlag x)
331+-- in over (field @"reqRParams") (newParam :) $ mkReq @t @m @sublayout @f xs
332+
333+-- getResp = getResp @t @m @sublayout @f
334+
335+-- instance (SupportsServantReflex t m,
336+-- EndpointR t m sublayout f,
337+-- MimeRender ct a,
338+-- Applicative f
339+-- ) => EndpointR t m (ReqBody (ct ': cts) a :> sublayout) f where
340+
341+
342+-- type EInputs t m (ReqBody (ct ': cts) a :> sublayout) f = a ': EInputs t m sublayout f
343+-- type EOutput t m (ReqBody (ct ': cts) a :> sublayout) f = EOutput t m sublayout f
344+
345+-- mkReq (HCons x xs) =
346+-- set (field @"reqRBody") (Just (mimeRender (Proxy @ct) x, T.pack (show (contentType (Proxy @ct))))) $ mkReq @t @m @sublayout @f xs
347+
348+-- getResp = getResp @t @m @sublayout @f
349+
350+
351+-- instance (SupportsServantReflex t m,
352+-- EndpointR t m sublayout f,
353+-- KnownSymbol sym,
354+-- ToHttpApiData a,
355+-- Applicative f
356+-- ) => EndpointR t m (Header sym a :> sublayout) f where
357+
358+
359+-- type EInputs t m (Header sym a :> sublayout) f = a ': EInputs t m sublayout f
360+-- type EOutput t m (Header sym a :> sublayout) f = EOutput t m sublayout f
361+
362+-- mkReq (HCons x xs) =
363+-- over (field @"reqRHeaders")
364+-- -- ((symbolVal (Proxy @sym), (mimeRender (Proxy @a) x)) :)
365+-- ((T.pack $ symbolVal (Proxy @sym), E.decodeUtf8 $ toHeader x) :)
366+-- $ mkReq @t @m @sublayout @f xs
367+
368+-- getResp = getResp @t @m @sublayout @f
369+
370+-- #if MIN_VERSION_servant(0,13,0)
371+-- -- TODO: Untested
372+-- instance (SupportsServantReflex t m,
373+-- EndpointR t m sublayout f,
374+-- KnownSymbol sym,
375+-- ToHttpApiData a,
376+-- Applicative f
377+-- ) => EndpointR t m (Summary desc :> sublayout) f where
378+
379+-- type EInputs t m (Summary desc :> sublayout) f = EInputs t m sublayout f
380+-- type EOutput t m (Summary desc :> sublayout) f = EOutput t m sublayout f
381+
382+-- mkReq = mkReq @t @m @sublayout @f
383+
384+-- getResp = getResp @t @m @sublayout @f
385+-- #endif
386+
387+
388+-- #if MIN_VERSION_servant(0,13,0)
389+-- -- TODO: Untested
390+-- instance (SupportsServantReflex t m,
391+-- EndpointR t m sublayout f,
392+-- KnownSymbol sym,
393+-- ToHttpApiData a,
394+-- Applicative f
395+-- ) => EndpointR t m (Description desc :> sublayout) f where
396+
397+-- type EInputs t m (Description desc :> sublayout) f = EInputs t m sublayout f
398+-- type EOutput t m (Description desc :> sublayout) f = EOutput t m sublayout f
399+
400+-- mkReq = mkReq @t @m @sublayout @f
401+
402+-- getResp = getResp @t @m @sublayout @f
403+-- #endif
404+
405+-- instance (SupportsServantReflex t m,
406+-- EndpointR t m sublayout f,
407+-- KnownSymbol sym,
408+-- ToHttpApiData a,
409+-- Applicative f
410+-- ) => EndpointR t m (IsSecure :> sublayout) f where
411+
412+
413+-- type EInputs t m (IsSecure :> sublayout) f = EInputs t m sublayout f
414+-- type EOutput t m (IsSecure :> sublayout) f = EOutput t m sublayout f
415+
416+-- mkReq = mkReq @t @m @sublayout @f
417+
418+-- getResp = getResp @t @m @sublayout @f
419+
420+-- instance (SupportsServantReflex t m,
421+-- EndpointR t m sublayout f,
422+-- KnownSymbol sym,
423+-- ToHttpApiData a,
424+-- Applicative f
425+-- ) => EndpointR t m (Vault :> sublayout) f where
426+
427+
428+-- type EInputs t m (Vault :> sublayout) f = EInputs t m sublayout f
429+-- type EOutput t m (Vault :> sublayout) f = EOutput t m sublayout f
430+
431+-- mkReq = mkReq @t @m @sublayout @f
432+
433+-- getResp = getResp @t @m @sublayout @f
434+
435+-- instance (SupportsServantReflex t m,
436+-- EndpointR t m sublayout f,
437+-- Applicative f
438+-- ) => EndpointR t m (BasicAuth realm usr :> sublayout) f where
439+
440+-- type EInputs t m (BasicAuth realm usr :> sublayout) f = BasicAuthData ': EInputs t m sublayout f
441+-- type EOutput t m (BasicAuth realm usr :> sublayout) f = EOutput t m sublayout f
442+
443+-- mkReq (HCons x xs) =
444+-- set (field @"reqRAuthData") (Just x) $ mkReq @t @m @sublayout @f xs
445+
446+-- getResp = getResp @t @m @sublayout @f
447+
448+
449+-- instance ToTuple (HList '[]) where
450+-- type Tuple (HList '[]) = ()
451+-- toTuple _ = ()
452+-- fromTuple _ = HNil
453+
454+-- instance ToTuple (HList '[a]) where
455+-- type Tuple (HList '[a]) = a
456+-- toTuple (HCons a HNil) = a
457+-- fromTuple a = HCons a HNil
458+
459+-- instance ToTuple (HList '[a,b]) where
460+-- type Tuple (HList '[a,b]) = (a,b)
461+-- toTuple (HCons a (HCons b HNil)) = (a,b)
462+-- fromTuple (a,b) = HCons a (HCons b HNil)
463+
464+-- instance ToTuple (HList '[a,b,c]) where
465+-- type Tuple (HList '[a,b,c]) = (a,b,c)
466+-- toTuple (HCons a (HCons b (HCons c HNil))) = (a,b,c)
467+-- fromTuple (a,b,c) = HCons a (HCons b (HCons c HNil))
468+
469+-- type MyAPI = Header "hi" Int :> Capture "hello" Int :> Capture "hello2" Bool :> Get '[JSON] (Headers '[Header "hi" Int] Int)
470+-- :<|> Get '[JSON] Bool
471+
472+-- type MyAPI2 = Raw
473+
474+
475+-- m :: forall t m.MonadWidget t m => m ()
476+-- m = do
477+-- let (a :<|> b) = clientR (Proxy @MyAPI) (Proxy :: Proxy m) (Proxy :: Proxy t)
478+-- (Proxy @[]) undefined
479+-- btn <- button "Hello"
480+-- resA <- a (fmap (:[]) ((2, 5,True) <$ btn))
481+-- resB <- b (fmap (:[]) (() <$ btn))
482+-- display =<< holdDyn "waiting" (leftmost [show <$> resB ])
483+-- -- [show <$> resA
484+-- -- , show <$> resB
485+-- -- ])
486+
487+-- let rawR = clientR (Proxy @MyAPI2) (Proxy @m) (Proxy @t) (Proxy @[]) undefined
488+-- return ()
489+
490+-- data ReqR = ReqR
491+-- { reqRMethod :: T.Text
492+-- , reqRPathParts :: [T.Text]
493+-- , reqRParams :: [(T.Text, QueryPart)]
494+-- , reqRBody :: Maybe (BL.ByteString, T.Text)
495+-- , reqRHeaders :: [(T.Text, T.Text)]
496+-- , reqRRespHeaders :: XhrResponseHeaders
497+-- , reqRAuthData :: Maybe BasicAuthData
498+-- } deriving (Generic)
499+
500+-- instance Show ReqR where
501+-- show (ReqR m pp ps b hs hs' a) = concat $ L.intersperse "\n" [
502+-- "ReqR {"
503+-- , ("reqRMethod = " <> show m)
504+-- , ("reqRPathParts = " <> show pp)
505+-- , ("reqRParams = " <> show ps)
506+-- , ("reqRBody = " <> show b)
507+-- , ("reqRHeaders = " <> show hs)
508+-- , ("reqRRespHeaders = " <> show hs')
509+-- , ("reqRAuthData = " <> maybe "Nothing" (const "Just <<redacted>>") a)
510+-- ]
511+
512+-- emptyReqR :: T.Text -> ReqR
513+-- emptyReqR method = ReqR method [] [] Nothing [] (OnlyHeaders mempty) Nothing
514+
515+-- data QueryPart = QueryPartParam (Maybe Text)
516+-- | QueryPartParams [Text]
517+-- | QueryPartFlag Bool
518+-- deriving (Show)
519+
520+-- mkXhrRequest :: BaseUrl -> ReqR -> XhrRequest T.Text
521+-- mkXhrRequest reqHost req =
522+-- let path = T.intercalate "/" $ reqRPathParts req
523+
524+-- queryPartString :: (Text, QueryPart) -> Maybe Text
525+-- queryPartString (pName, qp) = case qp of
526+
527+-- QueryPartParam Nothing -> Nothing
528+-- QueryPartParam (Just a) -> Just (pName <> "=" <> escape a)
529+
530+-- QueryPartParams ps -> Just . T.intercalate "&"
531+-- $ fmap (\p -> pName <> "=" <> escape p)
532+-- ps
533+
534+-- QueryPartFlag True -> Just pName
535+-- QueryPartFlag False -> Nothing
536+
537+-- (</>) :: Text -> Text -> Text
538+-- x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
539+-- | otherwise = x <> "/" <> y
540+
541+-- queryString = T.intercalate "&" . catMaybes . map queryPartString $ reqRParams req
542+-- url = showBaseUrl reqHost </> path <> if T.null queryString then "" else (T.cons '?' queryString)
543+
544+-- (headers, body) =
545+-- let headers0 = M.fromList $ reqRHeaders req
546+-- in case reqRBody req of
547+-- Nothing -> (headers0, "")
548+-- Just (rb,ct) -> (M.insert "Content-Type" ct headers0,
549+-- E.decodeUtf8 $ BL.toStrict rb)
550+
551+-- xhrReqConfig = def
552+-- { _xhrRequestConfig_headers = headers
553+-- , _xhrRequestConfig_user = E.decodeUtf8 . basicAuthUsername <$> reqRAuthData req
554+-- , _xhrRequestConfig_password = E.decodeUtf8 . basicAuthPassword <$> reqRAuthData req
555+-- , _xhrRequestConfig_responseType = Just XhrResponseType_ArrayBuffer
556+-- , _xhrRequestConfig_sendData = body
557+-- , _xhrRequestConfig_responseHeaders = reqRRespHeaders req
558+-- , _xhrRequestConfig_withCredentials = False
559+-- }
560+-- xhrReq = XhrRequest (reqRMethod req) url xhrReqConfig
561+-- in xhrReq
562+
563+-- transformExample :: MonadWidget t m => ((Int, Char) -> Performable m Bool) -> Event t (Int, Char) -> m (Event t Bool)
564+-- transformExample f = \triggers -> performEvent (f <$> triggers) 1
565+-- -- transformExample = undefined -- let x = performEvent :: _ in undefined
566+
567+-- transform :: MonadWidget t m => forall a. (inp -> IO outp) -> (Event t inp -> m (Event t outp))
568+-- transform = undefined
src/Servant/Reflex/TupleRequestMulti.hsnew file+153
@@ -0,0 +1,153 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE GADTs #-}
8+{-# LANGUAGE InstanceSigs #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
12+{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE TupleSections #-}
15+{-# LANGUAGE TypeApplications #-}
16+{-# LANGUAGE TypeFamilies #-}
17+{-# LANGUAGE TypeOperators #-}
18+{-# LANGUAGE UndecidableInstances #-}
19+
20+module Servant.Reflex.TupleRequestMulti where
21+
22+import Control.Arrow (first, second)
23+import Control.Lens
24+import qualified Data.ByteString.Lazy as BL
25+import Data.CaseInsensitive (mk)
26+import qualified Data.Default as Default
27+import Data.Generics.Product
28+import qualified Data.List as L
29+import qualified Data.Map as M
30+import Data.Maybe (catMaybes)
31+import Data.Proxy
32+import Data.Semigroup ((<>))
33+import qualified Data.Set as Set
34+import Data.Text (Text)
35+import qualified Data.Text as T
36+import qualified Data.Text.Encoding as E
37+import GHC.Generics
38+import GHC.TypeLits (KnownSymbol, symbolVal)
39+import Language.Javascript.JSaddle (MonadJSM)
40+import Reflex hiding (HList (..))
41+import Servant.API hiding (HList (..))
42+import Servant.Common.BaseUrl
43+import Servant.Common.Req hiding (QueryPart (..))
44+
45+import Reflex.Dom.Xhr hiding (HList (..))
46+
47+import Servant.Reflex (BuildHeaderKeysTo (..), toHeaders)
48+import Servant.Reflex.Internal.TupleRequest
49+
50+{-
51+TODO:
52+ - [x] :<|>
53+ - [x] Verb content
54+ - [x] Verb nocontent
55+ - [x] Verb (Headers content)
56+ - [x] Verb (Headers nocontent)
57+ - [ ] Stream
58+ - [X] Capture :> ...
59+ - [ ] Capture' :> ...
60+ - [x] CaptureAll :> ...
61+ - [x] QueryParam :> ...
62+ - [x] QueryParams :> ...
63+ - [x] QueryFlag :> ...
64+ - [x] ReqBody :> ...
65+ - [ ] Header' :> ...
66+ - [x] Header :> ...
67+ - [x] Summary :> ...
68+ - [x] Description :> ...
69+ - [x] path :> ...
70+ - [ ] RemoteHost :> ...
71+ - [x] IsSecure :> ...
72+ - [x] Vault :> ...
73+ - [ ] WithNamedContext :> ...
74+ - [ ] AuthenticatedRequest :> ...
75+ - [x] BasicAuth :> ...
76+ - [ ] EmptyClient :> ...
77+ - [ ] EmptyClient
78+ - [ ] HttpVersion :> ...
79+-}
80+
81+clientR
82+ :: forall t m layout f
83+ . ( HasClientR t m (Seal layout) f
84+ -- , Applicative f
85+ , Reflex t
86+ )
87+ => Proxy layout
88+ -> Proxy (m :: * -> *)
89+ -> Proxy t
90+ -> Proxy f
91+ -> Dynamic t BaseUrl
92+ -> ClientR t m (Seal layout) f
93+clientR _ mP tP fP url =
94+ clientRWithRoute (Proxy :: Proxy (Seal layout)) mP fP undefined url undefined
95+
96+-- | A class for generating top-level the API client function
97+class HasClientR t (m :: * -> *) api (f :: * -> *) where
98+ type ClientR t m api f :: *
99+ clientRWithRoute :: Proxy api -> Proxy m -> Proxy f
100+ -> Dynamic t (f (Req t)) -> Dynamic t BaseUrl
101+ -> ClientOptions -> ClientR t m api f
102+
103+
104+
105+------------------------------------------------------------------------------
106+-- | :<|> APIs
107+instance {-# OVERLAPPING #-}
108+ (HasClientR t m (layoutLeft) f,
109+ HasClientR t m (layoutRight) f
110+ ) => HasClientR t m (layoutLeft :<|> layoutRight) f where
111+ type ClientR t m (layoutLeft :<|> layoutRight) f =
112+ ClientR t m layoutLeft f :<|> ClientR t m layoutRight f
113+ clientRWithRoute _ tP mP _ url opts =
114+ clientRWithRoute (Proxy @layoutLeft) tP mP undefined url opts
115+ :<|>
116+ clientRWithRoute (Proxy @layoutRight) tP mP undefined url opts
117+
118+#if MIN_VERSION_servant (0,13,0)
119+-- TODO: Untested
120+instance HasClientR t m EmptyAPI f where
121+ type ClientR t m EmptyAPI f = EmptyAPI
122+ clientRWithRoute _ _ _ _ _ _ = EmptyAPI
123+#endif
124+
125+
126+-- | Turn a single Endpoint into a client, by turning the input HList
127+-- into a tuple
128+instance {-# OVERLAPPING #-} forall t m layout f .(EndpointR t m layout f,
129+ ToTuple (HList (EInputs t m layout f)),
130+ Monad m
131+ , Traversable f
132+ , SupportsServantReflex t m
133+ ) => HasClientR t (m :: * -> *) (Sealed layout) (f :: * -> *) where
134+ type ClientR t m (Sealed layout) f =
135+ Event t (f (Tuple (HList (EInputs t m layout f))))
136+ -> m (Event t (f (EOutput t m layout f)))
137+ clientRWithRoute _ mP fP dReq url opts (reqs :: Event t (f (Tuple (HList (EInputs t m layout f ))))) = do
138+ let reqTuples = fmap (fromTuple @(HList (EInputs t m layout f ))) <$> reqs
139+
140+ -- TODO: replace these 2 lines w/ the next 2. The trace is for library debugging
141+ reqRs = traceEventWith (concat . fmap ((<> "\n") . show)) $ fmap (mkReq @t @m @layout @f) <$> reqTuples
142+ reqs' = ffor (attachPromptlyDyn url reqRs) $ \(bUrl, rs) -> mkXhrRequest bUrl <$> rs
143+ -- reqs' = ffor (attachPromptlyDyn url reqTuples) $ \(bUrl, inputs) ->
144+ -- mkXhrRequest bUrl . mkReq @t @m @layout @f <$> inputs
145+ resps <- performRequestsAsync $ reqs'
146+ return (fmap (getResp @t @m @layout @f) <$> resps)
147+
148+
149+instance (SupportsServantReflex t m, Traversable f) => HasClientR t m SealedRaw f where
150+ type ClientR t m SealedRaw f = Event t (f (XhrRequest T.Text)) -> m (Event t (f XhrResponse))
151+ clientRWithRoute _ mP fP dReq url opts reqs = performRequestsAsync reqs
152+
153+
testdriver/default.nixnew file+16
@@ -0,0 +1,16 @@
1+{ mkDerivation, base, hspec, hspec-webdriver, stdenv, text
2+, webdriver
3+}:
4+mkDerivation {
5+ pname = "testdriver";
6+ version = "0.2";
7+ src = ./.;
8+ isLibrary = false;
9+ isExecutable = true;
10+ executableHaskellDepends = [
11+ base hspec hspec-webdriver text webdriver
12+ ];
13+ description = "Selenium test driver for Servant reflex";
14+ license = stdenv.lib.licenses.bsd3;
15+ hydraPlatforms = stdenv.lib.platforms.none;
16+}
testdriver/shell.nixnew file+1
@@ -0,0 +1 @@
1+(import ../release3.nix {}).testdriver.env
testserver/Main.hs+12−8
@@ -20,16 +20,16 @@ import Data.Text hiding (head, length, map,
2020import qualified Data.Text as T
2121import qualified Data.Text.IO as T
2222import GHC.Generics
23−import Servant.Server.Internal.SnapShims
24−import Snap.Core
23+import Snap.Core hiding (addHeader)
2524import Snap.Http.Server
2625
2726import Servant
28−import Servant.Server
27+import Servant.Server ()
28+import System.Directory
2929-- import Snap.Util.FileServe
3030import System.Environment (lookupEnv)
3131import API
32−import Snap
32+import Snap hiding (addHeader)
3333
3434-- * Example
3535
@@ -52,7 +52,7 @@ data App = App { appStaticDir :: String }
5252--
5353-- Each handler runs in the 'ExceptT ServantErr IO' monad.
5454server :: Server API '[BasicAuthCheck (Handler App App) ()] (Handler App App)
55−server = return () :<|> return 100 :<|> sayhi :<|> dbl
55+server = return () :<|> return 100 :<|> dblint :<|> sayhi :<|> dbl
5656 :<|> multi :<|> qna :<|> serveSecret
5757 :<|> statics
5858 where sayhi :: Maybe Text -> [Text] -> Bool -> Handler App App Text
@@ -66,15 +66,16 @@ server = return () :<|> return 100 :<|> sayhi :<|> dbl
6666 | otherwise = T.intercalate ", " (L.init greetings)
6767 <> ", and " <> L.last greetings <> ", "
6868 return . modifier $ greetPart <> n
69+ dblint = return . (* 2)
6970 dbl x = if x `elem` [4,13]
7071 then throwError $ err500 { errBody = "No unlucky numbers please" }
7172 else return $ x * 2
7273 multi = return . bool "Box unchecked" "Box Checked"
73− qna q = do
74+ qna q nm fav = do
7475 liftIO $ do
7576 putStrLn $ "qna got: " ++ show q
7677 T.putStrLn $ unQuestion q
77− return $ Answer $ unQuestion q
78+ return $ addHeader (fromMaybe 10 nm) $ addHeader (fromMaybe False fav) $ (Answer $ unQuestion q)
7879 serveSecret _ = do
7980 req <- getRequest
8081 liftIO $ putStrLn (show req)
@@ -99,4 +100,7 @@ initApp = makeSnaplet "myapp" "example" Nothing $ do
99100
100101-- Put this all to work!
101102main :: IO ()
102−main = serveSnaplet mempty initApp
103+main = commandLineConfig emptyConfig >>= \cfg -> print cfg >> serveSnaplet cfg initApp
104+-- main = serveSnaplet (defaultConfig { accessLog = "/tmp/log/testserver-access.log"
105+-- , errorLog = "/tmp/log/testserver-error.log"
106+-- }) initApp
testserver/default.nixnew file+14
@@ -0,0 +1,14 @@
1+{ mkDerivation, aeson, base, servant, servant-snap, snap, snap-core
2+, snap-server, stdenv, text
3+}:
4+mkDerivation {
5+ pname = "testserver";
6+ version = "0.1.0.0";
7+ src = ./.;
8+ isLibrary = false;
9+ isExecutable = true;
10+ executableHaskellDepends = [
11+ aeson base servant servant-snap snap snap-core snap-server text
12+ ];
13+ license = stdenv.lib.licenses.bsd3;
14+}
testserver/shell.nixnew file+1
@@ -0,0 +1 @@
1+(import ../release.nix {}).testserver.env
testserver/testserver.cabal+3−3
@@ -19,13 +19,13 @@ executable back
1919 main-is: Main.hs
2020 -- other-modules:
2121 -- other-extensions:
22− build-depends: aeson >= 0.9 && < 1.4
23− , base >=4.8 && <4.12
22+ build-depends: aeson >= 0.9 && < 1.5
23+ , base >=4.8 && <4.14
2424 , mtl
2525 , snap >= 1.0 && < 1.2
2626 , snap-server >= 1.0 && < 1.2
2727 , snap-core >= 1.0 && < 1.1
28− , servant >= 0.8 && < 0.15
28+ , servant >= 0.8 && < 0.18
2929 , servant-snap >= 0.8 && < 0.9
3030 , text >= 1.0 && < 1.3
3131 -- hs-source-dirs: