add tests, fix to make tests work
This commit is contained in:
parent
0c77a2b4b0
commit
38e87397e7
6 changed files with 143 additions and 15 deletions
|
@ -267,7 +267,7 @@ instance OVERLAPPABLE_
|
||||||
}
|
}
|
||||||
return . buildFromStream $ ResultStream $ \k ->
|
return . buildFromStream $ ResultStream $ \k ->
|
||||||
runStreamingResponse sresp $ \(status,_headers,_httpversion,reader) -> do
|
runStreamingResponse sresp $ \(status,_headers,_httpversion,reader) -> do
|
||||||
when (H.statusCode status /= 200) $ error "bad status" --fixme
|
when (H.statusCode status /= 200) $ error "bad status" -- TODO fixme
|
||||||
let unrender = unrenderFrames (Proxy :: Proxy framing) (Proxy :: Proxy a)
|
let unrender = unrenderFrames (Proxy :: Proxy framing) (Proxy :: Proxy a)
|
||||||
loop bs = do
|
loop bs = do
|
||||||
res <- BL.fromStrict <$> reader
|
res <- BL.fromStrict <$> reader
|
||||||
|
@ -283,9 +283,11 @@ instance OVERLAPPABLE_
|
||||||
res <- BL.fromStrict <$> reader
|
res <- BL.fromStrict <$> reader
|
||||||
let addIsEmptyInfo (a, r) = (r, (a, BL.null r && BL.null res))
|
let addIsEmptyInfo (a, r) = (r, (a, BL.null r && BL.null res))
|
||||||
if BL.null res
|
if BL.null res
|
||||||
then return . addIsEmptyInfo $ parseEOF frameParser res
|
then if BL.null bs
|
||||||
|
then return ("", (Right "", True))
|
||||||
|
else return . addIsEmptyInfo $ parseEOF frameParser bs
|
||||||
else let sofar = (bs <> res)
|
else let sofar = (bs <> res)
|
||||||
in case parseIncremental frameParser res of
|
in case parseIncremental frameParser sofar of
|
||||||
Just x -> return $ addIsEmptyInfo x
|
Just x -> return $ addIsEmptyInfo x
|
||||||
Nothing -> frameLoop sofar
|
Nothing -> frameLoop sofar
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@ test-suite spec
|
||||||
main-is: Spec.hs
|
main-is: Spec.hs
|
||||||
other-modules:
|
other-modules:
|
||||||
Servant.ClientSpec
|
Servant.ClientSpec
|
||||||
|
Servant.StreamSpec
|
||||||
build-depends:
|
build-depends:
|
||||||
base == 4.*
|
base == 4.*
|
||||||
, aeson
|
, aeson
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
||||||
|
|
||||||
#include "overlapping-compat.h"
|
#include "overlapping-compat.h"
|
||||||
module Servant.ClientSpec (spec) where
|
module Servant.ClientSpec (spec, Person(..), startWaiApp, endWaiApp) where
|
||||||
|
|
||||||
import Prelude ()
|
import Prelude ()
|
||||||
import Prelude.Compat
|
import Prelude.Compat
|
||||||
|
|
113
servant-client/test/Servant/StreamSpec.hs
Normal file
113
servant-client/test/Servant/StreamSpec.hs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
{-# LANGUAGE ConstraintKinds #-}
|
||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE FunctionalDependencies #-}
|
||||||
|
{-# LANGUAGE GADTs #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE PolyKinds #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
|
{-# LANGUAGE TypeFamilies #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
#if __GLASGOW_HASKELL__ >= 800
|
||||||
|
{-# OPTIONS_GHC -freduction-depth=100 #-}
|
||||||
|
#else
|
||||||
|
{-# OPTIONS_GHC -fcontext-stack=100 #-}
|
||||||
|
#endif
|
||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
||||||
|
|
||||||
|
#include "overlapping-compat.h"
|
||||||
|
module Servant.StreamSpec (spec) where
|
||||||
|
|
||||||
|
import Prelude ()
|
||||||
|
import Prelude.Compat
|
||||||
|
import Data.Proxy
|
||||||
|
import qualified Network.HTTP.Client as C
|
||||||
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
|
import Test.Hspec
|
||||||
|
|
||||||
|
import Servant.API ((:<|>) ((:<|>)),
|
||||||
|
(:>),
|
||||||
|
EmptyAPI, JSON,
|
||||||
|
StreamGet,
|
||||||
|
NewlineFraming,
|
||||||
|
NetstringFraming,
|
||||||
|
ResultStream(..),
|
||||||
|
StreamGenerator(..))
|
||||||
|
import Servant.Client
|
||||||
|
import Servant.Server
|
||||||
|
import qualified Servant.ClientSpec as CS
|
||||||
|
import Servant.ClientSpec (Person(..))
|
||||||
|
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = describe "Servant.Stream" $ do
|
||||||
|
streamSpec
|
||||||
|
|
||||||
|
type StreamApi f =
|
||||||
|
"streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
|
||||||
|
:<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
|
||||||
|
:<|> EmptyAPI
|
||||||
|
|
||||||
|
|
||||||
|
capi :: Proxy (StreamApi ResultStream)
|
||||||
|
capi = Proxy
|
||||||
|
|
||||||
|
sapi :: Proxy (StreamApi StreamGenerator)
|
||||||
|
sapi = Proxy
|
||||||
|
|
||||||
|
|
||||||
|
getGetNL :<|> getGetNS :<|> EmptyClient = client capi
|
||||||
|
|
||||||
|
|
||||||
|
getGetNL :: ClientM (ResultStream Person)
|
||||||
|
getGetNS :: ClientM (ResultStream Person)
|
||||||
|
|
||||||
|
alice :: Person
|
||||||
|
alice = Person "Alice" 42
|
||||||
|
|
||||||
|
bob :: Person
|
||||||
|
bob = Person "Bob" 25
|
||||||
|
|
||||||
|
server :: Application
|
||||||
|
server = serve sapi (
|
||||||
|
(return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
||||||
|
:: Handler (StreamGenerator Person))
|
||||||
|
:<|>
|
||||||
|
(return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
|
||||||
|
:: Handler (StreamGenerator Person))
|
||||||
|
:<|>
|
||||||
|
emptyServer)
|
||||||
|
|
||||||
|
|
||||||
|
{-# NOINLINE manager' #-}
|
||||||
|
manager' :: C.Manager
|
||||||
|
manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
|
||||||
|
|
||||||
|
runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
|
||||||
|
runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl')
|
||||||
|
|
||||||
|
runResultStream :: ResultStream a -> IO (Maybe (Either String a), Maybe (Either String a), Maybe (Either String a), Maybe (Either String a))
|
||||||
|
runResultStream (ResultStream k) = k $ \act -> (,,,) <$> act <*> act <*> act <*> act
|
||||||
|
|
||||||
|
streamSpec :: Spec
|
||||||
|
streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
|
||||||
|
|
||||||
|
it "Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
|
||||||
|
Right res <- runClient getGetNL baseUrl
|
||||||
|
let jra = Just (Right alice)
|
||||||
|
jrb = Just (Right bob)
|
||||||
|
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
||||||
|
|
||||||
|
it "Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
|
||||||
|
Right res <- runClient getGetNS baseUrl
|
||||||
|
let jra = Just (Right alice)
|
||||||
|
jrb = Just (Right bob)
|
||||||
|
runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
|
|
@ -83,13 +83,15 @@ import Servant.API.IsSecure (IsSecure (..))
|
||||||
import Servant.API.QueryParam (QueryFlag, QueryParam,
|
import Servant.API.QueryParam (QueryFlag, QueryParam,
|
||||||
QueryParams)
|
QueryParams)
|
||||||
import Servant.API.Raw (Raw)
|
import Servant.API.Raw (Raw)
|
||||||
import Servant.API.Stream (Stream, StreamGenerator (..),
|
import Servant.API.Stream (Stream, StreamGet, StreamPost,
|
||||||
|
StreamGenerator (..),
|
||||||
ToStreamGenerator (..),
|
ToStreamGenerator (..),
|
||||||
ResultStream(..), BuildFromStream (..),
|
ResultStream(..), BuildFromStream (..),
|
||||||
ByteStringParser (..),
|
ByteStringParser (..),
|
||||||
FramingRender (..), BoundaryStrategy (..),
|
FramingRender (..), BoundaryStrategy (..),
|
||||||
FramingUnrender (..),
|
FramingUnrender (..),
|
||||||
NewlineFraming)
|
NewlineFraming,
|
||||||
|
NetstringFraming)
|
||||||
import Servant.API.RemoteHost (RemoteHost)
|
import Servant.API.RemoteHost (RemoteHost)
|
||||||
import Servant.API.ReqBody (ReqBody)
|
import Servant.API.ReqBody (ReqBody)
|
||||||
import Servant.API.ResponseHeaders (AddHeader, addHeader, noHeader,
|
import Servant.API.ResponseHeaders (AddHeader, addHeader, noHeader,
|
||||||
|
|
|
@ -14,9 +14,12 @@ module Servant.API.Stream where
|
||||||
|
|
||||||
import Data.ByteString.Lazy (ByteString, empty)
|
import Data.ByteString.Lazy (ByteString, empty)
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
|
import Data.Monoid ((<>))
|
||||||
import Data.Proxy (Proxy)
|
import Data.Proxy (Proxy)
|
||||||
import Data.Typeable (Typeable)
|
import Data.Typeable (Typeable)
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
import Data.Bifunctor (first)
|
||||||
import Network.HTTP.Types.Method (StdMethod (..))
|
import Network.HTTP.Types.Method (StdMethod (..))
|
||||||
|
|
||||||
-- | 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.
|
-- | 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
|
||||||
|
|
||||||
instance FramingRender NetstringFraming a where
|
instance FramingRender NetstringFraming a where
|
||||||
header _ _ = empty
|
header _ _ = empty
|
||||||
boundary _ _ = BoundaryStrategyBracket $ \b -> (LB.pack . show . LB.length $ b, "")
|
boundary _ _ = BoundaryStrategyBracket $ \b -> ((<> ":") . LB.pack . show . LB.length $ b, ",")
|
||||||
trailer _ _ = empty
|
trailer _ _ = empty
|
||||||
|
|
||||||
{-
|
|
||||||
|
|
||||||
instance FramingUnrender NetstringFraming a where
|
instance FramingUnrender NetstringFraming a where
|
||||||
unrenderFrames _ _ = (, \b -> let (i,r) = LB.break (==':') b
|
unrenderFrames _ _ = ByteStringParser (Just . (go,)) (go,)
|
||||||
|
where go = ByteStringParser
|
||||||
|
(\b -> let (i,r) = LB.break (==':') b
|
||||||
in case readMaybe (LB.unpack i) of
|
in case readMaybe (LB.unpack i) of
|
||||||
Just len -> first Right $ LB.splitAt len . LB.drop 1 $ r
|
Just len -> if LB.length r > len
|
||||||
Nothing -> (Left ("Bad netstring frame, couldn't parse value as integer value: " ++ LB.unpack i), LB.drop 1 . LB.dropWhile (/= ',') $ r)
|
then Just . first Right . fmap (LB.drop 1) $ LB.splitAt len . LB.drop 1 $ r
|
||||||
)
|
else Nothing
|
||||||
-}
|
Nothing -> Just (Left ("Bad netstring frame, couldn't parse value as integer value: " ++ LB.unpack i), LB.drop 1 . LB.dropWhile (/= ',') $ r))
|
||||||
|
(\b -> let (i,r) = LB.break (==':') b
|
||||||
|
in case readMaybe (LB.unpack i) of
|
||||||
|
Just len -> if LB.length r > len
|
||||||
|
then first Right . fmap (LB.drop 1) $ LB.splitAt len . LB.drop 1 $ r
|
||||||
|
else (Right $ LB.take len r, LB.empty)
|
||||||
|
Nothing -> (Left ("Bad netstring frame, couldn't parse value as integer value: " ++ LB.unpack i), LB.drop 1 . LB.dropWhile (/= ',') $ r))
|
||||||
|
|
Loading…
Reference in a new issue