git.iko.soy/typeable/servant-reflex
on master help sign in
27 files changed+174−933
since splitsnapshots
unifiedsplit
.gitignore+2 .travis.yml+4−4 ChangeLog.md+8 README.md+1−1 default.nix+9−17 exec/API.hs+1−4 exec/Example.hs+18−157 nix.conf−3 nix/nix.conf+2−1 nix/reflex-platform.nix+2−2 nix/servant-reflex.nix−24 nix/servant-snap.nix+4−6 nix/testdriver.nix−3 release.nix−27 servant-reflex.cabal+16−11 src/Servant/Common/BaseUrl.hs+9−6 src/Servant/Common/Req.hs+6−4 src/Servant/Reflex.hs+45−27 src/Servant/Reflex/Multi.hs+35−22 src/Servant/Reflex/RequestAPI.hs−567 testdriver/default.nix−16 testdriver/shell.nix−1 testserver/Main.hs+8−12 testserver/default.nix−14 testserver/shell.nix−1 testserver/testserver.cabal+3−3 travis.nix+1
.gitignore+2
@@ -7,3 +7,5 @@ log
77*.stats
88out
99/.stack-work/
10+TAGS
11+dist-newstyle
.travis.yml+4−4
@@ -1,17 +1,17 @@
11language: nix
2−nix: 2.1.1
2+nix: 2.3.6
33sudo: require
44
5−install:
5+before_install:
66 - sudo cp -rT $TRAVIS_BUILD_DIR/nix /etc/nix
77
88before_script:
9+ - sudo systemctl restart nix-daemon.service
910 - git config --global user.email "travis-ci@example.com"
1011 - git config --global user.name "Travis-CI"
1112
1213script:
13− - nix-env -iA cachix -f https://cachix.org/api/v1/install
14− - cachix use imalsogreg
1514 - nix-build travis.nix -A build
15+ - nix-build travis.nix -A buildGhc
1616 - nix-build travis.nix -A testserver
1717 - nix-build travis.nix -A testresults
ChangeLog.md+8
@@ -1,3 +1,11 @@
1+0.3.5
2+-----
3+
4+ - Require reflex-dom-core >= 0.6 (for internal handling of case-insensitive headers)
5+ - Require servant >= 0.16
6+ - Eliminate use of deprecated widgets from reflex-dom-core
7+ - Bump the version of reflex-platform used in nix-shell and test suite
8+
190.3.4
210-----
311
README.md+1−1
@@ -7,7 +7,7 @@
77Keeping your frontend in sync with your API server can be difficult - when the
88API changes its input parameters or return type, XHR requests from the
99frontend will fail at runtime. If your API is defined by
10−[servant](haskell-servant.readthedocs.io) combinators, you can use
10+[servant](https://haskell-servant.readthedocs.io) combinators, you can use
1111`servant-reflex` to share the API between the server and frontend.
1212Syncronization between is checked at compile time, and rather than building XHR
1313requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
default.nix+9−17
@@ -1,28 +1,20 @@
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-core
4−, safe, scientific, servant, servant-auth, stdenv
5−, string-conversions, text, transformers
1+{ mkDerivation, base, bytestring, case-insensitive, containers
2+, data-default, exceptions, ghcjs-dom, http-api-data, http-media
3+, jsaddle, mtl, network-uri, reflex, reflex-dom, reflex-dom-core
4+, safe, servant, servant-auth, stdenv, string-conversions, text
5+, transformers
66}:
77mkDerivation {
88 pname = "servant-reflex";
9− version = "0.3.4";
10− src = builtins.filterSource
11− (path: type:
12− baseNameOf path != "result"
13− && baseNameOf path != "nix"
14− ) ./.;
15− configureFlags = [ "-fexample" ];
9+ version = "0.3.6";
10+ src = ./.;
1611 isLibrary = true;
1712 isExecutable = true;
1813 libraryHaskellDepends = [
1914 base bytestring case-insensitive containers data-default exceptions
2015 ghcjs-dom http-api-data http-media jsaddle mtl network-uri reflex
21− reflex-dom-core safe servant servant-auth string-conversions text
22− transformers
23− ];
24− executableHaskellDepends = [
25− aeson base reflex reflex-dom-core scientific servant text
16+ reflex-dom reflex-dom-core safe servant servant-auth string-conversions
17+ text transformers
2618 ];
2719 description = "servant API generator for reflex apps";
2820 license = stdenv.lib.licenses.bsd3;
exec/API.hs+1−4
@@ -31,7 +31,6 @@ 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
3534 :<|> "sayhi" :> QueryParam "username" Text
3635 :> QueryParams "greetings" Text
3736 :> QueryFlag "gusto"
@@ -40,9 +39,7 @@ type API = "getunit" :> Get '[JSON] ()
4039 :> Post '[JSON] Double
4140 :<|> "a" :> "b" :> QueryFlag "gusto" :> Get '[JSON] Text
4241 :<|> "qna" :> ReqBody '[JSON] Question
43− :> Header "qnum" Int
44− :> Header "is-favorite" Bool
45− :> Post '[JSON] (Headers '[Header "anum" Int, Header "was-favorite" Bool] Answer)
42+ :> Post '[JSON] Answer
4643 :<|> "secret" :> BasicAuth "realm" () :> Get '[JSON] Int
4744 :<|> Raw
4845
exec/Example.hs+18−157
@@ -5,8 +5,6 @@
55{-# LANGUAGE RankNTypes #-}
66{-# LANGUAGE RecursiveDo #-}
77{-# LANGUAGE ScopedTypeVariables #-}
8−{-# LANGUAGE TypeApplications #-}
9−{-# LANGUAGE TypeOperators #-}
108
119module Main where
1210
@@ -23,157 +21,21 @@ import Servant.API
2321import API
2422import Data.Proxy
2523import Text.Read (readMaybe)
26−import Reflex.Dom.Core hiding (run)
24+import Reflex.Dom.Core hiding (run, mainWidget)
25+import Reflex.Dom (mainWidget)
2726------------------------------------------------------------------------------
2827import Servant.Reflex
2928import Servant.Reflex.Multi
30−import qualified Servant.Reflex.RequestAPI as SR
3129
3230
3331api :: Proxy API
3432api = Proxy
3533
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−
5334main :: IO ()
5435main = mainWidget $ do
55− divClass "example-tuple" runTuple
5636 divClass "example-base" run
5737 divClass "example-multi" runMulti
5838
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−
17739
17840runMulti :: forall t m. (SupportsServantReflex t m,
17941 DomBuilder t m,
@@ -184,7 +46,7 @@ runMulti :: forall t m. (SupportsServantReflex t m,
18446runMulti = do
18547 url <- baseUrlWidget
18648 el "br" blank
187− let (_ :<|> _ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ :<|> _ :<|> _ ) =
49+ let (_ :<|> _ :<|> sayHi :<|> dbl :<|> _ :<|> _ :<|> _ :<|> _ ) =
18850 clientA api (Proxy :: Proxy m) (Proxy :: Proxy []) (Proxy :: Proxy Int) url
18951
19052 num :: Dynamic t (Either Text Double) <- fmap (note "No read" . readMaybe . T.unpack) . value <$> textInput def
@@ -237,7 +99,7 @@ run = mdo
23799 -- Name the computed API client functions
238100 let tweakRequest = ClientOptions $ \r -> do
239101 return $ r & withCredentials .~ True
240− let (getUnit :<|> getInt :<|> dblInt :<|> sayhi :<|> dbl
102+ let (getUnit :<|> getInt :<|> sayhi :<|> dbl
241103 :<|> multi :<|> qna :<|> secret :<|> doRaw) =
242104 clientWithOpts api (Proxy :: Proxy m) (Proxy :: Proxy Int) url tweakRequest
243105
@@ -297,7 +159,7 @@ run = mdo
297159 holdDyn "(no errors)" (fmapMaybe reqFailure (dblResp))
298160 el "br" (return ())
299161 divClass "double-result" $ el "p" $ dynText =<<
300− holdDyn "No number yet" (fmap tshow $
162+ holdDyn "No number yet" (fmap tShow $
301163 fmapMaybe reqSuccess (dblResp))
302164 return dblBtn
303165
@@ -306,7 +168,7 @@ run = mdo
306168 b <- value <$> checkbox False def
307169 mpGo <- button "Test"
308170 multiResp <- multi b (tag (current reqCount) mpGo)
309− dynText =<< holdDyn "No res yet" (fmap tshow $
171+ dynText =<< holdDyn "No res yet" (fmap tShow $
310172 fmapMaybe reqSuccess $
311173 (multiResp))
312174 return mpGo
@@ -320,15 +182,14 @@ run = mdo
320182 txt <- value <$> textInput def
321183 ev <- fmap (1 <$) $ button "Question"
322184 let dQ = Right . Question <$> traceDyn "will send: " txt
323− -- rr <- qna dQ ev _ _
185+ rr <- qna dQ ev
324186 el "p" $
325− return ()
326− -- dynText =<< holdDyn "No Answer" (unAnswer <$> fmapMaybe reqSuccess rr)
187+ dynText =<< holdDyn "No Answer" (unAnswer <$> fmapMaybe reqSuccess rr)
327188
328189 divClass "demo-group" $ do
329190 un <- fmap value $ text "Username" >> textArea def
330191 pw <- fmap value $ text "Unhidden PW" >> textArea def
331− let ba :: Dynamic t BasicAuthData = BasicAuthData
192+ let ba :: Dynamic t (BasicAuthData ) = BasicAuthData
332193 <$> fmap T.encodeUtf8 un
333194 <*> fmap T.encodeUtf8 pw
334195 b <- button "Get secret"
@@ -339,20 +200,20 @@ run = mdo
339200
340201showXhrResponse :: XhrResponse -> Text
341202showXhrResponse (XhrResponse stat stattxt rbmay rtmay respHeaders) =
342− T.unlines ["stat: " <> tshow stat
343− ,"stattxt: " <> tshow stattxt
203+ T.unlines ["stat: " <> tShow stat
204+ ,"stattxt: " <> tShow stattxt
344205 ,"resp: " <> maybe "" showRB rbmay
345− ,"rtext: " <> tshow rtmay
346− ,"rHeaders: " <> tshow respHeaders]
206+ ,"rtext: " <> tShow rtmay
207+ ,"rHeaders: " <> tShow respHeaders]
347208
348−tshow :: Show a => a -> Text
349−tshow = T.pack . show
209+tShow :: Show a => a -> Text
210+tShow = T.pack . show
350211
351212showRB :: XhrResponseBody -> Text
352−showRB (XhrResponseBody_Default t) = tshow t
353−showRB (XhrResponseBody_Text t) = tshow t
213+showRB (XhrResponseBody_Default t) = tShow t
214+showRB (XhrResponseBody_Text t) = tShow t
354215showRB (XhrResponseBody_Blob t) = "<Blob>"
355−showRB (XhrResponseBody_ArrayBuffer t) = tshow t
216+showRB (XhrResponseBody_ArrayBuffer t) = tShow t
356217
357218note :: e -> Maybe a -> Either e a
358219note e = maybe (Left e) Right
nix.confdeleted−3
@@ -1,3 +0,0 @@
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/nix.conf+2−1
@@ -1,2 +1,3 @@
1−substituters = https://cache.nixos.org https://nixcache.reflex-frp.org
1+substituters = https://cache.nixos.org/ https://nixcache.reflex-frp.org/
22trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= ryantrinkle.com-1:JJiAKaRv9mWgpVAz8dwewnZe0AzzEAzPkagE9SP5NWI=
3+trusted-users = travis
nix/reflex-platform.nix+2−2
@@ -1,7 +1,7 @@
11let
2− rev = "a15d3a2411e7ca7d4ee4853b57c72fe83faee272";
2+ rev = "1aba6f367982bd6dd78ec2fda75ab246a62d32c5" ;
33in import (builtins.fetchTarball
44 {
55 url = "https://github.com/reflex-frp/reflex-platform/archive/${rev}.tar.gz";
6− sha256 = "1dsvw0lah7761vndip1hqal4fjpjv84ravinnfhy83jgfav5ivna";
6+ sha256 = "137vg6i8yy4lp6rz4wbvvhfcc9d9qf7j1b3fnng3r7sj63k7j664" ;
77 }) {}
nix/servant-reflex.nixdeleted−24
@@ -1,24 +0,0 @@
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/servant-snap.nix+4−6
@@ -1,9 +1,7 @@
1−# Servant-snap is stuck a bit behind atm. But we have a fork that mostly works
2−# except for streaming, which is good enough for this test server. See:
3−# https://github.com/haskell-servant/servant-snap/issues/20
1+# Servant-snap is marked broken in nixpkgs
42{ nixpkgs ? import <nixpkgs> {}}: self: super:
53self.callCabal2nix "servant-snap" (nixpkgs.fetchgit {
6− url = "https://github.com/antislava/servant-snap.git";
7− rev = "fc9658e8f52ebce9e4f304ea0c6705d697d4fa84";
8− sha256 = "0zlipmx1fb73mhpnndwmdmigxxrsdnsnb1157pgsrxpx89l9pjig";
4+ url = "https://github.com/haskell-servant/servant-snap.git";
5+ rev = "af5172a6de5bb2a07eb1bf4c85952075ec6ecdf3";
6+ sha256 = "sha256-fzzQGwG8Ko72OO/kwLMG52eNZSt3F2dxhNgM9gaO4yQ=";
97}) {}
nix/testdriver.nixdeleted−3
@@ -1,3 +0,0 @@
1−{ reflexPlatform }:
2−
3−reflexPlatform.ghc.callPackage ../testdriver/default.nix {}
release.nixdeleted−27
@@ -1,27 +0,0 @@
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+16−11
@@ -1,5 +1,5 @@
11Name: servant-reflex
2−Version: 0.3.4
2+Version: 0.3.6
33Synopsis: servant API generator for reflex apps
44Description: Generate reflex-compatible client functions from servant API descriptions
55License: BSD3
@@ -17,12 +17,10 @@ Flag Example
1717
1818library
1919 exposed-modules:
20− Servant.Reflex
21− Servant.Reflex.Multi
22−
23− other-modules:
2420 Servant.Common.BaseUrl
2521 Servant.Common.Req
22+ Servant.Reflex
23+ Servant.Reflex.Multi
2624
2725 hs-source-dirs: src
2826 build-depends:
@@ -31,19 +29,17 @@ library
3129 case-insensitive >= 1.2.0.4 && < 1.3,
3230 containers >= 0.5.6 && < 0.7,
3331 data-default >= 0.5 && < 0.8,
34− generic-lens,
3532 exceptions >= 0.8 && < 0.11,
3633 ghcjs-dom >= 0.2 && < 0.10,
3734 http-api-data >= 0.3.6 && < 0.5,
3835 http-media >= 0.6 && < 0.9,
3936 jsaddle >= 0.8 && < 0.10,
40− lens,
4137 mtl >= 2.2.1 && < 2.3,
4238 network-uri >= 2.6 && < 2.7,
43− reflex >= 0.5 && < 0.7,
44− reflex-dom-core >= 0.4 && < 0.6,
39+ reflex >= 0.5 && < 0.9,
40+ reflex-dom-core >= 0.6 && < 0.8,
4541 safe >= 0.3.9 && < 0.4,
46− servant >= 0.8 && < 0.18,
42+ servant >= 0.16 && < 0.19,
4743 servant-auth >= 0.2.1 && < 0.4,
4844 string-conversions >= 0.4 && < 0.5,
4945 text >= 1.2 && < 1.3,
@@ -58,7 +54,16 @@ executable example
5854 buildable: True
5955 else
6056 buildable: False
61− build-depends: aeson, reflex, servant-reflex, base, scientific, servant, reflex-dom-core, text
57+ build-depends:
58+ aeson,
59+ reflex,
60+ servant-reflex,
61+ base,
62+ scientific,
63+ servant,
64+ reflex-dom,
65+ reflex-dom-core,
66+ text
6267 default-language: Haskell2010
6368 main-is: Example.hs
6469 other-modules: API
src/Servant/Common/BaseUrl.hs+9−6
@@ -1,4 +1,5 @@
11{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE CPP #-}
23{-# LANGUAGE DeriveDataTypeable #-}
34{-# LANGUAGE DeriveGeneric #-}
45{-# LANGUAGE FlexibleContexts #-}
@@ -31,8 +32,11 @@ import Reflex
3132import Reflex.Dom.Core
3233import Text.Read
3334
34−
35+#if !MIN_VERSION_reflex_dom_core(0,7,0)
3536type SupportsServantReflex t m = (Reflex t, TriggerEvent t m, PerformEvent t m, HasWebView (Performable m), MonadJSM (Performable m))
37+#else
38+type SupportsServantReflex t m = (Reflex t, TriggerEvent t m, PerformEvent t m, MonadJSM (Performable m))
39+#endif
3640
3741-- | URI scheme to use
3842data Scheme =
@@ -85,15 +89,14 @@ baseUrlWidget = elClass "div" "base-url" $ do
8589 where pathWidget :: m (Dynamic t BaseUrl)
8690 pathWidget = do
8791 text "Url base path"
88− t <- textInput (def {_textInputConfig_attributes =
89− constDyn ("placeholder" =: "/a/b")})
92+ t <- inputElement def
9093 return $ BasePath <$> value t
9194 fullUrlWidget :: m (Dynamic t BaseUrl)
9295 fullUrlWidget = do
9396 schm <- dropdown Https (constDyn $ Https =: "https" <> Http =: "http") def
94− srv <- textInput def {_textInputConfig_attributes = constDyn $ "placeholder" =: "example.com"}
97+ srv <- inputElement def
9598 text ":"
96− prt <- textInput def { _textInputConfig_attributes = constDyn $ "placeholder" =: "80"}
99+ prt <- inputElement def
97100 port :: Dynamic t Int <- holdDyn 80 (fmapMaybe (readMaybe . T.unpack) $ updated (value prt))
98− path <- textInput def { _textInputConfig_attributes = constDyn $ "placeholder" =: "a/b" }
101+ path <- inputElement def
99102 return $ BaseFullUrl <$> value schm <*> value srv <*> port <*> value path
src/Servant/Common/Req.hs+6−4
@@ -19,6 +19,7 @@ import Control.Monad.IO.Class (MonadIO, liftIO)
1919import Data.Bifunctor (first)
2020import qualified Data.ByteString.Builder as Builder
2121import qualified Data.ByteString.Lazy.Char8 as BL
22+import Data.ByteString (ByteString)
2223import qualified Data.Map as Map
2324import Data.Maybe (catMaybes, fromMaybe)
2425import Data.Functor.Compose
@@ -244,7 +245,7 @@ reqToReflexRequest reqMeth reqHost req =
244245 , _xhrRequestConfig_user = Nothing
245246 , _xhrRequestConfig_password = Nothing
246247 , _xhrRequestConfig_responseType = Just XhrResponseType_ArrayBuffer
247− , _xhrRequestConfig_sendData = ""
248+ , _xhrRequestConfig_sendData = mempty
248249 , _xhrRequestConfig_withCredentials = False
249250 }
250251 Just rBody -> liftA2 mkConfigBody xhrHeaders rBody
@@ -297,7 +298,9 @@ performRequests reqMeth rs reqHost opts trigger = do
297298performSomeRequestsAsync
298299 :: (MonadIO (Performable m),
299300 MonadJSM (Performable m),
301+#if !MIN_VERSION_reflex_dom_core(0,7,0)
300302 HasWebView (Performable m),
303+#endif
301304 PerformEvent t m,
302305 TriggerEvent t m,
303306 Traversable f,
@@ -335,9 +338,9 @@ performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs
335338
336339
337340
338−type XhrPayload = T.Text
341+type XhrPayload = ByteString
339342bytesToPayload :: BL.ByteString -> XhrPayload
340−bytesToPayload = TE.decodeUtf8 . BL.toStrict
343+bytesToPayload = BL.toStrict
341344
342345
343346performRequestsCT
@@ -427,4 +430,3 @@ builderToText = TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString
427430
428431escape :: T.Text -> T.Text
429432escape = T.pack . N.escapeURIString (not . N.isReserved) . T.unpack . TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString . toEncodedUrlPiece
430−
src/Servant/Reflex.hs+45−27
@@ -39,34 +39,36 @@ module Servant.Reflex
3939
4040------------------------------------------------------------------------------
4141import Control.Applicative
42−import Data.Monoid ((<>))
43−import qualified Data.Set as Set
44−import qualified Data.Text.Encoding as E
45−import Data.CaseInsensitive (mk)
42+import qualified Data.CaseInsensitive as CI
4643import Data.Functor.Identity
47−import Data.Proxy (Proxy (..))
4844import qualified Data.Map as Map
45+import Data.Monoid ((<>))
46+import Data.Proxy (Proxy (..))
47+import qualified Data.Set as Set
4948import Data.Text (Text)
5049import qualified Data.Text as T
50+import qualified Data.Text.Encoding as E
5151import GHC.Exts (Constraint)
5252import GHC.TypeLits (KnownSymbol, symbolVal)
53−import Servant.API ((:<|>)(..),(:>), BasicAuth,
54− BasicAuthData, BuildHeadersTo(..),
55− Capture, contentType, Header,
56− Headers(..), HttpVersion, IsSecure,
57− MimeRender(..), MimeUnrender,
58− NoContent, QueryFlag, QueryParam,
59− QueryParams, Raw, ReflectMethod(..),
53+import Servant.API ((:<|>) (..), (:>), BasicAuth,
54+ BasicAuthData, BuildHeadersTo (..),
55+ Capture, Header, Headers (..),
56+ HttpVersion, IsSecure,
57+ MimeRender (..), MimeUnrender,
58+ NoContent, QueryFlag, QueryParam',
59+ QueryParams, Raw, ReflectMethod (..),
6060 RemoteHost, ReqBody,
61− ToHttpApiData(..), Vault, Verb)
61+ ToHttpApiData (..), Vault, Verb,
62+ contentType)
63+import Servant.API.Description (Summary)
6264import qualified Servant.Auth as Auth
6365
6466import Reflex.Dom.Core (Dynamic, Event, Reflex,
65− XhrRequest(..),
66− XhrResponseHeaders(..),
67− XhrResponse(..), attachPromptlyDynWith, constDyn, ffor, fmapMaybe,
68− leftmost, performRequestsAsync,
69− )
67+ XhrRequest (..), XhrResponse (..),
68+ XhrResponseHeaders (..),
69+ attachPromptlyDynWith, constDyn, ffor,
70+ fmapMaybe, leftmost,
71+ performRequestsAsync)
7072------------------------------------------------------------------------------
7173import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
7274 showBaseUrl,
@@ -101,7 +103,7 @@ import Servant.Common.Req (ClientOptions(..),
101103-- >
102104-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
103105-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
104−-- -> m (Event t (l, ReqResult Book)))
106+-- > -> m (Event t (l, ReqResult Book)))
105107-- > (getAllBooks :<|> postNewBook) = client myApi host
106108-- > where host = constDyn $ BaseUrl Http "localhost" 8080
107109client
@@ -226,22 +228,26 @@ instance {-# OVERLAPPING #-}
226228
227229toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
228230toHeaders r =
229− let toBS = E.encodeUtf8
230− hdrs = maybe []
231− (\xhr -> fmap (\(h,v) -> (mk (toBS h), toBS v))
231+ let hdrs = maybe []
232+ (\xhr -> fmap (\(h,v) -> (CI.map E.encodeUtf8 h, E.encodeUtf8 v))
232233 (Map.toList $ _xhrResponse_headers xhr))
233234 (response r)
234235 in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
235236
237+
236238class BuildHeaderKeysTo hs where
237− buildHeaderKeysTo :: Proxy hs -> [T.Text]
239+ buildHeaderKeysTo :: Proxy hs -> [CI.CI T.Text]
238240
239241instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
240242 where buildHeaderKeysTo _ = []
241243
242244instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
243245 => BuildHeaderKeysTo ((Header h v) ': xs) where
244− buildHeaderKeysTo _ = T.pack (symbolVal (Proxy :: Proxy h)) : buildHeaderKeysTo (Proxy :: Proxy xs)
246+ buildHeaderKeysTo _ =
247+ let
248+ thisKey = CI.mk $ T.pack (symbolVal (Proxy :: Proxy h))
249+ in thisKey : buildHeaderKeysTo (Proxy :: Proxy xs)
250+
245251
246252-- HEADERS Verb (Content) --
247253-- Headers combinator not treated in fully general case,
@@ -323,6 +329,19 @@ instance HasClient t m sublayout tag
323329 clientWithRouteAndResultHandler Proxy =
324330 clientWithRouteAndResultHandler (Proxy :: Proxy sublayout)
325331
332+-- | Using a 'Summary' combinator in your API doesn't affect the client
333+-- functions.
334+instance (HasClient t m sublayout tag, KnownSymbol sym)
335+ => HasClient t m (Summary sym :> sublayout) tag where
336+
337+ type Client t m (Summary sym :> sublayout) tag =
338+ Client t m sublayout tag
339+
340+ clientWithRouteAndResultHandler Proxy =
341+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout)
342+
343+
344+
326345
327346-- | If you use a 'QueryParam' in one of your endpoints in your API,
328347-- the corresponding querying function will automatically take
@@ -351,9 +370,9 @@ instance HasClient t m sublayout tag
351370-- > -- 'getBooksBy Nothing' for all books
352371-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
353372instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
354− => HasClient t m (QueryParam sym a :> sublayout) tag where
373+ => HasClient t m (QueryParam' mods sym a :> sublayout) tag where
355374
356− type Client t m (QueryParam sym a :> sublayout) tag =
375+ type Client t m (QueryParam' mods sym a :> sublayout) tag =
357376 Dynamic t (QParam a) -> Client t m sublayout tag
358377
359378 -- if mparam = Nothing, we don't add it to the query string
@@ -603,4 +622,3 @@ type family HasCookieAuth xs :: Constraint where
603622 HasCookieAuth '[] = CookieAuthNotEnabled
604623
605624class CookieAuthNotEnabled
606−
src/Servant/Reflex/Multi.hs+35−22
@@ -37,28 +37,30 @@ module Servant.Reflex.Multi (
3737 ) where
3838
3939------------------------------------------------------------------------------
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)
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,
52+ MimeRender (..), MimeUnrender,
53+ NoContent, QueryFlag, QueryParam,
54+ QueryParams, Raw, ReflectMethod (..),
55+ RemoteHost, ReqBody,
56+ ToHttpApiData (..), Vault, Verb,
57+ contentType)
58+import Servant.API.Description (Summary)
59+
60+import Reflex.Dom.Core (Dynamic, Event, Reflex,
61+ XhrRequest (..),
62+ XhrResponseHeaders (..),
63+ attachPromptlyDynWith, constDyn)
6264------------------------------------------------------------------------------
6365import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
6466 SupportsServantReflex)
@@ -240,6 +242,17 @@ instance HasClientMulti t m sublayout f tag
240242 clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
241243
242244
245+------------------------------------------------------------------------------
246+instance (HasClientMulti t m sublayout f tag, KnownSymbol sym)
247+ => HasClientMulti t m (Summary sym :> sublayout) f tag where
248+
249+ type ClientMulti t m (Summary sym :> sublayout) f tag =
250+ ClientMulti t m sublayout f tag
251+
252+ clientWithRouteMulti Proxy q f tag =
253+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
254+
255+
243256------------------------------------------------------------------------------
244257instance (KnownSymbol sym,
245258 ToHttpApiData a,
src/Servant/Reflex/RequestAPI.hsdeleted−567
@@ -1,567 +0,0 @@
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 #-} -- Temporary
13−{-# LANGUAGE ScopedTypeVariables #-}
14−{-# LANGUAGE TupleSections #-}
15−{-# LANGUAGE TypeApplications #-}
16−{-# LANGUAGE TypeFamilies #-}
17−{-# LANGUAGE TypeOperators #-}
18−{-# LANGUAGE UndecidableInstances #-}
19−
20−module Servant.Reflex.RequestAPI where
21−
22−import Control.Arrow (first, second)
23−import Control.Lens
24−import Data.Generics.Product
25−import Data.Proxy
26−import Data.CaseInsensitive (mk)
27−import qualified Data.List as L
28−import qualified Data.Map as M
29−import Data.Maybe (catMaybes)
30−import Data.Semigroup ((<>))
31−import qualified Data.Set as Set
32−import qualified Data.Text.Encoding as E
33−import Data.Text (Text)
34−import qualified Data.Text as T
35−import qualified Data.ByteString.Lazy as BL
36−import GHC.Generics
37−import GHC.TypeLits (KnownSymbol, symbolVal)
38−import Reflex hiding (HList(..))
39−import Language.Javascript.JSaddle (MonadJSM)
40−import Servant.API hiding (HList(..))
41−import Servant.Common.Req hiding (QueryPart(..))
42−import Servant.Common.BaseUrl
43−
44−import Reflex.Dom hiding (HList(..))
45−
46−import Servant.Reflex (BuildHeaderKeysTo (..), toHeaders)
47−
48−{-
49−TODO:
50− - [x] :<|>
51− - [x] Verb content
52− - [x] Verb nocontent
53− - [x] Verb (Headers content)
54− - [x] Verb (Headers nocontent)
55− - [ ] Stream
56− - [X] Capture :> ...
57− - [ ] Capture' :> ...
58− - [x] CaptureAll :> ...
59− - [x] QueryParam :> ...
60− - [x] QueryParams :> ...
61− - [x] QueryFlag :> ...
62− - [x] ReqBody :> ...
63− - [ ] Header' :> ...
64− - [x] Header :> ...
65− - [x] Summary :> ...
66− - [x] Description :> ...
67− - [x] path :> ...
68− - [ ] RemoteHost :> ...
69− - [x] IsSecure :> ...
70− - [x] Vault :> ...
71− - [ ] WithNamedContext :> ...
72− - [ ] AuthenticatedRequest :> ...
73− - [x] BasicAuth :> ...
74− - [ ] EmptyClient :> ...
75− - [ ] EmptyClient
76− - [ ] HttpVersion :> ...
77−-}
78−data HList xs where
79− HNil :: HList '[]
80− HCons :: x -> HList xs -> HList (x ': xs)
81−
82−clientR :: forall t m layout f . (HasClientR t m (Seal layout) f, Applicative f, Reflex t)
83− => Proxy layout -> Proxy (m :: * -> *) -> Proxy t -> Proxy f
84− -> Dynamic t BaseUrl -> ClientR t m (Seal layout) f
85−clientR _ mP tP fP url =
86− clientRWithRoute (Proxy :: Proxy (Seal layout)) mP fP undefined url undefined
87−
88−-- | A class for generating top-level the API client function
89−class HasClientR t (m :: * -> *) api (f :: * -> *) where
90− type ClientR t m api f :: *
91− clientRWithRoute :: Proxy api -> Proxy m -> Proxy f
92− -> Dynamic t (f (Req t)) -> Dynamic t BaseUrl
93− -> ClientOptions -> ClientR t m api f
94−
95−-- instance EndpointR t m Raw f where
96−-- type EInputs t m Raw f = '[XhrRequest T.Text]
97−-- type EOutput t m Raw f = XhrResponse
98−
99−-- mkReq (HCons (XhrRequest method url conf) NHil) =
100−-- ReqR method
101−
102−
103−-- | A class for generating endpoint-level API client functions
104−-- We need two separate type classes because the needs of endpoints
105−-- and top-level clients are incompatible
106−--
107−-- Endpoints need to accumulate a list of their input types from
108−-- the API definition (the @EInputs@ associated type) when building
109−-- up a function from a sequence of @:>@ combinators. But for
110−-- sequences of @:<|>@ combinators, such a listing doesn't make
111−-- sense. So, we use one class for endpoint-level recursion and
112−-- another for collecting endpoints into the top-level API
113−class EndpointR t m api f where
114−
115− type EInputs t m api f :: [*]
116− type EOutput t m api f :: *
117−
118− mkReq :: HList (EInputs t m api f) -> ReqR
119− getResp :: XhrResponse -> EOutput t m api f
120−
121−
122−------------------------------------------------------------------------------
123−-- | :<|> APIs
124−instance {-# OVERLAPPING #-}
125− (HasClientR t m (layoutLeft) f,
126− HasClientR t m (layoutRight) f
127− ) => HasClientR t m (layoutLeft :<|> layoutRight) f where
128− type ClientR t m (layoutLeft :<|> layoutRight) f =
129− ClientR t m layoutLeft f :<|> ClientR t m layoutRight f
130− clientRWithRoute _ tP mP _ url opts =
131− clientRWithRoute (Proxy @layoutLeft) tP mP undefined url opts
132− :<|>
133− clientRWithRoute (Proxy @layoutRight) tP mP undefined url opts
134−
135−#if MIN_VERSION_servant (0,13,0)
136−-- TODO: Untested
137−instance HasClientR t m EmptyClient f where
138− type ClientR t m api f = EmptyClient
139− clientRWithRoute _ _ _ _ _ _ = EmptyClient
140−#endif
141−
142−
143−class ToTuple xs where
144− type Tuple xs :: *
145− toTuple :: xs -> Tuple xs
146− fromTuple :: Tuple xs -> xs
147−
148−data Sealed a
149−data SealedRaw
150−
151−type family Seal a where
152− Seal (a :<|> b) = Seal a :<|> Seal b
153− Seal Raw = SealedRaw
154− Seal a = Sealed a
155−
156−
157−-- | Turn a single Endpoint into a client, by turning the input HList
158−-- into a tuple
159−instance {-# OVERLAPPING #-} forall t m layout f .(EndpointR t m layout f,
160− ToTuple (HList (EInputs t m layout f)),
161− Monad m
162− , Traversable f
163− , SupportsServantReflex t m
164− ) => HasClientR t (m :: * -> *) (Sealed layout) (f :: * -> *) where
165− type ClientR t m (Sealed layout) f =
166− Event t (f (Tuple (HList (EInputs t m layout f))))
167− -> m (Event t (f (EOutput t m layout f)))
168− clientRWithRoute _ mP fP dReq url opts (reqs :: Event t (f (Tuple (HList (EInputs t m layout f ))))) = do
169− let reqTuples = fmap (fromTuple @(HList (EInputs t m layout f ))) <$> reqs
170−
171− -- TODO: replace these 2 lines w/ the next 2. The trace is for library debugging
172− reqRs = traceEventWith (concat . fmap ((<> "\n") . show)) $ fmap (mkReq @t @m @layout @f) <$> reqTuples
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 <- performRequestsAsync $ reqs'
177− return (fmap (getResp @t @m @layout @f) <$> resps)
178−
179−
180−instance (SupportsServantReflex t m, Traversable f) => HasClientR t m SealedRaw f where
181− type ClientR t m SealedRaw f = Event t (f (XhrRequest T.Text)) -> m (Event t (f XhrResponse))
182− clientRWithRoute _ mP fP dReq url opts reqs = performRequestsAsync reqs
183−
184−
185−instance {-# OVERLAPPING #-}(MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m, Applicative f
186− ) => EndpointR t m (Verb method status cts' a) f where
187− type EInputs t m (Verb method status cts' a) f = '[]
188− type EOutput t m (Verb method status cts' a) f = Either String a
189−
190− mkReq _ = emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
191− getResp resp = case _xhrResponse_response resp of
192− Just (XhrResponseBody_ArrayBuffer x) -> mimeUnrender (Proxy @ct) (BL.fromStrict x)
193−
194−instance {-# OVERLAPPING #-}(ReflectMethod method, SupportsServantReflex t m, Applicative f
195− ) => EndpointR t m (Verb method status cts' NoContent) f where
196− type EInputs t m (Verb method status cts' NoContent) f = '[]
197− type EOutput t m (Verb method status cts' NoContent) f = Either String NoContent
198−
199− mkReq _ = emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
200− getResp resp = Right NoContent -- TODO: Should EOutput be `NoContent` rather than Either... ?
201−
202−
203−instance {-# OVERLAPPING #-}
204− (MimeUnrender ct a,
205− BuildHeadersTo hs,
206− ReflectMethod method, cts' ~ (ct ': cts),
207− SupportsServantReflex t m,
208− Applicative f,
209− BuildHeaderKeysTo hs
210− ) => EndpointR t m (Verb method status cts' (Headers hs a)) f where
211− type EInputs t m (Verb method status cts' (Headers hs a)) f = '[]
212− type EOutput t m (Verb method status cts' (Headers hs a)) f = Either String (Headers hs a)
213−
214− mkReq _ = set (field @"reqRRespHeaders") (OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy @hs)))) $ emptyReqR $ E.decodeUtf8 $ reflectMethod (Proxy @method)
215− getResp resp = case _xhrResponse_response resp of
216− Just (XhrResponseBody_ArrayBuffer x) ->
217− let hs = buildHeadersTo
218− . fmap (first (mk . E.encodeUtf8) . second E.encodeUtf8)
219− . M.toList
220− $ _xhrResponse_headers resp
221− in fmap (flip Headers (hs)) $ mimeUnrender (Proxy @ct) (BL.fromStrict x)
222−
223−
224−instance {-# OVERLAPPING #-}(MimeUnrender ct a,
225− BuildHeadersTo hs,
226− BuildHeaderKeysTo hs,
227− ReflectMethod method,
228− SupportsServantReflex t m,
229− Applicative f
230− ) => EndpointR t m (Verb method status cts' (Headers hs NoContent)) f where
231− type EInputs t m (Verb method status cts' (Headers hs NoContent)) f = '[]
232− type EOutput t m (Verb method status cts' (Headers hs NoContent)) f = Either String (Headers hs NoContent)
233−
234− mkReq _ = set (field @"reqRRespHeaders") (OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy @hs)))) $ emptyReqR . E.decodeUtf8 $ reflectMethod (Proxy @method)
235− getResp resp = case _xhrResponse_response resp of
236− Just (XhrResponseBody_ArrayBuffer x) ->
237− let hs = buildHeadersTo
238− . fmap (first (mk . E.encodeUtf8) . second E.encodeUtf8)
239− . M.toList
240− $ _xhrResponse_headers resp
241− in Right (Headers NoContent hs)
242−
243−
244−instance (SupportsServantReflex t m,
245− ToHttpApiData a,
246− EndpointR t m sublayout f,
247− Applicative f
248− ) => EndpointR t m (Capture capture a :> sublayout) f where
249−
250− type EInputs t m (Capture capture a :> sublayout) f = a ': EInputs t m sublayout f
251− type EOutput t m (Capture capture a :> sublayout) f = EOutput t m sublayout f
252−
253− mkReq (HCons x xs) = (\a -> over (field @"reqRPathParts") (toUrlPiece a:)) x $
254− mkReq @t @m @sublayout @f xs
255−
256− getResp = getResp @t @m @sublayout @f
257−
258−instance (SupportsServantReflex t m,
259− ToHttpApiData a,
260− EndpointR t m sublayout f,
261− Applicative f
262− ) => EndpointR t m (CaptureAll capture a :> sublayout) f where
263−
264− type EInputs t m (CaptureAll capture a :> sublayout) f = [a] ': EInputs t m sublayout f
265− type EOutput t m (CaptureAll capture a :> sublayout) f = EOutput t m sublayout f
266−
267− mkReq (HCons x xs) = (\a -> over (field @"reqRPathParts") (fmap toUrlPiece a <>)) x $
268− mkReq @t @m @sublayout @f xs
269−
270− getResp = getResp @t @m @sublayout @f
271−
272−
273−instance (SupportsServantReflex t m,
274− KnownSymbol sym,
275− EndpointR t m sublayout f,
276− Applicative f
277− ) => EndpointR t m (sym :> sublayout) f where
278−
279− type EInputs t m (sym :> sublayout) f = EInputs t m sublayout f
280− type EOutput t m (sym :> sublayout) f = EOutput t m sublayout f
281−
282− mkReq r = over (field @"reqRPathParts") (T.pack (symbolVal (Proxy @sym)) :) $ mkReq @t @m @sublayout @f r
283−
284− getResp = getResp @t @m @sublayout @f
285−
286−instance (SupportsServantReflex t m,
287− ToHttpApiData a,
288− EndpointR t m sublayout f,
289− KnownSymbol sym,
290− Applicative f
291− ) => EndpointR t m (QueryParam sym a :> sublayout) f where
292−
293− type EInputs t m (QueryParam sym a :> sublayout) f = Maybe a ': EInputs t m sublayout f
294− type EOutput t m (QueryParam sym a :> sublayout) f = EOutput t m sublayout f
295−
296− mkReq (HCons x xs) = over (field @"reqRParams") ((T.pack $ symbolVal (Proxy @sym),
297− QueryPartParam $ fmap (toQueryParam) x) : ) $
298− mkReq @t @m @sublayout @f xs
299−
300−
301− getResp = getResp @t @m @sublayout @f
302−
303−instance (SupportsServantReflex t m,
304− ToHttpApiData a,
305− EndpointR t m sublayout f,
306− KnownSymbol sym,
307− Applicative f
308− ) => EndpointR t m (QueryParams sym a :> sublayout) f where
309−
310− type EInputs t m (QueryParams sym a :> sublayout) f = [a] ': EInputs t m sublayout f
311− type EOutput t m (QueryParams sym a :> sublayout) f = EOutput t m sublayout f
312−
313− mkReq (HCons x xs) =
314− let newParams = (T.pack (symbolVal (Proxy @sym)), QueryPartParams (toQueryParam <$> x))
315− in over (field @"reqRParams") (newParams :) $ mkReq @t @m @sublayout @f xs
316−
317− getResp = getResp @t @m @sublayout @f
318−
319−instance (SupportsServantReflex t m,
320− EndpointR t m sublayout f,
321− KnownSymbol sym,
322− Applicative f
323− ) => EndpointR t m (QueryFlag sym :> sublayout) f where
324−
325− type EInputs t m (QueryFlag sym :> sublayout) f = Bool ': EInputs t m sublayout f
326− type EOutput t m (QueryFlag sym :> sublayout) f = EOutput t m sublayout f
327−
328− mkReq (HCons x xs) =
329− let newParam = (T.pack (symbolVal (Proxy @sym)), QueryPartFlag x)
330− in over (field @"reqRParams") (newParam :) $ mkReq @t @m @sublayout @f xs
331−
332− getResp = getResp @t @m @sublayout @f
333−
334−instance (SupportsServantReflex t m,
335− EndpointR t m sublayout f,
336− MimeRender ct a,
337− Applicative f
338− ) => EndpointR t m (ReqBody (ct ': cts) a :> sublayout) f where
339−
340−
341− type EInputs t m (ReqBody (ct ': cts) a :> sublayout) f = a ': EInputs t m sublayout f
342− type EOutput t m (ReqBody (ct ': cts) a :> sublayout) f = EOutput t m sublayout f
343−
344− mkReq (HCons x xs) =
345− set (field @"reqRBody") (Just (mimeRender (Proxy @ct) x, T.pack (show (contentType (Proxy @ct))))) $ mkReq @t @m @sublayout @f xs
346−
347− getResp = getResp @t @m @sublayout @f
348−
349−
350−instance (SupportsServantReflex t m,
351− EndpointR t m sublayout f,
352− KnownSymbol sym,
353− ToHttpApiData a,
354− Applicative f
355− ) => EndpointR t m (Header sym a :> sublayout) f where
356−
357−
358− type EInputs t m (Header sym a :> sublayout) f = a ': EInputs t m sublayout f
359− type EOutput t m (Header sym a :> sublayout) f = EOutput t m sublayout f
360−
361− mkReq (HCons x xs) =
362− over (field @"reqRHeaders")
363− -- ((symbolVal (Proxy @sym), (mimeRender (Proxy @a) x)) :)
364− ((T.pack $ symbolVal (Proxy @sym), E.decodeUtf8 $ toHeader x) :)
365− $ mkReq @t @m @sublayout @f xs
366−
367− getResp = getResp @t @m @sublayout @f
368−
369−#if MIN_VERSION_servant(0,13,0)
370−-- TODO: Untested
371−instance (SupportsServantReflex t m,
372− EndpointR t m sublayout f,
373− KnownSymbol sym,
374− ToHttpApiData a,
375− Applicative f
376− ) => EndpointR t m (Summary desc :> sublayout) f where
377−
378− type EInputs t m (Summary desc :> sublayout) f = EInputs t m sublayout f
379− type EOutput t m (Summary desc :> sublayout) f = EOutput t m sublayout f
380−
381− mkReq = mkReq @t @m @sublayout @f
382−
383− getResp = getResp @t @m @sublayout @f
384−#endif
385−
386−
387−#if MIN_VERSION_servant(0,13,0)
388−-- TODO: Untested
389−instance (SupportsServantReflex t m,
390− EndpointR t m sublayout f,
391− KnownSymbol sym,
392− ToHttpApiData a,
393− Applicative f
394− ) => EndpointR t m (Description desc :> sublayout) f where
395−
396− type EInputs t m (Descriptiok desc :> sublayout) f = EInputs t m sublayout f
397− type EOutput t m (Description desc :> sublayout) f = EOutput t m sublayout f
398−
399− mkReq = mkReq @t @m @sublayout @f
400−
401− getResp = getResp @t @m @sublayout @f
402−#endif
403−
404−instance (SupportsServantReflex t m,
405− EndpointR t m sublayout f,
406− KnownSymbol sym,
407− ToHttpApiData a,
408− Applicative f
409− ) => EndpointR t m (IsSecure :> sublayout) f where
410−
411−
412− type EInputs t m (IsSecure :> sublayout) f = EInputs t m sublayout f
413− type EOutput t m (IsSecure :> sublayout) f = EOutput t m sublayout f
414−
415− mkReq = mkReq @t @m @sublayout @f
416−
417− getResp = getResp @t @m @sublayout @f
418−
419−instance (SupportsServantReflex t m,
420− EndpointR t m sublayout f,
421− KnownSymbol sym,
422− ToHttpApiData a,
423− Applicative f
424− ) => EndpointR t m (Vault :> sublayout) f where
425−
426−
427− type EInputs t m (Vault :> sublayout) f = EInputs t m sublayout f
428− type EOutput t m (Vault :> sublayout) f = EOutput t m sublayout f
429−
430− mkReq = mkReq @t @m @sublayout @f
431−
432− getResp = getResp @t @m @sublayout @f
433−
434−instance (SupportsServantReflex t m,
435− EndpointR t m sublayout f,
436− Applicative f
437− ) => EndpointR t m (BasicAuth realm usr :> sublayout) f where
438−
439− type EInputs t m (BasicAuth realm usr :> sublayout) f = BasicAuthData ': EInputs t m sublayout f
440− type EOutput t m (BasicAuth realm usr :> sublayout) f = EOutput t m sublayout f
441−
442− mkReq (HCons x xs) =
443− set (field @"reqRAuthData") (Just x) $ mkReq @t @m @sublayout @f xs
444−
445− getResp = getResp @t @m @sublayout @f
446−
447−
448−instance ToTuple (HList '[]) where
449− type Tuple (HList '[]) = ()
450− toTuple _ = ()
451− fromTuple _ = HNil
452−
453−instance ToTuple (HList '[a]) where
454− type Tuple (HList '[a]) = a
455− toTuple (HCons a HNil) = a
456− fromTuple a = HCons a HNil
457−
458−instance ToTuple (HList '[a,b]) where
459− type Tuple (HList '[a,b]) = (a,b)
460− toTuple (HCons a (HCons b HNil)) = (a,b)
461− fromTuple (a,b) = HCons a (HCons b HNil)
462−
463−instance ToTuple (HList '[a,b,c]) where
464− type Tuple (HList '[a,b,c]) = (a,b,c)
465− toTuple (HCons a (HCons b (HCons c HNil))) = (a,b,c)
466− fromTuple (a,b,c) = HCons a (HCons b (HCons c HNil))
467−
468−type MyAPI = Header "hi" Int :> Capture "hello" Int :> Capture "hello2" Bool :> Get '[JSON] (Headers '[Header "hi" Int] Int)
469− :<|> Get '[JSON] Bool
470−
471−type MyAPI2 = Raw
472−
473−
474−m :: forall t m.MonadWidget t m => m ()
475−m = do
476− let (a :<|> b) = clientR (Proxy @MyAPI) (Proxy :: Proxy m) (Proxy :: Proxy t)
477− (Proxy @[]) undefined
478− btn <- button "Hello"
479− resA <- a (fmap (:[]) ((2, 5,True) <$ btn))
480− resB <- b (fmap (:[]) (() <$ btn))
481− display =<< holdDyn "waiting" (leftmost [show <$> resB ])
482− -- [show <$> resA
483− -- , show <$> resB
484− -- ])
485−
486− let rawR = clientR (Proxy @MyAPI2) (Proxy @m) (Proxy @t) (Proxy @[]) undefined
487− return ()
488−
489−data ReqR = ReqR
490− { reqRMethod :: T.Text
491− , reqRPathParts :: [T.Text]
492− , reqRParams :: [(T.Text, QueryPart)]
493− , reqRBody :: Maybe (BL.ByteString, T.Text)
494− , reqRHeaders :: [(T.Text, T.Text)]
495− , reqRRespHeaders :: XhrResponseHeaders
496− , reqRAuthData :: Maybe BasicAuthData
497− } deriving (Generic)
498−
499−instance Show ReqR where
500− show (ReqR m pp ps b hs hs' a) = concat $ L.intersperse "\n" [
501− "ReqR {"
502− , ("reqRMethod = " <> show m)
503− , ("reqRPathParts = " <> show pp)
504− , ("reqRParams = " <> show ps)
505− , ("reqRBody = " <> show b)
506− , ("reqRHeaders = " <> show hs)
507− , ("reqRRespHeaders = " <> show hs')
508− , ("reqRAuthData = " <> maybe "Nothing" (const "Just <<redacted>>") a)
509− ]
510−
511−emptyReqR :: T.Text -> ReqR
512−emptyReqR method = ReqR method [] [] Nothing [] (OnlyHeaders mempty) Nothing
513−
514−data QueryPart = QueryPartParam (Maybe Text)
515− | QueryPartParams [Text]
516− | QueryPartFlag Bool
517− deriving (Show)
518−
519−mkXhrRequest :: BaseUrl -> ReqR -> XhrRequest T.Text
520−mkXhrRequest reqHost req =
521− let path = T.intercalate "/" $ reqRPathParts req
522−
523− queryPartString :: (Text, QueryPart) -> Maybe Text
524− queryPartString (pName, qp) = case qp of
525−
526− QueryPartParam Nothing -> Nothing
527− QueryPartParam (Just a) -> Just (pName <> "=" <> escape a)
528−
529− QueryPartParams ps -> Just . T.intercalate "&"
530− $ fmap (\p -> pName <> "=" <> escape p)
531− ps
532−
533− QueryPartFlag True -> Just pName
534− QueryPartFlag False -> Nothing
535−
536− (</>) :: Text -> Text -> Text
537− x </> y | ("/" `T.isSuffixOf` x) || ("/" `T.isPrefixOf` y) = x <> y
538− | otherwise = x <> "/" <> y
539−
540− queryString = T.intercalate "&" . catMaybes . map queryPartString $ reqRParams req
541− url = showBaseUrl reqHost </> path <> if T.null queryString then "" else (T.cons '?' queryString)
542−
543− (headers, body) =
544− let headers0 = M.fromList $ reqRHeaders req
545− in case reqRBody req of
546− Nothing -> (headers0, "")
547− Just (rb,ct) -> (M.insert "Content-Type" ct headers0,
548− E.decodeUtf8 $ BL.toStrict rb)
549−
550− xhrReqConfig = def
551− { _xhrRequestConfig_headers = headers
552− , _xhrRequestConfig_user = E.decodeUtf8 . basicAuthUsername <$> reqRAuthData req
553− , _xhrRequestConfig_password = E.decodeUtf8 . basicAuthPassword <$> reqRAuthData req
554− , _xhrRequestConfig_responseType = Just XhrResponseType_ArrayBuffer
555− , _xhrRequestConfig_sendData = body
556− , _xhrRequestConfig_responseHeaders = reqRRespHeaders req
557− , _xhrRequestConfig_withCredentials = False
558− }
559− xhrReq = XhrRequest (reqRMethod req) url xhrReqConfig
560− in xhrReq
561−
562−transformExample :: MonadWidget t m => ((Int, Char) -> Performable m Bool) -> Event t (Int, Char) -> m (Event t Bool)
563−transformExample f = \triggers -> performEvent (f <$> triggers)
564−-- transformExample = undefined -- let x = performEvent :: _ in undefined
565−
566−transform :: MonadWidget t m => forall a. (inp -> IO outp) -> (Event t inp -> m (Event t outp))
567−transform = undefined
testdriver/default.nixdeleted−16
@@ -1,16 +0,0 @@
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.nixdeleted−1
@@ -1 +0,0 @@
1−(import ../release3.nix {}).testdriver.env
testserver/Main.hs+8−12
@@ -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 Snap.Core hiding (addHeader)
23+import Servant.Server.Internal.SnapShims
24+import Snap.Core
2425import Snap.Http.Server
2526
2627import Servant
27−import Servant.Server ()
28−import System.Directory
28+import Servant.Server
2929-- import Snap.Util.FileServe
3030import System.Environment (lookupEnv)
3131import API
32−import Snap hiding (addHeader)
32+import Snap
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 :<|> dblint :<|> sayhi :<|> dbl
55+server = return () :<|> return 100 :<|> sayhi :<|> dbl
5656 :<|> multi :<|> qna :<|> serveSecret
5757 :<|> statics
5858 where sayhi :: Maybe Text -> [Text] -> Bool -> Handler App App Text
@@ -66,16 +66,15 @@ server = return () :<|> return 100 :<|> dblint :<|> sayhi :<|> dbl
6666 | otherwise = T.intercalate ", " (L.init greetings)
6767 <> ", and " <> L.last greetings <> ", "
6868 return . modifier $ greetPart <> n
69− dblint = return . (* 2)
7069 dbl x = if x `elem` [4,13]
7170 then throwError $ err500 { errBody = "No unlucky numbers please" }
7271 else return $ x * 2
7372 multi = return . bool "Box unchecked" "Box Checked"
74− qna q nm fav = do
73+ qna q = do
7574 liftIO $ do
7675 putStrLn $ "qna got: " ++ show q
7776 T.putStrLn $ unQuestion q
78− return $ addHeader (fromMaybe 10 nm) $ addHeader (fromMaybe False fav) $ (Answer $ unQuestion q)
77+ return $ Answer $ unQuestion q
7978 serveSecret _ = do
8079 req <- getRequest
8180 liftIO $ putStrLn (show req)
@@ -100,7 +99,4 @@ initApp = makeSnaplet "myapp" "example" Nothing $ do
10099
101100-- Put this all to work!
102101main :: IO ()
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
102+main = serveSnaplet mempty initApp
testserver/default.nixdeleted−14
@@ -1,14 +0,0 @@
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.nixdeleted−1
@@ -1 +0,0 @@
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.14
22+ build-depends: aeson >= 0.9 && < 1.5
23+ , base >=4.8 && < 5
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.16 && < 0.18
2929 , servant-snap >= 0.8 && < 0.9
3030 , text >= 1.0 && < 1.3
3131 -- hs-source-dirs:
travis.nix+1
@@ -48,6 +48,7 @@ in
4848 inherit reflexPlatform ghcPkgs ghcjsPkgs;
4949
5050 build = ghcjsPkgs.servant-reflex;
51+ buildGhc = ghcPkgs.servant-reflex;
5152 testserver = ghcPkgs.testserver;
5253 testdriver = ghcPkgs.testdriver;
5354 inherit testresults;