on master help sign in
Handle response headers
da0bd01Greg Hale
servant-reflex.cabal−3
@@ -40,9 +40,6 @@ library
40 text >= 1.2 && < 1.3,
40 text >= 1.2 && < 1.3,
41 transformers >= 0.4 && < 0.6
41 transformers >= 0.4 && < 0.6
42
42
43− if impl(ghcjs)
44− build-depends: ghcjs-base >= 0.2 && < 0.3
45−
46 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
43 ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
47
44
48 default-language: Haskell2010
45 default-language: Haskell2010
src/Servant/Common/Req.hs+11−9
@@ -32,6 +32,11 @@ data ReqResult a = ResponseSuccess a XhrResponse
32 | ResponseFailure Text XhrResponse
32 | ResponseFailure Text XhrResponse
33 | RequestFailure Text
33 | RequestFailure Text
34
34
35+instance Functor ReqResult where
36+ fmap f (ResponseSuccess a xhr) = ResponseSuccess (f a) xhr
37+ fmap _ (ResponseFailure r x) = ResponseFailure r x
38+ fmap _ (RequestFailure r) = RequestFailure r
39+
35reqSuccess :: ReqResult a -> Maybe a
40reqSuccess :: ReqResult a -> Maybe a
36reqSuccess (ResponseSuccess x _) = Just x
41reqSuccess (ResponseSuccess x _) = Just x
37reqSuccess _ = Nothing
42reqSuccess _ = Nothing
@@ -64,11 +69,12 @@ data Req t = Req
64 , qParams :: [(Text, QueryPart t)]
69 , qParams :: [(Text, QueryPart t)]
65 , reqBody :: Maybe (Dynamic t (Either Text (BL.ByteString, Text)))
70 , reqBody :: Maybe (Dynamic t (Either Text (BL.ByteString, Text)))
66 , headers :: [(Text, Dynamic t (Either Text Text))]
71 , headers :: [(Text, Dynamic t (Either Text Text))]
72+ , respHeaders :: XhrResponseHeaders
67 , authData :: Maybe (Dynamic t (Maybe BasicAuthData))
73 , authData :: Maybe (Dynamic t (Maybe BasicAuthData))
68 }
74 }
69
75
70defReq :: Req t
76defReq :: Req t
71−defReq = Req "GET" [] [] Nothing [] Nothing
77+defReq = Req "GET" [] [] Nothing [] def Nothing
72
78
73prependToPathParts :: Dynamic t (Either Text Text) -> Req t -> Req t
79prependToPathParts :: Dynamic t (Either Text Text) -> Req t -> Req t
74prependToPathParts p req =
80prependToPathParts p req =
@@ -204,17 +210,13 @@ bytesToPayload :: BL.ByteString -> XhrPayload
204bytesToPayload = T.pack . BL.unpack
210bytesToPayload = T.pack . BL.unpack
205#endif
211#endif
206
212
207−-- TODO implement
208−-- => String -> Req -> BaseUrl -> ExceptT ServantError IO [HTTP.Header]
209− -- TODO Proxy probably not needed
210performRequestNoBody ::
213performRequestNoBody ::
211 forall t m .MonadWidget t m => Text -> Req t -> Dynamic t BaseUrl
214 forall t m .MonadWidget t m => Text -> Req t -> Dynamic t BaseUrl
212−-- -> Event t () -> m (Event t (Maybe NoContent, XhrResponse))
213 -> Event t () -> m (Event t (ReqResult NoContent))
215 -> Event t () -> m (Event t (ReqResult NoContent))
214−performRequestNoBody _ _ _ _ = do
216+performRequestNoBody reqMeth req reqHost trigger = do
215− -- performRequest reqMeth req reqHost trigger
217+ (resp, badReq) <- performRequest reqMeth req reqHost trigger
216− undefined
218+ return $ leftmost [ fmap (ResponseSuccess NoContent) resp, fmap RequestFailure badReq]
217− -- return hdrs
219+
218
220
219performRequestCT :: (MonadWidget t m, MimeUnrender ct a)
221performRequestCT :: (MonadWidget t m, MimeUnrender ct a)
220 => Proxy ct -> Text -> Req t -> Dynamic t BaseUrl
222 => Proxy ct -> Text -> Req t -> Dynamic t BaseUrl
src/Servant/Reflex.hs+59−38
@@ -24,10 +24,14 @@ module Servant.Reflex
24 ) where
24 ) where
25
25
26import Control.Applicative
26import Control.Applicative
27+import qualified Data.Set as Set
28+import qualified Data.Text.Encoding as E
29+import Data.CaseInsensitive (mk)
27import Data.Proxy
30import Data.Proxy
28import Data.String.Conversions
31import Data.String.Conversions
32+import qualified Data.Map as Map
29−import Data.Text (Text)
33+import Data.Text (Text)
30−import qualified Data.Text as T
34+import qualified Data.Text as T
31import Data.Text.Encoding
35import Data.Text.Encoding
32import GHC.TypeLits
36import GHC.TypeLits
33import Servant.API
37import Servant.API
@@ -120,42 +124,60 @@ instance {-# OVERLAPPING #-}
120 performRequestNoBody method req baseurl
124 performRequestNoBody method req baseurl
121 where method = decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
125 where method = decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
122
126
127+toHeaders :: BuildHeadersTo ls => ReqResult a -> ReqResult (Headers ls a)
128+toHeaders r =
129+ let toBS = E.encodeUtf8
130+ hdrs = maybe []
131+ (\xhr -> fmap (\(h,v) -> (mk (toBS h), toBS v))
132+ (Map.toList $ _xhrResponse_headers xhr))
133+ (response r)
134+ in ffor r $ \a -> Headers {getResponse = a ,getHeadersHList = buildHeadersTo hdrs}
135+
136+class BuildHeaderKeysTo hs where
137+ buildHeaderKeysTo :: Proxy hs -> [T.Text]
138+
139+instance {-# OVERLAPPABLE #-} BuildHeaderKeysTo '[]
140+ where buildHeaderKeysTo _ = []
141+
142+instance {-# OVERLAPPABLE #-} (BuildHeaderKeysTo xs, KnownSymbol h)
143+ => BuildHeaderKeysTo '[(Header h v) ': xs] where
144+ buildHeaderKeysTo _ = (T.pack $ symbolVal (Proxy :: Proxy h)) : buildHeaderKeysTo (Proxy :: Proxy xs)
145+
123−-- -- HEADERS Verb (Content) --
146+-- HEADERS Verb (Content) --
124−-- -- Headers combinator not treated in fully general case,
147+-- Headers combinator not treated in fully general case,
125−-- -- in order to deny instances for (Headers ls (Capture "id" Int)),
148+-- in order to deny instances for (Headers ls (Capture "id" Int)),
126−-- -- a combinator that wouldn't make sense
149+-- a combinator that wouldn't make sense
127−-- -- TODO Overlapping??
128−-- instance {-# OVERLAPPABLE #-}
129−-- -- Note [Non-Empty Content Types]
130−-- ( MimeUnrender ct a, BuildHeadersTo ls,
131−-- ReflectMethod method, cts' ~ (ct ': cts),
132−-- MonadWidget t m
133−-- ) => HasClient t m (Verb method status cts' (Headers ls a)) where
134−-- type Client t m (Verb method status cts' (Headers ls a))
135−-- = Event t () -> m (Event t (Maybe a, XhrResponse))
136−-- -- ExceptT ServantError IO (Headers ls a)
137−-- clientWithRoute Proxy req baseurl = do
138−-- let method = reflectMethod (Proxy :: Proxy method)
139−-- (hdrs, resp) <- performRequestCT (Proxy :: Proxy ct) method req baseurl
140−-- return $ Headers { getResponse = resp
141−-- , getHeadersHList = buildHeadersTo hdrs
142−-- }
143−
144-- TODO Overlapping??
150-- TODO Overlapping??
151+instance {-# OVERLAPPABLE #-}
152+ -- Note [Non-Empty Content Types]
153+ ( MimeUnrender ct a, BuildHeadersTo ls, BuildHeaderKeysTo ls,
154+ ReflectMethod method, cts' ~ (ct ': cts),
155+ MonadWidget t m
156+ ) => HasClient t m (Verb method status cts' (Headers ls a)) where
157+ type Client t m (Verb method status cts' (Headers ls a)) =
158+ Event t () -> m (Event t (ReqResult (Headers ls a)))
159+ clientWithRoute Proxy _ req baseurl = do
160+ let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
161+ resp <- performRequestCT (Proxy :: Proxy ct) method req' baseurl
162+ return $ (fmap . fmap) toHeaders resp
163+ where req' = req { respHeaders =
164+ OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
165+ }
166+
145−-- -- HEADERS Verb (No content) --
167+-- HEADERS Verb (No content) --
146−-- instance {-# OVERLAPPABLE #-}
168+instance {-# OVERLAPPABLE #-}
147−-- ( BuildHeadersTo ls, ReflectMethod method,
169+ ( BuildHeadersTo ls, BuildHeaderKeysTo ls, ReflectMethod method,
148−-- MonadWidget t m
170+ MonadWidget t m
149−-- ) => HasClient t m (Verb method status cts (Headers ls NoContent)) where
171+ ) => HasClient t m (Verb method status cts (Headers ls NoContent)) where
150−-- type Client t m (Verb method status cts (Headers ls NoContent))
172+ type Client t m (Verb method status cts (Headers ls NoContent))
151−-- = Event t () -> m (Event t XhrResponse)
152−-- -- ExceptT ServantError IO (Headers ls NoContent)
173+ = Event t () -> m (Event t (ReqResult (Headers ls NoContent)))
153−-- clientWithRoute Proxy req baseurl = do
174+ clientWithRoute Proxy _ req baseurl = do
154−-- let method = reflectMethod (Proxy :: Proxy method)
175+ let method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
155−-- hdrs <- performRequestNoBody method req baseurl
176+ resp <- performRequestNoBody method req' baseurl
156−-- return $ Headers { getResponse = NoContent
177+ return $ (fmap . fmap) toHeaders resp
157−-- , getHeadersHList = buildHeadersTo hdrs
178+ where req' = req {respHeaders =
179+ OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
158−-- }
180+ }
159
181
160
182
161-- HEADER
183-- HEADER
@@ -227,7 +249,6 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout, Reflex t)
227 => HasClient t m (QueryParam sym a :> sublayout) where
249 => HasClient t m (QueryParam sym a :> sublayout) where
228
250
229 type Client t m (QueryParam sym a :> sublayout) =
251 type Client t m (QueryParam sym a :> sublayout) =
230− -- TODO (Maybe a), or (Maybe (Maybe a))? (should the user be able to send a Nothing)
231 Dynamic t (QParam a) -> Client t m sublayout
252 Dynamic t (QParam a) -> Client t m sublayout
232
253
233 -- if mparam = Nothing, we don't add it to the query string
254 -- if mparam = Nothing, we don't add it to the query string