Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8e0f089
commit 0799267
Showing
4 changed files
with
127 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,42 @@ | ||
{-# LANGUAGE FlexibleContexts #-} | ||
|
||
-- | Turn a 'Get' into a 'Sink' and a 'Put' into a 'Source' | ||
|
||
module Data.Conduit.Cereal (GetError, sinkGet, conduitGet, sourcePut) where | ||
|
||
import Control.Monad.Error | ||
import qualified Data.ByteString as BS | ||
import qualified Data.ByteString.Lazy as LBS | ||
import qualified Data.Conduit as C | ||
import Data.Conduit.Cereal.Internal | ||
import Data.Conduit.List (sourceList) | ||
import Data.Serialize hiding (get, put) | ||
|
||
data GetError = GetError String | ||
deriving (Show, Eq) | ||
|
||
instance Error GetError where | ||
noMsg = GetError "" | ||
strMsg = GetError | ||
|
||
-- | Run a 'Get' repeatedly on the input stream, producing an output stream of whatever the 'Get' outputs. | ||
conduitGet :: MonadError GetError m => Get output -> C.Conduit BS.ByteString m output | ||
conduitGet = mkConduitGet deserializarionError where | ||
deserializarionError msg _ = pipeError $ strMsg 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 :: MonadError GetError m => Get r -> C.Sink BS.ByteString m r | ||
sinkGet = mkSinkGet deserializarionError earlyTermination where | ||
deserializarionError msg _ = pipeError $ strMsg msg | ||
earlyTermination f _ = let Fail msg = f BS.empty in pipeError $ strMsg msg | ||
|
||
pipeError :: MonadError e m => e -> C.Pipe i o m r | ||
pipeError e = C.PipeM trow (lift trow) where | ||
trow = throwError e | ||
|
||
-- | Convert a 'Put' into a 'Source'. Runs in constant memory. | ||
sourcePut :: Monad m => Put -> C.Source m BS.ByteString | ||
{-# LANGUAGE FlexibleContexts #-} | ||
|
||
-- | Turn a 'Get' into a 'Sink' and a 'Put' into a 'Source' | ||
|
||
module Data.Conduit.Cereal (GetError, sinkGet, conduitGet, sourcePut) where | ||
|
||
import Control.Monad.Error | ||
import qualified Data.ByteString as BS | ||
import qualified Data.ByteString.Lazy as LBS | ||
import qualified Data.Conduit as C | ||
import Data.Conduit.Cereal.Internal | ||
import Data.Conduit.List (sourceList) | ||
import Data.Serialize hiding (get, put) | ||
|
||
data GetError = GetError String | ||
deriving (Show, Eq) | ||
|
||
instance Error GetError where | ||
noMsg = GetError "" | ||
strMsg = GetError | ||
|
||
-- | Run a 'Get' repeatedly on the input stream, producing an output stream of whatever the 'Get' outputs. | ||
conduitGet :: MonadError GetError m => Get output -> C.Conduit BS.ByteString m output | ||
conduitGet = mkConduitGet id errorHandler | ||
where errorHandler msg _ = pipeError $ strMsg 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 :: MonadError GetError m => Get r -> C.Sink BS.ByteString m r | ||
sinkGet = mkSinkGet id errorHandler terminationHandler | ||
where errorHandler msg _ = pipeError $ strMsg msg | ||
terminationHandler f _ = let Fail msg = f BS.empty in pipeError $ strMsg msg | ||
|
||
pipeError :: MonadError e m => e -> C.Pipe i o m r | ||
pipeError e = C.PipeM throw (lift throw) | ||
where throw = throwError e | ||
|
||
-- | Convert a 'Put' into a 'Source'. Runs in constant memory. | ||
sourcePut :: Monad m => Put -> C.Source m BS.ByteString | ||
sourcePut put = sourceList $ LBS.toChunks $ runPutLazy put |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
module Data.Conduit.Cereal.Internal | ||
( ErrorHandler | ||
, ResultMapper | ||
, 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 | ||
|
||
type ErrorHandler i o m r = String -> Maybe BS.ByteString -> C.Pipe i o m r | ||
|
||
type ResultMapper a b = a -> b | ||
|
||
type TerminationHandler i o m r = (BS.ByteString -> Result r) -> Maybe BS.ByteString -> C.Pipe i o m r | ||
|
||
mkConduitGet :: Monad m | ||
=> ResultMapper a o | ||
-> ErrorHandler BS.ByteString o m () | ||
-> Get a | ||
-> C.Conduit BS.ByteString m o | ||
mkConduitGet resultMapper 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 (resultMapper a) | ||
False -> C.HaveOutput (push (runGetPartial get) [] s') (return ()) (resultMapper a) | ||
where consumed = s : b | ||
infiniteSequence r = C.HaveOutput (infiniteSequence r) (return ()) r | ||
|
||
close b = C.Done (chunkedStreamToMaybe b) () | ||
|
||
mkSinkGet :: Monad m | ||
=> ResultMapper a r | ||
-> ErrorHandler BS.ByteString Void m r | ||
-> TerminationHandler BS.ByteString Void m r | ||
-> Get a | ||
-> C.Sink BS.ByteString m r | ||
mkSinkGet resultMapper 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') (resultMapper r) | ||
where consumed = s : b | ||
close f = terminationHandler (fmap resultMapper . 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters