{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_HADDOCK not-home #-}
module Servant.API.Stream (
Stream,
StreamGet,
StreamPost,
StreamBody,
StreamBody',
SourceIO,
ToSourceIO (..),
FromSourceIO (..),
SourceToSourceIO (..),
FramingRender (..),
FramingUnrender (..),
NoFraming,
NewlineFraming,
NetstringFraming,
) where
import Control.Applicative
((<|>))
import Control.Monad.IO.Class
(MonadIO (..))
import qualified Data.Attoparsec.ByteString as A
import qualified Data.Attoparsec.ByteString.Char8 as A8
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LBS
import qualified Data.ByteString.Lazy.Char8 as LBS8
import Data.List.NonEmpty
(NonEmpty (..))
import Data.Monoid
((<>))
import Data.Proxy
(Proxy)
import Data.Typeable
(Typeable)
import GHC.Generics
(Generic)
import GHC.TypeLits
(Nat)
import Network.HTTP.Types.Method
(StdMethod (..))
import Servant.Types.SourceT
data Stream (method :: k1) (status :: Nat) (framing :: *) (contentType :: *) (a :: *)
deriving (Typeable, Generic)
type StreamGet = Stream 'GET 200
type StreamPost = Stream 'POST 200
type StreamBody = StreamBody' '[]
data StreamBody' (mods :: [*]) (framing :: *) (contentType :: *) (a :: *)
deriving (Typeable, Generic)
type SourceIO = SourceT IO
class ToSourceIO chunk a | a -> chunk where
toSourceIO :: a -> SourceIO chunk
class SourceToSourceIO m where
sourceToSourceIO :: SourceT m a -> SourceT IO a
instance SourceToSourceIO IO where
sourceToSourceIO = id
instance SourceToSourceIO m => ToSourceIO chunk (SourceT m chunk) where
toSourceIO = sourceToSourceIO
instance ToSourceIO a (NonEmpty a) where
toSourceIO (x :| xs) = fromStepT (Yield x (foldr Yield Stop xs))
instance ToSourceIO a [a] where
toSourceIO = source
class FromSourceIO chunk a | a -> chunk where
fromSourceIO :: SourceIO chunk -> a
instance MonadIO m => FromSourceIO a (SourceT m a) where
fromSourceIO = sourceFromSourceIO
sourceFromSourceIO :: forall m a. MonadIO m => SourceT IO a -> SourceT m a
sourceFromSourceIO src =
SourceT $ \k ->
k $ Effect $ liftIO $ unSourceT src (return . go)
where
go :: StepT IO a -> StepT m a
go Stop = Stop
go (Error err) = Error err
go (Skip s) = Skip (go s)
go (Effect ms) = Effect (liftIO (fmap go ms))
go (Yield x s) = Yield x (go s)
{-# NOINLINE [2] sourceFromSourceIO #-}
{-# RULES "sourceFromSourceIO @IO" sourceFromSourceIO = id :: SourceT IO a -> SourceT IO a #-}
class FramingRender strategy where
framingRender :: Monad m => Proxy strategy -> (a -> LBS.ByteString) -> SourceT m a -> SourceT m LBS.ByteString
class FramingUnrender strategy where
framingUnrender :: Monad m => Proxy strategy -> (LBS.ByteString -> Either String a) -> SourceT m BS.ByteString -> SourceT m a
data NoFraming
instance FramingRender NoFraming where
framingRender _ = fmap
instance FramingUnrender NoFraming where
framingUnrender _ f = mapStepT go
where
go Stop = Stop
go (Error err) = Error err
go (Skip s) = Skip (go s)
go (Effect ms) = Effect (fmap go ms)
go (Yield x s) = case f (LBS.fromStrict x) of
Right y -> Yield y (go s)
Left err -> Error err
data NewlineFraming
instance FramingRender NewlineFraming where
framingRender _ f = fmap (\x -> f x <> "\n")
instance FramingUnrender NewlineFraming where
framingUnrender _ f = transformWithAtto $ do
bs <- A.takeWhile (/= 10)
() <$ A.word8 10 <|> A.endOfInput
either fail pure (f (LBS.fromStrict bs))
data NetstringFraming
instance FramingRender NetstringFraming where
framingRender _ f = fmap $ \x ->
let bs = f x
in LBS8.pack (show (LBS8.length bs)) <> ":" <> bs <> ","
instance FramingUnrender NetstringFraming where
framingUnrender _ f = transformWithAtto $ do
len <- A8.decimal
_ <- A8.char ':'
bs <- A.take len
_ <- A8.char ','
either fail pure (f (LBS.fromStrict bs))