{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-} ----------------------------------------------------------------------------- -- | -- Module : Sytem.IO.Streams.Cereal -- Copyright : Soostone Inc -- License : BSD3 -- -- Maintainer : Michael Xavier -- Stability : experimental -- -- io-streams interface to the cereal binary serialization library. ---------------------------------------------------------------------------- module System.IO.Streams.Cereal ( getFromStream , putToStream , getEachStream , putEachStream , contramapPut , GetException(..) ) where ------------------------------------------------------------------------------- import Control.Applicative import Control.Exception (Exception, throwIO) import Control.Monad import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as S import Data.Monoid import Data.Serialize import Data.Typeable import qualified System.IO.Streams as Streams import System.IO.Streams.Core ------------------------------------------------------------------------------- data GetException = GetException String deriving (Typeable) instance Show GetException where show (GetException s) = "Get exception: " ++ s instance Exception GetException ------------------------------------------------------------------------------- -- | Convert a 'Put' into an 'InputStream' -- -- Example: -- -- >>> putToStream (put False) putToStream :: Put -> IO (InputStream ByteString) putToStream = Streams.fromLazyByteString . runPutLazy {-# INLINE putToStream #-} ------------------------------------------------------------------------------- -- | Convert a stream of individual serialized 'ByteString's to a stream -- of Results. Throws a GetException on error. -- -- Example: -- -- >>> Streams.toList =<< getEachStream (get :: Get String) =<< Streams.fromList (map (runPut . put) ["foo", "bar"]) -- ["foo","bar"] getEachStream :: Get r -> InputStream ByteString -> IO (InputStream r) getEachStream g is = makeInputStream $ do atEnd <- atEOF is if atEnd then return Nothing else Just <$> getFromStream g is {-# INLINE getEachStream #-} ------------------------------------------------------------------------------- -- | Convert a stream of serializable objects into a stream of -- individual 'ByteString's -- Example: -- -- >>> Streams.toList =<< getEachStream (get :: Get String) =<< putEachStream put =<< Streams.fromList ["foo","bar"] -- ["foo","bar"] putEachStream :: Putter r -> InputStream r -> IO (InputStream ByteString) putEachStream p = Streams.map (runPut . p) {-# INLINE putEachStream #-} ------------------------------------------------------------------------------- -- | Take a 'Get' and an 'InputStream' and deserialize a -- value. Consumes only as much input as necessary to deserialize the -- value. Unconsumed input is left on the 'InputStream'. If there is -- an error while deserializing, a 'GetException' is thrown. -- -- Examples: -- -- >>> getFromStream (get :: Get String) =<< putToStream (put "serialize me") -- "serialize me" -- >>> getFromStream (get :: Get String) =<< Streams.fromByteString (Data.ByteString.drop 1 $ runPut $ put ("serialize me" :: String)) -- *** Exception: Get exception: too few bytes -- From: demandInput -- -- getFromStream :: Get r -> InputStream ByteString -> IO r getFromStream = getFromStreamInternal runGetPartial feed {-# INLINE getFromStream #-} ------------------------------------------------------------------------------- -- | Take an output stream of serializable values and create an output -- stream of bytestrings, one for each value. contramapPut :: Putter r -> OutputStream ByteString -> IO (OutputStream r) contramapPut p = Streams.contramap (runPut . p) ------------------------------------------------------------------------------- feed :: Result r -> ByteString -> Result r feed (Partial f) bs = f bs feed (Done r x) bs = Done r $ x <> bs feed (Fail s x) bs = Fail s $ x <> bs ------------------------------------------------------------------------------- getFromStreamInternal :: (Get r -> ByteString -> Result r) -> (Result r -> ByteString -> Result r) -> Get r -> InputStream ByteString -> IO r getFromStreamInternal getFunc feedFunc g is = Streams.read is >>= maybe (finish $ getFunc g "") (\s -> if S.null s then getFromStreamInternal getFunc feedFunc g is else go $! getFunc g s) where leftover x = unless (S.null x) $ Streams.unRead x is finish k = let k' = feedFunc (feedFunc k "") "" in case k' of Fail _ x -> leftover x >> err k' Partial _ -> err k' -- should be impossible Done r x -> leftover x >> return r err r = let (Left s) = eitherResult r in throwIO $ GetException s eitherResult (Done _ r) = Right r eitherResult (Fail msg _) = Left msg eitherResult _ = Left "Result: incomplete input" go r@(Fail _ x) = leftover x >> err r go (Done r x) = leftover x >> return r go r = Streams.read is >>= maybe (finish r) (\s -> if S.null s then go r else go $! feedFunc r s)