on master help sign in
16 files changed+274−705
since splitsnapshots
unifiedsplit
.gitignore+2 .travis.yml+4−4 ChangeLog.md+8 README.md+1−1 default.nix+10−18 exec/Example.hs+2−1 nix/nix.conf+2−1 nix/reflex-platform.nix+2−2 nix/servant-snap.nix+4−6 servant-reflex.cabal+17−10 src/Servant/Common/BaseUrl.hs+4−5 src/Servant/Common/Req.hs+79−134 src/Servant/Reflex.hs+79−296 src/Servant/Reflex/Multi.hs+55−224 testserver/testserver.cabal+3−3 travis.nix+2
.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+10−18
@@ -1,29 +1,21 @@
1−{ mkDerivation, aeson, base, bytestring, case-insensitive
2−, containers, data-default, exceptions, ghcjs-dom, http-api-data
3−, http-media, jsaddle, mtl, network-uri, reflex, reflex-dom
4−, reflex-dom-core, safe, scientific, servant, servant-auth, stdenv
5−, string-conversions, text, transformers, megaparsec, wreq
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 megaparsec wreq
16+ reflex-dom reflex-dom-core safe servant servant-auth string-conversions
17+ text transformers
2318 ];
24− executableHaskellDepends = [
25− aeson base reflex reflex-dom scientific servant text
26− ];
27− description = "Servant reflex API generator";
19+ description = "servant API generator for reflex apps";
2820 license = stdenv.lib.licenses.bsd3;
2921}
exec/Example.hs+2−1
@@ -21,7 +21,8 @@ import Servant.API
2121import API
2222import Data.Proxy
2323import Text.Read (readMaybe)
24−import Reflex.Dom.Core hiding (run)
24+import Reflex.Dom.Core hiding (run, mainWidget)
25+import Reflex.Dom (mainWidget)
2526------------------------------------------------------------------------------
2627import Servant.Reflex
2728import Servant.Reflex.Multi
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-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}) {}
servant-reflex.cabal+17−10
@@ -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
@@ -24,24 +24,22 @@ library
2424
2525 hs-source-dirs: src
2626 build-depends:
27− aeson >= 0.11 && < 1.5,
2827 base >= 4.8 && < 5,
2928 bytestring >= 0.10 && < 0.11,
3029 case-insensitive >= 1.2.0.4 && < 1.3,
31− containers >= 0.5.6 && < 0.6,
30+ containers >= 0.5.6 && < 0.7,
3231 data-default >= 0.5 && < 0.8,
3332 exceptions >= 0.8 && < 0.11,
3433 ghcjs-dom >= 0.2 && < 0.10,
35− http-api-data >= 0.3.6 && < 0.4,
36− http-media >= 0.6 && < 0.8,
34+ http-api-data >= 0.3.6 && < 0.5,
35+ http-media >= 0.6 && < 0.9,
3736 jsaddle >= 0.8 && < 0.10,
3837 mtl >= 2.2.1 && < 2.3,
3938 network-uri >= 2.6 && < 2.7,
40− reflex >= 0.5 && < 0.7,
41− reflex-dom-core >= 0.4 && < 0.6,
39+ reflex >= 0.5 && < 0.9,
40+ reflex-dom-core >= 0.6 && < 0.7,
4241 safe >= 0.3.9 && < 0.4,
43− servant >= 0.13 && < 0.16,
44− servant-checked-exceptions >= 2.0 && < 2.2,
42+ servant >= 0.16 && < 0.18,
4543 servant-auth >= 0.2.1 && < 0.4,
4644 string-conversions >= 0.4 && < 0.5,
4745 text >= 1.2 && < 1.3,
@@ -56,7 +54,16 @@ executable example
5654 buildable: True
5755 else
5856 buildable: False
59− 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
6067 default-language: Haskell2010
6168 main-is: Example.hs
6269 other-modules: API
src/Servant/Common/BaseUrl.hs+4−5
@@ -85,15 +85,14 @@ baseUrlWidget = elClass "div" "base-url" $ do
8585 where pathWidget :: m (Dynamic t BaseUrl)
8686 pathWidget = do
8787 text "Url base path"
88− t <- textInput (def {_textInputConfig_attributes =
89− constDyn ("placeholder" =: "/a/b")})
88+ t <- inputElement def
9089 return $ BasePath <$> value t
9190 fullUrlWidget :: m (Dynamic t BaseUrl)
9291 fullUrlWidget = do
9392 schm <- dropdown Https (constDyn $ Https =: "https" <> Http =: "http") def
94− srv <- textInput def {_textInputConfig_attributes = constDyn $ "placeholder" =: "example.com"}
93+ srv <- inputElement def
9594 text ":"
96− prt <- textInput def { _textInputConfig_attributes = constDyn $ "placeholder" =: "80"}
95+ prt <- inputElement def
9796 port :: Dynamic t Int <- holdDyn 80 (fmapMaybe (readMaybe . T.unpack) $ updated (value prt))
98− path <- textInput def { _textInputConfig_attributes = constDyn $ "placeholder" =: "a/b" }
97+ path <- inputElement def
9998 return $ BaseFullUrl <$> value schm <*> value srv <*> port <*> value path
src/Servant/Common/Req.hs+79−134
@@ -1,66 +1,46 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE DeriveFunctor #-}
3−{-# LANGUAGE ExistentialQuantification #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE FunctionalDependencies #-}
7−{-# LANGUAGE GADTs #-}
8−{-# LANGUAGE LambdaCase #-}
9−{-# LANGUAGE MultiParamTypeClasses #-}
10−{-# LANGUAGE NamedFieldPuns #-}
11−{-# LANGUAGE OverloadedStrings #-}
12−{-# LANGUAGE RankNTypes #-}
13−{-# LANGUAGE ScopedTypeVariables #-}
14−{-# LANGUAGE TupleSections #-}
15−{-# OPTIONS_GHC -fno-warn-orphans #-}
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveFunctor #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE GADTs #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE LambdaCase #-}
7+{-# LANGUAGE RankNTypes #-}
8+{-# LANGUAGE ScopedTypeVariables #-}
9+{-# LANGUAGE TupleSections #-}
1610
1711module Servant.Common.Req where
1812
1913-------------------------------------------------------------------------------
20−import Control.Applicative (liftA2, liftA3)
21−import Control.Arrow ((&&&))
14+import Control.Applicative (liftA2, liftA3)
15+import Control.Arrow ((&&&))
2216import Control.Concurrent
23−import Control.Monad (join)
24−import Control.Monad.IO.Class (MonadIO, liftIO)
25−import Data.Aeson
26−import Data.Bifunctor (first)
27−import qualified Data.ByteString as BS
28−import qualified Data.ByteString.Builder as Builder
29−import Data.ByteString.Lazy (ByteString)
30−import qualified Data.ByteString.Lazy.Char8 as BL
17+import Control.Monad (join)
18+import Control.Monad.IO.Class (MonadIO, liftIO)
19+import Data.Bifunctor (first)
20+import qualified Data.ByteString.Builder as Builder
21+import qualified Data.ByteString.Lazy.Char8 as BL
22+import Data.ByteString (ByteString)
23+import qualified Data.Map as Map
24+import Data.Maybe (catMaybes, fromMaybe)
3125import Data.Functor.Compose
32−import qualified Data.JSString.Text as JS (lazyTextFromJSString)
33−import qualified Data.Map as Map
34−import Data.Maybe (catMaybes, fromMaybe)
35−import Data.Monoid ((<>))
36−import Data.Proxy (Proxy (..))
37−import Data.Text (Text)
38−import qualified Data.Text as T
39−import qualified Data.Text.Encoding as TE
40−import qualified Data.Text.Lazy as TL
41−import Data.Traversable (forM)
42−import qualified Language.Javascript.JSaddle as JS
26+import Data.Monoid ((<>))
27+import Data.Proxy (Proxy(..))
28+import Data.Text (Text)
29+import qualified Data.Text as T
30+import qualified Data.Text.Encoding as TE
31+import Data.Traversable (forM)
4332import Language.Javascript.JSaddle.Monad (JSM, MonadJSM, liftJSM)
44−import qualified Network.URI as N
45−import Reflex.Dom.Core hiding (tag)
46−import Servant.API.ContentTypes (Accept, FormUrlEncoded,
47− JSON, NoContent (..),
48− OctetStream, PlainText)
49−import Servant.Common.BaseUrl (BaseUrl,
50− SupportsServantReflex,
51− showBaseUrl)
52−import Web.FormUrlEncoded (FromForm, urlDecodeAsForm)
53−import Web.HttpApiData (ToHttpApiData (..))
33+import qualified Network.URI as N
34+import Reflex.Dom.Core hiding (tag)
35+import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
36+ SupportsServantReflex)
37+import Servant.API.ContentTypes (MimeUnrender(..), NoContent(..))
38+import Web.HttpApiData (ToHttpApiData(..))
5439-------------------------------------------------------------------------------
5540import Servant.API.BasicAuth
56−-------------------------------------------------------------------------------
5741
58−-- #ifndef __GHCJS__
59−-- jsonDecode :: JS.JSString -> Maybe a
60−-- jsonDecode = error "jsonDecode is only available in GHCJS"
61−-- #endif
6242
63−-----------------------------------------------------------------------------
43+------------------------------------------------------------------------------
6444-- | The result of a request event
6545data ReqResult tag a
6646 = ResponseSuccess tag a XhrResponse
@@ -161,20 +141,18 @@ data QueryPart t = QueryPartParam (Dynamic t (Either Text (Maybe Text)))
161141-------------------------------------------------------------------------------
162142-- The data structure used to build up request information while traversing
163143-- the shape of a servant API
164−data Req t = forall o. (IsXhrPayload o, Show o) => Req
144+data Req t = Req
165145 { reqMethod :: Text
166146 , reqPathParts :: [Dynamic t (Either Text Text)]
167147 , qParams :: [(Text, QueryPart t)]
168− , reqBody :: Maybe (Dynamic t (Either Text (o, Text)))
148+ , reqBody :: Maybe (Dynamic t (Either Text (BL.ByteString, Text)))
169149 , headers :: [(Text, Dynamic t (Either Text Text))]
170150 , respHeaders :: XhrResponseHeaders
171151 , authData :: Maybe (Dynamic t (Maybe BasicAuthData))
172152 }
173153
174−data SomeXhrRequest = forall o. (IsXhrPayload o, Show o) => SomeXhrRequest (XhrRequest (Maybe o))
175−
176154defReq :: Req t
177−defReq = Req "GET" [] [] (Nothing :: Maybe (Dynamic t (Either Text ((), Text)))) [] def Nothing
155+defReq = Req "GET" [] [] Nothing [] def Nothing
178156
179157prependToPathParts :: Dynamic t (Either Text Text) -> Req t -> Req t
180158prependToPathParts p req =
@@ -183,13 +161,14 @@ prependToPathParts p req =
183161addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) -> Req t -> Req t
184162addHeader name val req = req { headers = (name, (fmap . fmap) (TE.decodeUtf8 . toHeader) val) : headers req }
185163
164+
186165reqToReflexRequest
187− :: forall t. Reflex t
188− => Text
189− -> Dynamic t BaseUrl
190− -> Req t
191− -> Dynamic t (Either Text SomeXhrRequest)
192−reqToReflexRequest reqMeth reqHost req@(Req _ _ _ (reqBody :: Maybe (Dynamic t (Either Text (o, Text)))) _ _ _) =
166+ :: forall t. Reflex t
167+ => Text
168+ -> Dynamic t BaseUrl
169+ -> Req t
170+ -> Dynamic t (Either Text (XhrRequest XhrPayload))
171+reqToReflexRequest reqMeth reqHost req =
193172 let t :: Dynamic t [Either Text Text]
194173 t = sequence $ reverse $ reqPathParts req
195174
@@ -241,33 +220,33 @@ reqToReflexRequest reqMeth reqHost req@(Req _ _ _ (reqBody :: Maybe (Dynamic t (
241220 fmap (fmap (\rightVal -> (headerName, rightVal))) dynam
242221
243222 mkConfigBody :: Either Text [(Text,Text)]
244− -> (Either Text (o, Text))
245− -> Either Text (XhrRequestConfig (Maybe o))
223+ -> (Either Text (BL.ByteString, Text))
224+ -> Either Text (XhrRequestConfig XhrPayload)
246225 mkConfigBody ehs rb = case (ehs, rb) of
247226 (_, Left e) -> Left e
248227 (Left e, _) -> Left e
249228 (Right hs, Right (bBytes, bCT)) ->
250229 Right $ XhrRequestConfig
251− { _xhrRequestConfig_sendData = Just bBytes
230+ { _xhrRequestConfig_sendData = bytesToPayload bBytes
252231 , _xhrRequestConfig_headers =
253232 Map.insert "Content-Type" bCT (Map.fromList hs)
254233 , _xhrRequestConfig_user = Nothing
255234 , _xhrRequestConfig_password = Nothing
256− , _xhrRequestConfig_responseType = Nothing
235+ , _xhrRequestConfig_responseType = Just XhrResponseType_ArrayBuffer
257236 , _xhrRequestConfig_withCredentials = False
258237 , _xhrRequestConfig_responseHeaders = def
259238 }
260239
261− xhrOpts :: Dynamic t (Either Text (XhrRequestConfig (Maybe o)))
262− xhrOpts = case reqBody of
240+ xhrOpts :: Dynamic t (Either Text (XhrRequestConfig XhrPayload))
241+ xhrOpts = case reqBody req of
263242 Nothing -> ffor xhrHeaders $ \case
264243 Left e -> Left e
265244 Right hs -> Right $ def { _xhrRequestConfig_headers = Map.fromList hs
266245 , _xhrRequestConfig_user = Nothing
267246 , _xhrRequestConfig_password = Nothing
268− , _xhrRequestConfig_responseType = Nothing
247+ , _xhrRequestConfig_responseType = Just XhrResponseType_ArrayBuffer
248+ , _xhrRequestConfig_sendData = mempty
269249 , _xhrRequestConfig_withCredentials = False
270− , _xhrRequestConfig_sendData = Nothing
271250 }
272251 Just rBody -> liftA2 mkConfigBody xhrHeaders rBody
273252
@@ -281,10 +260,10 @@ reqToReflexRequest reqMeth reqHost req@(Req _ _ _ (reqBody :: Maybe (Dynamic t (
281260 addAuth :: Dynamic t (Either Text (XhrRequestConfig x))
282261 -> Dynamic t (Either Text (XhrRequestConfig x))
283262 addAuth xhr = case authData req of
284− Nothing -> xhr
263+ Nothing -> xhr
285264 Just auth -> liftA2 mkAuth auth xhr
286265
287− xhrReq = (liftA2 . liftA2) (\p opt -> SomeXhrRequest $ XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
266+ xhrReq = (liftA2 . liftA2) (\p opt -> XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
288267
289268 in xhrReq
290269
@@ -293,14 +272,8 @@ reqToReflexRequest reqMeth reqHost req@(Req _ _ _ (reqBody :: Maybe (Dynamic t (
293272displayHttpRequest :: Text -> Text
294273displayHttpRequest httpmethod = "HTTP " <> httpmethod <> " request"
295274
296−instance IsXhrPayload o => IsXhrPayload (Maybe o) where
297− sendXhrPayload xhr = maybe (sendXhrPayload xhr ()) (sendXhrPayload xhr)
298−
299−instance (IsXhrPayload o, IsXhrPayload o') => IsXhrPayload (Either o o') where
300− sendXhrPayload xhr = either (sendXhrPayload xhr) (sendXhrPayload xhr)
301−
302275-- | This function performs the request
303−performRequests :: forall t m f tag. (SupportsServantReflex t m, Traversable f)
276+performRequests :: forall t m f tag.(SupportsServantReflex t m, Traversable f)
304277 => Text
305278 -> Dynamic t (f (Req t))
306279 -> Dynamic t BaseUrl
@@ -313,7 +286,6 @@ performRequests reqMeth rs reqHost opts trigger = do
313286 reqToReflexRequest reqMeth reqHost <$> fxhr) <$> rs
314287
315288 -- xhrReqs = fmap snd <$> xhrReqsAndDebugs
316− reqs :: Event t (Compose ((,) tag) f (Either Text SomeXhrRequest))
317289 reqs = attachPromptlyDynWith
318290 (\fxhr t -> Compose (t, fxhr)) xhrReqs trigger
319291
@@ -329,31 +301,32 @@ performSomeRequestsAsync
329301 HasWebView (Performable m),
330302 PerformEvent t m,
331303 TriggerEvent t m,
332− Traversable f
304+ Traversable f,
305+ IsXhrPayload a,
306+ Show a
333307 )
334308 => ClientOptions
335− -> Event t (f (Either Text SomeXhrRequest))
309+ -> Event t (f (Either Text (XhrRequest a)))
336310 -> m (Event t (f (Either Text XhrResponse)))
337−performSomeRequestsAsync opts sxhr =
338− performSomeRequestsAsync' opts newXMLHttpRequest $ return <$> sxhr
311+performSomeRequestsAsync opts =
312+ performSomeRequestsAsync' opts newXMLHttpRequest . fmap return
339313
340314
341315------------------------------------------------------------------------------
342316-- | A modified version or Reflex.Dom.Xhr.performRequestsAsync
343317-- that accepts 'f (Either e (XhrRequestb))' events
344318performSomeRequestsAsync'
345− :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f)
319+ :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f, Show b)
346320 => ClientOptions
347− -> (forall b. IsXhrPayload b => XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
348− -> Event t (Performable m (f (Either Text SomeXhrRequest)))
349− -> m (Event t (f (Either Text a)))
321+ -> (XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
322+ -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
350323performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
351324 rs <- hrs
352325 resps <- forM rs $ \r -> case r of
353326 Left e -> do
354327 resp <- liftIO $ newMVar (Left e)
355328 return resp
356− Right (SomeXhrRequest r') -> do
329+ Right r' -> do
357330 resp <- liftIO newEmptyMVar
358331 r'' <- liftJSM $ (optsRequestFixup opts) r'
359332 _ <- newXhr r'' $ liftIO . putMVar resp . Right
@@ -362,6 +335,12 @@ performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs
362335 return ()
363336
364337
338+
339+type XhrPayload = ByteString
340+bytesToPayload :: BL.ByteString -> XhrPayload
341+bytesToPayload = BL.toStrict
342+
343+
365344performRequestsCT
366345 :: (SupportsServantReflex t m,
367346 MimeUnrender ct a, Traversable f)
@@ -374,14 +353,15 @@ performRequestsCT
374353 -> m (Event t (f (ReqResult tag a)))
375354performRequestsCT ct reqMeth reqs reqHost opts trigger = do
376355 resps <- performRequests reqMeth reqs reqHost opts trigger
377− let decodeResp x = first T.pack
378− . mimeUnrender ct
379− . JS.textToJSString
380− =<< note "No body text" (_xhrResponse_responseText x)
356+ let decodeResp (Just (XhrResponseBody_ArrayBuffer x)) =
357+ first T.pack .
358+ mimeUnrender ct $
359+ BL.fromStrict x
360+ decodeResp _ = Left "No body"
381361 return $ fmap
382362 (\(t,rs) -> ffor rs $ \r -> case r of
383363 Left e -> RequestFailure t e
384− Right g -> evalResponse decodeResp (t,g)
364+ Right g -> evalResponse (decodeResp . _xhrResponse_response) (t,g)
385365 )
386366 resps
387367
@@ -408,7 +388,7 @@ evalResponse
408388 :: (XhrResponse -> Either Text a)
409389 -> (tag, XhrResponse)
410390 -> ReqResult tag a
411−evalResponse decodeRes (tag, xhr) =
391+evalResponse decode (tag, xhr) =
412392 let status = _xhrResponse_status xhr
413393 okStatus = status >= 200 && status < 400
414394 errMsg = fromMaybe
@@ -419,7 +399,7 @@ evalResponse decodeRes (tag, xhr) =
419399 then either
420400 (\e -> ResponseFailure tag e xhr)
421401 (\v -> ResponseSuccess tag v xhr)
422− (decodeRes xhr)
402+ (decode xhr)
423403 else ResponseFailure tag errMsg xhr
424404 in respPayld
425405
@@ -436,44 +416,8 @@ fanReqResult' :: Reflex t => Event t (ReqResult tag a) -> (Event t (tag, Text),
436416fanReqResult' = fmapMaybe reqFailure' &&& fmapMaybe reqSuccess'
437417
438418
439−-- | Similar to 'Servant.API.ContentType.MimeUnrender' but with the differnce that
440−-- the second argument expects a JSString instead of ByteString.
441−-- This can lead to better performance ghcjs.
442−class Accept contentType => MimeUnrender contentType a where
443− mimeUnrender :: Proxy contentType -> JS.JSString -> Either String a
444−
445−instance FromJSON a => MimeUnrender JSON a where
446− mimeUnrender _ = note "Failed to decode response" . jsonDecode
447−
448−-- | @urlDecodeAsForm@
449−-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
450−-- holds if every element of x is non-null (i.e., not @("", "")@)
451−instance FromForm a => MimeUnrender FormUrlEncoded a where
452− mimeUnrender _ = first T.unpack . urlDecodeAsForm . Builder.toLazyByteString . TE.encodeUtf8Builder . JS.textFromJSString
453−
454−-- | @left show . TextL.decodeUtf8'@
455−instance MimeUnrender PlainText TL.Text where
456− mimeUnrender _ = pure . JS.lazyTextFromJSString
457−
458−-- | @left show . TextS.decodeUtf8' . toStrict@
459−instance MimeUnrender PlainText T.Text where
460− mimeUnrender _ = pure . JS.textFromJSString
461−
462−-- | @Right . BC.unpack@
463−instance MimeUnrender PlainText String where
464− mimeUnrender _ = Right . T.unpack . JS.textFromJSString
465−
466−-- | @Right . id@
467−instance MimeUnrender OctetStream ByteString where
468− mimeUnrender _ = pure . Builder.toLazyByteString . TE.encodeUtf8Builder . JS.textFromJSString
469−
470−-- | @Right . toStrict@
471−instance MimeUnrender OctetStream BS.ByteString where
472− mimeUnrender _ = pure . TE.encodeUtf8 . JS.textFromJSString
473−
474419note :: e -> Maybe a -> Either e a
475−note e Nothing = Left e
476−note _ (Just a) = Right a
420+note e = maybe (Left e) Right
477421
478422fmapL :: (e -> e') -> Either e a -> Either e' a
479423fmapL _ (Right a) = Right a
@@ -484,3 +428,4 @@ builderToText = TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString
484428
485429escape :: T.Text -> T.Text
486430escape = T.pack . N.escapeURIString (not . N.isReserved) . T.unpack . TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString . toEncodedUrlPiece
431+
src/Servant/Reflex.hs+79−296
@@ -1,19 +1,14 @@
11{-# LANGUAGE AllowAmbiguousTypes #-}
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE DataKinds #-}
4−{-# LANGUAGE DefaultSignatures #-}
5−{-# LANGUAGE ExtendedDefaultRules #-}
64{-# LANGUAGE FlexibleContexts #-}
75{-# LANGUAGE FlexibleInstances #-}
8−{-# LANGUAGE FunctionalDependencies #-}
96{-# LANGUAGE GeneralizedNewtypeDeriving #-}
107{-# LANGUAGE InstanceSigs #-}
11−{-# LANGUAGE LambdaCase #-}
128{-# LANGUAGE MultiParamTypeClasses #-}
139{-# LANGUAGE OverloadedStrings #-}
1410{-# LANGUAGE PolyKinds #-}
1511{-# LANGUAGE Rank2Types #-}
16−{-# LANGUAGE RecordWildCards #-}
1712{-# LANGUAGE ScopedTypeVariables #-}
1813{-# LANGUAGE TupleSections #-}
1914{-# LANGUAGE TypeFamilies #-}
@@ -35,7 +30,6 @@ module Servant.Reflex
3530 , clientWithRoute
3631 , clientWithRouteAndResultHandler
3732 , BuildHeaderKeysTo(..)
38− , GHCJS'MimeRender(..)
3933 , toHeaders
4034 , HasClient
4135 , Client
@@ -45,101 +39,56 @@ module Servant.Reflex
4539
4640------------------------------------------------------------------------------
4741import Control.Applicative
48−import qualified Data.ByteString as BS
49−import qualified Data.ByteString.Lazy as BL
50−import Data.CaseInsensitive (mk)
42+import qualified Data.CaseInsensitive as CI
5143import Data.Functor.Identity
52−import Data.Kind (Type)
53−import qualified Data.Map as Map
54−import Data.Monoid ((<>))
55−import Data.Proxy (Proxy (..))
56−import qualified Data.Set as Set
57−import Data.Text (Text)
58−import qualified Data.Text as T
59−import qualified Data.Text.Encoding as E
60−import qualified Data.Text.Lazy as TL
61−import GHC.Exts (Constraint)
62−import GHC.TypeLits (KnownSymbol,
63− symbolVal)
64−import GHCJS.DOM.Types (Blob)
65−import Servant.API ((:<|>) (..),
66− (:>),
67− Accept (..),
68− BasicAuth,
69− BasicAuthData,
70− BuildHeadersTo (..),
71− Capture,
72− FormUrlEncoded,
73− Header,
74− Headers (..),
75− HttpVersion,
76− IsSecure,
77− JSON,
78− MimeRender (..),
79− NoContent,
80− OctetStream,
81− PlainText,
82− QueryFlag,
83− QueryParam,
84− QueryParams,
85− Raw,
86− ReflectMethod (..),
87− RemoteHost,
88− ReqBody,
89− ToHttpApiData (..),
90− Vault, Verb,
91− contentType)
92−import qualified Servant.Auth as Auth
93−
94−import Reflex.Dom.Core (Dynamic,
95− Event,
96− IsXhrPayload,
97− Reflex,
98− XhrRequest (..),
99− XhrResponse (..),
100− XhrResponseHeaders (..),
101− attachPromptlyDynWith,
102− constDyn,
103− ffor,
104− fmapMaybe,
105− leftmost,
106− performRequestsAsync)
44+import qualified Data.Map as Map
45+import Data.Monoid ((<>))
46+import Data.Proxy (Proxy (..))
47+import qualified Data.Set as Set
48+import Data.Text (Text)
49+import qualified Data.Text as T
50+import qualified Data.Text.Encoding as E
51+import GHC.Exts (Constraint)
52+import GHC.TypeLits (KnownSymbol, symbolVal)
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 (..),
60+ RemoteHost, ReqBody,
61+ ToHttpApiData (..), Vault, Verb,
62+ contentType)
63+import Servant.API.Description (Summary)
64+import qualified Servant.Auth as Auth
65+
66+import Reflex.Dom.Core (Dynamic, Event, Reflex,
67+ XhrRequest (..), XhrResponse (..),
68+ XhrResponseHeaders (..),
69+ attachPromptlyDynWith, constDyn, ffor,
70+ fmapMaybe, leftmost,
71+ performRequestsAsync)
10772------------------------------------------------------------------------------
108−import Servant.Checked.Exceptions.Internal (Envelope)
109−import Servant.Checked.Exceptions.Internal.Servant.API (NoThrow,
110− Throwing,
111− ThrowingNonterminal,
112− Throws)
113−import Servant.Common.BaseUrl (BaseUrl (..),
114− Scheme (..),
115− SupportsServantReflex,
116− baseUrlWidget,
117− showBaseUrl)
118−import Servant.Common.Req (ClientOptions (..),
119− MimeUnrender (..),
120− QParam (..),
121− QueryPart (..),
122− Req (..),
123− ReqResult (..),
124− addHeader,
125− authData,
126− defReq,
127− defaultClientOptions,
128− evalResponse,
129− performRequestsCT,
130− performRequestsNoBody,
131− performSomeRequestsAsync,
132− prependToPathParts,
133− qParamToQueryPart,
134− qParams,
135− reqBody,
136− reqFailure,
137− reqMethod,
138− reqSuccess,
139− reqTag,
140− respHeaders,
141− response,
142− withCredentials)
73+import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
74+ showBaseUrl,
75+ SupportsServantReflex)
76+import Servant.Common.Req (ClientOptions(..),
77+ defaultClientOptions,
78+ Req, ReqResult(..), QParam(..),
79+ QueryPart(..), addHeader, authData,
80+ defReq, evalResponse, prependToPathParts,
81+ -- performRequestCT,
82+ performRequestsCT,
83+ -- performRequestNoBody,
84+ performRequestsNoBody,
85+ performSomeRequestsAsync,
86+ qParamToQueryPart, reqBody,
87+ reqSuccess, reqFailure,
88+ reqMethod, respHeaders,
89+ response,
90+ reqTag,
91+ qParams, withCredentials)
14392
14493
14594-- * Accessing APIs as a Client
@@ -154,7 +103,7 @@ import Servant.Common.Req (ClientOptions
154103-- >
155104-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
156105-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
157−-- -> m (Event t (l, ReqResult Book)))
106+-- > -> m (Event t (l, ReqResult Book)))
158107-- > (getAllBooks :<|> postNewBook) = client myApi host
159108-- > where host = constDyn $ BaseUrl Http "localhost" 8080
160109client
@@ -279,22 +228,26 @@ instance {-# OVERLAPPING #-}
279228
280229toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
281230toHeaders r =
282− let toBS = E.encodeUtf8
283− hdrs = maybe []
284− (\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))
285233 (Map.toList $ _xhrResponse_headers xhr))
286234 (response r)
287235 in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
288236
237+
289238class BuildHeaderKeysTo hs where
290− buildHeaderKeysTo :: Proxy hs -> [T.Text]
239+ buildHeaderKeysTo :: Proxy hs -> [CI.CI T.Text]
291240
292241instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
293242 where buildHeaderKeysTo _ = []
294243
295244instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
296245 => BuildHeaderKeysTo ((Header h v) ': xs) where
297− 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+
298251
299252-- HEADERS Verb (Content) --
300253-- Headers combinator not treated in fully general case,
@@ -376,6 +329,19 @@ instance HasClient t m sublayout tag
376329 clientWithRouteAndResultHandler Proxy =
377330 clientWithRouteAndResultHandler (Proxy :: Proxy sublayout)
378331
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+
379345
380346-- | If you use a 'QueryParam' in one of your endpoints in your API,
381347-- the corresponding querying function will automatically take
@@ -547,65 +513,22 @@ instance SupportsServantReflex t m => HasClient t m Raw tag where
547513-- > where host = BaseUrl Http "localhost" 8080
548514-- > -- then you can just use "addBook" to query that endpoint
549515
550−instance (GHCJS'MimeRender ct a, IsXhrPayload (ToSend ct a), Show (ToSend ct a),
551− HasClient t m sublayout tag, Reflex t)
552− => HasClient t m (ReqBody (ct ': cts) (a :: Type) :> sublayout) tag where
516+instance (MimeRender ct a, HasClient t m sublayout tag, Reflex t)
517+ => HasClient t m (ReqBody (ct ': cts) a :> sublayout) tag where
553518
554519 type Client t m (ReqBody (ct ': cts) a :> sublayout) tag =
555− Dynamic t (Either Text (ToConvert ct a)) -> Client t m sublayout tag
520+ Dynamic t (Either Text a) -> Client t m sublayout tag
556521
557− clientWithRouteAndResultHandler Proxy q t Req{..} baseurl opts wrap body =
522+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap body =
558523 clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t req' baseurl opts wrap
559− where req' = Req { reqBody = Just $ (fmap . fmap)
560− (\b -> (ghcjsMimeRender ctProxy atProxy b, ctString)) body
561− , ..}
524+ where req' = req { reqBody = bodyBytesCT }
562525 ctProxy = Proxy :: Proxy ct
563− atProxy = Proxy :: Proxy a
564526 ctString = T.pack $ show $ contentType ctProxy
527+ bodyBytesCT = Just $ (fmap . fmap)
528+ (\b -> (mimeRender ctProxy b, ctString))
529+ body
565530
566−class Accept ctype => GHCJS'MimeRender (ctype :: Type) (a :: Type) where
567− type ToConvert ctype a :: Type
568− type ToConvert ctype a = a
569531
570− type ToSend ctype a :: Type
571− type ToSend ctype a = BS.ByteString
572−
573− ghcjsMimeRender
574− :: IsXhrPayload (ToSend ctype a)
575− => Proxy ctype -> Proxy a -> ToConvert ctype a -> ToSend ctype a
576− default ghcjsMimeRender
577− :: (MimeRender ctype a,
578− MimeRender ctype (ToConvert ctype a), ToSend ctype a ~ BS.ByteString)
579− => Proxy ctype -> Proxy a -> ToConvert ctype a -> ToSend ctype a
580− ghcjsMimeRender ctype _ = BL.toStrict . mimeRender ctype
581−
582−instance MimeRender JSON x => GHCJS'MimeRender JSON x
583−
584−instance MimeRender FormUrlEncoded x => GHCJS'MimeRender FormUrlEncoded x
585−
586−instance GHCJS'MimeRender OctetStream BL.ByteString where
587− type ToConvert OctetStream BL.ByteString = Either BL.ByteString Blob
588− type ToSend OctetStream BL.ByteString = Either BS.ByteString Blob
589− ghcjsMimeRender _ _ = \case Left bl -> Left $ BL.toStrict bl; Right x -> Right x
590−
591−instance GHCJS'MimeRender PlainText String where
592− type ToSend PlainText String = Text
593− ghcjsMimeRender _ _ = T.pack
594−
595−instance GHCJS'MimeRender PlainText Text where
596− type ToSend PlainText Text = Text
597− ghcjsMimeRender _ _ = id
598−
599−instance GHCJS'MimeRender PlainText TL.Text where
600− type ToSend PlainText TL.Text = Text
601− ghcjsMimeRender _ _ = TL.toStrict
602−
603−instance GHCJS'MimeRender PlainText BL.ByteString where
604− type ToSend PlainText BL.ByteString = BS.ByteString
605− ghcjsMimeRender _ _ = BL.toStrict
606−
607−instance GHCJS'MimeRender PlainText BS.ByteString where
608− ghcjsMimeRender _ _ = id
609532
610533-- | Make the querying function append @path@ to the request path.
611534instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient t m (path :> sublayout) tag where
@@ -700,143 +623,3 @@ type family HasCookieAuth xs :: Constraint where
700623
701624class CookieAuthNotEnabled
702625
703−{- servant-checked-exceptions
704−~~~~~~~~~~~~~~~~~~~~~
705−-}
706−
707−-- | Change a 'Throws' into 'Throwing'.
708−instance (Monad m, HasClient t m (Throwing '[e] :> api) tag) => HasClient t m (Throws e :> api) tag where
709− type Client t m (Throws e :> api) tag = Client t m (Throwing '[e] :> api) tag
710−
711− clientWithRouteAndResultHandler
712− :: Proxy (Throws e :> api)
713− -> Proxy m
714− -> Proxy tag
715− -> Req t
716− -> Dynamic t BaseUrl
717− -> ClientOptions
718− -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
719− -> Client t m (Throwing '[e] :> api) tag
720− clientWithRouteAndResultHandler _ = clientWithRouteAndResultHandler (Proxy :: Proxy (Throwing '[e] :> api))
721−
722−-- | When @'Throwing' es@ comes before a 'Verb', change it into the same 'Verb'
723−-- but returning an @'Envelope' es@.
724−instance (HasClient t m (Verb method status ctypes (Envelope es a)) tag) =>
725− HasClient t m (Throwing es :> Verb method status ctypes a) tag where
726−
727− type Client t m (Throwing es :> Verb method status ctypes a) tag =
728− Client t m (Verb method status ctypes (Envelope es a)) tag
729−
730− clientWithRouteAndResultHandler
731− :: Proxy (Throwing es :> Verb method status ctypes a)
732− -> Proxy m
733− -> Proxy tag
734− -> Req t
735− -> Dynamic t BaseUrl
736− -> ClientOptions
737− -> (forall x. Event t (ReqResult tag x) -> m (Event t (ReqResult tag x)))
738− -> Client t m (Verb method status ctypes (Envelope es a)) tag
739− clientWithRouteAndResultHandler Proxy =
740− clientWithRouteAndResultHandler (Proxy :: Proxy (Verb method status ctypes (Envelope es a)))
741−
742−-- | When 'NoThrow' comes before a 'Verb', change it into the same 'Verb'
743−-- but returning an @'Envelope' \'[]@.
744−instance (Monad m, HasClient t m (Verb method status ctypes (Envelope '[] a)) tag) =>
745− HasClient t m (NoThrow :> Verb method status ctypes a) tag where
746−
747− type Client t m (NoThrow :> Verb method status ctypes a) tag =
748− Client t m (Verb method status ctypes (Envelope '[] a)) tag
749−
750− clientWithRouteAndResultHandler
751− :: Proxy (NoThrow :> Verb method status ctypes a)
752− -> Proxy m
753− -> Proxy tag
754− -> Req t
755− -> Dynamic t BaseUrl
756− -> ClientOptions
757− -> (forall x. Event t (ReqResult tag x) -> m (Event t (ReqResult tag x)))
758− -> Client t m (Verb method status ctypes (Envelope '[] a)) tag
759− clientWithRouteAndResultHandler Proxy =
760− clientWithRouteAndResultHandler (Proxy :: Proxy (Verb method status ctypes (Envelope '[] a)))
761−
762−-- | When @'Throwing' es@ comes before ':<|>', push @'Throwing' es@ into each
763−-- branch of the API.
764−instance (Monad m, HasClient t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) tag) =>
765− HasClient t m (Throwing es :> (api1 :<|> api2)) tag where
766−
767− type Client t m (Throwing es :> (api1 :<|> api2)) tag =
768− Client t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) tag
769−
770− clientWithRouteAndResultHandler
771− :: Proxy (Throwing es :> (api1 :<|> api2))
772− -> Proxy m
773− -> Proxy tag
774− -> Req t
775− -> Dynamic t BaseUrl
776− -> ClientOptions
777− -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
778− -> Client t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) tag
779− clientWithRouteAndResultHandler _ =
780− clientWithRouteAndResultHandler (Proxy :: Proxy ((Throwing es :> api1) :<|> (Throwing es :> api2)))
781−
782−-- | When 'NoThrow' comes before ':<|>', push 'NoThrow' into each branch of the
783−-- API.
784−instance (Monad m, HasClient t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) tag) =>
785− HasClient t m (NoThrow :> (api1 :<|> api2)) tag where
786−
787− type Client t m (NoThrow :> (api1 :<|> api2)) tag =
788− Client t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) tag
789−
790− clientWithRouteAndResultHandler
791− :: Proxy (NoThrow :> (api1 :<|> api2))
792− -> Proxy m
793− -> Proxy tag
794− -> Req t
795− -> Dynamic t BaseUrl
796− -> ClientOptions
797− -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
798− -> Client t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) tag
799− clientWithRouteAndResultHandler _ =
800− clientWithRouteAndResultHandler (Proxy :: Proxy ((NoThrow :> api1) :<|> (NoThrow :> api2)))
801−
802−-- | When a @'Throws' e@ comes immediately after a @'Throwing' es@, 'Snoc' the
803−-- @e@ onto the @es@. Otherwise, if @'Throws' e@ comes before any other
804−-- combinator, push it down so it is closer to the 'Verb'.
805−instance (Monad m, HasClient t m (ThrowingNonterminal (Throwing es :> api :> apis)) tag) =>
806− HasClient t m (Throwing es :> api :> apis) tag where
807−
808− type Client t m (Throwing es :> api :> apis) tag =
809− Client t m (ThrowingNonterminal (Throwing es :> api :> apis)) tag
810−
811− clientWithRouteAndResultHandler
812− :: Proxy (Throwing es :> api :> apis)
813− -> Proxy m
814− -> Proxy tag
815− -> Req t
816− -> Dynamic t BaseUrl
817− -> ClientOptions
818− -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
819− -> Client t m (ThrowingNonterminal (Throwing es :> api :> apis)) tag
820− clientWithRouteAndResultHandler _ =
821− clientWithRouteAndResultHandler (Proxy :: Proxy (ThrowingNonterminal (Throwing es :> api :> apis)))
822−
823−-- | When 'NoThrow' comes before any other combinator, push it down so it is
824−-- closer to the 'Verb'.
825−instance (Monad m, HasClient t m (api :> NoThrow :> apis) tag) =>
826− HasClient t m (NoThrow :> api :> apis) tag where
827−
828− type Client t m (NoThrow :> api :> apis) tag =
829− Client t m (api :> NoThrow :> apis) tag
830−
831− clientWithRouteAndResultHandler
832− :: Proxy (NoThrow :> api :> apis)
833− -> Proxy m
834− -> Proxy tag
835− -> Req t
836− -> Dynamic t BaseUrl
837− -> ClientOptions
838− -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
839− -> Client t m (api :> NoThrow :> apis) tag
840− clientWithRouteAndResultHandler _ =
841− clientWithRouteAndResultHandler (Proxy :: Proxy (api :> NoThrow :> apis))
842−
src/Servant/Reflex/Multi.hs+55−224
@@ -7,7 +7,6 @@
77{-# LANGUAGE MultiParamTypeClasses #-}
88{-# LANGUAGE OverloadedStrings #-}
99{-# LANGUAGE PolyKinds #-}
10−{-# LANGUAGE RecordWildCards #-}
1110{-# LANGUAGE ScopedTypeVariables #-}
1211{-# LANGUAGE TupleSections #-}
1312{-# LANGUAGE TypeFamilies #-}
@@ -38,83 +37,46 @@ module Servant.Reflex.Multi (
3837 ) where
3938
4039------------------------------------------------------------------------------
41−import Control.Applicative (liftA2)
42−import Data.Functor.Compose (Compose (..),
43− getCompose)
44−import Data.Proxy (Proxy (..))
45−import qualified Data.Set as Set
46−import Data.Text (Text)
47−import qualified Data.Text as T
48−import qualified Data.Text.Encoding as E
49−import GHC.TypeLits (KnownSymbol,
50− symbolVal)
51−import Servant.API ((:<|>) (..),
52− (:>),
53− BasicAuth,
54− BasicAuthData,
55− BuildHeadersTo (..),
56− Capture,
57− Header,
58− Headers (..),
59− HttpVersion,
60− IsSecure,
61− NoContent,
62− QueryFlag,
63− QueryParam,
64− QueryParams,
65− Raw,
66− ReflectMethod (..),
67− RemoteHost,
68− ReqBody,
69− ToHttpApiData (..),
70− Vault, Verb,
71− contentType)
72−
73−import Reflex.Dom.Core (Dynamic,
74− Event,
75− IsXhrPayload,
76− Reflex,
77− XhrRequest (..),
78− XhrResponseHeaders (..),
79− attachPromptlyDynWith,
80− 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)
8164------------------------------------------------------------------------------
82−import Servant.Checked.Exceptions.Internal (Envelope)
83−import Servant.Checked.Exceptions.Internal.Servant.API (NoThrow,
84− Throwing,
85− ThrowingNonterminal,
86− Throws)
87−import Servant.Common.BaseUrl (BaseUrl (..),
88− Scheme (..),
89− SupportsServantReflex)
90−import Servant.Common.Req (ClientOptions,
91− MimeUnrender,
92− QParam (..),
93− QueryPart (..),
94− Req (..),
95− ReqResult (..),
96− SomeXhrRequest (..),
97− addHeader,
98− authData,
99− defReq,
100− defaultClientOptions,
101− performRequestsCT,
102− performRequestsNoBody,
103− performSomeRequestsAsync,
104− prependToPathParts,
105− qParamToQueryPart,
106− qParams,
107− reqBody,
108− reqFailure,
109− reqMethod,
110− reqSuccess,
111− reqSuccess',
112− respHeaders,
113− response,
114− withCredentials)
115−import Servant.Reflex (BuildHeaderKeysTo (..),
116− GHCJS'MimeRender (..),
117− toHeaders)
65+import Servant.Common.BaseUrl (BaseUrl (..), Scheme (..),
66+ SupportsServantReflex)
67+import Servant.Common.Req (ClientOptions,
68+ QParam (..), QueryPart (..), Req,
69+ ReqResult (..), addHeader, authData,
70+ defReq,
71+ defaultClientOptions,
72+ performRequestsCT,
73+ performRequestsNoBody,
74+ performSomeRequestsAsync,
75+ prependToPathParts, qParamToQueryPart,
76+ qParams, reqBody, reqFailure,
77+ reqMethod, reqSuccess, reqSuccess',
78+ respHeaders, response, withCredentials)
79+import Servant.Reflex (BuildHeaderKeysTo (..), toHeaders)
11880
11981
12082------------------------------------------------------------------------------
@@ -280,6 +242,17 @@ instance HasClientMulti t m sublayout f tag
280242 clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag
281243
282244
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+
283256------------------------------------------------------------------------------
284257instance (KnownSymbol sym,
285258 ToHttpApiData a,
@@ -353,31 +326,29 @@ instance (SupportsServantReflex t m,
353326 clientWithRouteMulti _ _ _ _ _ _ opts rawReqs triggers = do
354327 let rawReqs' = sequence rawReqs :: Dynamic t (f (Either Text (XhrRequest ())))
355328 rawReqs'' = attachPromptlyDynWith (\fxhr t -> Compose (t, fxhr)) rawReqs' triggers
356− rawReqs''' = (fmap . fmap . fmap) (SomeXhrRequest . fmap Just) rawReqs''
357− resps <- fmap (fmap aux . sequenceA . getCompose) <$> performSomeRequestsAsync opts rawReqs'''
329+ resps <- fmap (fmap aux . sequenceA . getCompose) <$> performSomeRequestsAsync opts rawReqs''
358330 return resps
359331 where
360332 aux (tag, Right r) = ResponseSuccess tag () r
361333 aux (tag, Left e) = RequestFailure tag e
362334
363335
364−instance (GHCJS'MimeRender ct a, IsXhrPayload (ToSend ct a), Show (ToSend ct a),
336+instance (MimeRender ct a,
365337 HasClientMulti t m sublayout f tag,
366338 Reflex t,
367339 Applicative f)
368340 => HasClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f tag where
369341
370342 type ClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f tag =
371− Dynamic t (f (Either Text (ToConvert ct a))) -> ClientMulti t m sublayout f tag
343+ Dynamic t (f (Either Text a)) -> ClientMulti t m sublayout f tag
372344
373345 clientWithRouteMulti Proxy q f tag reqs baseurl opts bodies =
374346 clientWithRouteMulti (Proxy :: Proxy sublayout) q f tag reqs' baseurl opts
375− where req' b Req{..} = Req { reqBody = bodyBytesCT (constDyn b), .. }
347+ where req' b r = r { reqBody = bodyBytesCT (constDyn b) }
376348 ctProxy = Proxy :: Proxy ct
377− atProxy = Proxy :: Proxy a
378349 ctString = T.pack $ show $ contentType ctProxy
379350 bodyBytesCT b = Just $ (fmap . fmap)
380− (\b' -> (ghcjsMimeRender ctProxy atProxy b', ctString))
351+ (\b' -> (mimeRender ctProxy b', ctString))
381352 b
382353 reqs' = liftA2 req' <$> bodies <*> reqs
383354
@@ -428,143 +399,3 @@ instance (HasClientMulti t m api f tag, Reflex t, Applicative f)
428399 where
429400 req' a r = r { authData = Just (constDyn a) }
430401 reqs' = liftA2 req' <$> authdatas <*> reqs
431−
432−{- servant-checked-exceptions
433−~~~~~~~~~~~~~~~~~~~~~
434−-}
435−
436−-- | Change a 'Throws' into 'Throwing'.
437−instance (HasClientMulti t m (Throwing '[e] :> api) f tag) => HasClientMulti t m (Throws e :> api) f tag where
438− type ClientMulti t m (Throws e :> api) f tag = ClientMulti t m (Throwing '[e] :> api) f tag
439−
440− clientWithRouteMulti
441− :: Proxy (Throws e :> api)
442− -> Proxy m
443− -> Proxy f
444− -> Proxy tag
445− -> Dynamic t (f (Req t))
446− -> Dynamic t BaseUrl
447− -> ClientOptions
448− -> ClientMulti t m (Throwing '[e] :> api) f tag
449− clientWithRouteMulti _ = clientWithRouteMulti (Proxy :: Proxy (Throwing '[e] :> api))
450−
451−-- | When @'Throwing' es@ comes before a 'Verb', change it into the same 'Verb'
452−-- but returning an @'Envelope' es@.
453−instance (HasClientMulti t m (Verb method status ctypes (Envelope es a)) f tag) =>
454− HasClientMulti t m (Throwing es :> Verb method status ctypes a) f tag where
455−
456− type ClientMulti t m (Throwing es :> Verb method status ctypes a) f tag =
457− ClientMulti t m (Verb method status ctypes (Envelope es a)) f tag
458−
459− clientWithRouteMulti
460− :: Proxy (Throwing es :> Verb method status ctypes a)
461− -> Proxy m
462− -> Proxy f
463− -> Proxy tag
464− -> Dynamic t (f (Req t))
465− -> Dynamic t BaseUrl
466− -> ClientOptions
467− -> ClientMulti t m (Verb method status ctypes (Envelope es a)) f tag
468− clientWithRouteMulti Proxy =
469− clientWithRouteMulti (Proxy :: Proxy (Verb method status ctypes (Envelope es a)))
470−
471−-- | When 'NoThrow' comes before a 'Verb', change it into the same 'Verb'
472−-- but returning an @'Envelope' \'[]@.
473−instance (HasClientMulti t m (Verb method status ctypes (Envelope '[] a)) f tag) =>
474− HasClientMulti t m (NoThrow :> Verb method status ctypes a) f tag where
475−
476− type ClientMulti t m (NoThrow :> Verb method status ctypes a) f tag =
477− ClientMulti t m (Verb method status ctypes (Envelope '[] a)) f tag
478−
479− clientWithRouteMulti
480− :: Proxy (NoThrow :> Verb method status ctypes a)
481− -> Proxy m
482− -> Proxy f
483− -> Proxy tag
484− -> Dynamic t (f (Req t))
485− -> Dynamic t BaseUrl
486− -> ClientOptions
487− -> ClientMulti t m (Verb method status ctypes (Envelope '[] a)) f tag
488− clientWithRouteMulti Proxy =
489− clientWithRouteMulti (Proxy :: Proxy (Verb method status ctypes (Envelope '[] a)))
490−
491−-- | When @'Throwing' es@ comes before ':<|>', push @'Throwing' es@ into each
492−-- branch of the API.
493−instance HasClientMulti t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) f tag =>
494− HasClientMulti t m (Throwing es :> (api1 :<|> api2)) f tag where
495−
496− type ClientMulti t m (Throwing es :> (api1 :<|> api2)) f tag =
497− ClientMulti t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) f tag
498−
499− clientWithRouteMulti
500− :: Proxy (Throwing es :> (api1 :<|> api2))
501− -> Proxy m
502− -> Proxy f
503− -> Proxy tag
504− -> Dynamic t (f (Req t))
505− -> Dynamic t BaseUrl
506− -> ClientOptions
507− -> ClientMulti t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) f tag
508− clientWithRouteMulti _ =
509− clientWithRouteMulti (Proxy :: Proxy ((Throwing es :> api1) :<|> (Throwing es :> api2)))
510−
511−-- | When 'NoThrow' comes before ':<|>', push 'NoThrow' into each branch of the
512−-- API.
513−instance HasClientMulti t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) f tag =>
514− HasClientMulti t m (NoThrow :> (api1 :<|> api2)) f tag where
515−
516− type ClientMulti t m (NoThrow :> (api1 :<|> api2)) f tag =
517− ClientMulti t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) f tag
518−
519− clientWithRouteMulti
520− :: Proxy (NoThrow :> (api1 :<|> api2))
521− -> Proxy m
522− -> Proxy f
523− -> Proxy tag
524− -> Dynamic t (f (Req t))
525− -> Dynamic t BaseUrl
526− -> ClientOptions
527− -> ClientMulti t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) f tag
528− clientWithRouteMulti _ =
529− clientWithRouteMulti (Proxy :: Proxy ((NoThrow :> api1) :<|> (NoThrow :> api2)))
530−
531−-- | When a @'Throws' e@ comes immediately after a @'Throwing' es@, 'Snoc' the
532−-- @e@ onto the @es@. Otherwise, if @'Throws' e@ comes before any other
533−-- combinator, push it down so it is closer to the 'Verb'.
534−instance HasClientMulti t m (ThrowingNonterminal (Throwing es :> api :> apis)) f tag =>
535− HasClientMulti t m (Throwing es :> api :> apis) f tag where
536−
537− type ClientMulti t m (Throwing es :> api :> apis) f tag =
538− ClientMulti t m (ThrowingNonterminal (Throwing es :> api :> apis)) f tag
539−
540− clientWithRouteMulti
541− :: Proxy (Throwing es :> api :> apis)
542− -> Proxy m
543− -> Proxy f
544− -> Proxy tag
545− -> Dynamic t (f (Req t))
546− -> Dynamic t BaseUrl
547− -> ClientOptions
548− -> ClientMulti t m (ThrowingNonterminal (Throwing es :> api :> apis)) f tag
549− clientWithRouteMulti _ =
550− clientWithRouteMulti (Proxy :: Proxy (ThrowingNonterminal (Throwing es :> api :> apis)))
551−
552−-- | When 'NoThrow' comes before any other combinator, push it down so it is
553−-- closer to the 'Verb'.
554−instance HasClientMulti t m (api :> NoThrow :> apis) f tag =>
555− HasClientMulti t m (NoThrow :> api :> apis) f tag where
556−
557− type ClientMulti t m (NoThrow :> api :> apis) f tag =
558− ClientMulti t m (api :> NoThrow :> apis) f tag
559−
560− clientWithRouteMulti
561− :: Proxy (NoThrow :> api :> apis)
562− -> Proxy m
563− -> Proxy f
564− -> Proxy tag
565− -> Dynamic t (f (Req t))
566− -> Dynamic t BaseUrl
567− -> ClientOptions
568− -> ClientMulti t m (api :> NoThrow :> apis) f tag
569− clientWithRouteMulti _ =
570− clientWithRouteMulti (Proxy :: Proxy (api :> NoThrow :> apis))
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 && < 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+2
@@ -31,6 +31,7 @@ let
3131 servant-snap = dontCheck ((import ./nix/servant-snap.nix {}) self super);
3232 testdriver = self.callCabal2nix "testdriver" ./testdriver {};
3333 testserver = import nix/testserver.nix ghcjsPkgs.servant-reflex self super;
34+ servant-reflex = self.callPackage ./default.nix {};
3435 };
3536 };
3637
@@ -47,6 +48,7 @@ in
4748 inherit reflexPlatform ghcPkgs ghcjsPkgs;
4849
4950 build = ghcjsPkgs.servant-reflex;
51+ buildGhc = ghcPkgs.servant-reflex;
5052 testserver = ghcPkgs.testserver;
5153 testdriver = ghcPkgs.testdriver;
5254 inherit testresults;