@@ -1,16 +1,23 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE AllowAmbiguousTypes #-}
2−{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE DataKinds #-}
3−{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleContexts #-}
4−{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
5−{-# LANGUAGE InstanceSigs #-}
7+{-# LANGUAGE InstanceSigs #-}
8+{-# LANGUAGE MultiParamTypeClasses #-}
6−{-# LANGUAGE OverloadedStrings #-}
9+{-# LANGUAGE OverloadedStrings #-}
7−{-# LANGUAGE PolyKinds #-}
10+{-# LANGUAGE PolyKinds #-}
11+{-# LANGUAGE Rank2Types #-}
8−{-# LANGUAGE ScopedTypeVariables #-}
12+{-# LANGUAGE ScopedTypeVariables #-}
9−{-# LANGUAGE TupleSections #-}
13+{-# LANGUAGE TupleSections #-}
10−{-# LANGUAGE TypeFamilies #-}
14+{-# LANGUAGE TypeFamilies #-}
11−{-# LANGUAGE TypeOperators #-}
15+{-# LANGUAGE TypeOperators #-}
12−{-# LANGUAGE UndecidableInstances #-}
16+{-# LANGUAGE UndecidableInstances #-}
13−{-# LANGUAGE MultiParamTypeClasses #-}
17+
18+#if MIN_VERSION_base(4,9,0)
19+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}
15-- #include "overlapping-compat.h"
22-- #include "overlapping-compat.h"
16-- | This module provides 'client' which can automatically generate
23-- | This module provides 'client' which can automatically generate
@@ -18,49 +25,71 @@
19module Servant.Reflex
26module Servant.Reflex
21− , HasClient(..)
28+ , clientWithOpts
29+ , clientWithOptsAndResultHandler
31+ , clientWithRouteAndResultHandler
32+ , BuildHeaderKeysTo(..)
22 , module Servant.Common.Req
36 , module Servant.Common.Req
23 , module Servant.Common.BaseUrl
37 , module Servant.Common.BaseUrl
26−-------------------------------------------------------------------------------
40+------------------------------------------------------------------------------
27import Control.Applicative
41import Control.Applicative
42+import qualified Data.CaseInsensitive as CI
43+import Data.Functor.Identity
44+import qualified Data.Map as Map
28import Data.Monoid ((<>))
45import Data.Monoid ((<>))
29−import qualified Data.Set as Set
30−import qualified Data.Text.Encoding as E
31−import Data.CaseInsensitive (mk)
32import Data.Proxy (Proxy (..))
46import Data.Proxy (Proxy (..))
33−import qualified Data.Map as Map
47+import qualified Data.Set as Set
34import Data.Text (Text)
48import Data.Text (Text)
35import qualified Data.Text as T
49import qualified Data.Text as T
50+import qualified Data.Text.Encoding as E
51+import GHC.Exts (Constraint)
36import GHC.TypeLits (KnownSymbol, symbolVal)
52import GHC.TypeLits (KnownSymbol, symbolVal)
37−import Servant.API ((:<|>)(..),(:>), BasicAuth,
53+import Servant.API ((:<|>) (..), (:>), BasicAuth,
38− BasicAuthData, BuildHeadersTo(..),
54+ BasicAuthData, BuildHeadersTo (..),
39− Capture, contentType, Header,
55+ Capture, Header, Headers (..),
40− Headers(..), HttpVersion, IsSecure,
56+ HttpVersion, IsSecure,
41− MimeRender(..), MimeUnrender,
57+ MimeRender (..), MimeUnrender,
42− NoContent, QueryFlag, QueryParam,
58+ NoContent, QueryFlag, QueryParam',
43− QueryParams, Raw, ReflectMethod(..),
59+ QueryParams, Raw, ReflectMethod (..),
44 RemoteHost, ReqBody,
60 RemoteHost, ReqBody,
45− ToHttpApiData(..), Vault, Verb)
61+ ToHttpApiData (..), Vault, Verb,
63+import Servant.API.Description (Summary)
64+import qualified Servant.Auth as Auth
66+import Reflex.Dom.Core (Dynamic, Event, Reflex,
67+ XhrRequest (..), XhrResponse (..),
68+ XhrResponseHeaders (..),
69+ attachPromptlyDynWith, constDyn, ffor,
71+ performRequestsAsync)
72+------------------------------------------------------------------------------
46import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
73import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
47 showBaseUrl,
74 showBaseUrl,
48 SupportsServantReflex)
75 SupportsServantReflex)
76+import Servant.Common.Req (ClientOptions(..),
77+ defaultClientOptions,
49−import Servant.Common.Req (Req, ReqResult(..), QParam(..),
78+ Req, ReqResult(..), QParam(..),
50 QueryPart(..), addHeader, authData,
79 QueryPart(..), addHeader, authData,
51− defReq, prependToPathParts,
80+ defReq, evalResponse, prependToPathParts,
52− performRequestCT,
81+ -- performRequestCT,
53− performRequestNoBody,
83+ -- performRequestNoBody,
84+ performRequestsNoBody,
85+ performSomeRequestsAsync,
54 qParamToQueryPart, reqBody,
86 qParamToQueryPart, reqBody,
55 reqSuccess, reqFailure,
87 reqSuccess, reqFailure,
56− reqMethod, respHeaders, response,
88+ reqMethod, respHeaders,
57− qParams)
91+ qParams, withCredentials)
58−import Reflex.Dom (Dynamic, Event, Reflex,
92+
60− XhrResponseHeaders(..),
61− XhrResponse(..), ffor, fmapMaybe,
62− leftmost, performRequestAsync,
65-- * Accessing APIs as a Client
94-- * Accessing APIs as a Client
@@ -72,28 +101,78 @@ import Reflex.Dom (Dynamic, Event, Reflex,
72-- > myApi :: Proxy MyApi
101-- > myApi :: Proxy MyApi
73-- > myApi = Proxy
102-- > myApi = Proxy
75−-- > getAllBooks :: Event t () -> m (Event t (Either XhrError ((),[Book])))
104+-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
76−-- > postNewBook :: Dynamic t (Maybe Book) -> Event t ()
105+-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
77−-- -> m (Event t (Either XhrError (Book,Book)))
106+-- > -> m (Event t (l, ReqResult Book)))
78-- > (getAllBooks :<|> postNewBook) = client myApi host
107-- > (getAllBooks :<|> postNewBook) = client myApi host
79-- > where host = constDyn $ BaseUrl Http "localhost" 8080
108-- > where host = constDyn $ BaseUrl Http "localhost" 8080
110+ :: (HasClient t m layout tag)
114+ -> Dynamic t BaseUrl
115+ -> Client t m layout tag
116+client p q t baseurl = clientWithRoute p q t defReq baseurl defaultClientOptions
80−client :: (HasClient t m layout)
119+ :: (HasClient t m layout tag)
81− => Proxy layout -> Proxy m -> Dynamic t BaseUrl -> Client t m layout
123+ -> Dynamic t BaseUrl
125+ -> Client t m layout tag
82−client p q baseurl = clientWithRoute p q defReq baseurl
126+clientWithOpts p q t baseurl = clientWithRoute p q t defReq baseurl
128+-- | Like 'clientWithOpts' but allows passing a function which will process the
129+-- result event in some way. This can be used to handle errors in a uniform way
130+-- across call sites.
131+clientWithOptsAndResultHandler
132+ :: (HasClient t m layout tag)
136+ -> Dynamic t BaseUrl
138+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
139+ -> Client t m layout tag
140+clientWithOptsAndResultHandler p q t = clientWithRouteAndResultHandler p q t defReq
84-- | This class lets us define how each API combinator
143-- | This class lets us define how each API combinator
85-- influences the creation of an HTTP request. It's mostly
144-- influences the creation of an HTTP request. It's mostly
86-- an internal class, you can just use 'client'.
145-- an internal class, you can just use 'client'.
87−class HasClient t m layout where
146+class Monad m => HasClient t m layout (tag :: *) where
88− type Client t m layout :: *
147+ type Client t m layout tag :: *
89− clientWithRoute :: Proxy layout -> Proxy m -> Req t -> Dynamic t BaseUrl -> Client t m layout
148+ clientWithRoute
153+ -> Dynamic t BaseUrl
155+ -> Client t m layout tag
156+ clientWithRoute l m t r b o = clientWithRouteAndResultHandler l m t r b o return
158+ clientWithRouteAndResultHandler
163+ -> Dynamic t BaseUrl
165+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
166+ -> Client t m layout tag
169+instance (HasClient t m a tag, HasClient t m b tag) => HasClient t m (a :<|> b) tag where
170+ type Client t m (a :<|> b) tag = Client t m a tag :<|> Client t m b tag
172+ clientWithRouteAndResultHandler Proxy q pTag req baseurl opts wrap =
173+ clientWithRouteAndResultHandler (Proxy :: Proxy a) q pTag req baseurl opts wrap :<|>
174+ clientWithRouteAndResultHandler (Proxy :: Proxy b) q pTag req baseurl opts wrap
92−instance (HasClient t m a, HasClient t m b) => HasClient t m (a :<|> b) where
93− type Client t m (a :<|> b) = Client t m a :<|> Client t m b
94− clientWithRoute Proxy q req baseurl =
95− clientWithRoute (Proxy :: Proxy a) q req baseurl :<|>
96− clientWithRoute (Proxy :: Proxy b) q req baseurl
98-- Capture. Example:
177-- Capture. Example:
99-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
178-- > type MyApi = "books" :> Capture "isbn" Text :> Get '[JSON] Book
@@ -103,67 +182,72 @@ instance (HasClient t m a, HasClient t m b) => HasClient t m (a :<|> b) where
103-- > getBook :: SupportsServantReflex t m
182-- > getBook :: SupportsServantReflex t m
104-- => Dynamic t BaseUrl
183-- => Dynamic t BaseUrl
105-- -> Dynamic t (Maybe Text)
184-- -> Dynamic t (Maybe Text)
106−-- -> Event t ()
185+-- -> Event t l
107−-- -> m (Event t (Either XhrError (Text, Book)))
186+-- -> m (Event t (l, ReqResult Book))
108-- > getBook = client myApi (constDyn host)
187-- > getBook = client myApi (constDyn host)
109−instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout)
110− => HasClient t m (Capture capture a :> sublayout) where
189+instance (SupportsServantReflex t m, ToHttpApiData a, HasClient t m sublayout tag)
112− type Client t m (Capture capture a :> sublayout) =
190+ => HasClient t m (Capture capture a :> sublayout) tag where
113− Dynamic t (Either Text a) -> Client t m sublayout
115− clientWithRoute Proxy q req baseurl val =
192+ type Client t m (Capture capture a :> sublayout) tag =
116− clientWithRoute (Proxy :: Proxy sublayout)
193+ Dynamic t (Either Text a) -> Client t m sublayout tag
118− (prependToPathParts p req)
195+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap val =
196+ clientWithRouteAndResultHandler
197+ (Proxy :: Proxy sublayout) q t (prependToPathParts p req) baseurl opts wrap
121 where p = (fmap . fmap) (toUrlPiece) val
198 where p = (fmap . fmap) (toUrlPiece) val
123-- VERB (Returning content) --
201-- VERB (Returning content) --
124instance {-# OVERLAPPABLE #-}
202instance {-# OVERLAPPABLE #-}
125 -- Note [Non-Empty Content Types]
203 -- Note [Non-Empty Content Types]
126 (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m
204 (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts), SupportsServantReflex t m
127− ) => HasClient t m (Verb method status cts' a) where
205+ ) => HasClient t m (Verb method status cts' a) tag where
128− type Client t m (Verb method status cts' a) =
206+ type Client t m (Verb method status cts' a) tag =
129− Event t () -> m (Event t (ReqResult a))
207+ Event t tag -> m (Event t (ReqResult tag a))
130 -- TODO how to access input types here?
208 -- TODO how to access input types here?
131 -- ExceptT ServantError IO a
209 -- ExceptT ServantError IO a
132− clientWithRoute Proxy _ req baseurl =
210+ clientWithRouteAndResultHandler Proxy _ _ req baseurl opts wrap trigs =
133− performRequestCT (Proxy :: Proxy ct) method req' baseurl
211+ wrap =<< fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity $ req') baseurl opts trigs
134 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
212 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
135 req' = req { reqMethod = method }
213 req' = req { reqMethod = method }
137-- -- VERB (No content) --
216-- -- VERB (No content) --
138instance {-# OVERLAPPING #-}
217instance {-# OVERLAPPING #-}
139 (ReflectMethod method, SupportsServantReflex t m) =>
218 (ReflectMethod method, SupportsServantReflex t m) =>
140− HasClient t m (Verb method status cts NoContent) where
219+ HasClient t m (Verb method status cts NoContent) tag where
141− type Client t m (Verb method status cts NoContent) =
220+ type Client t m (Verb method status cts NoContent) tag =
142− Event t () -> m (Event t (ReqResult NoContent))
221+ Event t tag -> m (Event t (ReqResult tag NoContent))
143 -- TODO: how to access input types here?
222 -- TODO: how to access input types here?
144 -- ExceptT ServantError IO NoContent
223 -- ExceptT ServantError IO NoContent
145− clientWithRoute Proxy _ req baseurl =
224+ clientWithRouteAndResultHandler Proxy _ _ req baseurl opts wrap trigs =
146− performRequestNoBody method req baseurl
225+ wrap =<< fmap runIdentity <$> performRequestsNoBody method (constDyn $ Identity req) baseurl opts trigs
147 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
226 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
149−toHeaders :: BuildHeadersTo ls => ReqResult a -> ReqResult (Headers ls a)
229+toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
150toHeaders r =
230toHeaders r =
151− let toBS = E.encodeUtf8
152− hdrs = maybe []
231+ let hdrs = maybe []
153− (\xhr -> fmap (\(h,v) -> (mk (toBS h), toBS v))
232+ (\xhr -> fmap (\(h,v) -> (CI.map E.encodeUtf8 h, E.encodeUtf8 v))
154 (Map.toList $ _xhrResponse_headers xhr))
233 (Map.toList $ _xhrResponse_headers xhr))
155 (response r)
234 (response r)
156 in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
235 in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
158class BuildHeaderKeysTo hs where
238class BuildHeaderKeysTo hs where
159− buildHeaderKeysTo :: Proxy hs -> [T.Text]
239+ buildHeaderKeysTo :: Proxy hs -> [CI.CI T.Text]
161instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
241instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
162 where buildHeaderKeysTo _ = []
242 where buildHeaderKeysTo _ = []
164instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
244instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
165− => BuildHeaderKeysTo '[(Header h v) ': xs] where
245+ => BuildHeaderKeysTo ((Header h v) ': xs) where
246+ buildHeaderKeysTo _ =
248+ thisKey = CI.mk $ T.pack (symbolVal (Proxy :: Proxy h))
166− buildHeaderKeysTo _ = (T.pack $ symbolVal (Proxy :: Proxy h)) : buildHeaderKeysTo (Proxy :: Proxy xs)
249+ in thisKey : buildHeaderKeysTo (Proxy :: Proxy xs)
168-- HEADERS Verb (Content) --
252-- HEADERS Verb (Content) --
169-- Headers combinator not treated in fully general case,
253-- Headers combinator not treated in fully general case,
@@ -175,33 +259,35 @@ instance {-# OVERLAPPABLE #-}
175 ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
259 ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
176 ReflectMethod method, cts' ~ (ct ': cts),
260 ReflectMethod method, cts' ~ (ct ': cts),
177 SupportsServantReflex t m
261 SupportsServantReflex t m
178− ) => HasClient t m (Verb method status cts' (Headers ls a)) where
262+ ) => HasClient t m (Verb method status cts' (Headers ls a)) tag where
179− type Client t m (Verb method status cts' (Headers ls a)) =
263+ type Client t m (Verb method status cts' (Headers ls a)) tag =
180− Event t () -> m (Event t (ReqResult (Headers ls a)))
264+ Event t tag -> m (Event t (ReqResult tag (Headers ls a)))
181− clientWithRoute Proxy _ req baseurl = do
265+ clientWithRouteAndResultHandler Proxy _ _ req baseurl opts wrap trigs = do
182 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
266 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
183− resp <- performRequestCT (Proxy :: Proxy ct) method req' baseurl
267+ resp <- fmap runIdentity <$> performRequestsCT (Proxy :: Proxy ct) method (constDyn $ Identity req') baseurl opts trigs
184− return $ (fmap . fmap) toHeaders resp
268+ wrap $ toHeaders <$> resp
185 where req' = req { respHeaders =
269 where req' = req { respHeaders =
186 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
270 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
189-- HEADERS Verb (No content) --
274-- HEADERS Verb (No content) --
190instance {-# OVERLAPPABLE #-}
275instance {-# OVERLAPPABLE #-}
191 ( BuildHeadersTo ls, BuildHeaderKeysTo ls, ReflectMethod method,
276 ( BuildHeadersTo ls, BuildHeaderKeysTo ls, ReflectMethod method,
192 SupportsServantReflex t m
277 SupportsServantReflex t m
193− ) => HasClient t m (Verb method status cts (Headers ls NoContent)) where
278+ ) => HasClient t m (Verb method status cts (Headers ls NoContent)) tag where
194− type Client t m (Verb method status cts (Headers ls NoContent))
279+ type Client t m (Verb method status cts (Headers ls NoContent)) tag
195− = Event t () -> m (Event t (ReqResult (Headers ls NoContent)))
280+ = Event t tag -> m (Event t (ReqResult tag (Headers ls NoContent)))
196− clientWithRoute Proxy _ req baseurl = do
281+ clientWithRouteAndResultHandler Proxy _ _ req baseurl opts wrap trigs = do
197 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
282 let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
198− resp <- performRequestNoBody method req' baseurl
283+ resp <- fmap runIdentity <$> performRequestsNoBody method (constDyn $ Identity req') baseurl opts trigs
199− return $ (fmap . fmap) toHeaders resp
284+ wrap $ toHeaders <$> resp
200 where req' = req {respHeaders =
285 where req' = req {respHeaders =
201 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
286 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
206-- > newtype Referer = Referer { referrer :: Text }
292-- > newtype Referer = Referer { referrer :: Text }
207-- > deriving (Eq, Show, Generic, FromText, ToHttpApiData)
293-- > deriving (Eq, Show, Generic, FromText, ToHttpApiData)
@@ -216,29 +302,45 @@ instance {-# OVERLAPPABLE #-}
216-- > -- then you can just use "viewRefer" to query that endpoint
302-- > -- then you can just use "viewRefer" to query that endpoint
217-- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments
303-- > -- specifying Nothing or e.g Just "http://haskell.org/" as arguments
218instance (KnownSymbol sym, ToHttpApiData a,
304instance (KnownSymbol sym, ToHttpApiData a,
219− HasClient t m sublayout, SupportsServantReflex t m)
305+ HasClient t m sublayout tag, SupportsServantReflex t m)
220− => HasClient t m (Header sym a :> sublayout) where
306+ => HasClient t m (Header sym a :> sublayout) tag where
222− type Client t m (Header sym a :> sublayout) =
308+ type Client t m (Header sym a :> sublayout) tag =
223− Dynamic t (Either Text a) -> Client t m sublayout
309+ Dynamic t (Either Text a) -> Client t m sublayout tag
225− clientWithRoute Proxy q req baseurl eVal =
311+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap eVal =
312+ clientWithRouteAndResultHandler
226− clientWithRoute (Proxy :: Proxy sublayout)
313+ (Proxy :: Proxy sublayout) q t
228− (Servant.Common.Req.addHeader hname eVal req)
314+ (Servant.Common.Req.addHeader hname eVal req)
229− baseurl
315+ baseurl opts wrap
230 where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
316 where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
232-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
321-- | Using a 'HttpVersion' combinator in your API doesn't affect the client
233-- functions.
322-- functions.
234−instance HasClient t m sublayout
323+instance HasClient t m sublayout tag
235− => HasClient t m (HttpVersion :> sublayout) where
324+ => HasClient t m (HttpVersion :> sublayout) tag where
326+ type Client t m (HttpVersion :> sublayout) tag =
327+ Client t m sublayout tag
329+ clientWithRouteAndResultHandler Proxy =
330+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout)
332+-- | Using a 'Summary' combinator in your API doesn't affect the client
334+instance (HasClient t m sublayout tag, KnownSymbol sym)
335+ => HasClient t m (Summary sym :> sublayout) tag where
337+ type Client t m (Summary sym :> sublayout) tag =
338+ Client t m sublayout tag
340+ clientWithRouteAndResultHandler Proxy =
341+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout)
237− type Client t m (HttpVersion :> sublayout) =
238− Client t m sublayout
240− clientWithRoute Proxy q =
241− clientWithRoute (Proxy :: Proxy sublayout) q
244-- | If you use a 'QueryParam' in one of your endpoints in your API,
346-- | If you use a 'QueryParam' in one of your endpoints in your API,
@@ -267,16 +369,16 @@ instance HasClient t m sublayout
267-- > -- then you can just use "getBooksBy" to query that endpoint.
369-- > -- then you can just use "getBooksBy" to query that endpoint.
268-- > -- 'getBooksBy Nothing' for all books
370-- > -- 'getBooksBy Nothing' for all books
269-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
371-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
270−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
372+instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
271− => HasClient t m (QueryParam sym a :> sublayout) where
373+ => HasClient t m (QueryParam' mods sym a :> sublayout) tag where
273− type Client t m (QueryParam sym a :> sublayout) =
375+ type Client t m (QueryParam' mods sym a :> sublayout) tag =
274− Dynamic t (QParam a) -> Client t m sublayout
376+ Dynamic t (QParam a) -> Client t m sublayout tag
276 -- if mparam = Nothing, we don't add it to the query string
378 -- if mparam = Nothing, we don't add it to the query string
277− clientWithRoute Proxy q req baseurl mparam =
379+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap mparam =
278− clientWithRoute (Proxy :: Proxy sublayout) q
380+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t
279− (req {qParams = paramPair : qParams req}) baseurl
381+ (req {qParams = paramPair : qParams req}) baseurl opts wrap
281 where pname = symbolVal (Proxy :: Proxy sym)
383 where pname = symbolVal (Proxy :: Proxy sym)
282 --p prm = QueryPartParam $ (fmap . fmap) (toQueryParam) prm
384 --p prm = QueryPartParam $ (fmap . fmap) (toQueryParam) prm
@@ -284,6 +386,8 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
284 p prm = QueryPartParam $ fmap qParamToQueryPart prm -- (fmap . fmap) (unpack . toQueryParam) prm
386 p prm = QueryPartParam $ fmap qParamToQueryPart prm -- (fmap . fmap) (unpack . toQueryParam) prm
285 paramPair = (T.pack pname, p mparam)
387 paramPair = (T.pack pname, p mparam)
287-- | If you use a 'QueryParams' in one of your endpoints in your API,
391-- | If you use a 'QueryParams' in one of your endpoints in your API,
288-- the corresponding querying function will automatically take
392-- the corresponding querying function will automatically take
289-- an additional argument, a list of values of the type specified
393-- an additional argument, a list of values of the type specified
@@ -312,21 +416,22 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
312-- > -- 'getBooksBy []' for all books
416-- > -- 'getBooksBy []' for all books
313-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
417-- > -- 'getBooksBy ["Isaac Asimov", "Robert A. Heinlein"]'
314-- > -- to get all books by Asimov and Heinlein
418-- > -- to get all books by Asimov and Heinlein
315−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
419+instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
316− => HasClient t m (QueryParams sym a :> sublayout) where
420+ => HasClient t m (QueryParams sym a :> sublayout) tag where
318− type Client t m (QueryParams sym a :> sublayout) =
422+ type Client t m (QueryParams sym a :> sublayout) tag =
319− Dynamic t [a] -> Client t m sublayout
423+ Dynamic t [a] -> Client t m sublayout tag
321− clientWithRoute Proxy q req baseurl paramlist =
425+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap paramlist =
322− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
426+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t req' baseurl opts wrap
324 where req' = req { qParams = (T.pack pname, params') : qParams req }
428 where req' = req { qParams = (T.pack pname, params') : qParams req }
325 pname = symbolVal (Proxy :: Proxy sym)
429 pname = symbolVal (Proxy :: Proxy sym)
326− params' = QueryPartParams $ (fmap . fmap) (toQueryParam)
430+ params' = QueryPartParams $ (fmap . fmap) toQueryParam
327 paramlist
431 paramlist
330-- | If you use a 'QueryFlag' in one of your endpoints in your API,
435-- | If you use a 'QueryFlag' in one of your endpoints in your API,
331-- the corresponding querying function will automatically take
436-- the corresponding querying function will automatically take
332-- an additional 'Bool' argument.
437-- an additional 'Bool' argument.
@@ -351,49 +456,42 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
351-- > -- 'getBooksBy True' to only get _already published_ books
456-- > -- 'getBooksBy True' to only get _already published_ books
353-- TODO Bring back
458-- TODO Bring back
354−instance (KnownSymbol sym, HasClient t m sublayout, Reflex t)
459+instance (KnownSymbol sym, HasClient t m sublayout tag, Reflex t)
355− => HasClient t m (QueryFlag sym :> sublayout) where
460+ => HasClient t m (QueryFlag sym :> sublayout) tag where
357− type Client t m (QueryFlag sym :> sublayout) =
462+ type Client t m (QueryFlag sym :> sublayout) tag =
358− Dynamic t Bool -> Client t m sublayout
463+ Dynamic t Bool -> Client t m sublayout tag
360− clientWithRoute Proxy q req baseurl flag =
465+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap flag =
361− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
466+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t req' baseurl opts wrap
363 where req' = req { qParams = thisPair : qParams req }
468 where req' = req { qParams = thisPair : qParams req }
364 thisPair = (T.pack pName, QueryPartFlag flag) :: (Text, QueryPart t)
469 thisPair = (T.pack pName, QueryPartFlag flag) :: (Text, QueryPart t)
365 pName = symbolVal (Proxy :: Proxy sym)
470 pName = symbolVal (Proxy :: Proxy sym)
368−-- | Pick a 'Method' and specify where the server you want to query is. You get
473+
369−-- back the full `Response`.
474+-- | Send a raw 'XhrRequest ()' directly to 'baseurl'
371−instance SupportsServantReflex t m => HasClient t m Raw where
475+instance SupportsServantReflex t m => HasClient t m Raw tag where
372− type Client t m Raw = Dynamic t (Either Text (XhrRequest ()))
476+ type Client t m Raw tag = Dynamic t (Either Text (XhrRequest ()))
373− -> Event t ()
477+ -> Event t tag
374− -> m (Event t (ReqResult ()))
478+ -> m (Event t (ReqResult tag ()))
376− -- clientWithRoute :: Proxy Raw -> Proxy m -> Req -> BaseUrl -> Client t m Raw
377− clientWithRoute _ _ _ baseurl xhrs triggers = do
480+ clientWithRouteAndResultHandler _ _ _ _ baseurl _ wrap xhrs triggers = do
378− let xhrs' = liftA2 (\x path -> case x of
482+ let xhrs' = liftA2 (\x path -> case x of
379− Left e -> Left e
483+ Left e -> Left e
380− Right jx -> Right $ jx {_xhrRequest_url = path
484+ Right jx -> Right $ jx { _xhrRequest_url = path <> _xhrRequest_url jx }
381− <> _xhrRequest_url jx})
383− (showBaseUrl <$> baseurl)
485+ ) xhrs (showBaseUrl <$> baseurl)
384− reqs = tagPromptlyDyn xhrs' triggers
486+ xhrs'' = attachPromptlyDynWith (flip (,)) xhrs' triggers :: Event t (tag, Either Text (XhrRequest ()))
385− okReq = fmapMaybe hush reqs
487+ badReq = fmapMaybe (\(t,x) -> either (Just . (t,)) (const Nothing) x) xhrs'' :: Event t (tag, Text)
386− badReq = fmapMaybe tattle reqs
488+ okReq = fmapMaybe (\(t,x) -> either (const Nothing) (Just . (t,)) x) xhrs'' :: Event t (tag, XhrRequest ())
387− resps <- performRequestAsync okReq
489+
388− return $ leftmost [fmap (ResponseSuccess () ) resps
490+ resps <- performRequestsAsync okReq
389− ,fmap RequestFailure badReq]
491+ wrap $ leftmost [ uncurry RequestFailure <$> badReq
390−
492+ , evalResponse (const $ Right ()) <$> resps
392−hush :: Either e a -> Maybe a
494+
393−hush = either (const Nothing) Just
395−tattle :: Either e a -> Maybe e
396−tattle = either Just (const Nothing)
398-- | If you use a 'ReqBody' in one of your endpoints in your API,
496-- | If you use a 'ReqBody' in one of your endpoints in your API,
399-- the corresponding querying function will automatically take
497-- the corresponding querying function will automatically take
@@ -415,14 +513,14 @@ tattle = either Just (const Nothing)
415-- > where host = BaseUrl Http "localhost" 8080
513-- > where host = BaseUrl Http "localhost" 8080
416-- > -- then you can just use "addBook" to query that endpoint
514-- > -- then you can just use "addBook" to query that endpoint
418−instance (MimeRender ct a, HasClient t m sublayout, Reflex t)
516+instance (MimeRender ct a, HasClient t m sublayout tag, Reflex t)
419− => HasClient t m (ReqBody (ct ': cts) a :> sublayout) where
517+ => HasClient t m (ReqBody (ct ': cts) a :> sublayout) tag where
421− type Client t m (ReqBody (ct ': cts) a :> sublayout) =
519+ type Client t m (ReqBody (ct ': cts) a :> sublayout) tag =
422− Dynamic t (Either Text a) -> Client t m sublayout
520+ Dynamic t (Either Text a) -> Client t m sublayout tag
424− clientWithRoute Proxy q req baseurl body =
522+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap body =
425− clientWithRoute (Proxy :: Proxy sublayout) q req' baseurl
523+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t req' baseurl opts wrap
426 where req' = req { reqBody = bodyBytesCT }
524 where req' = req { reqBody = bodyBytesCT }
427 ctProxy = Proxy :: Proxy ct
525 ctProxy = Proxy :: Proxy ct
428 ctString = T.pack $ show $ contentType ctProxy
526 ctString = T.pack $ show $ contentType ctProxy
@@ -430,43 +528,49 @@ instance (MimeRender ct a, HasClient t m sublayout, Reflex t)
430 (\b -> (mimeRender ctProxy b, ctString))
528 (\b -> (mimeRender ctProxy b, ctString))
433-- | Make the querying function append @path@ to the request path.
533-- | Make the querying function append @path@ to the request path.
434−instance (KnownSymbol path, HasClient t m sublayout, Reflex t) => HasClient t m (path :> sublayout) where
534+instance (KnownSymbol path, HasClient t m sublayout tag, Reflex t) => HasClient t m (path :> sublayout) tag where
435− type Client t m (path :> sublayout) = Client t m sublayout
535+ type Client t m (path :> sublayout) tag = Client t m sublayout tag
437− clientWithRoute Proxy q req baseurl =
537+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap =
438− clientWithRoute (Proxy :: Proxy sublayout) q
538+ clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t
439− (prependToPathParts (pure (Right $ T.pack p)) req)
539+ (prependToPathParts (pure (Right $ T.pack p)) req) baseurl opts wrap
442 where p = symbolVal (Proxy :: Proxy path)
541 where p = symbolVal (Proxy :: Proxy path)
444−instance HasClient t m api => HasClient t m (Vault :> api) where
445− type Client t m (Vault :> api) = Client t m api
447− clientWithRoute Proxy q req baseurl =
544+instance HasClient t m api tag => HasClient t m (Vault :> api) tag where
448− clientWithRoute (Proxy :: Proxy api) q req baseurl
545+ type Client t m (Vault :> api) tag = Client t m api tag
547+ clientWithRouteAndResultHandler Proxy =
548+ clientWithRouteAndResultHandler (Proxy :: Proxy api)
450−instance HasClient t m api => HasClient t m (RemoteHost :> api) where
551+instance HasClient t m api tag => HasClient t m (RemoteHost :> api) tag where
451− type Client t m (RemoteHost :> api) = Client t m api
552+ type Client t m (RemoteHost :> api) tag = Client t m api tag
453− clientWithRoute Proxy q req baseurl =
554+ clientWithRouteAndResultHandler Proxy =
454− clientWithRoute (Proxy :: Proxy api) q req baseurl
555+ clientWithRouteAndResultHandler (Proxy :: Proxy api)
456−instance HasClient t m api => HasClient t m (IsSecure :> api) where
457− type Client t m (IsSecure :> api) = Client t m api
459− clientWithRoute Proxy q req baseurl =
460− clientWithRoute (Proxy :: Proxy api) q req baseurl
462−instance (HasClient t m api, Reflex t)
559+instance HasClient t m api tag => HasClient t m (IsSecure :> api) tag where
463− => HasClient t m (BasicAuth realm usr :> api) where
560+ type Client t m (IsSecure :> api) tag = Client t m api tag
465− type Client t m (BasicAuth realm usr :> api) = Dynamic t (Maybe BasicAuthData)
562+ clientWithRouteAndResultHandler Proxy =
466− -> Client t m api
563+ clientWithRouteAndResultHandler (Proxy :: Proxy api)
566+instance (HasClient t m api tag, Reflex t)
567+ => HasClient t m (BasicAuth realm usr :> api) tag where
569+ type Client t m (BasicAuth realm usr :> api) tag = Dynamic t (Maybe BasicAuthData)
570+ -> Client t m api tag
468− clientWithRoute Proxy q req baseurl authdata =
572+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap authdata =
469− clientWithRoute (Proxy :: Proxy api) q req' baseurl
573+ clientWithRouteAndResultHandler (Proxy :: Proxy api) q t req' baseurl opts wrap
471 req' = req { authData = Just authdata }
575 req' = req { authData = Just authdata }
@@ -492,3 +596,29 @@ non-empty lists, but is otherwise more specific, no instance will be overall
492more specific. This in turn generally means adding yet another instance (one
596more specific. This in turn generally means adding yet another instance (one
493for empty and one for non-empty lists).
597for empty and one for non-empty lists).
601+-- SUPPORT FOR servant-auth --
603+-- For JavaScript clients we should be sending/storing JSON web tokens in a
604+-- way that is inaccessible to JavaScript.
606+-- For @servant-auth@ this is done with HTTP-only cookies. In a Reflex-DOM
607+-- app this means the @servant-auth@ client should only verify that the API
608+-- supports Cookie-based authentication but do nothing with the token
611+-- @HasCookieAuth auths@ is nominally a redundant constraint, but ensures
612+-- we're not trying to rely on cookies when the API does not use them.
613+instance (HasCookieAuth auths, HasClient t m api tag) => HasClient t m (Auth.Auth auths a :> api) tag where
615+ type Client t m (Auth.Auth auths a :> api) tag = Client t m api tag
616+ clientWithRouteAndResultHandler Proxy = clientWithRouteAndResultHandler (Proxy :: Proxy api)
619+type family HasCookieAuth xs :: Constraint where
620+ HasCookieAuth (Auth.Cookie ': xs) = ()
621+ HasCookieAuth (x ': xs) = HasCookieAuth xs
622+ HasCookieAuth '[] = CookieAuthNotEnabled
624+class CookieAuthNotEnabled