@@ -1,23 +1,48 @@
1−{-# LANGUAGE DataKinds #-}
1+{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3−{-# LANGUAGE DeriveGeneric #-}
3+{-# LANGUAGE DeriveGeneric #-}
4−{-# LANGUAGE FlexibleInstances #-}
4+{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FunctionalDependencies #-}
5{-# LANGUAGE FunctionalDependencies #-}
6−{-# LANGUAGE KindSignatures #-}
6+{-# LANGUAGE KindSignatures #-}
7−{-# LANGUAGE MultiParamTypeClasses #-}
7+{-# LANGUAGE MultiParamTypeClasses #-}
8−{-# LANGUAGE OverloadedStrings #-}
8+{-# LANGUAGE OverloadedStrings #-}
9−{-# LANGUAGE PolyKinds #-}
9+{-# LANGUAGE PolyKinds #-}
10−{-# LANGUAGE RankNTypes #-}
10+{-# LANGUAGE RankNTypes #-}
11−{-# LANGUAGE TupleSections #-}
11+{-# LANGUAGE TupleSections #-}
12{-# OPTIONS_HADDOCK not-home #-}
12{-# OPTIONS_HADDOCK not-home #-}
14−module Servant.API.Stream where
14+module Servant.API.Stream (
20+ -- | Both 'StreamGenerator' and 'ResultStream' are equivalent
21+ -- to some *source* in streaming libraries.
22+ StreamGenerator (..),
23+ ToStreamGenerator (..),
25+ FromResultStream (..),
28+ FramingUnrender (..),
29+ BoundaryStrategy (..),
30+ ByteStringParser (..),
16import Control.Arrow
37import Control.Arrow
18import Data.ByteString.Lazy
39import Data.ByteString.Lazy
19 (ByteString, empty)
40 (ByteString, empty)
20import qualified Data.ByteString.Lazy.Char8 as LB
41import qualified Data.ByteString.Lazy.Char8 as LB
44+import Data.List.NonEmpty
21import Data.Monoid
46import Data.Monoid
23import Data.Proxy
48import Data.Proxy
@@ -30,35 +55,82 @@ import GHC.TypeLits
31import Network.HTTP.Types.Method
56import Network.HTTP.Types.Method
32 (StdMethod (..))
57 (StdMethod (..))
58+import System.IO.Unsafe
33import Text.Read
60import Text.Read
34 (readMaybe)
61 (readMaybe)
63+-- | A Stream endpoint for a given method emits a stream of encoded values at a
36−-- | A Stream endpoint for a given method emits a stream of encoded values at a given Content-Type, delimited by a framing strategy. Stream endpoints always return response code 200 on success. Type synonyms are provided for standard methods.
64+-- given Content-Type, delimited by a framing strategy. Stream endpoints always
65+-- return response code 200 on success. Type synonyms are provided for standard
37data Stream (method :: k1) (status :: Nat) (framing :: *) (contentType :: *) (a :: *)
67data Stream (method :: k1) (status :: Nat) (framing :: *) (contentType :: *) (a :: *)
38 deriving (Typeable, Generic)
68 deriving (Typeable, Generic)
40type StreamGet = Stream 'GET 200
70type StreamGet = Stream 'GET 200
41type StreamPost = Stream 'POST 200
71type StreamPost = Stream 'POST 200
73+-- | Stream endpoints may be implemented as producing a @StreamGenerator@ a
74+-- function that itself takes two emit functions the first to be used on the
75+-- first value the stream emits, and the second to be used on all subsequent
43−-- | Stream endpoints may be implemented as producing a @StreamGenerator@ -- a function that itself takes two emit functions -- the first to be used on the first value the stream emits, and the second to be used on all subsequent values (to allow interspersed framing strategies such as comma separation).
76+-- values (to allow interspersed framing strategies such as comma separation).
44−newtype StreamGenerator a = StreamGenerator {getStreamGenerator :: (a -> IO ()) -> (a -> IO ()) -> IO ()}
77+newtype StreamGenerator a = StreamGenerator { getStreamGenerator :: (a -> IO ()) -> (a -> IO ()) -> IO () }
46-- | ToStreamGenerator is intended to be implemented for types such as Conduit, Pipe, etc. By implementing this class, all such streaming abstractions can be used directly as endpoints.
79-- | ToStreamGenerator is intended to be implemented for types such as Conduit, Pipe, etc. By implementing this class, all such streaming abstractions can be used directly as endpoints.
47class ToStreamGenerator a b | a -> b where
80class ToStreamGenerator a b | a -> b where
48− toStreamGenerator :: a -> StreamGenerator b
81+ toStreamGenerator :: a -> StreamGenerator b
50−instance ToStreamGenerator (StreamGenerator a) a
83+instance ToStreamGenerator (StreamGenerator a) a where
84+ toStreamGenerator x = x
86+instance ToStreamGenerator (NonEmpty a) a where
87+ toStreamGenerator (x :| xs) = StreamGenerator $ \f g -> f x >> traverse_ g xs
89+instance ToStreamGenerator [a] a where
90+ toStreamGenerator [] = StreamGenerator $ \_ _ -> return ()
51− where toStreamGenerator x = x
91+ toStreamGenerator (x : xs) = StreamGenerator $ \f g -> f x >> traverse_ g xs
53−-- | Clients reading from streaming endpoints can be implemented as producing a @ResultStream@ that captures the setup, takedown, and incremental logic for a read, being an IO continuation that takes a producer of Just either values or errors that terminates with a Nothing.
93+-- | Clients reading from streaming endpoints can be implemented as producing a
94+-- @ResultStream@ that captures the setup, takedown, and incremental logic for
95+-- a read, being an IO continuation that takes a producer of Just either values
96+-- or errors that terminates with a Nothing.
54−newtype ResultStream a = ResultStream (forall b. (IO (Maybe (Either String a)) -> IO b) -> IO b)
97+newtype ResultStream a = ResultStream { runResultStream :: forall b. (IO (Maybe (Either String a)) -> IO b) -> IO b }
56−-- | BuildFromStream is intended to be implemented for types such as Conduit, Pipe, etc. By implementing this class, all such streaming abstractions can be used directly on the client side for talking to streaming endpoints.
99+-- | FromResultStream is intended to be implemented for types such as Conduit, Pipe, etc. By implementing this class, all such streaming abstractions can be used directly on the client side for talking to streaming endpoints.
57−class BuildFromStream a b where
100+class FromResultStream a b | b -> a where
58− buildFromStream :: ResultStream a -> b
101+ fromResultStream :: ResultStream a -> IO b
60−instance BuildFromStream a (ResultStream a)
103+instance FromResultStream a (ResultStream a) where
61− where buildFromStream x = x
104+ fromResultStream = return
106+-- | Uses 'unsafeInterleaveIO'
107+instance FromResultStream a [a] where
108+ fromResultStream x = runResultStream x lazyRead
110+-- | Uses 'unsafeInterleaveIO'
111+instance FromResultStream a (NonEmpty a) where
112+ fromResultStream x = runResultStream x $ \r -> do
115+ Nothing -> fail "Empty stream"
116+ Just (Left er) -> fail er
117+ Just (Right y) -> do
121+lazyRead :: IO (Maybe (Either String a)) -> IO [a]
124+ go = unsafeInterleaveIO loop
129+ Nothing -> return []
130+ Just (Left er) -> fail er
131+ Just (Right y) -> do
63-- | The FramingRender class provides the logic for emitting a framing strategy. The strategy emits a header, followed by boundary-delimited data, and finally a termination character. For many strategies, some of these will just be empty bytestrings.
135-- | The FramingRender class provides the logic for emitting a framing strategy. The strategy emits a header, followed by boundary-delimited data, and finally a termination character. For many strategies, some of these will just be empty bytestrings.
64class FramingRender strategy a where
136class FramingRender strategy a where
@@ -74,10 +146,10 @@ data BoundaryStrategy = BoundaryStrategyBracket (ByteString -> (ByteString,ByteS
74 | BoundaryStrategyGeneral (ByteString -> ByteString)
146 | BoundaryStrategyGeneral (ByteString -> ByteString)
76-- | A type of parser that can never fail, and has different parsing strategies (incremental, or EOF) depending if more input can be sent. The incremental parser should return `Nothing` if it would like to be sent a longer ByteString. If it returns a value, it also returns the remainder following that value.
148-- | A type of parser that can never fail, and has different parsing strategies (incremental, or EOF) depending if more input can be sent. The incremental parser should return `Nothing` if it would like to be sent a longer ByteString. If it returns a value, it also returns the remainder following that value.
77−data ByteStringParser a = ByteStringParser {
149+data ByteStringParser a = ByteStringParser
78− parseIncremental :: ByteString -> Maybe (a, ByteString),
150+ { parseIncremental :: ByteString -> Maybe (a, ByteString)
79− parseEOF :: ByteString -> (a, ByteString)
151+ , parseEOF :: ByteString -> (a, ByteString)
82-- | The FramingUnrender class provides the logic for parsing a framing strategy. The outer @ByteStringParser@ strips the header from a stream of bytes, and yields a parser that can handle the remainder, stepwise. Each frame may be a ByteString, or a String indicating the error state for that frame. Such states are per-frame, so that protocols that can resume after errors are able to do so. Eventually this returns an empty ByteString to indicate termination.
154-- | The FramingUnrender class provides the logic for parsing a framing strategy. The outer @ByteStringParser@ strips the header from a stream of bytes, and yields a parser that can handle the remainder, stepwise. Each frame may be a ByteString, or a String indicating the error state for that frame. Such states are per-frame, so that protocols that can resume after errors are able to do so. Eventually this returns an empty ByteString to indicate termination.
83class FramingUnrender strategy a where
155class FramingUnrender strategy a where