cereal-conduit 0.5 → 0.6
raw patch · 4 files changed
+194/−142 lines, 4 filesdep +transformersdep −voiddep ~conduitPVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependencies removed: void
Dependency ranges changed: conduit
API changes (from Hackage documentation)
- Data.Conduit.Cereal.Internal: type ErrorHandler i o m r = String -> Maybe ByteString -> Pipe i o m r
- Data.Conduit.Cereal.Internal: type TerminationHandler i o m r = (ByteString -> Result r) -> Maybe ByteString -> Pipe i o m r
+ Data.Conduit.Cereal.Internal: type ConduitErrorHandler m o = String -> GLConduit ByteString m o
+ Data.Conduit.Cereal.Internal: type SinkErrorHandler m r = String -> GLSink ByteString m r
+ Data.Conduit.Cereal.Internal: type SinkTerminationHandler m r = (ByteString -> Result r) -> GLSink ByteString m r
- Data.Conduit.Cereal: conduitGet :: MonadThrow m => Get output -> Conduit ByteString m output
+ Data.Conduit.Cereal: conduitGet :: MonadThrow m => Get o -> GLConduit ByteString m o
- Data.Conduit.Cereal: conduitPut :: Monad m => Putter input -> Conduit input m ByteString
+ Data.Conduit.Cereal: conduitPut :: Monad m => Putter a -> GInfConduit a m ByteString
- Data.Conduit.Cereal: sinkGet :: MonadThrow m => Get r -> Sink ByteString m r
+ Data.Conduit.Cereal: sinkGet :: MonadThrow m => Get r -> GLSink ByteString m r
- Data.Conduit.Cereal: sourcePut :: Monad m => Put -> Source m ByteString
+ Data.Conduit.Cereal: sourcePut :: Monad m => Put -> GSource m ByteString
- Data.Conduit.Cereal.Internal: mkConduitGet :: Monad m => ErrorHandler ByteString o m () -> Get o -> Conduit ByteString m o
+ Data.Conduit.Cereal.Internal: mkConduitGet :: MonadThrow m => ConduitErrorHandler m o -> Get o -> GLConduit ByteString m o
- Data.Conduit.Cereal.Internal: mkSinkGet :: Monad m => ErrorHandler ByteString Void m r -> TerminationHandler ByteString Void m r -> Get r -> Sink ByteString m r
+ Data.Conduit.Cereal.Internal: mkSinkGet :: Monad m => SinkErrorHandler m r -> SinkTerminationHandler m r -> Get r -> GLSink ByteString m r
Files
- Data/Conduit/Cereal.hs +59/−55
- Data/Conduit/Cereal/Internal.hs +64/−62
- Test/Main.hs +66/−20
- cereal-conduit.cabal +5/−5
Data/Conduit/Cereal.hs view
@@ -1,55 +1,59 @@-{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE DeriveDataTypeable #-} - --- | Turn a 'Get' into a 'Sink' and a 'Put' into a 'Source' --- These functions are built upno the Data.Conduit.Cereal.Internal functions with default --- implementations of 'ErrorHandler' and 'TerminationHandler' --- --- The default 'ErrorHandler' and 'TerminationHandler' both throw a 'GetException'. - -module Data.Conduit.Cereal ( GetException - , sinkGet - , conduitGet - , sourcePut - , conduitPut - ) where - -import Control.Exception.Base -import qualified Data.ByteString as BS -import qualified Data.ByteString.Lazy as LBS -import qualified Data.Conduit as C -import qualified Data.Conduit.List as CL -import Data.Serialize hiding (get, put) -import Data.Typeable - -import Data.Conduit.Cereal.Internal - -data GetException = GetException String - deriving (Show, Typeable) - -instance Exception GetException - --- | Run a 'Get' repeatedly on the input stream, producing an output stream of whatever the 'Get' outputs. -conduitGet :: C.MonadThrow m => Get output -> C.Conduit BS.ByteString m output -conduitGet = mkConduitGet errorHandler - where errorHandler msg _ = pipeError $ GetException msg - --- | Convert a 'Get' into a 'Sink'. The 'Get' will be streamed bytes until it returns 'Done' or 'Fail'. --- --- If 'Get' succeed it will return the data read and unconsumed part of the input stream. --- If the 'Get' fails due to deserialization error or early termination of the input stream it raise an error. -sinkGet :: C.MonadThrow m => Get r -> C.Sink BS.ByteString m r -sinkGet = mkSinkGet errorHandler terminationHandler - where errorHandler msg _ = pipeError $ GetException msg - terminationHandler f _ = let Fail msg = f BS.empty in pipeError $ GetException msg - -pipeError :: (C.MonadThrow m, Exception e) => e -> C.Pipe i o m r -pipeError e = C.PipeM (C.monadThrow e) undefined - --- | Convert a 'Put' into a 'Source'. Runs in constant memory. -sourcePut :: Monad m => Put -> C.Source m BS.ByteString -sourcePut put = CL.sourceList $ LBS.toChunks $ runPutLazy put - --- | Run a 'Putter' repeatedly on the input stream, producing a concatenated 'ByteString' stream. -conduitPut :: Monad m => Putter input -> C.Conduit input m BS.ByteString -conduitPut p = CL.map $ runPut . p +{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE RankNTypes #-}++-- | Turn a 'Get' into a 'Sink' and a 'Put' into a 'Source'+-- These functions are built upno the Data.Conduit.Cereal.Internal functions with default+-- implementations of 'ErrorHandler' and 'TerminationHandler'+--+-- The default 'ErrorHandler' and 'TerminationHandler' both throw a 'GetException'.++module Data.Conduit.Cereal ( GetException+ , sinkGet+ , conduitGet+ , sourcePut+ , conduitPut+ ) where++import Control.Exception.Base+import Control.Monad.Trans.Class (MonadTrans, lift)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LBS+import qualified Data.Conduit as C+import qualified Data.Conduit.List as CL+import Data.Serialize hiding (get, put)+import Data.Typeable++import Data.Conduit.Cereal.Internal++data GetException = GetException String+ deriving (Show, Typeable)++instance Exception GetException++-- | Run a 'Get' repeatedly on the input stream, producing an output stream of whatever the 'Get' outputs.+conduitGet :: C.MonadThrow m => Get o -> C.GLConduit BS.ByteString m o+conduitGet = mkConduitGet errorHandler+ where errorHandler msg = pipeError $ GetException msg++-- | Convert a 'Get' into a 'Sink'. The 'Get' will be streamed bytes until it returns 'Done' or 'Fail'.+--+-- If 'Get' succeed it will return the data read and unconsumed part of the input stream.+-- If the 'Get' fails due to deserialization error or early termination of the input stream it raise an error.+sinkGet :: C.MonadThrow m => Get r -> C.GLSink BS.ByteString m r+sinkGet = mkSinkGet errorHandler terminationHandler+ where errorHandler msg = pipeError $ GetException msg+ terminationHandler f = let Fail msg = f BS.empty in pipeError $ GetException msg ++pipeError :: (C.MonadThrow m, MonadTrans t, Exception e) => e -> t m a+pipeError e = lift $ C.monadThrow e++-- | Convert a 'Put' into a 'Source'. Runs in constant memory.+sourcePut :: Monad m => Put -> C.GSource m BS.ByteString+sourcePut put = CL.sourceList $ LBS.toChunks $ runPutLazy put++-- | Run a 'Putter' repeatedly on the input stream, producing a concatenated 'ByteString' stream.+conduitPut :: Monad m => Putter a -> C.GInfConduit a m BS.ByteString+conduitPut p = CL.map $ runPut . p
Data/Conduit/Cereal/Internal.hs view
@@ -1,62 +1,64 @@-module Data.Conduit.Cereal.Internal - ( ErrorHandler - , TerminationHandler - - , mkConduitGet - , mkSinkGet - ) where - -import qualified Data.ByteString as BS -import qualified Data.Conduit as C -import Data.Serialize hiding (get, put) -import Data.Void - --- | What should we do if the Get fails? -type ErrorHandler i o m r = String -> Maybe BS.ByteString -> C.Pipe i o m r - --- | What should we do if the stream is done before the Get is done? -type TerminationHandler i o m r = (BS.ByteString -> Result r) -> Maybe BS.ByteString -> C.Pipe i o m r - --- | Construct a conduitGet with the specified 'ErrorHandler' -mkConduitGet :: Monad m - => ErrorHandler BS.ByteString o m () - -> Get o - -> C.Conduit BS.ByteString m o -mkConduitGet errorHandler get = consume True (runGetPartial get) [] BS.empty - where push f b s | BS.null s = C.NeedInput (push f b) (close b) - | otherwise = consume False f b s - consume initial f b s = case f s of - Fail msg -> errorHandler msg (chunkedStreamToMaybe consumed) - Partial p -> C.NeedInput (push p consumed) (close consumed) - Done a s' -> case initial of - True -> infiniteSequence a - False -> C.HaveOutput (push (runGetPartial get) [] s') (return ()) a - where consumed = s : b - infiniteSequence r = C.HaveOutput (infiniteSequence r) (return ()) r - -- infinteSequence only works because the Get will either _always_ consume no input, or _never_ consume no input. - close b = C.Done (chunkedStreamToMaybe b) () - --- | Construct a sinkGet with the specified 'ErrorHandler' and 'TerminationHandler' -mkSinkGet :: Monad m - => ErrorHandler BS.ByteString Void m r - -> TerminationHandler BS.ByteString Void m r - -> Get r - -> C.Sink BS.ByteString m r -mkSinkGet errorHandler terminationHandler get = consume (runGetPartial get) [] BS.empty - where push f b s - | BS.null s = C.NeedInput (push f b) (close f b) - | otherwise = consume f b s - consume f b s = case f s of - Fail msg -> errorHandler msg (chunkedStreamToMaybe consumed) - Partial p -> C.NeedInput (push p consumed) (close p consumed) - Done r s' -> C.Done (streamToMaybe s') r - where consumed = s : b - close f = terminationHandler f . chunkedStreamToMaybe - -chunkedStreamToMaybe :: [BS.ByteString] -> Maybe BS.ByteString -chunkedStreamToMaybe = streamToMaybe . BS.concat . reverse - -streamToMaybe :: BS.ByteString -> Maybe BS.ByteString -streamToMaybe s = if BS.null s - then Nothing - else Just s +{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE Rank2Types #-}++module Data.Conduit.Cereal.Internal+ ( ConduitErrorHandler+ , SinkErrorHandler+ , SinkTerminationHandler++ , mkConduitGet+ , mkSinkGet+ ) where++import Control.Monad (when)+import qualified Data.ByteString as BS+import qualified Data.Conduit as C+import Data.Serialize hiding (get, put)++-- | What should we do if the Get fails?+type ConduitErrorHandler m o = String -> C.GLConduit BS.ByteString m o+type SinkErrorHandler m r = String -> C.GLSink BS.ByteString m r++-- | What should we do if the stream is done before the Get is done?+type SinkTerminationHandler m r = (BS.ByteString -> Result r) -> C.GLSink BS.ByteString m r++-- | Construct a conduitGet with the specified 'ErrorHandler'+mkConduitGet :: C.MonadThrow m+ => ConduitErrorHandler m o+ -> Get o+ -> C.GLConduit BS.ByteString m o+mkConduitGet errorHandler get = consume True (runGetPartial get) [] BS.empty+ where pull f b s+ | BS.null s = C.await >>= maybe (when (not $ null b) (C.leftover $ BS.concat $ reverse b)) (pull f b)+ | otherwise = consume False f b s+ consume initial f b s = case f s of+ Fail msg -> do+ when (not $ null b) (C.leftover $ BS.concat $ reverse consumed)+ errorHandler msg+ Partial p -> pull p consumed BS.empty+ Done a s' -> case initial of+ -- this only works because the Get will either _always_ consume no input, or _never_ consume no input.+ True -> sequence_ $ repeat $ C.yield a+ False -> C.yield a >> pull (runGetPartial get) [] s'+-- False -> C.yield a >> C.leftover s' >> mkConduitGet errorHandler get+ where consumed = s : b++-- | Construct a sinkGet with the specified 'ErrorHandler' and 'TerminationHandler'+mkSinkGet :: Monad m+ => SinkErrorHandler m r+ -> SinkTerminationHandler m r+ -> Get r+ -> C.GLSink BS.ByteString m r+mkSinkGet errorHandler terminationHandler get = consume (runGetPartial get) [] BS.empty+ where pull f b s+ | BS.null s = C.await >>= \ x -> case x of+ Nothing -> when (not $ null b) (C.leftover $ BS.concat $ reverse b) >> terminationHandler f+ Just a -> pull f b a+ | otherwise = consume f b s+ consume f b s = case f s of+ Fail msg -> do+ when (not $ null b) (C.leftover $ BS.concat $ reverse consumed)+ errorHandler msg+ Partial p -> pull p consumed BS.empty+ Done r s' -> when (not $ BS.null s') (C.leftover s') >> return r+ where consumed = s : b
Test/Main.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE Rank2Types #-} import Control.Exception.Base import Control.Monad.Identity@@ -26,6 +28,13 @@ y <- getWord8 return $ x + y +threeItemGet :: Get Word8+threeItemGet = do+ x <- getWord8+ y <- getWord8+ z <- getWord8+ return $ x + y + z+ putter :: Putter Char putter c = put x >> put (x + 1) where x = (fromIntegral $ (fromEnum c) - (fromEnum 'a') :: Word8)@@ -70,21 +79,27 @@ -- Current sink implementation will terminate the pipe in case of error. -- One may need non-terminating version like one defined below to get access to Leftovers -sinkGetMaybe :: Monad m => Get output -> C.Sink BS.ByteString m (Maybe output)-sinkGetMaybe = mkSinkGet errorHandler terminationHandler . fmap Just- where errorHandler msg s = C.Done s Nothing- terminationHandler f s = C.Done s Nothing+sinkGetMaybe :: Get Word8 -> C.GLSink BS.ByteString (ExceptionT Identity) Word8+sinkGetMaybe = mkSinkGet errorHandler terminationHandler+ where errorHandler _ = return 34+ terminationHandler _ = return 114 sinktest7 :: Test-sinktest7 = TestCase (assertBool "Leftover input with failure works"- (case runIdentity $ do- (CL.sourceList [BS.pack [1, 2]]) C.$$ (do- output <- sinkGetMaybe (getWord8 >> fail "" :: Get Word8)- output' <- CL.consume- return (output, BS.concat output')) of- (Nothing, bs) -> bs == BS.pack [1, 2]- otherwise -> False))+sinktest7 = TestCase (assertEqual "Leftover input with failure works"+ (Right (34, BS.pack [1, 2]))+ (runIdentity $ runExceptionT $ (CL.sourceList [BS.pack [1, 2]]) C.$$ (do+ output <- sinkGetMaybe (getWord8 >> fail "" :: Get Word8)+ output' <- CL.consume+ return (output, BS.concat output')))) +sinktest8 :: Test+sinktest8 = TestCase (assertEqual "Leftover with incomplete input works"+ (Right (114, BS.singleton 1))+ (runIdentity $ runExceptionT $ (CL.sourceList [BS.singleton 1]) C.$$ (do+ output <- sinkGetMaybe twoItemGet+ output' <- CL.consume+ return (output, BS.concat output'))))+ conduittest1 :: Test conduittest1 = TestCase (assertEqual "Handles starting with empty bytestring" (Right [])@@ -148,23 +163,50 @@ Left _ -> True Right _ -> False)) --- This test CAN'T work because of the type of HaveOutput. conduittest13 :: Test conduittest13 = TestCase (assertEqual "Leftover success conduit input works"- (Right ([12], BS.pack [3, 4, 5]))- (runIdentity $ runExceptionT $ (CL.sourceList [BS.pack [10, 2, 3], BS.pack [4, 5]]) C.$$ (do- output <- (conduitGet twoItemGet) C.=$ (CL.take 1)- output' <- CL.consume- return (output, BS.concat output'))))+ (Right [Right 12, Right 7, Left (BS.pack [5])])+ (runIdentity $ runExceptionT $ (CL.sourceList [BS.pack [10, 2, 3], BS.pack [4, 5]]) C.$= fancyConduit C.$$ CL.consume))+ where fancyConduit = do+ conduitGet twoItemGet C.=$= CL.map (\ x -> Right x)+ recurse+ where recurse = C.await >>= maybe (return ()) (\ x -> C.yield (Left x) >> recurse) conduittest14 :: Test-conduittest14 = TestCase (assertEqual "Leftover failure conduit input works"+conduittest14 = TestCase (assertEqual "Leftover coercing works"+ (Right [Left (BS.pack [10, 2])])+ (runIdentity $ runExceptionT $ (CL.sourceList [BS.pack [10], BS.pack [2]]) C.$= fancyConduit C.$$ CL.consume))+ where fancyConduit = do+ conduitGet threeItemGet C.=$= CL.map (\ x -> Right x)+ recurse+ where recurse = C.await >>= maybe (return ()) (\ x -> C.yield (Left x) >> recurse)++conduittest15 :: Test+conduittest15 = TestCase (assertEqual "Leftover premature end conduit input works" (Right ([], BS.singleton 1)) (runIdentity $ runExceptionT $ (CL.sourceList [BS.singleton 1]) C.$$ (do output <- (conduitGet twoItemGet) C.=$ (CL.take 1) output' <- CL.consume return (output, BS.concat output')))) +conduittest16 :: Test+conduittest16 = TestCase (assertEqual "Leftover failure conduit input works"+ (Right [Left $ BS.pack [10, 11], Left $ BS.singleton 2])+ (runIdentity $ runExceptionT $ (CL.sourceList [BS.pack [10, 11], BS.pack [2]]) C.$= fancyConduit C.$$ CL.consume))+ where fancyConduit = do+ mkConduitGet (const $ return ()) (getWord8 >> fail "asdf" :: Get Word8) C.=$= CL.map (\ x -> Right x)+ recurse+ where recurse = C.await >>= maybe (return ()) (\ x -> C.yield (Left x) >> recurse)++conduittest17 :: Test+conduittest17 = TestCase (assertEqual "Leftover failure conduit with broken input works"+ (Right [Left $ BS.pack [10, 11], Left $ BS.singleton 12])+ (runIdentity $ runExceptionT $ (CL.sourceList [BS.singleton 10, BS.singleton 11, BS.singleton 12]) C.$= fancyConduit C.$$ CL.consume))+ where fancyConduit = do+ mkConduitGet (const $ return ()) (twoItemGet >> fail "asdf" :: Get Word8) C.=$= CL.map (\ x -> Right x)+ recurse+ where recurse = C.await >>= maybe (return ()) (\ x -> C.yield (Left x) >> recurse)+ puttest1 :: Test puttest1 = TestCase (assertEqual "conduitPut works" [BS.pack [0, 1]]@@ -187,6 +229,7 @@ , sinktest5 , sinktest6 , sinktest7+ , sinktest8 ] conduittests = TestList [ conduittest1@@ -201,8 +244,11 @@ , conduittest10 , conduittest11 , conduittest12- --, conduittest13+ , conduittest13 , conduittest14+ , conduittest15+ , conduittest16+ , conduittest17 ] puttests = TestList [ puttest1
cereal-conduit.cabal view
@@ -1,5 +1,5 @@ name: cereal-conduit-version: 0.5+version: 0.6 license: BSD3 license-file: LICENSE author: Myles C. Maxfield <myles.maxfield@gmail.com>@@ -14,10 +14,10 @@ library build-depends: base >= 4 && < 5- , conduit >= 0.4.0 && < 0.5.0+ , conduit >= 0.5.0 && < 0.6.0 , cereal >= 0.3.1.0 , bytestring- , void+ , transformers >= 0.2.0.0 exposed-modules: Data.Conduit.Cereal , Data.Conduit.Cereal.Internal ghc-options: -Wall@@ -26,14 +26,14 @@ type: exitcode-stdio-1.0 main-is: Test/Main.hs build-depends: base >= 4 && < 5- , conduit >= 0.4.0 && < 0.5.0+ , conduit >= 0.5.0 && < 0.6.0 , cereal >= 0.3.1.0 , bytestring --, test-framework-hunit , HUnit- , void , resourcet , mtl+ , transformers >= 0.2.0.0 source-repository head type: git