packages feed

attoparsec-framer 0.1.0.0 → 0.1.0.1

raw patch · 4 files changed

+98/−31 lines, 4 filesdep ~bytestringPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: bytestring

API changes (from Hackage documentation)

+ Data.Attoparsec.Framer.Testing: linkedSrcAndSink :: [ByteString] -> IO (ByteSource IO, ByteString -> IO ())
+ Data.Attoparsec.Framer.Testing: linkedSrcAndSink' :: [ByteString] -> IO (ByteSource IO, ByteString -> IO ())
- Data.Attoparsec.Framer: mkFramer :: MonadThrow m => Parser a -> (a -> m ()) -> (Word32 -> m ByteString) -> Framer m a
+ Data.Attoparsec.Framer: mkFramer :: MonadThrow m => Parser frame -> (frame -> m ()) -> ByteSource m -> Framer m frame
- Data.Attoparsec.Framer: runFramer :: MonadThrow m => Framer m a -> m ()
+ Data.Attoparsec.Framer: runFramer :: MonadThrow m => Framer m frame -> m ()
- Data.Attoparsec.Framer: runOneFrame :: MonadThrow m => Maybe ByteString -> Framer m a -> m (Maybe ByteString, Bool)
+ Data.Attoparsec.Framer: runOneFrame :: MonadThrow m => Maybe ByteString -> Framer m frame -> m (Maybe ByteString, Bool)

Files

