@@ -0,0 +1,328 @@
1+{-# LANGUAGE AllowAmbiguousTypes #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE InstanceSigs #-}
7+{-# LANGUAGE OverloadedStrings #-}
8+{-# LANGUAGE PolyKinds #-}
9+{-# LANGUAGE ScopedTypeVariables #-}
10+{-# LANGUAGE TupleSections #-}
11+{-# LANGUAGE TypeFamilies #-}
12+{-# LANGUAGE TypeOperators #-}
13+{-# LANGUAGE UndecidableInstances #-}
14+{-# LANGUAGE MultiParamTypeClasses #-}
16+module Servant.Reflex.Multi
21+------------------------------------------------------------------------------
22+import Control.Applicative
23+import Data.Monoid ((<>))
24+import qualified Data.Set as Set
25+import qualified Data.Text.Encoding as E
26+import Data.CaseInsensitive (mk)
27+import Data.Proxy (Proxy (..))
28+import qualified Data.Map as Map
29+import Data.Text (Text)
30+import qualified Data.Text as T
31+import GHC.TypeLits (KnownSymbol, symbolVal)
32+import Servant.API ((:<|>)(..),(:>), BasicAuth,
33+ BasicAuthData, BuildHeadersTo(..),
34+ Capture, contentType, Header,
35+ Headers(..), HttpVersion, IsSecure,
36+ MimeRender(..), MimeUnrender,
37+ NoContent, QueryFlag, QueryParam,
38+ QueryParams, Raw, ReflectMethod(..),
40+ ToHttpApiData(..), Vault, Verb)
42+import Reflex.Dom (Dynamic, Event, Reflex,
44+ XhrResponseHeaders(..),
45+ XhrResponse(..), ffor, fmapMaybe,
46+ leftmost, performRequestAsync,
48+------------------------------------------------------------------------------
49+import Servant.Common.BaseUrl (BaseUrl(..), Scheme(..), baseUrlWidget,
51+ SupportsServantReflex)
52+import Servant.Common.Req (Req, ReqResult(..), QParam(..),
53+ QueryPart(..), addHeader, authData,
54+ defReq, prependToPathParts,
57+ performRequestNoBody,
58+ performRequestsNoBody,
59+ performSomeRequestsAsync,
60+ qParamToQueryPart, reqBody,
61+ reqSuccess, reqFailure,
62+ reqMethod, respHeaders, response,
64+import Servant.Reflex (BuildHeaderKeysTo(..), toHeaders)
66+clientA :: (HasClientMulti t m layout f, Applicative f)
67+ => Proxy layout -> Proxy m -> Proxy f -> Dynamic t BaseUrl -> ClientMulti t m layout f
68+clientA p q f baseurl = clientWithRouteMulti p q f (pure defReq) baseurl
70+class HasClientMulti t m layout f where
71+ type ClientMulti t m layout f :: *
72+ clientWithRouteMulti :: Proxy layout -> Proxy m -> Proxy f -> f (Req t) -> Dynamic t BaseUrl -> ClientMulti t m layout f
74+instance (HasClientMulti t m a f, HasClientMulti t m b f) => HasClientMulti t m (a :<|> b) f where
75+ type ClientMulti t m (a :<|> b) f = ClientMulti t m a f :<|> ClientMulti t m b f
76+ clientWithRouteMulti Proxy q f reqs baseurl =
77+ clientWithRouteMulti (Proxy :: Proxy a) q f reqs baseurl :<|>
78+ clientWithRouteMulti (Proxy :: Proxy b) q f reqs baseurl
81+instance (SupportsServantReflex t m, ToHttpApiData a, HasClientMulti t m sublayout f, Applicative f)
82+ => HasClientMulti t m (Capture capture a :> sublayout) f where
84+ type ClientMulti t m (Capture capture a :> sublayout) f =
85+ f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f
87+ clientWithRouteMulti l q f reqs baseurl vals = clientWithRouteMulti (Proxy :: Proxy sublayout) q f reqs' baseurl
89+ reqs' = prependToPathParts <$> ps <*> reqs
90+ ps = (fmap . fmap . fmap) toUrlPiece vals
92+-- VERB (Returning content) --
93+instance {-# OVERLAPPABLE #-}
94+ -- Note [Non-Empty Content Types]
95+ (MimeUnrender ct a, ReflectMethod method, cts' ~ (ct ': cts),
96+ SupportsServantReflex t m,
99+ ) => HasClientMulti t m (Verb method status cts' a) f where
101+ type ClientMulti t m (Verb method status cts' a) f =
102+ Event t () -> m (Event t (f (ReqResult a)))
104+ clientWithRouteMulti _ _ f reqs baseurl =
105+ performRequestsCT (Proxy :: Proxy ct) method reqs' baseurl
106+ where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
107+ reqs' = (\r -> r { reqMethod = method }) <$> reqs
109+-- -- VERB (No content) --
110+instance {-# OVERLAPPING #-}
111+ (ReflectMethod method, SupportsServantReflex t m, Traversable f) =>
112+ HasClientMulti t m (Verb method status cts NoContent) f where
113+ type ClientMulti t m (Verb method status cts NoContent) f =
114+ Event t () -> m (Event t (f (ReqResult NoContent)))
115+ -- TODO: how to access input types here?
116+ -- ExceptT ServantError IO NoContent
117+ clientWithRouteMulti Proxy _ _ req baseurl =
118+ performRequestsNoBody method req baseurl
119+ where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
121+instance {-# OVERLAPPABLE #-}
122+ -- Note [Non-Empty Content Types]
123+ ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
124+ ReflectMethod method, cts' ~ (ct ': cts),
125+ SupportsServantReflex t m,
127+ ) => HasClientMulti t m (Verb method status cts' (Headers ls a)) f where
128+ type ClientMulti t m (Verb method status cts' (Headers ls a)) f =
129+ Event t () -> m (Event t (f (ReqResult (Headers ls a))))
130+ clientWithRouteMulti Proxy _ _ reqs baseurl = do
131+ let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
132+ resp <- performRequestsCT (Proxy :: Proxy ct) method reqs' baseurl
133+ return $ (fmap . fmap) toHeaders <$> resp
134+ where reqs' = ffor reqs $ \r -> r { respHeaders =
135+ OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
139+instance {-# OVERLAPPABLE #-}
140+ ( BuildHeadersTo ls,
141+ BuildHeaderKeysTo ls,
142+ ReflectMethod method,
143+ SupportsServantReflex t m,
145+ ) => HasClientMulti t m (Verb method status cts (Headers ls NoContent)) f where
146+ type ClientMulti t m (Verb method status cts (Headers ls NoContent)) f
147+ = Event t () -> m (Event t (f (ReqResult (Headers ls NoContent))))
148+ clientWithRouteMulti Proxy _ _ reqs baseurl = do
149+ let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
150+ resp <- performRequestsNoBody method reqs' baseurl
151+ return $ (fmap . fmap) toHeaders <$> resp
152+ where reqs' = ffor reqs $ \req ->
153+ req {respHeaders = OnlyHeaders (Set.fromList
154+ (buildHeaderKeysTo (Proxy :: Proxy ls)))
158+instance (KnownSymbol sym,
160+ HasClientMulti t m sublayout f,
161+ SupportsServantReflex t m,
164+ => HasClientMulti t m (Header sym a :> sublayout) f where
166+ type ClientMulti t m (Header sym a :> sublayout) f =
167+ f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f
169+ clientWithRouteMulti Proxy f q reqs baseurl eVals =
170+ clientWithRouteMulti (Proxy :: Proxy sublayout) f
174+ where hname = T.pack $ symbolVal (Proxy :: Proxy sym)
175+ reqs' = (\eVal req -> Servant.Common.Req.addHeader hname eVal req)
179+instance HasClientMulti t m sublayout f
180+ => HasClientMulti t m (HttpVersion :> sublayout) f where
182+ type ClientMulti t m (HttpVersion :> sublayout) f =
183+ ClientMulti t m sublayout f
185+ clientWithRouteMulti Proxy q f =
186+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f
189+instance (KnownSymbol sym, ToHttpApiData a, HasClientMulti t m sublayout f, Reflex t, Traversable f, Applicative f)
190+ => HasClientMulti t m (QueryParam sym a :> sublayout) f where
192+ type ClientMulti t m (QueryParam sym a :> sublayout) f =
193+ f (Dynamic t (QParam a)) -> ClientMulti t m sublayout f
195+ -- if mparam = Nothing, we don't add it to the query string
196+ -- TODO: Check the above comment
197+ clientWithRouteMulti Proxy q f reqs baseurl mparams =
198+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f
201+ where pname = symbolVal (Proxy :: Proxy sym)
202+ p prm = QueryPartParam $ fmap qParamToQueryPart prm
203+ paramPair mp = (T.pack pname, p mp)
204+ reqs' = (\param req -> req {qParams = paramPair param : qParams req})
205+ <$> mparams <*> reqs
208+instance (KnownSymbol sym,
210+ HasClientMulti t m sublayout f,
214+ => HasClientMulti t m (QueryParams sym a :> sublayout) f where
216+ type ClientMulti t m (QueryParams sym a :> sublayout) f =
217+ f (Dynamic t [a]) -> ClientMulti t m sublayout f
219+ clientWithRouteMulti Proxy q f reqs baseurl paramlists =
220+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f reqs' baseurl
222+ where req' l r = r { qParams = (T.pack pname, params' l) : qParams r }
223+ pname = symbolVal (Proxy :: Proxy sym)
224+ params' l = QueryPartParams $ (fmap . fmap) (toQueryParam)
226+ reqs' = req' <$> paramlists <*> reqs
229+instance (KnownSymbol sym,
230+ HasClientMulti t m sublayout f,
234+ => HasClientMulti t m (QueryFlag sym :> sublayout) f where
236+ type ClientMulti t m (QueryFlag sym :> sublayout) f =
237+ f (Dynamic t Bool) -> ClientMulti t m sublayout f
239+ clientWithRouteMulti Proxy q f reqs baseurl flags =
240+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f reqs' baseurl
242+ where req' f req = req { qParams = thisPair f : qParams req }
243+ thisPair f = (T.pack pName, QueryPartFlag f) :: (Text, QueryPart t)
244+ pName = symbolVal (Proxy :: Proxy sym)
245+ reqs' = req' <$> flags <*> reqs
248+instance (SupportsServantReflex t m,
249+ Traversable f) => HasClientMulti t m Raw f where
250+ type ClientMulti t m Raw f = f (Dynamic t (Either Text (XhrRequest ())))
252+ -> m (Event t (f (ReqResult ())))
254+ clientWithRouteMulti Proxy Proxy Proxy oldReqs baseurl (rawReqs) triggers = do
255+ resps <- performSomeRequestsAsync (tagPromptlyDyn (sequence rawReqs) triggers )
256+ return $ (fmap . fmap) aux resps
258+ aux (Right r) = ResponseSuccess () r
259+ aux (Left e) = RequestFailure e
262+instance (MimeRender ct a,
263+ HasClientMulti t m sublayout f,
267+ => HasClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f where
269+ type ClientMulti t m (ReqBody (ct ': cts) a :> sublayout) f =
270+ f (Dynamic t (Either Text a)) -> ClientMulti t m sublayout f
272+ clientWithRouteMulti Proxy q f reqs baseurl bodies =
273+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f reqs' baseurl
274+ where req' b r = r { reqBody = bodyBytesCT b }
275+ ctProxy = Proxy :: Proxy ct
276+ ctString = T.pack $ show $ contentType ctProxy
277+ bodyBytesCT b = Just $ (fmap . fmap)
278+ (\b' -> (mimeRender ctProxy b', ctString))
280+ reqs' = req' <$> bodies <*> reqs
283+instance (KnownSymbol path,
284+ HasClientMulti t m sublayout f,
286+ Functor f) => HasClientMulti t m (path :> sublayout) f where
287+ type ClientMulti t m (path :> sublayout) f = ClientMulti t m sublayout f
289+ clientWithRouteMulti Proxy q f reqs baseurl =
290+ clientWithRouteMulti (Proxy :: Proxy sublayout) q f
291+ (prependToPathParts (pure (Right $ T.pack p)) <$> reqs)
294+ where p = symbolVal (Proxy :: Proxy path)
297+instance HasClientMulti t m api f => HasClientMulti t m (Vault :> api) f where
298+ type ClientMulti t m (Vault :> api) f = ClientMulti t m api f
300+ clientWithRouteMulti Proxy q f reqs baseurl =
301+ clientWithRouteMulti (Proxy :: Proxy api) q f reqs baseurl
304+instance HasClientMulti t m api f => HasClientMulti t m (RemoteHost :> api) f where
305+ type ClientMulti t m (RemoteHost :> api) f = ClientMulti t m api f
307+ clientWithRouteMulti Proxy q f reqs baseurl =
308+ clientWithRouteMulti (Proxy :: Proxy api) q f reqs baseurl
311+instance HasClientMulti t m api f => HasClientMulti t m (IsSecure :> api) f where
312+ type ClientMulti t m (IsSecure :> api) f = ClientMulti t m api f
314+ clientWithRouteMulti Proxy q f reqs baseurl =
315+ clientWithRouteMulti (Proxy :: Proxy api) q f reqs baseurl
318+instance (HasClientMulti t m api f, Reflex t, Traversable f, Applicative f)
319+ => HasClientMulti t m (BasicAuth realm usr :> api) f where
321+ type ClientMulti t m (BasicAuth realm usr :> api) f = f (Dynamic t (Maybe BasicAuthData))
322+ -> ClientMulti t m api f
324+ clientWithRouteMulti Proxy q f reqs baseurl authdatas =
325+ clientWithRouteMulti (Proxy :: Proxy api) q f reqs' baseurl
327+ req' a r = r { authData = Just a }
328+ reqs' = req' <$> authdatas <*> reqs