streaming-utils 0.1.0.1 → 0.1.1.0
raw patch · 2 files changed
+160/−8 lines, 2 filesdep +aesondep +json-streamdep ~attoparsecdep ~basedep ~streaming
Dependencies added: aeson, json-stream
Dependency ranges changed: attoparsec, base, streaming, streaming-bytestring
Files
- Data/ByteString/Streaming/Aeson.hs +148/−0
- streaming-utils.cabal +12/−8
+ Data/ByteString/Streaming/Aeson.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE RankNTypes #-}+-- | The @encode@, @decode@ and @decoded@ functions replicate +-- the similar functions in Renzo Carbonara's `pipes-aeson`.+-- @streamParse@ accepts parsers from the 'json-streams' library.+-- Aeson must consume a whole top level json array or+-- object before coming to any conclusion. The 'json-streams'+-- parsers use aeson types, but will stream suitable elements+-- as they arise. ++module Data.ByteString.Streaming.Aeson+ ( DecodingError(..)+ , encode+ , decode+ , decoded+ , streamParse+ ) where+import Control.Exception (Exception)+import Control.Monad.Trans+import qualified Control.Monad.Trans.State.Strict as S+import Control.Monad.Trans.State.Strict (StateT(..))++import qualified Data.Aeson as Ae+import qualified Data.Attoparsec.ByteString as Attoparsec+import qualified Data.ByteString as S+import qualified Data.ByteString.Internal as S (isSpaceWord8)+import Data.Data (Data, Typeable)+-- import Pipes+import qualified Data.Attoparsec.ByteString.Streaming as PA+import Data.ByteString.Streaming +import Data.ByteString.Streaming.Internal+import qualified Data.ByteString.Streaming as B+import Streaming+import Streaming.Internal (Stream(..))+import Streaming.Prelude (yield)+import qualified Data.JsonStream.Parser as J+import Data.JsonStream.Parser (ParseOutput (..))+--------------------------------------------------------------------------------+-- | An error while decoding a JSON value.++type ParsingError = ([String],String)++data DecodingError+ = AttoparsecError ParsingError+ -- ^An @attoparsec@ error that happened while parsing the raw JSON string.+ | FromJSONError String+ -- ^An @aeson@ error that happened while trying to convert a+ -- 'Data.Aeson.Value' to an 'A.FromJSON' instance, as reported by+ -- 'Data.Aeson.Error'.+ deriving (Show, Eq, Data, Typeable)++instance Exception DecodingError++-- | This instance allows using 'Pipes.Lift.errorP' with 'Pipes.Aeson.decoded'+-- and 'Pipes.Aeson.decodedL'+-- instance Error (DecodingError, Producer a m r)++--------------------------------------------------------------------------------++-- | Consecutively parse 'a' elements from the given 'Producer' using the given+-- parser (such as 'Pipes.Aeson.decode' or 'Pipes.Aeson.parseValue'), skipping+-- any leading whitespace each time.+--+-- This 'Producer' runs until it either runs out of input or until a decoding+-- failure occurs, in which case it returns 'Left' with a 'DecodingError' and+-- a 'Producer' with any leftovers. You can use 'Pipes.Lift.errorP' to turn the+-- 'Either' return value into an 'Control.Monad.Trans.Error.ErrorT'+-- monad transformer.+++-- | Like 'Pipes.Aeson.encode', except it accepts any 'Ae.ToJSON' instance,+-- not just 'Ae.Array' or 'Ae.Object'.+encode :: (Monad m, Ae.ToJSON a) => a -> ByteString m ()+encode = fromLazy . Ae.encode++decode+ :: (Monad m, Ae.FromJSON a)+ => StateT (ByteString m x) m (Either DecodingError a)+decode = do+ mev <- StateT (PA.parse Ae.json')+ return $ case mev of+ Right l -> Left (AttoparsecError l)+ Left v -> case Ae.fromJSON v of+ Ae.Error e -> Left (FromJSONError e)+ Ae.Success a -> Right a++decoded :: (Monad m, Ae.FromJSON a) =>+ ByteString m r+ -> Stream (Of a) m (Either (DecodingError, ByteString m r) r)+decoded = consecutively decode + where+ consecutively+ :: (Monad m)+ => StateT (ByteString m r) m (Either e a)+ -> ByteString m r + -> Stream (Of a) m (Either (e, ByteString m r) r)+ consecutively parser = step where+ step p0 = do+ x <- lift $ nextSkipBlank p0+ case x of+ Left r -> Return (Right r)+ Right (bs, p1) -> do+ (mea, p2) <- lift $ S.runStateT parser (Chunk bs p1)+ case mea of+ Right a -> do + yield a+ step p2+ Left e -> Return (Left (e, p2))++ + nextSkipBlank p0 = do+ x <- nextChunk p0+ case x of+ Left _ -> return x+ Right (a,p1) -> do+ let a' = S.dropWhile S.isSpaceWord8 a+ if S.null a' then nextSkipBlank p1+ else return (Right (a', p1))++{- | Experimental. Parse a bytestring with a @json-streams@ parser. + The function will read through+ the whole of a single top level json entity, streaming the valid parses as they+ arise. (It will thus for example parse an infinite json bytestring, though these+ are rare in practice ...) If the parser is fitted to recognize only one thing, + then zero or one item will be yielded; if it uses combinators like @arrayOf@, + it will stream many values as they arise. This function is modelled on + 'Data.JsonStream.Parser.parseByteString' and + 'Data.JsonStream.Parser.parseLazyByteString'+ + -}+streamParse+ :: (Monad m) =>+ J.Parser a+ -> ByteString m r + -> Stream (Of a) m (Maybe String, ByteString m r)+streamParse parser input = loop input (J.runParser parser) where+ loop bytes p0 = case p0 of + ParseFailed s -> return (Just s,bytes)+ ParseDone bs -> return (Nothing, chunk bs >> bytes)+ ParseYield a p1 -> yield a >> loop bytes p1+ ParseNeedData f -> do + e <- lift $ nextChunk bytes+ case e of+ Left r -> return (Just "Not enough data",return r)+ Right (bs, rest) -> loop rest (f bs)++
streaming-utils.cabal view
@@ -1,5 +1,5 @@ name: streaming-utils-version: 0.1.0.1+version: 0.1.1.0 synopsis: http, attoparsec and pipes utilities for streaming and streaming-bytestring description: Experimental http-client, attoparsec and pipes utilities for use with the <http://hackage.haskell.org/package/streaming streaming> and @@ -7,8 +7,9 @@ Other modules are planned. . This package of course heaps together a number of dependencies. - Please raise an issue if this is a problem. Questions about usage can be- raised as issue or addressed to the + Please raise an issue if this is a problem. + .+ Questions about usage can be raised as issues, or addressed to the <https://groups.google.com/forum/#!forum/haskell-pipes pipes list>. license: BSD3@@ -31,20 +32,23 @@ library exposed-modules: Data.Attoparsec.ByteString.Streaming, Data.ByteString.Streaming.HTTP,+ Data.ByteString.Streaming.Aeson, Streaming.Pipes -- other-modules: other-extensions: CPP, Trustworthy - build-depends: base >=4.6 && <4.9, + build-depends: base >=4.7 && <4.9, transformers >=0.4 && <0.5, mtl >=2.2 && <2.3,- attoparsec,- streaming > 0.1.0.15 && < 0.1.1,- streaming-bytestring > 0.1.0.5 && < 0.1.1,+ attoparsec >=0.13.0.1,+ streaming > 0.1.0.20 && < 0.1.1.2,+ streaming-bytestring > 0.1.0.8 && < 0.1.1.2, bytestring, pipes >= 4.0 && < 4.2, http-client >=0.2 && <0.5, - http-client-tls <0.3+ http-client-tls <0.3,+ aeson,+ json-stream == 0.3.2.* -- hs-source-dirs: