symantic-http-pipes-0.0.0.20190324: Symantic/HTTP/Pipes.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Symantic.HTTP.Pipes where
import Control.Arrow (first, right)
import Control.Monad (Monad(..))
import Control.Monad.Trans.Class (MonadTrans(..))
import Data.Bool
import Data.Either (Either(..))
import Data.Eq (Eq(..))
import Data.Function (($), (.))
import Data.Functor ((<$>))
import Data.Maybe (Maybe(..))
import Data.Ord (Ord(..))
import Data.Semigroup (Semigroup(..))
import Data.String (String, IsString(..))
import Data.Word (Word8)
import Prelude (fromIntegral, Num(..))
import System.IO (IO)
import Text.Show (Show(..))
import qualified Control.Monad.Classes as MC
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as BSL8
import qualified Data.Char as Char
import qualified Data.List as List
import qualified Lens.Family as Lens
import qualified Lens.Family.State.Strict as Lens
import qualified Pipes as P
import qualified Pipes.ByteString as Pbs
import qualified Pipes.Group as Pg
import qualified Pipes.Parse as Pp
import qualified Pipes.Safe as Ps
import Symantic.HTTP.API
instance IsString () where
fromString _ = ()
-- | Pass any executable effect to the underlying 'Monad'.
type instance MC.CanDo (P.Effect m) (MC.EffExec w) = 'False
-- | Pass any executable effect to the underlying 'Monad'.
type instance MC.CanDo (P.Proxy a' a b' b m) (MC.EffExec w) = 'False
type instance FramingMonad (P.Producer a m r) = m
type instance FramingYield (P.Producer a m r) = a
type instance FramingReturn (P.Producer a m r) = r
type instance FramingMonad (P.ListT m a) = m
type instance FramingYield (P.ListT m a) = a
type instance FramingReturn (P.ListT m a) = ()
-- | Produce 'BS.ByteString' from a 'Monad'.
produceBS ::
IsString r =>
Monad m =>
m BS.ByteString -> P.Producer' BS.ByteString m r
produceBS mbs = go
where
go = do
bs <- lift mbs
if BS.null bs
then return ""
else do
P.yield bs
go
-- * 'NoFraming'
instance FramingEncode NoFraming (P.Producer a IO r) where
framingEncode _framing mimeEnc p =
right (first mimeEnc) <$> P.next p
instance FramingEncode NoFraming (P.Producer a (Ps.SafeT IO) r) where
framingEncode _framing mimeEnc p =
right (first mimeEnc) <$> Ps.runSafeT (P.next p)
instance FramingEncode NoFraming (P.ListT IO a) where
framingEncode _framing mimeEnc p =
right (\(a,n) -> (mimeEnc a, P.Select n)) <$> P.next (P.enumerate p)
instance IsString r => FramingDecode NoFraming (P.Producer a m r) where
framingDecode _framing mimeDec mbs =
-- TODO: use drawAll
produceBS mbs P.>-> go
where go = do
bs <- P.await
case mimeDec $ BSL.fromStrict bs of
Left err -> return $ fromString err
Right a -> P.yield a >> go
-- * 'NewlineFraming'
-- TODO: see how to use Pbs._unlines?
instance FramingEncode NewlineFraming (P.Producer a IO r) where
framingEncode _framing mimeEnc p =
right (first (newlineEncode mimeEnc))
<$> P.next p
instance FramingEncode NewlineFraming (P.Producer a (Ps.SafeT IO) r) where
framingEncode _framing mimeEnc p =
right (first (newlineEncode mimeEnc))
<$> Ps.runSafeT (P.next p)
instance IsString r => FramingDecode NewlineFraming (P.Producer a m r) where
framingDecode _framing mimeDec mbs =
Pg.concats $
Pg.maps
(\p -> P.for p $ \bs ->
case mimeDec $ BSL.fromStrict bs of
Left _err -> return ()
Right a -> P.yield a) $
Lens.view Pbs.lines $
produceBS mbs
newlineEncode :: (a -> BSL.ByteString) -> a -> BSL.ByteString
newlineEncode mimeEnc a = mimeEnc a <> BSL.singleton (fromIntegral (Char.ord '\n'))
instance FramingEncode NetstringFraming (P.Producer a IO r) where
framingEncode _framing mimeEnc p =
right (first (encodeNetstring mimeEnc))
<$> P.next p
instance FramingEncode NetstringFraming (P.Producer a (Ps.SafeT IO) r) where
framingEncode _framing mimeEnc p =
right (first (encodeNetstring mimeEnc))
<$> Ps.runSafeT (P.next p)
instance IsString r => FramingDecode NetstringFraming (P.Producer a m r) where
framingDecode _framing mimeDec mbs =
Pg.concats $
parseMany
(Pp.execStateT $ decodeNetstring @r mimeDec)
(produceBS mbs)
digit0, digit9 :: Word8
colon, comma :: Word8
newline :: Word8
digit0 = fromIntegral (Char.ord '0')
digit9 = fromIntegral (Char.ord '9')
colon = fromIntegral (Char.ord ':')
comma = fromIntegral (Char.ord ',')
newline = fromIntegral (Char.ord '\n')
encodeNetstring :: (a -> BSL.ByteString) -> a -> BSL.ByteString
encodeNetstring mimeEnc a =
let bs = mimeEnc a in
BSL8.pack (show (BSL8.length bs))
<> ":" <> bs <> ","
decodeNetstring ::
IsString r =>
Monad m =>
(BSL.ByteString -> Either String a) ->
ParserP BS.ByteString a m r
decodeNetstring mimeDec = do
lenBSs <- Lens.zoom (Pbs.span (\b -> digit0 <= b && b <= digit9)) drawAllP
case lenBSs >>= BS.unpack of
[] -> return "empty length"
w0:_:_ | w0 == digit0 -> return "leading zero"
lenWs -> do
let len = List.foldl' (\acc d -> acc * 10 + (fromIntegral d - digit0)) 0 lenWs
colonW <- drawByteP
if colonW /= Just colon
then return "colon expected"
else do
-- TODO: make mimeDecode directly able to use Pipes?
dataBS <- BSL.fromChunks <$> Lens.zoom (Pbs.splitAt len) drawAllP
commaW <- drawByteP
if commaW /= Just comma
then return "comma expected"
else do
case mimeDec dataBS of
Left err -> return $ fromString err
Right a -> do
yieldP a
decodeNetstring mimeDec
-- * Type 'P.Parser'
-- | A 'P.Parser', which is itself a 'P.Producer',
-- and thus can 'yieldP' immediately.
type ParserP inp out m r =
forall x. Pp.StateT (P.Producer inp m x) (P.Producer out m) r
yieldP :: Monad m => out -> ParserP inp out m ()
yieldP = lift . P.yield
drawP :: Monad m => ParserP inp out m (Maybe inp)
drawP = P.hoist lift Pp.draw
drawAllP :: Monad m => ParserP inp out m [inp]
drawAllP = P.hoist lift Pp.drawAll
drawByteP :: Monad m => ParserP BS.ByteString out m (Maybe Word8)
drawByteP = P.hoist lift Pbs.drawByte
unDrawP :: Monad m => inp -> ParserP inp out m ()
unDrawP = P.hoist lift . Pp.unDraw
-- | @'parseMany f'@ groups a 'P.Producer' of 'BS.ByteString's
-- into a series of 'P.Producer's delimited by f,
-- where the delimiter is dropped
parseMany ::
Monad m =>
(P.Producer a m r -> P.Producer b m (P.Producer a m r)) ->
P.Producer a m r ->
Pg.FreeT (P.Producer b m) m r
parseMany f = Pg.FreeT . go0
where
go0 p = do
P.next p >>= \case
Left r -> return (Pg.Pure r)
Right (bs, p') -> return $ Pg.Free (go1 (P.yield bs >> p'))
go1 p = Pg.FreeT . go0 <$> f p
{-
-- * Type |Lens'|
-- | Package agnostic lens.
type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)
(^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b
a ^. lens = getConstant (lens Constant a)
-}