@@ -39,41 +39,39 @@ module Servant.Reflex
40------------------------------------------------------------------------------
40------------------------------------------------------------------------------
41import Control.Applicative
41import Control.Applicative
42−import qualified Data.CaseInsensitive as CI
43−import Data.Functor.Identity
44−import qualified Data.Map as Map
45import Data.Monoid ((<>))
42import Data.Monoid ((<>))
46−import Data.Proxy (Proxy (..))
47import qualified Data.Set as Set
43import qualified Data.Set as Set
44+import qualified Data.Text.Encoding as E
45+import Data.CaseInsensitive (mk)
46+import Data.Functor.Identity
47+import Data.Proxy (Proxy (..))
48+import qualified Data.Map as Map
48import Data.Text (Text)
49import Data.Text (Text)
49import qualified Data.Text as T
50import qualified Data.Text as T
50−import qualified Data.Text.Encoding as E
51import GHC.Exts (Constraint)
51import GHC.Exts (Constraint)
52import GHC.TypeLits (KnownSymbol, symbolVal)
52import GHC.TypeLits (KnownSymbol, symbolVal)
53−import Servant.API ((:<|>) (..), (:>), BasicAuth,
53+import Servant.API ((:<|>)(..),(:>), BasicAuth,
54− BasicAuthData, BuildHeadersTo (..),
54+ BasicAuthData, BuildHeadersTo(..),
55− Capture, Header, Headers (..),
55+ Capture, contentType, Header,
56− HttpVersion, IsSecure,
56+ Headers(..), HttpVersion, IsSecure,
57− MimeRender (..), MimeUnrender,
57+ MimeRender(..),
58 NoContent, QueryFlag, QueryParam,
58 NoContent, QueryFlag, QueryParam,
59− QueryParams, Raw, ReflectMethod (..),
59+ QueryParams, Raw, ReflectMethod(..),
60 RemoteHost, ReqBody,
60 RemoteHost, ReqBody,
61− ToHttpApiData (..), Vault, Verb,
61+ ToHttpApiData(..), Vault, Verb)
63−import Servant.API.Description (Summary)
64import qualified Servant.Auth as Auth
62import qualified Servant.Auth as Auth
66import Reflex.Dom.Core (Dynamic, Event, Reflex,
64import Reflex.Dom.Core (Dynamic, Event, Reflex,
67− XhrRequest (..), XhrResponse (..),
65+ XhrRequest(..),
68− XhrResponseHeaders (..),
66+ XhrResponseHeaders(..),
69− attachPromptlyDynWith, constDyn, ffor,
67+ XhrResponse(..), attachPromptlyDynWith, constDyn, ffor, fmapMaybe,
71− performRequestsAsync)
68+ leftmost, performRequestsAsync,
72------------------------------------------------------------------------------
70------------------------------------------------------------------------------
73import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
71import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
74 showBaseUrl,
72 showBaseUrl,
75 SupportsServantReflex)
73 SupportsServantReflex)
76−import Servant.Common.Req (ClientOptions(..),
74+import Servant.Common.Req (ClientOptions(..), MimeUnrender(..),
77 defaultClientOptions,
75 defaultClientOptions,
78 Req, ReqResult(..), QParam(..),
76 Req, ReqResult(..), QParam(..),
79 QueryPart(..), addHeader, authData,
77 QueryPart(..), addHeader, authData,
@@ -89,6 +87,8 @@ import Servant.Common.Req (ClientOptions(..),
91 qParams, withCredentials)
89 qParams, withCredentials)
90+import Servant.Checked.Exceptions.Internal.Envelope (Envelope)
91+import Servant.Checked.Exceptions.Internal.Servant.API (NoThrow, Throws, Throwing, ThrowingNonterminal)
94-- * Accessing APIs as a Client
94-- * Accessing APIs as a Client
@@ -103,7 +103,7 @@ import Servant.Common.Req (ClientOptions(..),
104-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
104-- > getAllBooks :: Event t l -> m (Event t (l, ReqResult [Book]))
105-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
105-- > postNewBook :: Dynamic t (Maybe Book) -> Event t l
106−-- > -> m (Event t (l, ReqResult Book)))
106+-- -> m (Event t (l, ReqResult Book)))
107-- > (getAllBooks :<|> postNewBook) = client myApi host
107-- > (getAllBooks :<|> postNewBook) = client myApi host
108-- > where host = constDyn $ BaseUrl Http "localhost" 8080
108-- > where host = constDyn $ BaseUrl Http "localhost" 8080
@@ -228,26 +228,22 @@ instance {-# OVERLAPPING #-}
229toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
229toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
230toHeaders r =
230toHeaders r =
231+ let toBS = E.encodeUtf8
231− let hdrs = maybe []
232+ hdrs = maybe []
232− (\xhr -> fmap (\(h,v) -> (CI.map E.encodeUtf8 h, E.encodeUtf8 v))
233+ (\xhr -> fmap (\(h,v) -> (mk (toBS h), toBS v))
233 (Map.toList $ _xhrResponse_headers xhr))
234 (Map.toList $ _xhrResponse_headers xhr))
234 (response r)
235 (response r)
235 in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
236 in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
238class BuildHeaderKeysTo hs where
238class BuildHeaderKeysTo hs where
239− buildHeaderKeysTo :: Proxy hs -> [CI.CI T.Text]
239+ buildHeaderKeysTo :: Proxy hs -> [T.Text]
241instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
241instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
242 where buildHeaderKeysTo _ = []
242 where buildHeaderKeysTo _ = []
244instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
244instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
245 => 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))
249− in thisKey : buildHeaderKeysTo (Proxy :: Proxy xs)
246+ buildHeaderKeysTo _ = T.pack (symbolVal (Proxy :: Proxy h)) : buildHeaderKeysTo (Proxy :: Proxy xs)
252-- HEADERS Verb (Content) --
248-- HEADERS Verb (Content) --
253-- Headers combinator not treated in fully general case,
249-- Headers combinator not treated in fully general case,
@@ -329,19 +325,6 @@ instance HasClient t m sublayout tag
329 clientWithRouteAndResultHandler Proxy =
325 clientWithRouteAndResultHandler Proxy =
330 clientWithRouteAndResultHandler (Proxy :: Proxy sublayout)
326 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)
346-- | If you use a 'QueryParam' in one of your endpoints in your API,
329-- | If you use a 'QueryParam' in one of your endpoints in your API,
347-- the corresponding querying function will automatically take
330-- the corresponding querying function will automatically take
@@ -623,3 +606,143 @@ type family HasCookieAuth xs :: Constraint where
624class CookieAuthNotEnabled
607class CookieAuthNotEnabled
609+{- servant-checked-exceptions
610+~~~~~~~~~~~~~~~~~~~~~
613+-- | Change a 'Throws' into 'Throwing'.
614+instance (Monad m, HasClient t m (Throwing '[e] :> api) tag) => HasClient t m (Throws e :> api) tag where
615+ type Client t m (Throws e :> api) tag = Client t m (Throwing '[e] :> api) tag
617+ clientWithRouteAndResultHandler
618+ :: Proxy (Throws e :> api)
622+ -> Dynamic t BaseUrl
624+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
625+ -> Client t m (Throwing '[e] :> api) tag
626+ clientWithRouteAndResultHandler _ = clientWithRouteAndResultHandler (Proxy :: Proxy (Throwing '[e] :> api))
628+-- | When @'Throwing' es@ comes before a 'Verb', change it into the same 'Verb'
629+-- but returning an @'Envelope' es@.
630+instance (HasClient t m (Verb method status ctypes (Envelope es a)) tag) =>
631+ HasClient t m (Throwing es :> Verb method status ctypes a) tag where
633+ type Client t m (Throwing es :> Verb method status ctypes a) tag =
634+ Client t m (Verb method status ctypes (Envelope es a)) tag
636+ clientWithRouteAndResultHandler
637+ :: Proxy (Throwing es :> Verb method status ctypes a)
641+ -> Dynamic t BaseUrl
643+ -> (forall x. Event t (ReqResult tag x) -> m (Event t (ReqResult tag x)))
644+ -> Client t m (Verb method status ctypes (Envelope es a)) tag
645+ clientWithRouteAndResultHandler Proxy =
646+ clientWithRouteAndResultHandler (Proxy :: Proxy (Verb method status ctypes (Envelope es a)))
648+-- | When 'NoThrow' comes before a 'Verb', change it into the same 'Verb'
649+-- but returning an @'Envelope' \'[]@.
650+instance (Monad m, HasClient t m (Verb method status ctypes (Envelope '[] a)) tag) =>
651+ HasClient t m (NoThrow :> Verb method status ctypes a) tag where
653+ type Client t m (NoThrow :> Verb method status ctypes a) tag =
654+ Client t m (Verb method status ctypes (Envelope '[] a)) tag
656+ clientWithRouteAndResultHandler
657+ :: Proxy (NoThrow :> Verb method status ctypes a)
661+ -> Dynamic t BaseUrl
663+ -> (forall x. Event t (ReqResult tag x) -> m (Event t (ReqResult tag x)))
664+ -> Client t m (Verb method status ctypes (Envelope '[] a)) tag
665+ clientWithRouteAndResultHandler Proxy =
666+ clientWithRouteAndResultHandler (Proxy :: Proxy (Verb method status ctypes (Envelope '[] a)))
668+-- | When @'Throwing' es@ comes before ':<|>', push @'Throwing' es@ into each
669+-- branch of the API.
670+instance (Monad m, HasClient t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) tag) =>
671+ HasClient t m (Throwing es :> (api1 :<|> api2)) tag where
673+ type Client t m (Throwing es :> (api1 :<|> api2)) tag =
674+ Client t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) tag
676+ clientWithRouteAndResultHandler
677+ :: Proxy (Throwing es :> (api1 :<|> api2))
681+ -> Dynamic t BaseUrl
683+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
684+ -> Client t m ((Throwing es :> api1) :<|> (Throwing es :> api2)) tag
685+ clientWithRouteAndResultHandler _ =
686+ clientWithRouteAndResultHandler (Proxy :: Proxy ((Throwing es :> api1) :<|> (Throwing es :> api2)))
688+-- | When 'NoThrow' comes before ':<|>', push 'NoThrow' into each branch of the
690+instance (Monad m, HasClient t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) tag) =>
691+ HasClient t m (NoThrow :> (api1 :<|> api2)) tag where
693+ type Client t m (NoThrow :> (api1 :<|> api2)) tag =
694+ Client t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) tag
696+ clientWithRouteAndResultHandler
697+ :: Proxy (NoThrow :> (api1 :<|> api2))
701+ -> Dynamic t BaseUrl
703+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
704+ -> Client t m ((NoThrow :> api1) :<|> (NoThrow :> api2)) tag
705+ clientWithRouteAndResultHandler _ =
706+ clientWithRouteAndResultHandler (Proxy :: Proxy ((NoThrow :> api1) :<|> (NoThrow :> api2)))
708+-- | When a @'Throws' e@ comes immediately after a @'Throwing' es@, 'Snoc' the
709+-- @e@ onto the @es@. Otherwise, if @'Throws' e@ comes before any other
710+-- combinator, push it down so it is closer to the 'Verb'.
711+instance (Monad m, HasClient t m (ThrowingNonterminal (Throwing es :> api :> apis)) tag) =>
712+ HasClient t m (Throwing es :> api :> apis) tag where
714+ type Client t m (Throwing es :> api :> apis) tag =
715+ Client t m (ThrowingNonterminal (Throwing es :> api :> apis)) tag
717+ clientWithRouteAndResultHandler
718+ :: Proxy (Throwing es :> api :> apis)
722+ -> Dynamic t BaseUrl
724+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
725+ -> Client t m (ThrowingNonterminal (Throwing es :> api :> apis)) tag
726+ clientWithRouteAndResultHandler _ =
727+ clientWithRouteAndResultHandler (Proxy :: Proxy (ThrowingNonterminal (Throwing es :> api :> apis)))
729+-- | When 'NoThrow' comes before any other combinator, push it down so it is
730+-- closer to the 'Verb'.
731+instance (Monad m, HasClient t m (api :> NoThrow :> apis) tag) =>
732+ HasClient t m (NoThrow :> api :> apis) tag where
734+ type Client t m (NoThrow :> api :> apis) tag =
735+ Client t m (api :> NoThrow :> apis) tag
737+ clientWithRouteAndResultHandler
738+ :: Proxy (NoThrow :> api :> apis)
742+ -> Dynamic t BaseUrl
744+ -> (forall a. Event t (ReqResult tag a) -> m (Event t (ReqResult tag a)))
745+ -> Client t m (api :> NoThrow :> apis) tag
746+ clientWithRouteAndResultHandler _ =
747+ clientWithRouteAndResultHandler (Proxy :: Proxy (api :> NoThrow :> apis))