@@ -1,56 +1,65 @@
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 #-}
10−
11−{-# LANGUAGE MultiParamTypeClasses #-}
12−{-# LANGUAGE FlexibleInstances #-}
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 #-}
16+
1317module Servant.Common.Req where
1418
1519-------------------------------------------------------------------------------
16−import Control.Applicative (liftA2, liftA3)
20+import Control.Applicative (liftA2, liftA3)
1721import Control.Concurrent
18−import Control.Monad (join)
19−import Control.Monad.IO.Class (MonadIO, liftIO)
22+import Control.Monad (join)
23+import Control.Monad.IO.Class (MonadIO, liftIO)
2024import Data.Aeson
21−import Data.Bifunctor (first)
22−import qualified Data.ByteString.Builder as BB
23−import qualified Data.ByteString as BS
24−import Data.ByteString.Lazy (ByteString)
25−import qualified Data.ByteString.Lazy.Char8 as BL
26−import qualified Data.Map as Map
27−import Data.Maybe (catMaybes, fromMaybe)
25+import Data.Bifunctor (first)
26+import qualified Data.ByteString as BS
27+import qualified Data.ByteString.Builder as BB
28+import Data.ByteString.Lazy (ByteString)
29+import qualified Data.ByteString.Lazy.Char8 as BL
2830import Data.Functor.Compose
29−import Data.Monoid ((<>))
30−import Data.Proxy (Proxy(..))
31−import Data.Text (Text)
32−import qualified Data.Text as T
33−import qualified Data.Text.Lazy as TL
34−import qualified Data.Text.Encoding as T
35−import Data.Traversable (forM)
31+import qualified Data.JSString.Text as JS (lazyTextFromJSString)
32+import qualified Data.Map as Map
33+import Data.Maybe (catMaybes, fromMaybe)
34+import Data.Monoid ((<>))
35+import Data.Proxy (Proxy (..))
36+import Data.Text (Text)
37+import qualified Data.Text as T
38+import qualified Data.Text.Encoding as T
39+import qualified Data.Text.Lazy as TL
40+import Data.Traversable (forM)
41+import qualified Language.Javascript.JSaddle as JS
3642import Language.Javascript.JSaddle.Monad (JSM, MonadJSM, liftJSM)
37−import qualified Language.Javascript.JSaddle as JS
38−import qualified Data.JSString.Text as JS (lazyTextFromJSString)
39−import qualified Network.URI as N
40−import Reflex.Dom.Core hiding (tag)
41−import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
42− SupportsServantReflex)
43−import Servant.API.ContentTypes ( NoContent(..), Accept, JSON
44− , OctetStream, PlainText
45− , FormUrlEncoded)
46−import Web.HttpApiData (ToHttpApiData(..))
47−import Web.FormUrlEncoded (FromForm, urlDecodeAsForm)
43+import qualified Network.URI as N
44+import Reflex.Dom.Core hiding (tag)
45+import Servant.API.ContentTypes (Accept, FormUrlEncoded,
46+ JSON, NoContent (..),
47+ OctetStream, PlainText)
48+import Servant.Common.BaseUrl (BaseUrl,
49+ SupportsServantReflex,
50+ showBaseUrl)
51+import Web.FormUrlEncoded (FromForm, urlDecodeAsForm)
52+import Web.HttpApiData (ToHttpApiData (..))
4853-------------------------------------------------------------------------------
4954import Servant.API.BasicAuth
5055-------------------------------------------------------------------------------
5156
57+-- #ifndef __GHCJS__
58+-- jsonDecode :: JS.JSString -> Maybe a
59+-- jsonDecode = error "jsonDecode is only available in GHCJS"
60+-- #endif
5261
53−------------------------------------------------------------------------------
62+-----------------------------------------------------------------------------
5463-- | The result of a request event
5564data ReqResult tag a
5665 = ResponseSuccess tag a XhrResponse
@@ -144,18 +153,20 @@ data QueryPart t = QueryPartParam (Dynamic t (Either Text (Maybe Text)))
144153-------------------------------------------------------------------------------
145154-- The data structure used to build up request information while traversing
146155-- the shape of a servant API
147−data Req t = Req
156+data Req t = forall o. (IsXhrPayload o, Show o) => Req
148157 { reqMethod :: Text
149158 , reqPathParts :: [Dynamic t (Either Text Text)]
150159 , qParams :: [(Text, QueryPart t)]
151− , reqBody :: Maybe (Dynamic t (Either Text (BL.ByteString, Text)))
160+ , reqBody :: Maybe (Dynamic t (Either Text (o, Text)))
152161 , headers :: [(Text, Dynamic t (Either Text Text))]
153162 , respHeaders :: XhrResponseHeaders
154163 , authData :: Maybe (Dynamic t (Maybe BasicAuthData))
155164 }
156165
166+data SomeXhrRequest = forall o. (IsXhrPayload o, Show o) => SomeXhrRequest (XhrRequest (Maybe o))
167+
157168defReq :: Req t
158−defReq = Req "GET" [] [] Nothing [] def Nothing
169+defReq = Req "GET" [] [] (Nothing :: Maybe (Dynamic t (Either Text ((), Text)))) [] def Nothing
159170
160171prependToPathParts :: Dynamic t (Either Text Text) -> Req t -> Req t
161172prependToPathParts p req =
@@ -164,14 +175,13 @@ prependToPathParts p req =
164175addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) -> Req t -> Req t
165176addHeader name val req = req { headers = (name, (fmap . fmap) (T.decodeUtf8 . toHeader) val) : headers req }
166177
167−
168178reqToReflexRequest
169− :: forall t. Reflex t
170− => Text
171− -> Dynamic t BaseUrl
172− -> Req t
173− -> Dynamic t (Either Text (XhrRequest XhrPayload))
174−reqToReflexRequest reqMeth reqHost req =
179+ :: forall t. Reflex t
180+ => Text
181+ -> Dynamic t BaseUrl
182+ -> Req t
183+ -> Dynamic t (Either Text SomeXhrRequest)
184+reqToReflexRequest reqMeth reqHost req@(Req _ _ _ (reqBody :: Maybe (Dynamic t (Either Text (o, Text)))) _ _ _) =
175185 let t :: Dynamic t [Either Text Text]
176186 t = sequence $ reverse $ reqPathParts req
177187
@@ -223,14 +233,14 @@ reqToReflexRequest reqMeth reqHost req =
223233 fmap (fmap (\rightVal -> (headerName, rightVal))) dynam
224234
225235 mkConfigBody :: Either Text [(Text,Text)]
226− -> (Either Text (BL.ByteString, Text))
227− -> Either Text (XhrRequestConfig XhrPayload)
236+ -> (Either Text (o, Text))
237+ -> Either Text (XhrRequestConfig (Maybe o))
228238 mkConfigBody ehs rb = case (ehs, rb) of
229239 (_, Left e) -> Left e
230240 (Left e, _) -> Left e
231241 (Right hs, Right (bBytes, bCT)) ->
232242 Right $ XhrRequestConfig
233− { _xhrRequestConfig_sendData = bytesToPayload bBytes
243+ { _xhrRequestConfig_sendData = Just bBytes
234244 , _xhrRequestConfig_headers =
235245 Map.insert "Content-Type" bCT (Map.fromList hs)
236246 , _xhrRequestConfig_user = Nothing
@@ -240,16 +250,16 @@ reqToReflexRequest reqMeth reqHost req =
240250 , _xhrRequestConfig_responseHeaders = def
241251 }
242252
243− xhrOpts :: Dynamic t (Either Text (XhrRequestConfig XhrPayload))
244− xhrOpts = case reqBody req of
253+ xhrOpts :: Dynamic t (Either Text (XhrRequestConfig (Maybe o)))
254+ xhrOpts = case reqBody of
245255 Nothing -> ffor xhrHeaders $ \case
246256 Left e -> Left e
247257 Right hs -> Right $ def { _xhrRequestConfig_headers = Map.fromList hs
248258 , _xhrRequestConfig_user = Nothing
249259 , _xhrRequestConfig_password = Nothing
250260 , _xhrRequestConfig_responseType = Nothing
251− , _xhrRequestConfig_sendData = ""
252261 , _xhrRequestConfig_withCredentials = False
262+ , _xhrRequestConfig_sendData = Nothing
253263 }
254264 Just rBody -> liftA2 mkConfigBody xhrHeaders rBody
255265
@@ -263,10 +273,10 @@ reqToReflexRequest reqMeth reqHost req =
263273 addAuth :: Dynamic t (Either Text (XhrRequestConfig x))
264274 -> Dynamic t (Either Text (XhrRequestConfig x))
265275 addAuth xhr = case authData req of
266− Nothing -> xhr
276+ Nothing -> xhr
267277 Just auth -> liftA2 mkAuth auth xhr
268278
269− xhrReq = (liftA2 . liftA2) (\p opt -> XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
279+ xhrReq = (liftA2 . liftA2) (\p opt -> SomeXhrRequest $ XhrRequest reqMeth p opt) xhrUrl (addAuth xhrOpts)
270280
271281 in xhrReq
272282
@@ -275,8 +285,14 @@ reqToReflexRequest reqMeth reqHost req =
275285displayHttpRequest :: Text -> Text
276286displayHttpRequest httpmethod = "HTTP " <> httpmethod <> " request"
277287
288+instance IsXhrPayload o => IsXhrPayload (Maybe o) where
289+ sendXhrPayload xhr = maybe (sendXhrPayload xhr ()) (sendXhrPayload xhr)
290+
291+instance (IsXhrPayload o, IsXhrPayload o') => IsXhrPayload (Either o o') where
292+ sendXhrPayload xhr = either (sendXhrPayload xhr) (sendXhrPayload xhr)
293+
278294-- | This function performs the request
279−performRequests :: forall t m f tag.(SupportsServantReflex t m, Traversable f)
295+performRequests :: forall t m f tag. (SupportsServantReflex t m, Traversable f)
280296 => Text
281297 -> Dynamic t (f (Req t))
282298 -> Dynamic t BaseUrl
@@ -289,6 +305,7 @@ performRequests reqMeth rs reqHost opts trigger = do
289305 reqToReflexRequest reqMeth reqHost <$> fxhr) <$> rs
290306
291307 -- xhrReqs = fmap snd <$> xhrReqsAndDebugs
308+ reqs :: Event t (Compose ((,) tag) f (Either Text SomeXhrRequest))
292309 reqs = attachPromptlyDynWith
293310 (\fxhr t -> Compose (t, fxhr)) xhrReqs trigger
294311
@@ -304,32 +321,31 @@ performSomeRequestsAsync
304321 HasWebView (Performable m),
305322 PerformEvent t m,
306323 TriggerEvent t m,
307− Traversable f,
308− IsXhrPayload a,
309− Show a
324+ Traversable f
310325 )
311326 => ClientOptions
312− -> Event t (f (Either Text (XhrRequest a)))
327+ -> Event t (f (Either Text SomeXhrRequest))
313328 -> m (Event t (f (Either Text XhrResponse)))
314−performSomeRequestsAsync opts =
315− performSomeRequestsAsync' opts newXMLHttpRequest . fmap return
329+performSomeRequestsAsync opts sxhr =
330+ performSomeRequestsAsync' opts newXMLHttpRequest $ return <$> sxhr
316331
317332
318333------------------------------------------------------------------------------
319334-- | A modified version or Reflex.Dom.Xhr.performRequestsAsync
320335-- that accepts 'f (Either e (XhrRequestb))' events
321336performSomeRequestsAsync'
322− :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f, Show b)
337+ :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f)
323338 => ClientOptions
324− -> (XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
325− -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
339+ -> (forall b. IsXhrPayload b => XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
340+ -> Event t (Performable m (f (Either Text SomeXhrRequest)))
341+ -> m (Event t (f (Either Text a)))
326342performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
327343 rs <- hrs
328344 resps <- forM rs $ \r -> case r of
329345 Left e -> do
330346 resp <- liftIO $ newMVar (Left e)
331347 return resp
332− Right r' -> do
348+ Right (SomeXhrRequest r') -> do
333349 resp <- liftIO newEmptyMVar
334350 r'' <- liftJSM $ (optsRequestFixup opts) r'
335351 _ <- newXhr r'' $ liftIO . putMVar resp . Right
@@ -338,11 +354,6 @@ performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs
338354 return ()
339355
340356
341−type XhrPayload = T.Text
342−bytesToPayload :: BL.ByteString -> XhrPayload
343−bytesToPayload = T.decodeUtf8 . BL.toStrict
344−
345−
346357performRequestsCT
347358 :: (SupportsServantReflex t m,
348359 MimeUnrender ct a, Traversable f)