git.iko.soy/typeable/servant-reflex
on master help sign in
UVerb
commit41a077c9d36dfc99f8fed842fc22194b9fec7d41 authorAleksey Uymanov <s9gf4ult@gmail.com> dateWed 29 Sep 2021, 11:30 (5 years ago) parentd73b5c6
6 files changed+110−24
default.nix+7−7 servant-reflex.cabal+2 src/Servant/Common/Req.hs+2−1 src/Servant/Reflex.hs+95−13 testserver/testserver.cabal+1−1 travis.nix+3−2
default.nix+7−7
@@ -1,8 +1,8 @@
11{ mkDerivation, base, bytestring, case-insensitive, containers
22, data-default, exceptions, ghcjs-dom, http-api-data, http-media
3−, jsaddle, mtl, network-uri, reflex, reflex-dom, reflex-dom-core
4−, safe, servant, servant-auth, stdenv, string-conversions, text
5−, transformers
3+, http-types, jsaddle, lib, mtl, network-uri, reflex
4+, reflex-dom-core, reflex-dom, safe, servant, servant-auth, sop-core
5+, string-conversions, text, transformers
66}:
77mkDerivation {
88 pname = "servant-reflex";
@@ -12,10 +12,10 @@ mkDerivation {
1212 isExecutable = true;
1313 libraryHaskellDepends = [
1414 base bytestring case-insensitive containers data-default exceptions
15− ghcjs-dom http-api-data http-media jsaddle mtl network-uri reflex
16− reflex-dom reflex-dom-core safe servant servant-auth string-conversions
17− text transformers
15+ ghcjs-dom http-api-data http-media http-types jsaddle mtl
16+ network-uri reflex reflex-dom-core reflex-dom safe servant servant-auth
17+ sop-core string-conversions text transformers
1818 ];
1919 description = "servant API generator for reflex apps";
20− license = stdenv.lib.licenses.bsd3;
20+ license = lib.licenses.bsd3;
2121}
servant-reflex.cabal+2
@@ -32,6 +32,7 @@ library
3232 exceptions >= 0.8 && < 0.11,
3333 ghcjs-dom >= 0.2 && < 0.10,
3434 http-api-data >= 0.3.6 && < 0.5,
35+ http-types,
3536 http-media >= 0.6 && < 0.9,
3637 jsaddle >= 0.8 && < 0.10,
3738 mtl >= 2.2.1 && < 2.3,
@@ -42,6 +43,7 @@ library
4243 servant >= 0.16 && < 0.19,
4344 servant-auth >= 0.2.1 && < 0.4,
4445 string-conversions >= 0.4 && < 0.5,
46+ sop-core,
4547 text >= 1.2 && < 1.3,
4648 transformers >= 0.4 && < 0.6
4749
src/Servant/Common/Req.hs+2−1
@@ -321,7 +321,8 @@ performSomeRequestsAsync'
321321 :: (MonadJSM (Performable m), PerformEvent t m, TriggerEvent t m, Traversable f, Show b)
322322 => ClientOptions
323323 -> (XhrRequest b -> (a -> JSM ()) -> Performable m XMLHttpRequest)
324− -> Event t (Performable m (f (Either Text (XhrRequest b)))) -> m (Event t (f (Either Text a)))
324+ -> Event t (Performable m (f (Either Text (XhrRequest b))))
325+ -> m (Event t (f (Either Text a)))
325326performSomeRequestsAsync' opts newXhr req = performEventAsync $ ffor req $ \hrs cb -> do
326327 rs <- hrs
327328 resps <- forM rs $ \r -> case r of
src/Servant/Reflex.hs+95−13
@@ -1,4 +1,5 @@
11{-# LANGUAGE AllowAmbiguousTypes #-}
2+{-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE CPP #-}
34{-# LANGUAGE DataKinds #-}
45{-# LANGUAGE FlexibleContexts #-}
@@ -65,6 +66,7 @@ import qualified Servant.Auth as Auth
6566
6667import Reflex.Dom.Core (Dynamic, Event, Reflex,
6768 XhrRequest (..), XhrResponse (..),
69+ XhrResponseBody(..),
6870 XhrResponseHeaders (..),
6971 attachPromptlyDynWith, constDyn, ffor,
7072 fmapMaybe, leftmost,
@@ -80,6 +82,7 @@ import Servant.Common.Req (ClientOptions(..),
8082 defReq, evalResponse, prependToPathParts,
8183 -- performRequestCT,
8284 performRequestsCT,
85+ performRequests,
8386 -- performRequestNoBody,
8487 performRequestsNoBody,
8588 performSomeRequestsAsync,
@@ -90,6 +93,17 @@ import Servant.Common.Req (ClientOptions(..),
9093 reqTag,
9194 qParams, withCredentials)
9295
96+#if MIN_VERSION_servant(0, 18, 1)
97+import Control.Arrow
98+import Data.Either
99+import Data.SOP
100+import Data.SOP.NP
101+import Servant.API.ContentTypes (AllMimeUnrender(..))
102+import Servant.API.UVerb
103+import qualified Data.ByteString.Lazy as BL
104+import qualified Network.HTTP.Media as M
105+import Network.HTTP.Types.Status (Status(..))
106+#endif
93107
94108-- * Accessing APIs as a Client
95109
@@ -225,7 +239,6 @@ instance {-# OVERLAPPING #-}
225239 wrap =<< fmap runIdentity <$> performRequestsNoBody method (constDyn $ Identity req) baseurl opts trigs
226240 where method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
227241
228−
229242toHeaders :: BuildHeadersTo ls => ReqResult tag a -> ReqResult tag (Headers ls a)
230243toHeaders r =
231244 let hdrs = maybe []
@@ -286,6 +299,75 @@ instance {-# OVERLAPPABLE #-}
286299 OnlyHeaders (Set.fromList (buildHeaderKeysTo (Proxy :: Proxy ls)))
287300 }
288301
302+#if MIN_VERSION_servant(0, 18, 1)
303+instance
304+ ( ReflectMethod method, cts' ~ (ct ': cts)
305+ , as' ~ (a ': as)
306+ , All (AllMimeUnrender cts') as'
307+ , All HasStatus as'
308+ , SupportsServantReflex t m
309+ ) =>
310+ HasClient t m (UVerb method cts' as') tag where
311+ type Client t m (UVerb method cts' as') tag
312+ = Event t tag -> m (Event t (ReqResult tag (Union as')))
313+ clientWithRouteAndResultHandler Proxy _ _ req baseurl opts wrap' trigs =
314+ wrap' =<< fmap runIdentity <$> perform
315+ where
316+ method = E.decodeUtf8 $ reflectMethod (Proxy :: Proxy method)
317+ perform = do
318+ resp <- performRequests method (constDyn $ Identity req)
319+ baseurl opts trigs
320+ return $ fmap
321+ (\(t,rs) -> ffor rs $ \case
322+ Left e -> RequestFailure t e
323+ Right g -> evalResponse' t g
324+ )
325+ resp
326+ -- Try to decode the content. If decoding fails, we declare
327+ -- response failure. Note that HTTP status 500 response is not
328+ -- necessary a failure.
329+ evalResponse' t g = case decodeResp g of
330+ Left err -> ResponseFailure t err g
331+ Right v -> ResponseSuccess t v g
332+ decodeResp r =
333+ let
334+ status = _xhrResponse_status r
335+ -- We go through all possible response types and find types with
336+ -- the HTTP status we got. Try to parse these response types
337+ -- and use the first one that succeeds.
338+ parse
339+ :: forall xs. All HasStatus xs
340+ => NP ([] :.: Either (M.MediaType, String)) xs
341+ -> Either Text (Union xs)
342+ parse Nil =
343+ -- No more response types to try
344+ Left "Unknown response"
345+ parse (Comp x :* xs)
346+ | status == (fromIntegral . statusCode $ statusOf (Comp x)) =
347+ -- Found response type for the response status we got
348+ case partitionEithers x of
349+ -- can't parse the type, go to the next candidate
350+ (_, []) -> S <$> parse xs
351+ -- parse succeeded, return the result
352+ (_, (res : _)) -> Right . inject . I $ res
353+ -- Status doesn't match, go to the next candidate
354+ | otherwise = S <$> parse xs
355+ in case _xhrResponse_response r of
356+ Just (XhrResponseBody_ArrayBuffer x) ->
357+ parse (mimeUnrenders (Proxy :: Proxy cts') (BL.fromStrict x))
358+ _ -> Left "No Body"
359+
360+mimeUnrenders ::
361+ forall cts xs.
362+ All (AllMimeUnrender cts) xs =>
363+ Proxy cts ->
364+ BL.ByteString ->
365+ NP ([] :.: Either (M.MediaType, String)) xs
366+mimeUnrenders ctp body = cpure_NP
367+ (Proxy :: Proxy (AllMimeUnrender cts))
368+ (Comp . map (\(mediaType, parser) -> left ((,) mediaType) (parser body))
369+ . allMimeUnrender $ ctp)
370+#endif
289371
290372
291373-- HEADER
@@ -369,23 +451,24 @@ instance (HasClient t m sublayout tag, KnownSymbol sym)
369451-- > -- then you can just use "getBooksBy" to query that endpoint.
370452-- > -- 'getBooksBy Nothing' for all books
371453-- > -- 'getBooksBy (Just "Isaac Asimov")' to get all books by Isaac Asimov
372−instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex t)
373− => HasClient t m (QueryParam' mods sym a :> sublayout) tag where
454+instance
455+ ( KnownSymbol sym
456+ , ToHttpApiData a
457+ , HasClient t m sublayout tag
458+ , Reflex t
459+ ) => HasClient t m (QueryParam' mods sym a :> sublayout) tag where
374460
375461 type Client t m (QueryParam' mods sym a :> sublayout) tag =
376462 Dynamic t (QParam a) -> Client t m sublayout tag
377463
378464 -- if mparam = Nothing, we don't add it to the query string
379− clientWithRouteAndResultHandler Proxy q t req baseurl opts wrap mparam =
465+ clientWithRouteAndResultHandler Proxy q t req baseurl opts wrp mparam =
380466 clientWithRouteAndResultHandler (Proxy :: Proxy sublayout) q t
381− (req {qParams = paramPair : qParams req}) baseurl opts wrap
382−
383− where pname = symbolVal (Proxy :: Proxy sym)
384− --p prm = QueryPartParam $ (fmap . fmap) (toQueryParam) prm
385− --paramPair = (T.pack pname, p mparam)
386− p prm = QueryPartParam $ fmap qParamToQueryPart prm -- (fmap . fmap) (unpack . toQueryParam) prm
387− paramPair = (T.pack pname, p mparam)
388−
467+ (req {qParams = paramPair : qParams req}) baseurl opts wrp
468+ where
469+ pname = symbolVal (Proxy :: Proxy sym)
470+ p prm = QueryPartParam $ fmap qParamToQueryPart prm
471+ paramPair = (T.pack pname, p mparam)
389472
390473
391474-- | If you use a 'QueryParams' in one of your endpoints in your API,
@@ -431,7 +514,6 @@ instance (KnownSymbol sym, ToHttpApiData a, HasClient t m sublayout tag, Reflex
431514 paramlist
432515
433516
434−
435517-- | If you use a 'QueryFlag' in one of your endpoints in your API,
436518-- the corresponding querying function will automatically take
437519-- an additional 'Bool' argument.
testserver/testserver.cabal+1−1
@@ -25,7 +25,7 @@ executable back
2525 , snap >= 1.0 && < 1.2
2626 , snap-server >= 1.0 && < 1.2
2727 , snap-core >= 1.0 && < 1.1
28− , servant >= 0.16 && < 0.18
28+ , servant >= 0.16 && < 0.19
2929 , servant-snap >= 0.8 && < 0.9
3030 , text >= 1.0 && < 1.3
3131 -- hs-source-dirs:
travis.nix+3−2
@@ -19,7 +19,7 @@ let
1919 ghcjsPkgs = with lib; reflexPlatform.${jsCompiler}.override {
2020 overrides = self: super: {
2121 http-media = dontCheck super.http-media;
22− servant = dontCheck super.servant;
22+ servant = dontCheck (super.callHackage "servant" "0.18.2" {}) ;
2323 lens-aeson = dontCheck super.lens-aeson;
2424 servant-reflex = lib.appendConfigureFlag
2525 (self.callPackage ./default.nix {}) "-fExample";
@@ -28,7 +28,8 @@ let
2828
2929 ghcPkgs = with lib; reflexPlatform.${nativeCompiler}.override {
3030 overrides = self: super: {
31− servant-snap = dontCheck ((import ./nix/servant-snap.nix {}) self super);
31+ servant-snap = doJailbreak ( dontCheck ((import ./nix/servant-snap.nix {}) self super)) ;
32+ servant = dontCheck (super.callHackage "servant" "0.18.2" {}) ;
3233 testdriver = self.callCabal2nix "testdriver" ./testdriver {};
3334 testserver = import nix/testserver.nix ghcjsPkgs.servant-reflex self super;
3435 servant-reflex = self.callPackage ./default.nix {};