packages feed

pipes-aeson 0.4.1.8 → 0.4.2

raw patch · 6 files changed

+183/−27 lines, 6 filesdep +attoparsec-aesonsetup-changedPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: attoparsec-aeson

API changes (from Hackage documentation)

+ Pipes.Aeson: loop :: (Monad m, FromJSON a) => (Producer ByteString m r -> Producer ByteString m r) -> Producer ByteString m r -> Producer' (Either DecodingError a) m r
+ Pipes.Aeson: loopL :: (Monad m, FromJSON a) => (Producer ByteString m r -> Producer ByteString m r) -> Producer ByteString m r -> Proxy x' x () (Either DecodingError (Int, a)) m r
+ Pipes.Aeson.Unchecked: loop :: (Monad m, FromJSON a) => (Producer ByteString m r -> Producer ByteString m r) -> Producer ByteString m r -> Producer' (Either DecodingError a) m r
+ Pipes.Aeson.Unchecked: loopL :: (Monad m, FromJSON a) => (Producer ByteString m r -> Producer ByteString m r) -> Producer ByteString m r -> Proxy x' x () (Either DecodingError (Int, a)) m r
- Pipes.Aeson: FromJSONError :: String -> DecodingError
+ Pipes.Aeson: FromJSONError :: Value -> String -> DecodingError
- Pipes.Aeson: encodeArray :: Monad m => Array -> Producer' ByteString m ()
+ Pipes.Aeson: encodeArray :: Monad m => Array -> Proxy x' x () ByteString m ()
- Pipes.Aeson: encodeObject :: Monad m => Object -> Producer' ByteString m ()
+ Pipes.Aeson: encodeObject :: Monad m => Object -> Proxy x' x () ByteString m ()
- Pipes.Aeson.Unchecked: encode :: (Monad m, ToJSON a) => a -> Producer' ByteString m ()
+ Pipes.Aeson.Unchecked: encode :: (Monad m, ToJSON a) => a -> Proxy x' x () ByteString m ()

Files

− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
changelog.md view
@@ -1,3 +1,16 @@+# 0.4.2++* BREAKING CHANGE: `FromJSONError` now carries the failing `Value` as payload.++* BREAKING CHANGE: Removed support for `ErrorT`.++* Added `loop` and `loopL`.++* GHC >= 9.0 compatibility.++* Add dependency on `attoparsec-aeson`.++ # 0.4.1.8  * Remove upper bound dependencies on everything but `base`.
pipes-aeson.cabal view
@@ -1,17 +1,16 @@+cabal-version:      2.4 name:               pipes-aeson-version:            0.4.1.8-license:            BSD3+version:            0.4.2+license:            BSD-3-Clause license-file:       LICENSE-copyright:          Copyright (c) Renzo Carbonara 2013-2017+copyright:          Copyright (c) Renzo Carbonara 2013 author:             Renzo Carbonara-maintainer:         renzocarbonaraλgmail.com+maintainer:         renλren.zone stability:          Experimental-tested-with:        GHC ==8.0.2 homepage:           https://github.com/k0001/pipes-aeson bug-reports:        https://github.com/k0001/pipes-aeson/issues category:           Pipes, Parser build-type:         Simple-cabal-version:      >=1.8 synopsis:           Encode and decode JSON streams using Aeson and Pipes. extra-source-files: README.md PEOPLE changelog.md description:@@ -25,6 +24,7 @@     location: git://github.com/k0001/pipes-aeson.git  library+  default-language: Haskell2010   hs-source-dirs:  src   exposed-modules: Pipes.Aeson                    Pipes.Aeson.Unchecked@@ -32,6 +32,7 @@   build-depends:       aeson            (>=0.6.1)     , attoparsec       (>=0.10)+    , attoparsec-aeson (>=2.2)     , base             (>=4.5 && <5.0)     , pipes            (>=4.1)     , pipes-attoparsec (>=0.5)
src/Pipes/Aeson.hs view
@@ -24,16 +24,18 @@     -- $decoding   , decode   , decoded+  , loop     -- ** Including lengths   , decodeL   , decodedL+  , loopL      -- * Types   , I.DecodingError(..)   ) where -import           Control.Monad         (liftM) import qualified Data.Aeson            as Ae+import qualified Data.Aeson.Parser     as Ae import qualified Data.ByteString.Char8 as B import           Pipes import qualified Pipes.Aeson.Internal  as I@@ -60,7 +62,7 @@ -- @ -- 'for' 'cat' 'encodeObject' :: 'Monad' m => 'Pipe' 'Ae.Object' 'B.ByteString' m r -- @-encodeObject :: Monad m => Ae.Object -> Producer' B.ByteString m ()+encodeObject :: Monad m => Ae.Object -> Proxy x' x () B.ByteString m () encodeObject = U.encode {-# INLINABLE encodeObject #-} {-# RULES "p >-> for cat encodeObject" forall p .@@ -75,7 +77,7 @@ -- @ -- 'for' 'cat' 'encodeArray' :: 'Monad' m => 'Pipe' 'Ae.Array' 'B.ByteString' m r -- @-encodeArray :: Monad m => Ae.Array -> Producer' B.ByteString m ()+encodeArray :: Monad m => Ae.Array -> Proxy x' x () B.ByteString m () encodeArray = U.encode {-# INLINABLE encodeArray #-} {-# RULES "p >-> for cat encodeArray" forall p .@@ -108,7 +110,7 @@ decode   :: (Monad m, Ae.FromJSON a)   => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError a))-decode = fmap (fmap snd) `liftM` decodeL+decode = fmap (fmap (fmap snd)) decodeL {-# INLINABLE decode #-}  -- | Like 'decode', except it also returns the length of JSON input that was@@ -172,6 +174,61 @@          Right r      -> return r {-# INLINABLE decodedL #-} +-- | Repeteadly try to parse raw JSON bytes into @a@ values, reporting any+-- 'I.DecodingError's downstream as they happen.+--+-- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level+-- entities, which is why these functions restrict their input to them. If you+-- prefer to ignore the standard and encode any 'Ae.Value', then use 'U.encode'+-- from the "Pipes.Aeson.Unchecked" module.+loop+  :: (Monad m, Ae.FromJSON a)+  => (Pipes.Producer B.ByteString m r -> Pipes.Producer B.ByteString m r)+  -- ^ In case of 'I.AttoparsecError', this function will be called to modify+  -- the leftovers 'Pipes.Producer' before using it.+  --+  -- Ideally you will want to drop everything until the beginning of the next+  -- JSON element. This is easy to accomplish if there is a clear whitespace+  -- delimiter between the JSON elements, such as a newline (i.e.,+  -- @'Pipes.ByteString.drop' 1 . 'Pipes.ByteString.dropWhile' (/= 0xA)@).+  -- However, it can be hard to do correctly is there is no such delimiter.+  -- Skipping the first character (i.e., @'Pipes.ByteString.drop' 1@) should be+  -- sufficient in most cases, but not when parsing recursive data structures+  -- because you can accidentally parse a child in its parent's stead.+  --+  -- Notice that unless you advance the 'Pipes.Producer' somehow, 'loop'+  -- will never terminate.+  -> Pipes.Producer B.ByteString m r+  -- ^ Raw JSON input.+  -> Pipes.Producer' (Either I.DecodingError a) m r+{-# INLINABLE loop #-}+loop fp p0 = Pipes.for (loopL fp p0) (Pipes.yield . fmap snd)++-- | Like 'loop', except it also outputs the length of JSON input that was+-- consumed in order to obtain the value, not including the length of whitespace+-- before nor after the parsed JSON input.+loopL+  :: (Monad m, Ae.FromJSON a)+  => (Pipes.Producer B.ByteString m r -> Pipes.Producer B.ByteString m r)+  -- ^ In case of 'I.AttoparsecError', this function will be called to modify+  -- the leftovers 'Pipes.Producer' before using it.+  --+  -- Ideally you will want to drop everything until the beginning of the next+  -- JSON element. This is easy to accomplish if there is a clear whitespace+  -- delimiter between the JSON elements, such as a newline (i.e.,+  -- @'Pipes.ByteString.drop' 1 . 'Pipes.ByteString.dropWhile' (/= 0xA)@).+  -- However, it can be hard to do correctly is there is no such delimiter.+  -- Skipping the first character (i.e., @'Pipes.ByteString.drop' 1@) should be+  -- sufficient in most cases, but not when parsing recursive data structures+  -- because you can accidentally parse a child in its parent's stead.+  --+  -- Notice that unless you advance the 'Pipes.Producer' somehow, 'loopL'+  -- will never terminate.+  -> Pipes.Producer B.ByteString m r+  -- ^ Raw JSON input.+  -> Pipes.Proxy x' x () (Either I.DecodingError (Int, a)) m r+{-# INLINABLE loopL #-}+loopL = I.loopL Ae.json'  -------------------------------------------------------------------------------- -- Internal tools --------------------------------------------------------------
src/Pipes/Aeson/Internal.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE LambdaCase         #-} {-# LANGUAGE RankNTypes         #-}  -- | This module provides internal utilities and it is likely@@ -10,15 +11,16 @@   ( DecodingError(..)   , consecutively   , decodeL+  , loopL   ) where import           Control.Exception                (Exception)-import           Control.Monad.Trans.Error        (Error) import qualified Control.Monad.Trans.State.Strict as S import qualified Data.Aeson                       as Ae import qualified Data.Attoparsec.Types            as Attoparsec import qualified Data.ByteString                  as B import qualified Data.ByteString.Internal         as B (isSpaceWord8) import           Data.Data                        (Data, Typeable)+import           Data.Function                    (fix) import           Pipes import qualified Pipes.Attoparsec                 as PA import qualified Pipes.Parse                      as Pipes@@ -29,19 +31,14 @@ data DecodingError   = AttoparsecError PA.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+  | FromJSONError Ae.Value String+    -- ^An @aeson@ error that happened while trying to convert the given+    -- 'Data.Aeson.Value' to an 'Ae.FromJSON' instance, as reported by     -- 'Data.Aeson.Error'.   deriving (Show, Eq, Data, Typeable)  instance Exception DecodingError-instance Error     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@@ -50,11 +47,11 @@ -- -- 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'+-- a 'Producer' with any leftovers. You can use 'Pipes.Lift.exceptP' to turn the+-- 'Either' return value into an 'Control.Monad.Trans.ExceptT' -- monad transformer. consecutively-  :: (Monad m)+  :: Monad m   => Pipes.Parser B.ByteString m (Maybe (Either e a))   -> Producer B.ByteString m r  -- ^Producer from which to draw raw input.   -> Producer a m (Either (e, Producer B.ByteString m r) r)@@ -87,10 +84,46 @@        Nothing             -> Nothing        Just (Left l)       -> Just (Left (AttoparsecError l))        Just (Right (n, v)) -> case Ae.fromJSON v of-          Ae.Error e   -> Just (Left (FromJSONError e))+          Ae.Error e   -> Just (Left (FromJSONError v e))           Ae.Success a -> Just (Right (n, a)) {-# INLINABLE decodeL #-} +--------------------------------------------------------------------------------++-- | Repeteadly try to parse raw JSON bytes into @a@ values, reporting any+-- 'I.DecodingError's downstream as they happen.+loopL+  :: (Monad m, Ae.FromJSON a)+  => Attoparsec.Parser B.ByteString Ae.Value+  -> (Pipes.Producer B.ByteString m r -> Pipes.Producer B.ByteString m r)+  -- ^ In case of 'AttoparsecError', this function will be called to modify+  -- the leftovers 'Pipes.Producer' before using it.+  --+  -- Ideally you will want to drop everything until the beginning of the next+  -- JSON element. This is easy to accomplish if there is a clear whitespace+  -- delimiter between the JSON elements, such as a newline (i.e.,+  -- @'Pipes.ByteString.drop' 1 . 'Pipes.ByteString.dropWhile' (/= 0xA)@).+  -- However, it can be hard to do correctly is there is no such delimiter.+  -- Skipping the first character (i.e., @'Pipes.ByteString.drop' 1@) should be+  -- sufficient in most cases, but not when parsing recursive data structures+  -- because you can accidentally parse a child in its parent's stead.+  --+  -- Notice that unless you advance the 'Pipes.Producer' somehow, 'loopL' will+  -- never terminate.+  -> Pipes.Producer B.ByteString m r+  -- ^ Raw JSON input.+  -> Pipes.Proxy x' x () (Either DecodingError (Int, a)) m r+{-# INLINABLE loopL #-}+loopL parser fp = fix $ \k p0 -> do+   (ye, p1) <- lift (S.runStateT (decodeL parser) p0)+   case ye of+      Just (Left e@AttoparsecError{}) -> do+         Pipes.yield (Left e)+         k (fp p1)+      Just ea -> Pipes.yield ea >> k p1+      Nothing -> lift (Pipes.next p1) >>= \case+         Left r -> pure r+         Right _ -> error "Pipes.Aeson.Internal.loopL: impossible"  -------------------------------------------------------------------------------- -- Internal stuff
src/Pipes/Aeson/Unchecked.hs view
@@ -12,9 +12,11 @@     -- * Decoding   , decode   , decoded+  , loop     -- ** Including lenghts   , decodeL   , decodedL+  , loopL   ) where  import           Control.Monad        (liftM)@@ -30,8 +32,9 @@  -- | 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 -> Producer' B.ByteString m ()-encode = PB.fromLazy . Ae.encode+-- encode :: (Monad m, Ae.ToJSON a) => a -> Producer' B.ByteString m ()+encode :: (Monad m, Ae.ToJSON a) => a -> Proxy x' x () PB.ByteString m ()+encode a = PB.fromLazy (Ae.encode a) {-# INLINABLE encode #-} {-# RULES "p >-> for cat encode" forall p .     p >-> for cat encode = for p (\a -> PB.fromLazy (Ae.encode a))@@ -93,6 +96,57 @@     {-# INLINE _encode #-} {-# INLINABLE decodedL #-} ++-- | Repeteadly try to parse raw JSON bytes into @a@ values, reporting any+-- 'I.DecodingError's downstream as they happen.+loop+  :: (Monad m, Ae.FromJSON a)+  => (Pipes.Producer B.ByteString m r -> Pipes.Producer B.ByteString m r)+  -- ^ In case of 'I.AttoparsecError', this function will be called to modify+  -- the leftovers 'Pipes.Producer' before using it.+  --+  -- Ideally you will want to drop everything until the beginning of the next+  -- JSON element. This is easy to accomplish if there is a clear whitespace+  -- delimiter between the JSON elements, such as a newline (i.e.,+  -- @'Pipes.ByteString.drop' 1 . 'Pipes.ByteString.dropWhile' (/= 0xA)@).+  -- However, it can be hard to do correctly is there is no such delimiter.+  -- Skipping the first character (i.e., @'Pipes.ByteString.drop' 1@) should be+  -- sufficient in most cases, but not when parsing recursive data structures+  -- because you can accidentally parse a child in its parent's stead.+  --+  -- Notice that unless you advance the 'Pipes.Producer' somehow, 'loop'+  -- will never terminate.+  -> Pipes.Producer B.ByteString m r+  -- ^ Raw JSON input.+  -> Pipes.Producer' (Either I.DecodingError a) m r+{-# INLINABLE loop #-}+loop fp p0 = Pipes.for (loopL fp p0) (Pipes.yield . fmap snd)++-- | Like 'loop', except it also outputs the length of JSON input that was+-- consumed in order to obtain the value, not including the length of whitespace+-- before nor after the parsed JSON input.+loopL+  :: (Monad m, Ae.FromJSON a)+  => (Pipes.Producer B.ByteString m r -> Pipes.Producer B.ByteString m r)+  -- ^ In case of 'I.AttoparsecError', this function will be called to modify+  -- the leftovers 'Pipes.Producer' before using it.+  --+  -- Ideally you will want to drop everything until the beginning of the next+  -- JSON element. This is easy to accomplish if there is a clear whitespace+  -- delimiter between the JSON elements, such as a newline (i.e.,+  -- @'Pipes.ByteString.drop' 1 . 'Pipes.ByteString.dropWhile' (/= 0xA)@).+  -- However, it can be hard to do correctly is there is no such delimiter.+  -- Skipping the first character (i.e., @'Pipes.ByteString.drop' 1@) should be+  -- sufficient in most cases, but not when parsing recursive data structures+  -- because you can accidentally parse a child in its parent's stead.+  --+  -- Notice that unless you advance the 'Pipes.Producer' somehow, 'loopL'+  -- will never terminate.+  -> Pipes.Producer B.ByteString m r+  -- ^ Raw JSON input.+  -> Pipes.Proxy x' x () (Either I.DecodingError (Int, a)) m r+{-# INLINABLE loopL #-}+loopL = I.loopL Ae.value'  -------------------------------------------------------------------------------- -- Internal tools --------------------------------------------------------------