1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE RankNTypes #-}
3 {-# OPTIONS_GHC -Wno-orphans #-}
4 module Symantic.HTTP.Pipes where
6 import Control.Arrow (first, right)
7 import Control.Monad (Monad(..))
8 import Control.Monad.Trans.Class (MonadTrans(..))
10 import Data.Either (Either(..))
11 import Data.Eq (Eq(..))
12 import Data.Function (($), (.))
13 import Data.Functor ((<$>))
14 import Data.Maybe (Maybe(..))
15 import Data.Ord (Ord(..))
16 import Data.Semigroup (Semigroup(..))
17 import Data.String (String, IsString(..))
18 import Data.Word (Word8)
19 import Prelude (fromIntegral, Num(..))
21 import Text.Show (Show(..))
22 import qualified Control.Monad.Classes as MC
23 import qualified Data.ByteString as BS
24 import qualified Data.ByteString.Lazy as BSL
25 import qualified Data.ByteString.Lazy.Char8 as BSL8
26 import qualified Data.Char as Char
27 import qualified Data.List as List
28 import qualified Lens.Family as Lens
29 import qualified Lens.Family.State.Strict as Lens
30 import qualified Pipes as P
31 import qualified Pipes.ByteString as Pbs
32 import qualified Pipes.Group as Pg
33 import qualified Pipes.Parse as Pp
34 import qualified Pipes.Safe as Ps
36 import Symantic.HTTP.API
38 instance IsString () where
41 -- | Pass any executable effect to the underlying 'Monad'.
42 type instance MC.CanDo (P.Effect m) (MC.EffExec w) = 'False
43 -- | Pass any executable effect to the underlying 'Monad'.
44 type instance MC.CanDo (P.Proxy a' a b' b m) (MC.EffExec w) = 'False
46 type instance FramingMonad (P.Producer a m r) = m
47 type instance FramingYield (P.Producer a m r) = a
48 type instance FramingReturn (P.Producer a m r) = r
50 type instance FramingMonad (P.ListT m a) = m
51 type instance FramingYield (P.ListT m a) = a
52 type instance FramingReturn (P.ListT m a) = ()
54 -- | Produce 'BS.ByteString' from a 'Monad'.
58 m BS.ByteString -> P.Producer' BS.ByteString m r
70 instance FramingEncode NoFraming (P.Producer a IO r) where
71 framingEncode _framing mimeEnc p =
72 right (first mimeEnc) <$> P.next p
73 instance FramingEncode NoFraming (P.Producer a (Ps.SafeT IO) r) where
74 framingEncode _framing mimeEnc p =
75 right (first mimeEnc) <$> Ps.runSafeT (P.next p)
76 instance FramingEncode NoFraming (P.ListT IO a) where
77 framingEncode _framing mimeEnc p =
78 right (\(a,n) -> (mimeEnc a, P.Select n)) <$> P.next (P.enumerate p)
79 instance IsString r => FramingDecode NoFraming (P.Producer a m r) where
80 framingDecode _framing mimeDec mbs =
82 produceBS mbs P.>-> go
85 case mimeDec $ BSL.fromStrict bs of
86 Left err -> return $ fromString err
87 Right a -> P.yield a >> go
90 -- TODO: see how to use Pbs._unlines?
91 instance FramingEncode NewlineFraming (P.Producer a IO r) where
92 framingEncode _framing mimeEnc p =
93 right (first (newlineEncode mimeEnc))
95 instance FramingEncode NewlineFraming (P.Producer a (Ps.SafeT IO) r) where
96 framingEncode _framing mimeEnc p =
97 right (first (newlineEncode mimeEnc))
98 <$> Ps.runSafeT (P.next p)
99 instance IsString r => FramingDecode NewlineFraming (P.Producer a m r) where
100 framingDecode _framing mimeDec mbs =
103 (\p -> P.for p $ \bs ->
104 case mimeDec $ BSL.fromStrict bs of
105 Left _err -> return ()
106 Right a -> P.yield a) $
107 Lens.view Pbs.lines $
110 newlineEncode :: (a -> BSL.ByteString) -> a -> BSL.ByteString
111 newlineEncode mimeEnc a = mimeEnc a <> BSL.singleton (fromIntegral (Char.ord '\n'))
113 instance FramingEncode NetstringFraming (P.Producer a IO r) where
114 framingEncode _framing mimeEnc p =
115 right (first (encodeNetstring mimeEnc))
117 instance FramingEncode NetstringFraming (P.Producer a (Ps.SafeT IO) r) where
118 framingEncode _framing mimeEnc p =
119 right (first (encodeNetstring mimeEnc))
120 <$> Ps.runSafeT (P.next p)
121 instance IsString r => FramingDecode NetstringFraming (P.Producer a m r) where
122 framingDecode _framing mimeDec mbs =
125 (Pp.execStateT $ decodeNetstring @r mimeDec)
128 digit0, digit9 :: Word8
129 colon, comma :: Word8
131 digit0 = fromIntegral (Char.ord '0')
132 digit9 = fromIntegral (Char.ord '9')
133 colon = fromIntegral (Char.ord ':')
134 comma = fromIntegral (Char.ord ',')
135 newline = fromIntegral (Char.ord '\n')
137 encodeNetstring :: (a -> BSL.ByteString) -> a -> BSL.ByteString
138 encodeNetstring mimeEnc a =
139 let bs = mimeEnc a in
140 BSL8.pack (show (BSL8.length bs))
146 (BSL.ByteString -> Either String a) ->
147 ParserP BS.ByteString a m r
148 decodeNetstring mimeDec = do
149 lenBSs <- Lens.zoom (Pbs.span (\b -> digit0 <= b && b <= digit9)) drawAllP
150 case lenBSs >>= BS.unpack of
151 [] -> return "empty length"
152 w0:_:_ | w0 == digit0 -> return "leading zero"
154 let len = List.foldl' (\acc d -> acc * 10 + (fromIntegral d - digit0)) 0 lenWs
156 if colonW /= Just colon
157 then return "colon expected"
159 -- TODO: make mimeDecode directly able to use Pipes?
160 dataBS <- BSL.fromChunks <$> Lens.zoom (Pbs.splitAt len) drawAllP
162 if commaW /= Just comma
163 then return "comma expected"
165 case mimeDec dataBS of
166 Left err -> return $ fromString err
169 decodeNetstring mimeDec
173 -- | A 'P.Parser', which is itself a 'P.Producer',
174 -- and thus can 'yieldP' immediately.
175 type ParserP inp out m r =
176 forall x. Pp.StateT (P.Producer inp m x) (P.Producer out m) r
178 yieldP :: Monad m => out -> ParserP inp out m ()
179 yieldP = lift . P.yield
181 drawP :: Monad m => ParserP inp out m (Maybe inp)
182 drawP = P.hoist lift Pp.draw
184 drawAllP :: Monad m => ParserP inp out m [inp]
185 drawAllP = P.hoist lift Pp.drawAll
187 drawByteP :: Monad m => ParserP BS.ByteString out m (Maybe Word8)
188 drawByteP = P.hoist lift Pbs.drawByte
190 unDrawP :: Monad m => inp -> ParserP inp out m ()
191 unDrawP = P.hoist lift . Pp.unDraw
193 -- | @'parseMany f'@ groups a 'P.Producer' of 'BS.ByteString's
194 -- into a series of 'P.Producer's delimited by f,
195 -- where the delimiter is dropped
198 (P.Producer a m r -> P.Producer b m (P.Producer a m r)) ->
200 Pg.FreeT (P.Producer b m) m r
201 parseMany f = Pg.FreeT . go0
205 Left r -> return (Pg.Pure r)
206 Right (bs, p') -> return $ Pg.Free (go1 (P.yield bs >> p'))
207 go1 p = Pg.FreeT . go0 <$> f p
211 -- | Package agnostic lens.
212 type Lens' a b = forall f . Functor f => (b -> f b) -> (a -> f a)
213 (^.) :: a -> ((b -> Constant b b) -> (a -> Constant b a)) -> b
214 a ^. lens = getConstant (lens Constant a)