git.iko.soy/typeable/servant
on master help sign in
add tests, fix to make tests work
commit38e87397e77b07e9527afa5e75027c6aeb290546 authorGershom <gershom@awakesecurity.com> dateSat 4 Nov 2017, 00:10 (9 years ago) parent0c77a2b
6 files changed+143−15
servant-client-core/src/Servant/Client/Core/Internal/HasClient.hs+6−4 servant-client/servant-client.cabal+1 servant-client/test/Servant/ClientSpec.hs+1−1 servant-client/test/Servant/StreamSpec.hs+113 servant/src/Servant/API.hs+4−2 servant/src/Servant/API/Stream.hs+18−8
servant-client-core/src/Servant/Client/Core/Internal/HasClient.hs+6−4
@@ -267,7 +267,7 @@ instance OVERLAPPABLE_
267267 }
268268 return . buildFromStream $ ResultStream $ \k ->
269269 runStreamingResponse sresp $ \(status,_headers,_httpversion,reader) -> do
270− when (H.statusCode status /= 200) $ error "bad status" --fixme
270+ when (H.statusCode status /= 200) $ error "bad status" -- TODO fixme
271271 let unrender = unrenderFrames (Proxy :: Proxy framing) (Proxy :: Proxy a)
272272 loop bs = do
273273 res <- BL.fromStrict <$> reader
@@ -283,10 +283,12 @@ instance OVERLAPPABLE_
283283 res <- BL.fromStrict <$> reader
284284 let addIsEmptyInfo (a, r) = (r, (a, BL.null r && BL.null res))
285285 if BL.null res
286− then return . addIsEmptyInfo $ parseEOF frameParser res
286+ then if BL.null bs
287+ then return ("", (Right "", True))
288+ else return . addIsEmptyInfo $ parseEOF frameParser bs
287289 else let sofar = (bs <> res)
288− in case parseIncremental frameParser res of
289− Just x -> return $ addIsEmptyInfo x
290+ in case parseIncremental frameParser sofar of
291+ Just x -> return $ addIsEmptyInfo x
290292 Nothing -> frameLoop sofar
291293
292294 go = processResult <$> modifyMVar state frameLoop
servant-client/servant-client.cabal+1
@@ -66,6 +66,7 @@ test-suite spec
6666 main-is: Spec.hs
6767 other-modules:
6868 Servant.ClientSpec
69+ Servant.StreamSpec
6970 build-depends:
7071 base == 4.*
7172 , aeson
servant-client/test/Servant/ClientSpec.hs+1−1
@@ -24,7 +24,7 @@
2424{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
2525
2626#include "overlapping-compat.h"
27−module Servant.ClientSpec (spec) where
27+module Servant.ClientSpec (spec, Person(..), startWaiApp, endWaiApp) where
2828
2929import Prelude ()
3030import Prelude.Compat
servant-client/test/Servant/StreamSpec.hsnew file+113
@@ -0,0 +1,113 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE DataKinds #-}
4+{-# LANGUAGE DeriveGeneric #-}
5+{-# LANGUAGE FlexibleContexts #-}
6+{-# LANGUAGE FlexibleInstances #-}
7+{-# LANGUAGE FunctionalDependencies #-}
8+{-# LANGUAGE GADTs #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE OverloadedStrings #-}
11+{-# LANGUAGE PolyKinds #-}
12+{-# LANGUAGE RecordWildCards #-}
13+{-# LANGUAGE ScopedTypeVariables #-}
14+{-# LANGUAGE StandaloneDeriving #-}
15+{-# LANGUAGE TypeFamilies #-}
16+{-# LANGUAGE TypeOperators #-}
17+{-# LANGUAGE UndecidableInstances #-}
18+#if __GLASGOW_HASKELL__ >= 800
19+{-# OPTIONS_GHC -freduction-depth=100 #-}
20+#else
21+{-# OPTIONS_GHC -fcontext-stack=100 #-}
22+#endif
23+{-# OPTIONS_GHC -fno-warn-orphans #-}
24+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
25+
26+#include "overlapping-compat.h"
27+module Servant.StreamSpec (spec) where
28+
29+import Prelude ()
30+import Prelude.Compat
31+import Data.Proxy
32+import qualified Network.HTTP.Client as C
33+import System.IO.Unsafe (unsafePerformIO)
34+import Test.Hspec
35+
36+import Servant.API ((:<|>) ((:<|>)),
37+ (:>),
38+ EmptyAPI, JSON,
39+ StreamGet,
40+ NewlineFraming,
41+ NetstringFraming,
42+ ResultStream(..),
43+ StreamGenerator(..))
44+import Servant.Client
45+import Servant.Server
46+import qualified Servant.ClientSpec as CS
47+import Servant.ClientSpec (Person(..))
48+
49+
50+spec :: Spec
51+spec = describe "Servant.Stream" $ do
52+ streamSpec
53+
54+type StreamApi f =
55+ "streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
56+ :<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
57+ :<|> EmptyAPI
58+
59+
60+capi :: Proxy (StreamApi ResultStream)
61+capi = Proxy
62+
63+sapi :: Proxy (StreamApi StreamGenerator)
64+sapi = Proxy
65+
66+
67+getGetNL :<|> getGetNS :<|> EmptyClient = client capi
68+
69+
70+getGetNL :: ClientM (ResultStream Person)
71+getGetNS :: ClientM (ResultStream Person)
72+
73+alice :: Person
74+alice = Person "Alice" 42
75+
76+bob :: Person
77+bob = Person "Bob" 25
78+
79+server :: Application
80+server = serve sapi (
81+ (return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
82+ :: Handler (StreamGenerator Person))
83+ :<|>
84+ (return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
85+ :: Handler (StreamGenerator Person))
86+ :<|>
87+ emptyServer)
88+
89+
90+{-# NOINLINE manager' #-}
91+manager' :: C.Manager
92+manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
93+
94+runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
95+runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl')
96+
97+runResultStream :: ResultStream a -> IO (Maybe (Either String a), Maybe (Either String a), Maybe (Either String a), Maybe (Either String a))
98+runResultStream (ResultStream k) = k $ \act -> (,,,) <$> act <*> act <*> act <*> act
99+
100+streamSpec :: Spec
101+streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
102+
103+ it "Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
104+ Right res <- runClient getGetNL baseUrl
105+ let jra = Just (Right alice)
106+ jrb = Just (Right bob)
107+ runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
108+
109+ it "Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
110+ Right res <- runClient getGetNS baseUrl
111+ let jra = Just (Right alice)
112+ jrb = Just (Right bob)
113+ runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
servant/src/Servant/API.hs+4−2
@@ -83,13 +83,15 @@ import Servant.API.IsSecure (IsSecure (..))
8383import Servant.API.QueryParam (QueryFlag, QueryParam,
8484 QueryParams)
8585import Servant.API.Raw (Raw)
86−import Servant.API.Stream (Stream, StreamGenerator (..),
86+import Servant.API.Stream (Stream, StreamGet, StreamPost,
87+ StreamGenerator (..),
8788 ToStreamGenerator (..),
8889 ResultStream(..), BuildFromStream (..),
8990 ByteStringParser (..),
9091 FramingRender (..), BoundaryStrategy (..),
9192 FramingUnrender (..),
92− NewlineFraming)
93+ NewlineFraming,
94+ NetstringFraming)
9395import Servant.API.RemoteHost (RemoteHost)
9496import Servant.API.ReqBody (ReqBody)
9597import Servant.API.ResponseHeaders (AddHeader, addHeader, noHeader,
servant/src/Servant/API/Stream.hs+18−8
@@ -14,9 +14,12 @@ module Servant.API.Stream where
1414
1515import Data.ByteString.Lazy (ByteString, empty)
1616import qualified Data.ByteString.Lazy.Char8 as LB
17+import Data.Monoid ((<>))
1718import Data.Proxy (Proxy)
1819import Data.Typeable (Typeable)
1920import GHC.Generics (Generic)
21+import Text.Read (readMaybe)
22+import Data.Bifunctor (first)
2023import Network.HTTP.Types.Method (StdMethod (..))
2124
2225-- | A Stream endpoint for a given method emits a stream of encoded values at a given Content-Type, delimited by a framing strategy. Steam endpoints always return response code 200 on success. Type synonyms are provided for standard methods.
@@ -93,15 +96,22 @@ data NetstringFraming
9396
9497instance FramingRender NetstringFraming a where
9598 header _ _ = empty
96− boundary _ _ = BoundaryStrategyBracket $ \b -> (LB.pack . show . LB.length $ b, "")
99+ boundary _ _ = BoundaryStrategyBracket $ \b -> ((<> ":") . LB.pack . show . LB.length $ b, ",")
97100 trailer _ _ = empty
98101
99−{-
100102
101103instance FramingUnrender NetstringFraming a where
102− unrenderFrames _ _ = (, \b -> let (i,r) = LB.break (==':') b
103− in case readMaybe (LB.unpack i) of
104− Just len -> first Right $ LB.splitAt len . LB.drop 1 $ r
105− Nothing -> (Left ("Bad netstring frame, couldn't parse value as integer value: " ++ LB.unpack i), LB.drop 1 . LB.dropWhile (/= ',') $ r)
106− )
107−-}
104+ unrenderFrames _ _ = ByteStringParser (Just . (go,)) (go,)
105+ where go = ByteStringParser
106+ (\b -> let (i,r) = LB.break (==':') b
107+ in case readMaybe (LB.unpack i) of
108+ Just len -> if LB.length r > len
109+ then Just . first Right . fmap (LB.drop 1) $ LB.splitAt len . LB.drop 1 $ r
110+ else Nothing
111+ Nothing -> Just (Left ("Bad netstring frame, couldn't parse value as integer value: " ++ LB.unpack i), LB.drop 1 . LB.dropWhile (/= ',') $ r))
112+ (\b -> let (i,r) = LB.break (==':') b
113+ in case readMaybe (LB.unpack i) of
114+ Just len -> if LB.length r > len
115+ then first Right . fmap (LB.drop 1) $ LB.splitAt len . LB.drop 1 $ r
116+ else (Right $ LB.take len r, LB.empty)
117+ Nothing -> (Left ("Bad netstring frame, couldn't parse value as integer value: " ++ LB.unpack i), LB.drop 1 . LB.dropWhile (/= ',') $ r))