pipes-aeson-0.4.1.8/0000755000000000000000000000000013057556173012307 5ustar0000000000000000pipes-aeson-0.4.1.8/Setup.hs0000644000000000000000000000005613057556173013744 0ustar0000000000000000import Distribution.Simple main = defaultMain pipes-aeson-0.4.1.8/LICENSE0000644000000000000000000000277513057556173013327 0ustar0000000000000000Copyright (c) 2013-2014, Renzo Carbonara All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Renzo Carbonara nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pipes-aeson-0.4.1.8/README.md0000644000000000000000000000061213057556173013565 0ustar0000000000000000# pipes-aeson Utilities to encode and decode streams of **JSON** values using **Pipes** and **Aeson**. Check the source or rendered Haddocks for documentation. This code is licensed under the terms of the so called **3-clause BSD license**. Read the file named ``LICENSE`` found in this same directory for details. See the ``PEOPLE`` file to learn about the people involved in this effort. pipes-aeson-0.4.1.8/changelog.md0000644000000000000000000000277313057556173014571 0ustar0000000000000000# 0.4.1.8 * Remove upper bound dependencies on everything but `base`. # 0.4.1.7 * Raise upper bound dependency on `aeson`. * Raise upper bound dependency on `pipes`. # 0.4.1.6 * Raise upper bound dependency on `aeson`. * Raise upper bound dependency on `transformers`. # 0.4.1.5 * Raise upper bound dependency on `aeson`. # 0.4.1.4 * Raise upper bound dependency on `aeson`. # 0.4.1.3 * Raise upper bound dependency on `attoparsec`. # 0.4.1.2 * Raise upper bound dependency on `aeson`. # 0.4.1.1 * Raise upper bound dependency on `pipes-bytestring`. # 0.4.1 * Raise upper bound dependency on `attoparsec`. # 0.4 * Remove `Pipes.Aeson.encode` in favour of `encodeObject` and `encodeArray`. * `decode` and `decodeL` now return `Nothing` on end of input, instead of failing with a `DecodingError`. This follows the approach taken by `pipes-attoparsec-0.5`. * Solved quadratic time complexity issue when decoding (#10). * Depend on `pipes-attoparsec-0.5.*`. * Raise upper bound for `transformers` to `0.4.*`. # 0.3.0 * API revamped to be compatible with `pipes-parse-3.0.*`. * Renamed `Pipes.Aeson.Unsafe` module to `Pipes.Aeson.Unchecked`. # 0.2.1 * Generalize `encode` from `Producer` to `Producer'`. * Depend on newer versions of `aeson`, `attoparsec`, `pipes-attoparsec`. # 0.2.0 * Version compatible with `pipes-4.0.0` and `pipes-parse-2.0.0`. * API radically changed. Removed `parseValue`, `fromValue` and `TopLevelValue`. Other things renamed. # 0.1.0.0 * First version. pipes-aeson-0.4.1.8/pipes-aeson.cabal0000644000000000000000000000260313057556173015517 0ustar0000000000000000name: pipes-aeson version: 0.4.1.8 license: BSD3 license-file: LICENSE copyright: Copyright (c) Renzo Carbonara 2013-2017 author: Renzo Carbonara maintainer: renzocarbonaraλgmail.com 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: Utilities to encode and decode Pipes streams of JSON. . See the @changelog.md@ file in the source distribution to learn about any important changes between version. source-repository head type: git location: git://github.com/k0001/pipes-aeson.git library hs-source-dirs: src exposed-modules: Pipes.Aeson Pipes.Aeson.Unchecked other-modules: Pipes.Aeson.Internal build-depends: aeson (>=0.6.1) , attoparsec (>=0.10) , base (>=4.5 && <5.0) , pipes (>=4.1) , pipes-attoparsec (>=0.5) , pipes-bytestring (>=2.0) , pipes-parse (>=3.0.1) , bytestring (>=0.9.2.1) , transformers (>=0.2) ghc-options: -Wall -O2 pipes-aeson-0.4.1.8/PEOPLE0000644000000000000000000000040713057556173013217 0ustar0000000000000000The following people have participated in creating this library, either by directly contributing code or by providing thoughtful input in discussions about the library design. Renzo Carbonara Michael Thompson David Flemström Gabriel Gonzalez Pierre Radermecker pipes-aeson-0.4.1.8/src/0000755000000000000000000000000013057556173013076 5ustar0000000000000000pipes-aeson-0.4.1.8/src/Pipes/0000755000000000000000000000000013057556173014156 5ustar0000000000000000pipes-aeson-0.4.1.8/src/Pipes/Aeson.hs0000644000000000000000000001510513057556173015561 0ustar0000000000000000{-# LANGUAGE RankNTypes #-} -- | This module allows you to encode and decode JSON values flowing downstream -- through Pipes streams. -- -- This module builds on top of the @aeson@, @pipes@ and @pipes-parse@ -- libraries, and assumes you know how to use them. Please read the examples -- in "Pipes.Parse.Tutorial" to understand how to use these functions. -- -- In this module, the following type synonym compatible with the @lens@, -- @lens-family@ and @lens-family-core@ libraries is used but not exported: -- -- @ -- type Lens' s a = forall f . 'Functor' f => (a -> f a) -> (s -> f s) -- @ module Pipes.Aeson ( -- * Encoding -- $encoding encodeArray , encodeObject -- * Decoding -- $decoding , decode , decoded -- ** Including lengths , decodeL , decodedL -- * Types , I.DecodingError(..) ) where import Control.Monad (liftM) import qualified Data.Aeson as Ae import qualified Data.ByteString.Char8 as B import Pipes import qualified Pipes.Aeson.Internal as I import qualified Pipes.Aeson.Unchecked as U import qualified Pipes.Parse as Pipes -------------------------------------------------------------------------------- -- $encoding -- -- Encode 'Ae.Array' or 'Ae.Object' values as JSON and send them downstream, -- possibly in more than one 'B.ByteString' chunk. -- -- /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. -- -- | Encode an 'Ae.Object' as JSON and send it downstream, -- -- /Hint:/ You can easily turn this 'Producer'' into a 'Pipe' that encodes -- 'Ae.Object' values as JSON as they flow downstream using: -- -- @ -- 'for' 'cat' 'encodeObject' :: 'Monad' m => 'Pipe' 'Ae.Object' 'B.ByteString' m r -- @ encodeObject :: Monad m => Ae.Object -> Producer' B.ByteString m () encodeObject = U.encode {-# INLINABLE encodeObject #-} {-# RULES "p >-> for cat encodeObject" forall p . p >-> for cat encodeObject = for p encodeObject #-} -- | Encode an 'Ae.Array' as JSON and send it downstream, -- -- /Hint:/ You can easily turn this 'Producer'' into a 'Pipe' that encodes -- 'Ae.Array' values as JSON as they flow downstream using: -- -- @ -- 'for' 'cat' 'encodeArray' :: 'Monad' m => 'Pipe' 'Ae.Array' 'B.ByteString' m r -- @ encodeArray :: Monad m => Ae.Array -> Producer' B.ByteString m () encodeArray = U.encode {-# INLINABLE encodeArray #-} {-# RULES "p >-> for cat encodeArray" forall p . p >-> for cat encodeArray = for p encodeArray #-} -------------------------------------------------------------------------------- -- $decoding -- -- Decoding JSON as a Haskell value involves two different steps: -- -- * Parsing a raw JSON 'B.ByteString' into an 'Ae.Object' or an 'Ae.Array'. -- -- * Converting the obtained 'Ae.Object' or 'Ae.Array' to the desired -- 'Ae.FromJSON' instance. -- -- Any of those steps can fail, in which case a 'I.DecodingError' will report -- the precise error and at which step it happened. -- | Decodes an 'Ae.Object' or 'Ae.Array' JSON value from the underlying state. -- -- It returns 'Nothing' if the underlying 'Producer' is exhausted, otherwise -- it returns either the decoded entity or a 'I.DecodingError' in case of error. -- -- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level -- entities, which is why this 'Pipes.Parser' restricts its output to them. If -- you prefer to ignore the standard and decode any 'Ae.Value', then use -- 'U.decode' from the "Pipes.Aeson.Unchecked" module. decode :: (Monad m, Ae.FromJSON a) => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError a)) decode = fmap (fmap snd) `liftM` decodeL {-# INLINABLE decode #-} -- | Like 'decode', except it also returns 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. decodeL :: (Monad m, Ae.FromJSON a) => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError (Int, a))) decodeL = I.decodeL Ae.json' {-# INLINABLE decodeL #-} -- | /Improper lens/ that turns a stream of raw JSON input into a stream of -- 'Ae.FromJSON' and back. -- -- By /improper lens/ we mean that in practice you can't expect the -- /Monad Morphism Laws/ to be true when using 'decoded' with -- 'Control.Lens.zoom'. -- -- @ -- 'Control.Lens.zoom' 'decoded' ('return' r) /= 'return' r -- 'Control.Lens.zoom' 'decoded' (m >>= k) /= 'Control.Lens.zoom' m >>= 'Control.Lens.zoom' . f -- @ -- -- /Note:/ The JSON RFC-4627 standard only allows arrays or objects as top-level -- entities, which is why this function restricts its stream values to them. If -- you prefer to ignore the standard and encode or decode any 'Ae.Value', then -- use 'U.decoded' from the "Pipes.Aeson.Unchecked" module. decoded :: (Monad m, Ae.FromJSON a, Ae.ToJSON a) => (Ae.Value -> Either Ae.Object Ae.Array) -- ^ A witness that @a@ can be represented either as an 'Ae.Object' or as -- an 'Ae.Array'. The passed in 'Ae.Value' is @'Ae.toJSON' a@ -> Lens' (Producer B.ByteString m r) (Producer a m (Either (I.DecodingError, Producer B.ByteString m r) r)) decoded f k p0 = fmap _encode (k (I.consecutively decode p0)) where _encode = \p -> do er <- for p (\a -> either encodeObject encodeArray (f (Ae.toJSON a))) case er of Left (_, p') -> p' Right r -> return r {-# INLINABLE decoded #-} -- | Like 'decoded', except it also tags each decoded entity with the length of -- JSON input that was consumed in order to obtain the value, not including the -- length of whitespace between each parsed JSON input. decodedL :: (Monad m, Ae.FromJSON a, Ae.ToJSON a) => (Ae.Value -> Either Ae.Object Ae.Array) -- ^ A witness that @a@ can be represented either as an 'Ae.Object' or as -- an 'Ae.Array'. The passed in 'Ae.Value' is @'Ae.toJSON' a@ -> Lens' (Producer B.ByteString m r) (Producer (Int, a) m (Either (I.DecodingError, Producer B.ByteString m r) r)) decodedL f k p0 = fmap _encode (k (I.consecutively decode p0)) where _encode = \p -> do er <- for p (\(_, a) -> either encodeObject encodeArray (f (Ae.toJSON a))) case er of Left (_, p') -> p' Right r -> return r {-# INLINABLE decodedL #-} -------------------------------------------------------------------------------- -- Internal tools -------------------------------------------------------------- type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s pipes-aeson-0.4.1.8/src/Pipes/Aeson/0000755000000000000000000000000013057556173015223 5ustar0000000000000000pipes-aeson-0.4.1.8/src/Pipes/Aeson/Internal.hs0000644000000000000000000001056013057556173017335 0ustar0000000000000000{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE RankNTypes #-} -- | This module provides internal utilities and it is likely -- to be modified in backwards-incompatible ways in the future. -- -- Use the stable API exported by the "Pipes.Aeson" module instead. module Pipes.Aeson.Internal ( DecodingError(..) , consecutively , decodeL ) 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 Pipes import qualified Pipes.Attoparsec as PA import qualified Pipes.Parse as Pipes -------------------------------------------------------------------------------- -- | An error while decoding a JSON value. 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 -- '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 -- 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. consecutively :: (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) 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 (yield bs >> p1) case mea of Just (Right a) -> yield a >> step p2 Just (Left e) -> return (Left (e, p2)) Nothing -> error "Pipes.Aeson.Internal.consecutively: impossible" {-# INLINABLE consecutively #-} -- | Decodes a 'Ae.FromJSON' value from the underlying state using the given -- 'Attoparsec.Parser' in order to obtain an 'Ae.Value' first. -- -- It returns 'Nothing' if the underlying 'Producer' is exhausted, otherwise -- it returns either the decoded entity or a 'I.DecodingError' in case of error. decodeL :: (Monad m, Ae.FromJSON a) => Attoparsec.Parser B.ByteString Ae.Value -> Pipes.Parser B.ByteString m (Maybe (Either DecodingError (Int, a))) -- ^ decodeL parser = do mev <- PA.parseL parser return $ case mev of 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.Success a -> Just (Right (n, a)) {-# INLINABLE decodeL #-} -------------------------------------------------------------------------------- -- Internal stuff -- | Like 'Pipes.next', except it skips leading whitespace and 'B.null' chunks. nextSkipBlank :: (Monad m) => Producer B.ByteString m r -> m (Either r (B.ByteString, Producer B.ByteString m r)) nextSkipBlank = go where go p0 = do x <- next p0 case x of Left _ -> return x Right (a,p1) -> do let a' = B.dropWhile B.isSpaceWord8 a if B.null a' then go p1 else return (Right (a', p1)) {-# INLINABLE nextSkipBlank #-} pipes-aeson-0.4.1.8/src/Pipes/Aeson/Unchecked.hs0000644000000000000000000000670613057556173017461 0ustar0000000000000000{-# LANGUAGE RankNTypes #-} -- | This module exports facilities similar to those exported by the -- "Pipes.Aeson" module, except they do not restrict the 'Ae.Value's -- that might be encoded or decoded to be just valid top-level values. That is, -- not only 'Ae.Object's or 'Ae.Array's, according to to the RFC-4627 JSON -- standard. module Pipes.Aeson.Unchecked ( -- * Encoding encode -- * Decoding , decode , decoded -- ** Including lenghts , decodeL , decodedL ) where import Control.Monad (liftM) import qualified Data.Aeson as Ae import qualified Data.Aeson.Parser as Ae (value') import qualified Data.ByteString as B import Pipes import qualified Pipes.Aeson.Internal as I import qualified Pipes.ByteString as PB import qualified Pipes.Parse as Pipes -------------------------------------------------------------------------------- -- | 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 {-# INLINABLE encode #-} {-# RULES "p >-> for cat encode" forall p . p >-> for cat encode = for p (\a -> PB.fromLazy (Ae.encode a)) #-} -------------------------------------------------------------------------------- -- | Like 'Pipes.Aeson.decode', except it will decode any 'Ae.FromJSON' -- instance, not just 'Ae.Array' or 'Ae.Object'. decode :: (Monad m, Ae.FromJSON a) => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError a)) -- ^ decode = fmap (fmap snd) `liftM` decodeL {-# INLINABLE decode #-} -- | Like 'decode', except it also returns the length of JSON input that was -- consumed in order to obtain the value, not including the length of whitespace -- between each parsed JSON input. decodeL :: (Monad m, Ae.FromJSON a) => Pipes.Parser B.ByteString m (Maybe (Either I.DecodingError (Int, a))) -- ^ decodeL = I.decodeL Ae.value' {-# INLINABLE decodeL #-} -- | Like 'Pipes.Aeson.decoded', except it will decode and decode any -- 'Ae.FromJSON' and 'Ae.ToJSON' instance, not just 'Ae.Array' or 'Ae.Object'. decoded :: (Monad m, Ae.FromJSON a, Ae.ToJSON a) => Lens' (Producer B.ByteString m r) (Producer a m (Either (I.DecodingError, Producer B.ByteString m r) r)) -- ^ decoded k p = fmap _encode (k (I.consecutively decode p)) where _encode = \p0 -> do er <- for p0 (\a -> encode a) case er of Left (_, p1) -> p1 Right r -> return r {-# INLINE _encode #-} {-# INLINABLE decoded #-} -- | Like 'decoded', except it also tags each decoded entity with the length of -- JSON input that was consumed in order to obtain the value, not including the -- length of whitespace between each parsed JSON input. decodedL :: (Monad m, Ae.FromJSON a, Ae.ToJSON a) => Lens' (Producer B.ByteString m r) (Producer (Int, a) m (Either (I.DecodingError, Producer B.ByteString m r) r)) -- ^ decodedL k p = fmap _encode (k (I.consecutively decodeL p)) where _encode = \p0 -> do er <- for p0 (\(_, a) -> encode a) case er of Left (_, p1) -> p1 Right r -> return r {-# INLINE _encode #-} {-# INLINABLE decodedL #-} -------------------------------------------------------------------------------- -- Internal tools -------------------------------------------------------------- type Lens' s a = forall f . Functor f => (a -> f a) -> s -> f s