ChangeLog.md view
@@ -2,6 +2,9 @@  `attoparsec-framer` uses [PVP Versioning][1]. +## 0.1.0.1 -- 2023-07-19++* Adjust upper bound for ByteString to allow use of 0.12.0.0  ## 0.1.0.0 -- 2023-03-01 
attoparsec-framer.cabal view
@@ -1,11 +1,11 @@ cabal-version:      3.0 name:               attoparsec-framer-version:            0.1.0.0-synopsis:           Use Attoparsec to parse framed protocol bytestreams+version:            0.1.0.1+synopsis:           Use Attoparsec to parse framed protocol byte streams description:   A library that simplifies the use of   [Attoparsec](https://hackage.haskell.org/package/attoparsec) when processing-  framed protocol bytestreams.+  framed protocol byte streams.    As well as reading the haddocks, please take a look at a   [demo server](https://github.com/adetokunbo/attoparsec-framer/blob/main/toy/Server.hs)@@ -36,7 +36,7 @@   build-depends:     , attoparsec  >=0.14.4   && <0.15     , base        >=4.10     && <5-    , bytestring  >=0.10.8.2 && <0.12.0.0+    , bytestring  >=0.10.8.2 && <0.12.1.0     , exceptions  >= 0.10.4  && < 0.11     , text        >=1.2.3    && <2.2 @@ -55,7 +55,7 @@     , attoparsec-framer     , exceptions     , base               >=4.10     && <5-    , bytestring         >=0.10.8.2 && <0.12.0.0+    , bytestring         >=0.10.8.2 && <0.12.1.0     , network            >=3.1.0    && <4     , network-run        >=0.2.2    && <0.3     , text               >=1.2.3    && <2.2@@ -78,7 +78,7 @@     , attoparsec-binary  >=0.2      && <0.3     , attoparsec-framer     , base               >=4.10     && <5-    , bytestring         >=0.10.8.2 && <0.12.0.0+    , bytestring         >=0.10.8.2 && <0.12.1.0     , network            >=3.1.0    && <4     , network-run        >=0.2.2    && <0.3     , text               >=1.2.3    && <2.2
src/Data/Attoparsec/Framer.hs view
@@ -11,22 +11,21 @@  Provides the 'Framer' data type that combines an @Attoparsec 'A.Parser'@ with a a few additional combinators that allow the parser to be used to process frames-from the framed byte streams commonly used in network protocol implementations.+of the framed byte streams commonly used in network protocol implementations.  A @'Framer'@ specifies how the processing function @'runFramer'@ should parse a byte stream.  Minimally, a @Framer@ specifies -* An @'A.Parser'@, used to extract frames from the byte stream-* a @'FrameHandler'@ responsible using the parsed frames-* the bytestream source, represented a 'ByteSource'-+* a @'A.Parser'@, used to extract frames from the byte stream+* a @'FrameHandler'@ responsible for using the parsed frames+* the byte stream source, represented by a 'ByteSource' -@'runFramer'@ the 'FrameHandler' is invoked repeatedly; on each-invocation it returns a 'Progression', which indicates if processing should-continue. This makes it possible to terminate for the 'FrameHandler' to signal-that frame processing should terminate.+@'runFramer'@ reads chunks from the @ByteSource@, parses these into frames and+invokes the 'FrameHandler'. Each invocation returns a 'Progression', which+indicates if processing should continue. This allows the 'FrameHandler' to+trigger termination of 'runFramer'. -} module Data.Attoparsec.Framer (   -- * Framer@@ -71,7 +70,7 @@ type FrameHandler m frame = frame -> m Progression  --- | A byte stream from which chunks are to be repeatedly retrieved.+-- | A byte stream from which chunks are to be retrieved. type ByteSource m = Word32 -> m ByteString  @@ -83,7 +82,7 @@   deriving (Eq, Show)  --- | Use 'A.Parser' to parse a stream of @frames@ from a bytestream+-- | Uses a 'A.Parser' to parse a stream of @frames@ from a byte stream data Framer m frame = Framer   { framerChunkSize :: !Word32   , frameByteSource :: !(ByteSource m)@@ -94,8 +93,8 @@   }  -{- | Construct @'Framer'@ that will handle @frames@ repeatedly until a returned- @'Progression'@ stops it.+{- | Construct a @'Framer'@ that will handle @frames@ repeatedly until the+@FrameHandler@ returns a @'Progression'@ that stops it. -} mkFramer' ::   MonadThrow m =>@@ -114,13 +113,16 @@     }  --- | Construct @'Framer'@ that loops continuously.+-- | Construct a @'Framer'@ that loops continuously. mkFramer ::   MonadThrow m =>-  A.Parser a ->-  (a -> m ()) ->-  (Word32 -> m ByteString) ->-  Framer m a+  -- | parses frames from the byte stream+  A.Parser frame ->+  -- | handles parsed frames+  (frame -> m ()) ->+  -- | obtains the next chunk from the byte stream+  ByteSource m ->+  Framer m frame mkFramer parser onFrame fetchBytes =   let onFrameContinue x = do         onFrame x@@ -131,7 +133,7 @@ -- | Repeatedly parse and handle frames until the configured @FrameHandler@ ends handling. runFramer ::   MonadThrow m =>-  Framer m a ->+  Framer m frame ->   m () runFramer f =   let Framer@@ -147,13 +149,19 @@  {- | Parse and handle a single frame. -The result is tuple of the outstanding unparsed bytes from the bytestream if-any, and a value indicating if the bytestream has terminated.+The result is a tuple: (Maybe @unparsed@, @terminated@)++where++@unparsed@ are outstanding bytes fetched from the @ByteSource@ and+@terminated@ is @True@ if the @ByteSource@ has no further input. -} runOneFrame ::   MonadThrow m =>+  -- | the unparsed bytes from an earlier invocation, if any   Maybe ByteString ->-  Framer m a ->+  -- | the 'Framer' used to parse the @frame@+  Framer m frame ->   m ((Maybe ByteString), Bool) runOneFrame restMb f =   let Framer@@ -255,9 +263,9 @@ On failures, @'runFramer'@ throws @'Exception's@ using @'MonadThrow'@ rather than using an @Either@ or @MonadError@ -This is because it is intended to be used to parse framed protocol byte streams;-where parsing or connection errors here are typically not recoverable. In haskell-non-recoverable failures are better modelled using @Exceptions@.+This is because its intended use is for parsing framed protocol byte streams;+where parsing or connection errors are typically not recoverable. In+haskell non-recoverable failures are better modelled using @Exceptions@.  Although it throws 'NoMoreInput' or 'BrokenFrame' when appropriate, it provides hooks to override these when constructing a 'Framer'.
src/Data/Attoparsec/Framer/Testing.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_HADDOCK prune not-home #-} @@ -15,13 +16,18 @@   -- * testing combinators   parsesFromFramerOk,   chunksOfN,+  linkedSrcAndSink,+  linkedSrcAndSink', ) where  import Control.Exception (catch)+import Control.Monad (when) import qualified Data.Attoparsec.ByteString as A import Data.Attoparsec.Framer import Data.ByteString (ByteString) import qualified Data.ByteString as BS+import Data.ByteString.Builder (byteStringHex, toLazyByteString)+import qualified Data.ByteString.Lazy.Char8 as C8 import Data.IORef (   IORef,   modifyIORef',@@ -70,3 +76,53 @@     Just (x : xs) -> do       writeIORef chunkStore $ Just xs       pure x+++{- | A @'ByteSource'@ linked to a byte sink.++Provides a @ByteSource@ and @byte sink@ that emulate a responding endpoint.++The @responses@ are consumed each time the byte sink is invoked.++Whenever the sink is invoked, the head of the provided responses is removed+and starts to be returned in chunks by the @ByteSource@,+-}+linkedSrcAndSink :: [ByteString] -> IO (ByteSource IO, (ByteString -> IO ()))+linkedSrcAndSink responses = do+  refSrc <- newIORef Nothing+  refSink <- newIORef responses+  pure (ioRefByteSource refSrc, ioRefByteSink False refSink refSrc)+++-- | Like 'linkedSrcAndSink', but prints the src and sink to output as debug+linkedSrcAndSink' :: [ByteString] -> IO (ByteSource IO, (ByteString -> IO ()))+linkedSrcAndSink' responses = do+  refSrc <- newIORef Nothing+  refSink <- newIORef responses+  pure (ioRefByteSource refSrc, ioRefByteSink True refSink refSrc)+++ioRefByteSource :: IORef (Maybe ByteString) -> ByteSource IO+ioRefByteSource refSrc size = do+  readIORef refSrc >>= \case+    Nothing -> pure BS.empty+    Just src -> do+      let taken = BS.take (fromIntegral size) src+          rest = BS.drop (fromIntegral size) src+          stored = if BS.null taken then Nothing else Just rest+      writeIORef refSrc stored+      pure taken+++ioRefByteSink :: Bool -> IORef [ByteString] -> IORef (Maybe ByteString) -> ByteString -> IO ()+ioRefByteSink debug refResponses refSrc _ignored = do+  let asHex = toLazyByteString . byteStringHex+  when debug $ C8.putStrLn $ "bytesink got: " <> (asHex _ignored)+  readIORef refResponses >>= \case+    [] -> do+      when debug $ C8.putStrLn "bytesource has nothing"+      writeIORef refSrc Nothing+    (x : xs) -> do+      when debug $ C8.putStrLn $ "bytesink will reply with: " <> (asHex x)+      writeIORef refSrc $ Just x+      writeIORef refResponses $ xs