@@ -8,6 +8,8 @@
8{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE ScopedTypeVariables #-}
9{-# LANGUAGE TupleSections #-}
9{-# LANGUAGE TupleSections #-}
11+{-# LANGUAGE MultiParamTypeClasses #-}
12+{-# LANGUAGE FlexibleInstances #-}
11module Servant.Common.Req where
13module Servant.Common.Req where
13-------------------------------------------------------------------------------
15-------------------------------------------------------------------------------
@@ -15,8 +17,11 @@ import Control.Applicative (liftA2, liftA3)
15import Control.Concurrent
17import Control.Concurrent
16import Control.Monad (join)
18import Control.Monad (join)
17import Control.Monad.IO.Class (MonadIO, liftIO)
19import Control.Monad.IO.Class (MonadIO, liftIO)
18import Data.Bifunctor (first)
21import Data.Bifunctor (first)
19−import qualified Data.ByteString.Builder as Builder
22+import qualified Data.ByteString.Builder as BB
23+import qualified Data.ByteString as BS
24+import Data.ByteString.Lazy (ByteString)
20import qualified Data.ByteString.Lazy.Char8 as BL
25import qualified Data.ByteString.Lazy.Char8 as BL
21import qualified Data.Map as Map
26import qualified Data.Map as Map
22import Data.Maybe (catMaybes, fromMaybe)
27import Data.Maybe (catMaybes, fromMaybe)
@@ -25,17 +30,24 @@ import Data.Monoid ((<>))
25import Data.Proxy (Proxy(..))
30import Data.Proxy (Proxy(..))
26import Data.Text (Text)
31import Data.Text (Text)
27import qualified Data.Text as T
32import qualified Data.Text as T
28−import qualified Data.Text.Encoding as TE
33+import qualified Data.Text.Lazy as TL
34+import qualified Data.Text.Encoding as T
29import Data.Traversable (forM)
35import Data.Traversable (forM)
30import Language.Javascript.JSaddle.Monad (JSM, MonadJSM, liftJSM)
36import Language.Javascript.JSaddle.Monad (JSM, MonadJSM, liftJSM)
37+import qualified Language.Javascript.JSaddle as JS
38+import qualified Data.JSString.Text as JS (lazyTextFromJSString)
31import qualified Network.URI as N
39import qualified Network.URI as N
32import Reflex.Dom.Core hiding (tag)
40import Reflex.Dom.Core hiding (tag)
33import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
41import Servant.Common.BaseUrl (BaseUrl, showBaseUrl,
34 SupportsServantReflex)
42 SupportsServantReflex)
35−import Servant.API.ContentTypes (MimeUnrender(..), NoContent(..))
43+import Servant.API.ContentTypes ( NoContent(..), Accept, JSON
44+ , OctetStream, PlainText
36import Web.HttpApiData (ToHttpApiData(..))
46import Web.HttpApiData (ToHttpApiData(..))
47+import Web.FormUrlEncoded (FromForm, urlDecodeAsForm)
37-------------------------------------------------------------------------------
48-------------------------------------------------------------------------------
38import Servant.API.BasicAuth
49import Servant.API.BasicAuth
50+-------------------------------------------------------------------------------
41------------------------------------------------------------------------------
53------------------------------------------------------------------------------
@@ -150,7 +162,7 @@ prependToPathParts p req =
150 req { reqPathParts = p : reqPathParts req }
162 req { reqPathParts = p : reqPathParts req }
152addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) -> Req t -> Req t
164addHeader :: (ToHttpApiData a, Reflex t) => Text -> Dynamic t (Either Text a) -> Req t -> Req t
153−addHeader name val req = req { headers = (name, (fmap . fmap) (TE.decodeUtf8 . toHeader) val) : headers req }
165+addHeader name val req = req { headers = (name, (fmap . fmap) (T.decodeUtf8 . toHeader) val) : headers req }
156reqToReflexRequest
168reqToReflexRequest
@@ -245,8 +257,8 @@ reqToReflexRequest reqMeth reqHost req =
245 mkAuth _ (Left e) = Left e
257 mkAuth _ (Left e) = Left e
246 mkAuth Nothing r = r
258 mkAuth Nothing r = r
247 mkAuth (Just (BasicAuthData u p)) (Right config) = Right $ config
259 mkAuth (Just (BasicAuthData u p)) (Right config) = Right $ config
248− { _xhrRequestConfig_user = Just $ TE.decodeUtf8 u
260+ { _xhrRequestConfig_user = Just $ T.decodeUtf8 u
249− , _xhrRequestConfig_password = Just $ TE.decodeUtf8 p}
261+ , _xhrRequestConfig_password = Just $ T.decodeUtf8 p}
251 addAuth :: Dynamic t (Either Text (XhrRequestConfig x))
263 addAuth :: Dynamic t (Either Text (XhrRequestConfig x))
252 -> Dynamic t (Either Text (XhrRequestConfig x))
264 -> Dynamic t (Either Text (XhrRequestConfig x))
@@ -326,10 +338,9 @@ performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs
326 return ()
338 return ()
330type XhrPayload = T.Text
341type XhrPayload = T.Text
331bytesToPayload :: BL.ByteString -> XhrPayload
342bytesToPayload :: BL.ByteString -> XhrPayload
332−bytesToPayload = TE.decodeUtf8 . BL.toStrict
343+bytesToPayload = T.decodeUtf8 . BL.toStrict
335performRequestsCT
346performRequestsCT
@@ -344,11 +355,10 @@ performRequestsCT
344 -> m (Event t (f (ReqResult tag a)))
355 -> m (Event t (f (ReqResult tag a)))
345performRequestsCT ct reqMeth reqs reqHost opts trigger = do
356performRequestsCT ct reqMeth reqs reqHost opts trigger = do
346 resps <- performRequests reqMeth reqs reqHost opts trigger
357 resps <- performRequests reqMeth reqs reqHost opts trigger
347− let decodeResp x = first T.pack .
358+ let decodeResp x = first T.pack
348− mimeUnrender ct .
359+ . mimeUnrender ct
349− BL.fromStrict .
360+ . JS.textToJSString
350− TE.encodeUtf8 =<< note "No body text"
351− (_xhrResponse_responseText x)
361+ =<< note "No body text" (_xhrResponse_responseText x)
352 return $ fmap
362 return $ fmap
353 (\(t,rs) -> ffor rs $ \r -> case r of
363 (\(t,rs) -> ffor rs $ \r -> case r of
354 Left e -> RequestFailure t e
364 Left e -> RequestFailure t e
@@ -379,7 +389,7 @@ evalResponse
379 :: (XhrResponse -> Either Text a)
389 :: (XhrResponse -> Either Text a)
380 -> (tag, XhrResponse)
390 -> (tag, XhrResponse)
381 -> ReqResult tag a
391 -> ReqResult tag a
382−evalResponse decode (tag, xhr) =
392+evalResponse decodeRes (tag, xhr) =
383 let okStatus = _xhrResponse_status xhr < 400
393 let okStatus = _xhrResponse_status xhr < 400
384 errMsg = fromMaybe
394 errMsg = fromMaybe
385 ("Empty response with error code " <>
395 ("Empty response with error code " <>
@@ -389,20 +399,56 @@ evalResponse decode (tag, xhr) =
389 then either
399 then either
390 (\e -> ResponseFailure tag e xhr)
400 (\e -> ResponseFailure tag e xhr)
391 (\v -> ResponseSuccess tag v xhr)
401 (\v -> ResponseSuccess tag v xhr)
392− (decode xhr)
402+ (decodeRes xhr)
393 else ResponseFailure tag errMsg xhr
403 else ResponseFailure tag errMsg xhr
394 in respPayld
404 in respPayld
407+-- | Similar to 'Servant.API.ContentType.MimeUnrender' but with the differnce that
408+-- the second argument expects a JSString instead of ByteString.
409+-- This can lead to better performance ghcjs.
410+class Accept contentType => MimeUnrender contentType a where
411+ mimeUnrender :: Proxy contentType -> JS.JSString -> Either String a
413+instance FromJSON a => MimeUnrender JSON a where
414+ mimeUnrender _ = note "Failed to decode response" . jsonDecode
416+-- | @urlDecodeAsForm@
417+-- Note that the @mimeUnrender p (mimeRender p x) == Right x@ law only
418+-- holds if every element of x is non-null (i.e., not @("", "")@)
419+instance FromForm a => MimeUnrender FormUrlEncoded a where
420+ mimeUnrender _ = first T.unpack . urlDecodeAsForm . BB.toLazyByteString . T.encodeUtf8Builder . JS.textFromJSString
422+-- | @left show . TextL.decodeUtf8'@
423+instance MimeUnrender PlainText TL.Text where
424+ mimeUnrender _ = pure . JS.lazyTextFromJSString
426+-- | @left show . TextS.decodeUtf8' . toStrict@
427+instance MimeUnrender PlainText T.Text where
428+ mimeUnrender _ = pure . JS.textFromJSString
430+-- | @Right . BC.unpack@
431+instance MimeUnrender PlainText String where
432+ mimeUnrender _ = Right . T.unpack . JS.textFromJSString
435+instance MimeUnrender OctetStream ByteString where
436+ mimeUnrender _ = pure . BB.toLazyByteString . T.encodeUtf8Builder . JS.textFromJSString
438+-- | @Right . toStrict@
439+instance MimeUnrender OctetStream BS.ByteString where
440+ mimeUnrender _ = pure . T.encodeUtf8 . JS.textFromJSString
397note :: e -> Maybe a -> Either e a
442note :: e -> Maybe a -> Either e a
398−note e = maybe (Left e) Right
443+note e Nothing = Left e
444+note _ (Just a) = Right a
400fmapL :: (e -> e') -> Either e a -> Either e' a
446fmapL :: (e -> e') -> Either e a -> Either e' a
401fmapL _ (Right a) = Right a
447fmapL _ (Right a) = Right a
402fmapL f (Left e) = Left (f e)
448fmapL f (Left e) = Left (f e)
404−builderToText :: Builder.Builder -> T.Text
450+builderToText :: BB.Builder -> T.Text
405−builderToText = TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString
451+builderToText = T.decodeUtf8 . BL.toStrict . BB.toLazyByteString
407escape :: T.Text -> T.Text
453escape :: T.Text -> T.Text
408−escape = T.pack . N.escapeURIString (not . N.isReserved) . T.unpack . TE.decodeUtf8 . BL.toStrict . Builder.toLazyByteString . toEncodedUrlPiece
454+escape = T.pack . N.escapeURIString (not . N.isReserved) . T.unpack . T.decodeUtf8 . BL.toStrict . BB.toLazyByteString . toEncodedUrlPiece