avro 0.3.3.1 → 0.3.4.0
raw patch · 13 files changed
+907/−220 lines, 13 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Data.Avro.Decode: instance (Data.Avro.Decode.GetAvro a, GHC.Classes.Ord a) => Data.Avro.Decode.GetAvro (Data.Set.Internal.Set a)
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro Data.Avro.Decode.ContainerHeader
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro Data.ByteString.Internal.ByteString
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro Data.ByteString.Lazy.Internal.ByteString
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro Data.Text.Internal.Text
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro GHC.Base.String
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro GHC.Int.Int32
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro GHC.Int.Int64
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro GHC.Types.Bool
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro GHC.Types.Double
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro GHC.Types.Float
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro a => Data.Avro.Decode.GetAvro (Data.Vector.Vector a)
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro a => Data.Avro.Decode.GetAvro (GHC.Arr.Array GHC.Types.Int a)
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro a => Data.Avro.Decode.GetAvro (GHC.Base.Maybe a)
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro a => Data.Avro.Decode.GetAvro [a]
- Data.Avro.Decode: instance Data.Avro.Decode.GetAvro ty => Data.Avro.Decode.GetAvro (Data.Map.Internal.Map Data.Text.Internal.Text ty)
- Data.Avro.Encode: instance Data.Avro.Encode.EncodeAvro (Data.Avro.Types.Value Data.Avro.Schema.Type)
- Data.Avro.Schema: instance Data.Aeson.Types.ToJSON.ToJSON (Data.Avro.Types.Value Data.Avro.Schema.Type)
- Data.Avro.Types: Array :: (Vector (Value f)) -> Value f
- Data.Avro.Types: Boolean :: !Bool -> Value f
- Data.Avro.Types: Bytes :: {-# UNPACK #-} !ByteString -> Value f
- Data.Avro.Types: Double :: {-# UNPACK #-} !Double -> Value f
- Data.Avro.Types: Enum :: f -> {-# UNPACK #-} !Int -> Text -> Value f
- Data.Avro.Types: Fixed :: f -> {-# UNPACK #-} !ByteString -> Value f
- Data.Avro.Types: Float :: {-# UNPACK #-} !Float -> Value f
- Data.Avro.Types: Int :: {-# UNPACK #-} !Int32 -> Value f
- Data.Avro.Types: Long :: {-# UNPACK #-} !Int64 -> Value f
- Data.Avro.Types: Map :: (HashMap Text (Value f)) -> Value f
- Data.Avro.Types: Null :: Value f
- Data.Avro.Types: Record :: f -> (HashMap Text (Value f)) -> Value f
- Data.Avro.Types: String :: {-# UNPACK #-} !Text -> Value f
- Data.Avro.Types: Union :: (NonEmpty f) -> f -> (Value f) -> Value f
- Data.Avro.Types: data Value f
- Data.Avro.Types: instance GHC.Classes.Eq f => GHC.Classes.Eq (Data.Avro.Types.Value f)
- Data.Avro.Types: instance GHC.Show.Show f => GHC.Show.Show (Data.Avro.Types.Value f)
+ Data.Avro.Decode.Get: ContainerHeader :: !ByteString -> ByteString -> Get ByteString -> !Schema -> ContainerHeader
+ Data.Avro.Decode.Get: [containedSchema] :: ContainerHeader -> !Schema
+ Data.Avro.Decode.Get: [decompress] :: ContainerHeader -> ByteString -> Get ByteString
+ Data.Avro.Decode.Get: [syncBytes] :: ContainerHeader -> !ByteString
+ Data.Avro.Decode.Get: class GetAvro a
+ Data.Avro.Decode.Get: data ContainerHeader
+ Data.Avro.Decode.Get: getArray :: GetAvro ty => Get [ty]
+ Data.Avro.Decode.Get: getAvro :: GetAvro a => Get a
+ Data.Avro.Decode.Get: getBoolean :: Get Bool
+ Data.Avro.Decode.Get: getBytes :: Get ByteString
+ Data.Avro.Decode.Get: getCodec :: Monad m => Maybe ByteString -> m (ByteString -> m ByteString)
+ Data.Avro.Decode.Get: getDouble :: Get Double
+ Data.Avro.Decode.Get: getFloat :: Get Float
+ Data.Avro.Decode.Get: getInt :: Get Int32
+ Data.Avro.Decode.Get: getLong :: Get Int64
+ Data.Avro.Decode.Get: getMap :: GetAvro ty => Get (Map Text ty)
+ Data.Avro.Decode.Get: getString :: Get Text
+ Data.Avro.Decode.Get: getZigZag :: (Bits i, Integral i, DecodeRaw i) => Get i
+ Data.Avro.Decode.Get: instance (Data.Avro.Decode.Get.GetAvro a, GHC.Classes.Ord a) => Data.Avro.Decode.Get.GetAvro (Data.Set.Internal.Set a)
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro Data.Avro.Decode.Get.ContainerHeader
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro Data.ByteString.Internal.ByteString
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro Data.ByteString.Lazy.Internal.ByteString
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro Data.Text.Internal.Text
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro GHC.Base.String
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro GHC.Int.Int32
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro GHC.Int.Int64
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro GHC.Types.Bool
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro GHC.Types.Double
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro GHC.Types.Float
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro a => Data.Avro.Decode.Get.GetAvro (Data.Vector.Vector a)
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro a => Data.Avro.Decode.Get.GetAvro (GHC.Arr.Array GHC.Types.Int a)
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro a => Data.Avro.Decode.Get.GetAvro (GHC.Base.Maybe a)
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro a => Data.Avro.Decode.Get.GetAvro [a]
+ Data.Avro.Decode.Get: instance Data.Avro.Decode.Get.GetAvro ty => Data.Avro.Decode.Get.GetAvro (Data.Map.Internal.Map Data.Text.Internal.Text ty)
+ Data.Avro.Decode.Get: nrSyncBytes :: Integral sb => sb
+ Data.Avro.Decode.Get: sFromIntegral :: forall a b m. (Monad m, Bounded a, Bounded b, Integral a, Integral b) => a -> m b
+ Data.Avro.Decode.Lazy: class GetAvro a
+ Data.Avro.Decode.Lazy: decodeAvro :: Schema -> ByteString -> LazyValue Type
+ Data.Avro.Decode.Lazy: decodeContainer :: forall a. FromAvro a => ByteString -> [Either String a]
+ Data.Avro.Decode.Lazy: decodeContainer' :: forall a. FromAvro a => ByteString -> Either String [[Either String a]]
+ Data.Avro.Decode.Lazy: decodeContainerWithSchema :: FromAvro a => Schema -> ByteString -> [Either String a]
+ Data.Avro.Decode.Lazy: decodeContainerWithSchema' :: FromAvro a => Schema -> ByteString -> Either String [[Either String a]]
+ Data.Avro.Decode.Lazy: getAvro :: GetAvro a => Get a
+ Data.Avro.Decode.Lazy: getAvroOf :: Schema -> ByteString -> (ByteString, LazyValue Type)
+ Data.Avro.Decode.Lazy: getContainerValues :: ByteString -> Either String (Schema, [[LazyValue Type]])
+ Data.Avro.Decode.Lazy: getContainerValuesWith :: (Schema -> ByteString -> (ByteString, LazyValue Type)) -> ByteString -> Either String (Schema, [[LazyValue Type]])
+ Data.Avro.Decode.Lazy.Convert: fromStrictValue :: Value f -> LazyValue f
+ Data.Avro.Decode.Lazy.Convert: toStrictValue :: LazyValue f -> Either String (Value f)
+ Data.Avro.Decode.Lazy.Deconflict: deconflict :: Schema -> Schema -> LazyValue Type -> LazyValue Type
+ Data.Avro.Decode.Lazy.LazyValue: Array :: (Vector (LazyValue f)) -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Boolean :: Bool -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Bytes :: ByteString -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Double :: Double -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Enum :: f -> Int -> Text -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Error :: !String -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Fixed :: f -> ByteString -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Float :: Float -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Int :: Int32 -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Long :: Int64 -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Map :: (HashMap Text (LazyValue f)) -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Null :: LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Record :: f -> (HashMap Text (LazyValue f)) -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: String :: Text -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: Union :: (NonEmpty f) -> f -> (LazyValue f) -> LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: data LazyValue f
+ Data.Avro.Decode.Lazy.LazyValue: instance GHC.Classes.Eq f => GHC.Classes.Eq (Data.Avro.Decode.Lazy.LazyValue.LazyValue f)
+ Data.Avro.Decode.Lazy.LazyValue: instance GHC.Show.Show f => GHC.Show.Show (Data.Avro.Decode.Lazy.LazyValue.LazyValue f)
+ Data.Avro.Deriving.NormSchema: extractDerivables :: Schema -> [Schema]
+ Data.Avro.Deriving.NormSchema: getTypes :: Type -> [(TypeName, Type)]
+ Data.Avro.Deriving.NormSchema: normSchema :: Schema -> State (Map TypeName Schema) Schema
+ Data.Avro.EitherN: E3_1 :: a -> Either3 a b c
+ Data.Avro.EitherN: E3_2 :: b -> Either3 a b c
+ Data.Avro.EitherN: E3_3 :: c -> Either3 a b c
+ Data.Avro.EitherN: E4_1 :: a -> Either4 a b c d
+ Data.Avro.EitherN: E4_2 :: b -> Either4 a b c d
+ Data.Avro.EitherN: E4_3 :: c -> Either4 a b c d
+ Data.Avro.EitherN: E4_4 :: d -> Either4 a b c d
+ Data.Avro.EitherN: E5_1 :: a -> Either5 a b c d e
+ Data.Avro.EitherN: E5_2 :: b -> Either5 a b c d e
+ Data.Avro.EitherN: E5_3 :: c -> Either5 a b c d e
+ Data.Avro.EitherN: E5_4 :: d -> Either5 a b c d e
+ Data.Avro.EitherN: E5_5 :: e -> Either5 a b c d e
+ Data.Avro.EitherN: data Either3 a b c
+ Data.Avro.EitherN: data Either4 a b c d
+ Data.Avro.EitherN: data Either5 a b c d e
+ Data.Avro.EitherN: instance (Data.Avro.FromAvro.FromAvro a, Data.Avro.FromAvro.FromAvro b, Data.Avro.FromAvro.FromAvro c) => Data.Avro.FromAvro.FromAvro (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance (Data.Avro.FromAvro.FromAvro a, Data.Avro.FromAvro.FromAvro b, Data.Avro.FromAvro.FromAvro c, Data.Avro.FromAvro.FromAvro d) => Data.Avro.FromAvro.FromAvro (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance (Data.Avro.FromAvro.FromAvro a, Data.Avro.FromAvro.FromAvro b, Data.Avro.FromAvro.FromAvro c, Data.Avro.FromAvro.FromAvro d, Data.Avro.FromAvro.FromAvro e) => Data.Avro.FromAvro.FromAvro (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.EitherN: instance (Data.Avro.HasAvroSchema.HasAvroSchema a, Data.Avro.HasAvroSchema.HasAvroSchema b, Data.Avro.HasAvroSchema.HasAvroSchema c) => Data.Avro.HasAvroSchema.HasAvroSchema (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance (Data.Avro.HasAvroSchema.HasAvroSchema a, Data.Avro.HasAvroSchema.HasAvroSchema b, Data.Avro.HasAvroSchema.HasAvroSchema c, Data.Avro.HasAvroSchema.HasAvroSchema d) => Data.Avro.HasAvroSchema.HasAvroSchema (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance (Data.Avro.HasAvroSchema.HasAvroSchema a, Data.Avro.HasAvroSchema.HasAvroSchema b, Data.Avro.HasAvroSchema.HasAvroSchema c, Data.Avro.HasAvroSchema.HasAvroSchema d, Data.Avro.HasAvroSchema.HasAvroSchema e) => Data.Avro.HasAvroSchema.HasAvroSchema (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.EitherN: instance (Data.Avro.ToAvro.ToAvro a, Data.Avro.ToAvro.ToAvro b, Data.Avro.ToAvro.ToAvro c) => Data.Avro.ToAvro.ToAvro (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance (Data.Avro.ToAvro.ToAvro a, Data.Avro.ToAvro.ToAvro b, Data.Avro.ToAvro.ToAvro c, Data.Avro.ToAvro.ToAvro d) => Data.Avro.ToAvro.ToAvro (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance (Data.Avro.ToAvro.ToAvro a, Data.Avro.ToAvro.ToAvro b, Data.Avro.ToAvro.ToAvro c, Data.Avro.ToAvro.ToAvro d, Data.Avro.ToAvro.ToAvro e) => Data.Avro.ToAvro.ToAvro (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.EitherN: instance (GHC.Classes.Eq a, GHC.Classes.Eq b, GHC.Classes.Eq c) => GHC.Classes.Eq (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance (GHC.Classes.Eq a, GHC.Classes.Eq b, GHC.Classes.Eq c, GHC.Classes.Eq d) => GHC.Classes.Eq (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance (GHC.Classes.Eq a, GHC.Classes.Eq b, GHC.Classes.Eq c, GHC.Classes.Eq d, GHC.Classes.Eq e) => GHC.Classes.Eq (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.EitherN: instance (GHC.Classes.Ord a, GHC.Classes.Ord b, GHC.Classes.Ord c) => GHC.Classes.Ord (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance (GHC.Classes.Ord a, GHC.Classes.Ord b, GHC.Classes.Ord c, GHC.Classes.Ord d) => GHC.Classes.Ord (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance (GHC.Classes.Ord a, GHC.Classes.Ord b, GHC.Classes.Ord c, GHC.Classes.Ord d, GHC.Classes.Ord e) => GHC.Classes.Ord (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.EitherN: instance (GHC.Show.Show a, GHC.Show.Show b, GHC.Show.Show c) => GHC.Show.Show (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance (GHC.Show.Show a, GHC.Show.Show b, GHC.Show.Show c, GHC.Show.Show d) => GHC.Show.Show (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance (GHC.Show.Show a, GHC.Show.Show b, GHC.Show.Show c, GHC.Show.Show d, GHC.Show.Show e) => GHC.Show.Show (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.EitherN: instance GHC.Generics.Generic (Data.Avro.EitherN.Either3 a b c)
+ Data.Avro.EitherN: instance GHC.Generics.Generic (Data.Avro.EitherN.Either4 a b c d)
+ Data.Avro.EitherN: instance GHC.Generics.Generic (Data.Avro.EitherN.Either5 a b c d e)
+ Data.Avro.Encode: instance Data.Avro.Encode.EncodeAvro (Data.Avro.Types.Value.Value Data.Avro.Schema.Type)
+ Data.Avro.Schema: instance Data.Aeson.Types.ToJSON.ToJSON (Data.Avro.Types.Value.Value Data.Avro.Schema.Type)
+ Data.Avro.Schema: resultToEither :: Result b -> Either String b
+ Data.Avro.Types.Value: Array :: (Vector (Value f)) -> Value f
+ Data.Avro.Types.Value: Boolean :: !Bool -> Value f
+ Data.Avro.Types.Value: Bytes :: {-# UNPACK #-} !ByteString -> Value f
+ Data.Avro.Types.Value: Double :: {-# UNPACK #-} !Double -> Value f
+ Data.Avro.Types.Value: Enum :: f -> {-# UNPACK #-} !Int -> Text -> Value f
+ Data.Avro.Types.Value: Fixed :: f -> {-# UNPACK #-} !ByteString -> Value f
+ Data.Avro.Types.Value: Float :: {-# UNPACK #-} !Float -> Value f
+ Data.Avro.Types.Value: Int :: {-# UNPACK #-} !Int32 -> Value f
+ Data.Avro.Types.Value: Long :: {-# UNPACK #-} !Int64 -> Value f
+ Data.Avro.Types.Value: Map :: (HashMap Text (Value f)) -> Value f
+ Data.Avro.Types.Value: Null :: Value f
+ Data.Avro.Types.Value: Record :: f -> (HashMap Text (Value f)) -> Value f
+ Data.Avro.Types.Value: String :: {-# UNPACK #-} !Text -> Value f
+ Data.Avro.Types.Value: Union :: (NonEmpty f) -> f -> (Value f) -> Value f
+ Data.Avro.Types.Value: data Value f
+ Data.Avro.Types.Value: instance GHC.Classes.Eq f => GHC.Classes.Eq (Data.Avro.Types.Value.Value f)
+ Data.Avro.Types.Value: instance GHC.Show.Show f => GHC.Show.Show (Data.Avro.Types.Value.Value f)
Files
- avro.cabal +15/−7
- src/Data/Avro/Decode.hs +13/−187
- src/Data/Avro/Decode/Get.hs +222/−0
- src/Data/Avro/Decode/Lazy.hs +283/−0
- src/Data/Avro/Decode/Lazy/Convert.hs +45/−0
- src/Data/Avro/Decode/Lazy/Deconflict.hs +117/−0
- src/Data/Avro/Decode/Lazy/LazyValue.hs +27/−0
- src/Data/Avro/Deriving.hs +6/−1
- src/Data/Avro/EitherN.hs +104/−0
- src/Data/Avro/Schema.hs +9/−1
- src/Data/Avro/Types.hs +5/−24
- src/Data/Avro/Types/Value.hs +25/−0
- test/Avro/Decode/Lazy/ValuesSpec.hs +36/−0
avro.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: fb70ba948e5cb538713c28699ed1af9e5f81d4b7fcc982938ed9b462e7511448+-- hash: 8b98a8b89f40922a3fbe1ab0ebd5a8bef25531bd5d1571b3216bb3b3ed75bed3 name: avro-version: 0.3.3.1+version: 0.3.4.0 synopsis: Avro serialization support for Haskell description: Avro serialization and deserialization support for Haskell category: Data@@ -51,21 +51,28 @@ exposed-modules: Data.Avro Data.Avro.Decode+ Data.Avro.Decode.Get+ Data.Avro.Decode.Lazy+ Data.Avro.Decode.Lazy.Convert+ Data.Avro.Decode.Lazy.Deconflict+ Data.Avro.Decode.Lazy.LazyValue Data.Avro.DecodeRaw Data.Avro.Deconflict- Data.Avro.Encode Data.Avro.Deriving+ Data.Avro.Deriving.NormSchema+ Data.Avro.EitherN+ Data.Avro.Encode Data.Avro.EncodeRaw+ Data.Avro.FromAvro+ Data.Avro.HasAvroSchema Data.Avro.JSON Data.Avro.Schema+ Data.Avro.ToAvro Data.Avro.Types+ Data.Avro.Types.Value Data.Avro.Zag Data.Avro.Zig- Data.Avro.HasAvroSchema- Data.Avro.FromAvro- Data.Avro.ToAvro other-modules:- Data.Avro.Deriving.NormSchema Paths_avro hs-source-dirs: src@@ -113,6 +120,7 @@ Avro.Codec.NestedSpec Avro.Codec.TextSpec Avro.Codec.ZigZagSpec+ Avro.Decode.Lazy.ValuesSpec Avro.Deconflict.Reader Avro.Deconflict.Writer Avro.DeconflictSpec
src/Data/Avro/Decode.hs view
@@ -1,9 +1,9 @@-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-}-{-# LANGUAGE OverloadedStrings #-} module Data.Avro.Decode ( decodeAvro@@ -14,35 +14,36 @@ , GetAvro(..) ) where -import Prelude as P-import Control.Monad (replicateM,when) import qualified Codec.Compression.Zlib as Z+import Control.Monad (replicateM, when) import qualified Data.Aeson as A import qualified Data.Array as Array+import Data.Binary.Get (Get, runGetOrFail) import qualified Data.Binary.Get as G-import Data.Binary.Get (Get,runGetOrFail) import Data.Binary.IEEE754 as IEEE import Data.Bits-import qualified Data.ByteString.Lazy as BL import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Char8 as BC+import qualified Data.HashMap.Strict as HashMap import Data.Int import Data.List (foldl')-import qualified Data.List.NonEmpty as NE-import Data.Maybe+import qualified Data.List.NonEmpty as NE import qualified Data.Map as Map+import Data.Maybe import Data.Monoid ((<>))-import qualified Data.HashMap.Strict as HashMap import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.Vector as V+import Prelude as P +import Data.Avro.Decode.Get import Data.Avro.DecodeRaw+import Data.Avro.Schema as S+import qualified Data.Avro.Types as T import Data.Avro.Zag-import Data.Avro.Schema as S-import qualified Data.Avro.Types as T -- |Decode bytes into a 'Value' as described by Schema. decodeAvro :: Schema -> BL.ByteString -> Either String (T.Value Type)@@ -62,39 +63,6 @@ Left (_,_,s) -> Left s {-# INLINABLE decodeContainerWith #-} -data ContainerHeader = ContainerHeader- { syncBytes :: !BL.ByteString- , decompress :: BL.ByteString -> Get BL.ByteString- , containedSchema :: !Schema- }--nrSyncBytes :: Integral sb => sb-nrSyncBytes = 16--instance GetAvro ContainerHeader where- getAvro =- do magic <- getFixed avroMagicSize- when (BL.fromStrict magic /= avroMagicBytes)- (fail "Invalid magic number at start of container.")- metadata <- getMap :: Get (Map.Map Text BL.ByteString) -- avro.schema, avro.codec- sync <- BL.fromStrict <$> getFixed nrSyncBytes- codec <- getCodec (Map.lookup "avro.codec" metadata)- schema <- case Map.lookup "avro.schema" metadata of- Nothing -> fail "Invalid container object: no schema."- Just s -> case A.eitherDecode' s of- Left e -> fail ("Can not decode container schema: " <> e)- Right x -> return x- return ContainerHeader { syncBytes = sync, decompress = codec, containedSchema = schema }- where avroMagicSize :: Integral a => a- avroMagicSize = 4-- avroMagicBytes :: BL.ByteString- avroMagicBytes = BC.pack "Obj" <> BL.pack [1]-- getFixed :: Int -> Get ByteString- getFixed = G.getByteString-- getContainerWith :: (Schema -> Get a) -> Get (Schema, [[a]]) getContainerWith schemaToGet = do ContainerHeader {..} <- getAvro@@ -180,145 +148,3 @@ else do vs <- replicateM (fromIntegral blockLength) (go t) (vs:) <$> getBlocksOf t {-# INLINE getBlocksOf #-}--class GetAvro a where- getAvro :: Get a--instance GetAvro ty => GetAvro (Map.Map Text ty) where- getAvro = getMap-instance GetAvro Bool where- getAvro = getBoolean-instance GetAvro Int32 where- getAvro = getInt-instance GetAvro Int64 where- getAvro = getLong-instance GetAvro BL.ByteString where- getAvro = BL.fromStrict <$> getBytes-instance GetAvro ByteString where- getAvro = getBytes-instance GetAvro Text where- getAvro = getString-instance GetAvro Float where- getAvro = getFloat-instance GetAvro Double where- getAvro = getDouble-instance GetAvro String where- getAvro = Text.unpack <$> getString-instance GetAvro a => GetAvro [a] where- getAvro = getArray-instance GetAvro a => GetAvro (Maybe a) where- getAvro =- do t <- getLong- case t of- 0 -> return Nothing- 1 -> Just <$> getAvro- n -> fail $ "Invalid tag for expected {null,a} Avro union, received: " <> show n--instance GetAvro a => GetAvro (Array.Array Int a) where- getAvro =- do ls <- getAvro- return $ Array.listArray (0,length ls - 1) ls-instance GetAvro a => GetAvro (V.Vector a) where- getAvro = V.fromList <$> getAvro-instance (GetAvro a, Ord a) => GetAvro (Set.Set a) where- getAvro = Set.fromList <$> getAvro------------------------------------------------------------------------------------- Specialized Getters--getBoolean :: Get Bool-getBoolean =- do w <- G.getWord8- return (w == 0x01)---- |Get a 32-bit int (zigzag encoded, max of 5 bytes)-getInt :: Get Int32-getInt = getZigZag---- |Get a 64 bit int (zigzag encoded, max of 10 bytes)-getLong :: Get Int64-getLong = getZigZag---- |Get an zigzag encoded integral value consuming bytes till the msb is 0.-getZigZag :: (Bits i, Integral i, DecodeRaw i) => Get i-getZigZag = decodeRaw--getBytes :: Get ByteString-getBytes =- do w <- getLong- G.getByteString (fromIntegral w)--getString :: Get Text-getString = Text.decodeUtf8 <$> getBytes---- a la Java:--- Bit 31 (the bit that is selected by the mask 0x80000000) represents the--- sign of the floating-point number. Bits 30-23 (the bits that are--- selected by the mask 0x7f800000) represent the exponent. Bits 22-0 (the--- bits that are selected by the mask 0x007fffff) represent the--- significand (sometimes called the mantissa) of the floating-point--- number.------ If the argument is positive infinity, the result is 0x7f800000.------ If the argument is negative infinity, the result is 0xff800000.------ If the argument is NaN, the result is 0x7fc00000.-getFloat :: Get Float-getFloat = IEEE.wordToFloat <$> G.getWord32le---- As in Java:--- Bit 63 (the bit that is selected by the mask 0x8000000000000000L)--- represents the sign of the floating-point number. Bits 62-52 (the bits--- that are selected by the mask 0x7ff0000000000000L) represent the--- exponent. Bits 51-0 (the bits that are selected by the mask--- 0x000fffffffffffffL) represent the significand (sometimes called the--- mantissa) of the floating-point number.------ If the argument is positive infinity, the result is--- 0x7ff0000000000000L.------ If the argument is negative infinity, the result is--- 0xfff0000000000000L.------ If the argument is NaN, the result is 0x7ff8000000000000L-getDouble :: Get Double-getDouble = IEEE.wordToDouble <$> G.getWord64le------------------------------------------------------------------------------------- Complex AvroValue Getters---- getRecord :: GetAvro ty => Get (AvroValue ty)--- getRecord = getAvro--getArray :: GetAvro ty => Get [ty]-getArray =- do nr <- getLong- if- | nr == 0 -> return []- | nr < 0 ->- do _len <- getLong- rs <- replicateM (fromIntegral (abs nr)) getAvro- (rs <>) <$> getArray- | otherwise ->- do rs <- replicateM (fromIntegral nr) getAvro- (rs <>) <$> getArray--getMap :: GetAvro ty => Get (Map.Map Text ty)-getMap = go Map.empty- where- go acc =- do nr <- getLong- if nr == 0- then return acc- else do m <- Map.fromList <$> replicateM (fromIntegral nr) getKVs- go (Map.union m acc)- getKVs = (,) <$> getString <*> getAvro---- Safe-ish from integral-sFromIntegral :: forall a b m. (Monad m, Bounded a, Bounded b, Integral a, Integral b) => a -> m b-sFromIntegral a- | aI > fromIntegral (maxBound :: b) ||- aI < fromIntegral (minBound :: b) = fail "Integral overflow."- | otherwise = return (fromIntegral a)- where aI = fromIntegral a :: Integer
+ src/Data/Avro/Decode/Get.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Data.Avro.Decode.Get+where++import qualified Codec.Compression.Zlib as Z+import Control.Monad (foldM, replicateM, when)+import qualified Data.Aeson as A+import qualified Data.Array as Array+import Data.Binary.Get (Get, runGetOrFail)+import qualified Data.Binary.Get as G+import Data.Binary.IEEE754 as IEEE+import Data.Bits+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Lazy.Char8 as BC+import qualified Data.HashMap.Strict as HashMap+import Data.Int+import Data.List (foldl')+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as Map+import Data.Maybe+import Data.Monoid ((<>))+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Vector as V+import Prelude as P++import Data.Avro.DecodeRaw+import Data.Avro.Schema as S+import Data.Avro.Zag++class GetAvro a where+ getAvro :: Get a++instance GetAvro ty => GetAvro (Map.Map Text ty) where+ getAvro = getMap+instance GetAvro Bool where+ getAvro = getBoolean+instance GetAvro Int32 where+ getAvro = getInt+instance GetAvro Int64 where+ getAvro = getLong+instance GetAvro BL.ByteString where+ getAvro = BL.fromStrict <$> getBytes+instance GetAvro ByteString where+ getAvro = getBytes+instance GetAvro Text where+ getAvro = getString+instance GetAvro Float where+ getAvro = getFloat+instance GetAvro Double where+ getAvro = getDouble+instance GetAvro String where+ getAvro = Text.unpack <$> getString+instance GetAvro a => GetAvro [a] where+ getAvro = getArray+instance GetAvro a => GetAvro (Maybe a) where+ getAvro =+ do t <- getLong+ case t of+ 0 -> return Nothing+ 1 -> Just <$> getAvro+ n -> fail $ "Invalid tag for expected {null,a} Avro union, received: " <> show n++instance GetAvro a => GetAvro (Array.Array Int a) where+ getAvro =+ do ls <- getAvro+ return $ Array.listArray (0,length ls - 1) ls+instance GetAvro a => GetAvro (V.Vector a) where+ getAvro = V.fromList <$> getAvro+instance (GetAvro a, Ord a) => GetAvro (Set.Set a) where+ getAvro = Set.fromList <$> getAvro+++data ContainerHeader = ContainerHeader+ { syncBytes :: !BL.ByteString+ , decompress :: BL.ByteString -> Get BL.ByteString+ , containedSchema :: !Schema+ }++nrSyncBytes :: Integral sb => sb+nrSyncBytes = 16++instance GetAvro ContainerHeader where+ getAvro =+ do magic <- getFixed avroMagicSize+ when (BL.fromStrict magic /= avroMagicBytes)+ (fail "Invalid magic number at start of container.")+ metadata <- getMap :: Get (Map.Map Text BL.ByteString) -- avro.schema, avro.codec+ sync <- BL.fromStrict <$> getFixed nrSyncBytes+ codec <- getCodec (Map.lookup "avro.codec" metadata)+ schema <- case Map.lookup "avro.schema" metadata of+ Nothing -> fail "Invalid container object: no schema."+ Just s -> case A.eitherDecode' s of+ Left e -> fail ("Can not decode container schema: " <> e)+ Right x -> return x+ return ContainerHeader { syncBytes = sync, decompress = codec, containedSchema = schema }+ where avroMagicSize :: Integral a => a+ avroMagicSize = 4++ avroMagicBytes :: BL.ByteString+ avroMagicBytes = BC.pack "Obj" <> BL.pack [1]++ getFixed :: Int -> Get ByteString+ getFixed = G.getByteString+++getCodec :: Monad m => Maybe BL.ByteString -> m (BL.ByteString -> m BL.ByteString)+getCodec code | Just "null" <- code =+ return return+ | Just "deflate" <- code =+ return (either (fail . show) return . Z.decompress)+ | Just x <- code =+ fail ("Unrecognized codec: " <> BC.unpack x)+ | otherwise = return return+++--------------------------------------------------------------------------------+-- Specialized Getters++getBoolean :: Get Bool+getBoolean =+ do w <- G.getWord8+ return (w == 0x01)++-- |Get a 32-bit int (zigzag encoded, max of 5 bytes)+getInt :: Get Int32+getInt = getZigZag++-- |Get a 64 bit int (zigzag encoded, max of 10 bytes)+getLong :: Get Int64+getLong = getZigZag++-- |Get an zigzag encoded integral value consuming bytes till the msb is 0.+getZigZag :: (Bits i, Integral i, DecodeRaw i) => Get i+getZigZag = decodeRaw++getBytes :: Get ByteString+getBytes =+ do w <- getLong+ G.getByteString (fromIntegral w)++getString :: Get Text+getString = Text.decodeUtf8 <$> getBytes++-- a la Java:+-- Bit 31 (the bit that is selected by the mask 0x80000000) represents the+-- sign of the floating-point number. Bits 30-23 (the bits that are+-- selected by the mask 0x7f800000) represent the exponent. Bits 22-0 (the+-- bits that are selected by the mask 0x007fffff) represent the+-- significand (sometimes called the mantissa) of the floating-point+-- number.+--+-- If the argument is positive infinity, the result is 0x7f800000.+--+-- If the argument is negative infinity, the result is 0xff800000.+--+-- If the argument is NaN, the result is 0x7fc00000.+getFloat :: Get Float+getFloat = IEEE.wordToFloat <$> G.getWord32le++-- As in Java:+-- Bit 63 (the bit that is selected by the mask 0x8000000000000000L)+-- represents the sign of the floating-point number. Bits 62-52 (the bits+-- that are selected by the mask 0x7ff0000000000000L) represent the+-- exponent. Bits 51-0 (the bits that are selected by the mask+-- 0x000fffffffffffffL) represent the significand (sometimes called the+-- mantissa) of the floating-point number.+--+-- If the argument is positive infinity, the result is+-- 0x7ff0000000000000L.+--+-- If the argument is negative infinity, the result is+-- 0xfff0000000000000L.+--+-- If the argument is NaN, the result is 0x7ff8000000000000L+getDouble :: Get Double+getDouble = IEEE.wordToDouble <$> G.getWord64le++--------------------------------------------------------------------------------+-- Complex AvroValue Getters++-- getRecord :: GetAvro ty => Get (AvroValue ty)+-- getRecord = getAvro++getArray :: GetAvro ty => Get [ty]+getArray =+ do nr <- getLong+ if+ | nr == 0 -> return []+ | nr < 0 ->+ do _len <- getLong+ rs <- replicateM (fromIntegral (abs nr)) getAvro+ (rs <>) <$> getArray+ | otherwise ->+ do rs <- replicateM (fromIntegral nr) getAvro+ (rs <>) <$> getArray++getMap :: GetAvro ty => Get (Map.Map Text ty)+getMap = go Map.empty+ where+ go acc =+ do nr <- getLong+ if nr == 0+ then return acc+ else do m <- Map.fromList <$> replicateM (fromIntegral nr) getKVs+ go (Map.union m acc)+ getKVs = (,) <$> getString <*> getAvro++-- Safe-ish from integral+sFromIntegral :: forall a b m. (Monad m, Bounded a, Bounded b, Integral a, Integral b) => a -> m b+sFromIntegral a+ | aI > fromIntegral (maxBound :: b) ||+ aI < fromIntegral (minBound :: b) = fail "Integral overflow."+ | otherwise = return (fromIntegral a)+ where aI = fromIntegral a :: Integer
+ src/Data/Avro/Decode/Lazy.hs view
@@ -0,0 +1,283 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-}++module Data.Avro.Decode.Lazy+ ( decodeAvro+ , decodeContainer+ , decodeContainer'+ , decodeContainerWithSchema+ , decodeContainerWithSchema'++ -- * Lower level interface+ , getContainerValues+ , getContainerValuesWith+ , getAvroOf+ , GetAvro(..)+ ) where++import qualified Codec.Compression.Zlib as Z+import Control.Monad (foldM, replicateM, when)+import qualified Data.Aeson as A+import qualified Data.Array as Array+import Data.Binary.Get (Get, runGetOrFail)+import qualified Data.Binary.Get as G+import Data.Binary.IEEE754 as IEEE+import Data.Bits+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Lazy.Char8 as BC+import Data.Either (isRight)+import qualified Data.HashMap.Strict as HashMap+import Data.Int+import Data.List (foldl')+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as Map+import Data.Maybe+import Data.Monoid ((<>))+import qualified Data.Set as Set+import Data.Tagged (Tagged, untag)+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Vector as V+import Prelude as P++import qualified Data.Avro.Decode.Lazy.LazyValue as T+import Data.Avro.DecodeRaw+import Data.Avro.HasAvroSchema (schema)+import Data.Avro.Schema as S+import Data.Avro.Zag++import Data.Avro.Decode.Get+import Data.Avro.Decode.Lazy.Convert (toStrictValue)+import Data.Avro.Decode.Lazy.Deconflict as C+import Data.Avro.FromAvro++-- | Decodes the container as a lazy list of values of the requested type.+--+-- The schema for the requested type will be de-conflicted with the schema+-- embedded with the container.+--+-- Errors are reported as a part of the list and the list will stop at first+-- error. This means that the consumer will get all the "good" content from+-- the container until the error is detected, then this error and then the list+-- is finished.+decodeContainer :: forall a. FromAvro a => BL.ByteString -> [Either String a]+decodeContainer bs =+ let vals = either (\err -> [Left err]) concat (decodeContainer' bs)+ in takeWhileInclusive isRight vals++-- | Decodes the container as a lazy list of values of the requested type.+--+-- The schema for the requested type will be de-conflicted with the schema+-- embedded with the container.+--+-- The content of the container is returned as a list of "blocks" of values+-- inside this container, so the notion of blocks in the container is preserved.+-- Since decoding is lazy it should be safe to concat these values into one lazy list.+--+-- The "outer" error represents the error in opening the container itself+-- (including problems like reading schemas embedded into the container.)+--+-- The "inner" errors represent problems in decoding individual values.+--+-- Note that this function will not stop decoding at the first occurance of the "inner"+-- error, and will continue attempting decoding values, so it is possible to+-- get 'Right' after 'Left'. It is up to the user to decide whether it is correct or not to+-- continue after errors (most likely it will not be correct).+--+-- 'decodeContainer' function makes a choice to stop after the first error.+decodeContainer' :: forall a. FromAvro a => BL.ByteString -> Either String [[Either String a]]+decodeContainer' = decodeContainerWithSchema' (untag (schema :: Tagged a Schema))++-- | Same as 'decodeContainer' but uses provided schema as a reader schema for the container+-- instead of the schema obtained from the type of 'a'.+--+-- It is up to the user to make sure that the provided schema is compatible with 'a'+-- and with the container's writer schema.+decodeContainerWithSchema :: FromAvro a => Schema -> BL.ByteString -> [Either String a]+decodeContainerWithSchema s bs =+ either (\err -> [Left err]) concat (decodeContainerWithSchema' s bs)++-- | Same as 'decodeContainer'' but uses provided schema as a reader schema for the container+-- instead of the schema obtained from the type of 'a'.+--+-- It is up to the user to make sure that the provided schema is compatible with 'a'+-- and with the container's writer schema.+decodeContainerWithSchema' :: FromAvro a => Schema -> BL.ByteString -> Either String [[Either String a]]+decodeContainerWithSchema' readerSchema bs = do+ (writerSchema, vals) <- getContainerValues bs+ pure $ (fmap . fmap) (convertValue writerSchema) vals+ where+ convertValue w v = toStrictValue (C.deconflict w readerSchema v) >>= (resultToEither . fromAvro)++-- |Decode bytes into a 'Value' as described by Schema.+decodeAvro :: Schema -> BL.ByteString -> T.LazyValue Type+decodeAvro s = snd . getAvroOf s+{-# INLINABLE decodeAvro #-}++-- | Decodes the container into a list of blocks of raw Avro values.+--+-- The content of the container is returned as a list of "blocks" of values+-- inside this container, so the notion of blocks in the container is preserved.+-- Since decoding is lazy it should be safe to concat these values into one lazy list.+--+-- Each 'LazyValue' can be an `Error' and this function doesn't make any attempts+-- of dealing with them leaving it up to the user.+--+-- The "outer" error represents the error in opening the container itself+-- (including problems like reading schemas embedded into the container.)+getContainerValues :: BC.ByteString -> Either String (Schema, [[T.LazyValue Type]])+getContainerValues = getContainerValuesWith getAvroOf+{-# INLINABLE getContainerValues #-}++getContainerValuesWith :: (Schema -> BL.ByteString -> (BL.ByteString, T.LazyValue Type))+ -> BL.ByteString+ -> Either String (Schema, [[T.LazyValue Type]])+getContainerValuesWith schemaToGet bs =+ case runGetOrFail getAvro bs of+ Left (bs', _, err) -> Left err+ Right (bs', _, ContainerHeader {..}) ->+ Right (containedSchema, snd $ getBlocks (schemaToGet containedSchema) syncBytes bs' decompress)+ where+ getRawBlock :: (BL.ByteString -> Get BL.ByteString) -> Get (Int, BC.ByteString)+ getRawBlock decompress = do+ nrObj <- getLong >>= sFromIntegral+ nrBytes <- getLong+ bytes <- G.getLazyByteString nrBytes >>= decompress+ pure (nrObj, bytes)++ checkMarker :: BL.ByteString -> BL.ByteString -> Either String BL.ByteString+ checkMarker sync bs =+ case BL.splitAt nrSyncBytes bs of+ (marker, _) | marker /= sync -> Left "Invalid marker, does not match sync bytes."+ (_, rest) -> Right rest++ getBlocks :: (BL.ByteString -> (BL.ByteString, T.LazyValue Type))+ -> BL.ByteString+ -> BL.ByteString+ -> (BL.ByteString -> Get BL.ByteString)+ -> (BL.ByteString, [[T.LazyValue Type]])+ getBlocks getValue sync bs decompress =+ case runGetOrFail (getRawBlock decompress) bs of+ Left (bs', _, err) -> (bs', [[T.Error err]])+ Right (bs', _, (nrObj, bytes)) ->+ let (_, vs) = consumeN (fromIntegral nrObj) getValue bytes+ in case checkMarker sync bs' of+ Left err -> (bs', [[T.Error err]])+ Right bs'' | BL.null bs'' -> (bs'', [vs])+ Right bs'' ->+ let (rest, vs') = getBlocks getValue bs'' sync decompress+ in (rest, vs : vs')++decodeGet :: GetAvro a => (a -> T.LazyValue Type) -> BL.ByteString -> (BL.ByteString, T.LazyValue Type)+decodeGet f bs =+ let res = runGetOrFail (f <$> getAvro) bs+ in either (\(rest,_,s) -> (rest, T.Error s)) (\(rest,_,a) -> (rest, a)) res+{-# INLINE decodeGet #-}++consumeN :: Int64 -> (a -> (a, b)) -> a -> (a, [b])+consumeN n f a =+ if n == 0+ then (a, [])+ else+ let (a', b) = f a+ (r, bs) = consumeN (n-1) f a'+ in (r, b:bs)+{-# INLINE consumeN #-}++getAvroOf :: Schema -> BL.ByteString -> (BL.ByteString, T.LazyValue Type)+getAvroOf ty0 bs = go ty0 bs+ where+ env = S.buildTypeEnvironment envFail ty0+ envFail t = fail $ "Named type not in schema: " <> show t++ go :: Type -> BL.ByteString -> (BL.ByteString, T.LazyValue Type)+ go ty bs =+ case ty of+ Null -> (bs, T.Null)+ Boolean -> decodeGet T.Boolean bs+ Int -> decodeGet T.Int bs+ Long -> decodeGet T.Long bs+ Float -> decodeGet T.Float bs+ Double -> decodeGet T.Double bs+ Bytes -> decodeGet T.Bytes bs+ String -> decodeGet T.String bs+ Array t -> T.Array . V.fromList . mconcat <$> getElements bs (go t)+ Map t -> T.Map . HashMap.fromList . mconcat <$> getKVPairs bs (go t)+ NamedType tn ->+ case runGetOrFail (env tn) bs of+ Left (bs', _, err) -> (bs', T.Error err)+ Right (bs', _, v) -> go v bs'++ Record {..} -> do+ let getField bs' Field {..} = (fldName,) <$> go fldType bs'+ let flds = foldl' (\(bs', as) fld -> (:as) <$> getField bs' fld ) (bs, []) fields+ T.Record ty . HashMap.fromList <$> flds++ Enum {..} ->+ case runGetOrFail getLong bs of+ Left (bs', _, err) -> (bs', T.Error err)+ Right (bs', _, i) ->+ case symbolLookup i of+ Nothing -> (bs', T.Error ("Unknown value {" <> show i <> "} for enum " <> Text.unpack (typeName ty) ))+ Just sym -> (bs', T.Enum ty (fromIntegral i) sym)++ Union ts unionLookup ->+ case runGetOrFail getLong bs of+ Left (bs', _, err) -> (bs', T.Error err)+ Right (bs', _, i) ->+ case unionLookup i of+ Nothing -> (bs', T.Error $ "Decoded Avro tag is outside the expected range for a Union. Tag: " <> show i <> " union of: " <> show (P.map typeName $ NE.toList ts))+ Just t -> T.Union ts t <$> go t bs'++ Fixed {..} ->+ case runGetOrFail (G.getByteString (fromIntegral size)) bs of+ Left (bs', _, err) -> (bs', T.Error err)+ Right (bs', _, v) -> (bs', T.Fixed ty v)+{-# INLINABLE getAvroOf #-}++getKVPair getElement bs =+ case runGetOrFail getString bs of+ Left (bs'', _, err) -> (bs'', ("", T.Error err))+ Right (bs'', _, v) -> (v,) <$> getElement bs''+{-# INLINE getKVPair #-}++getKVPairs :: BL.ByteString+ -> (BL.ByteString -> (BL.ByteString, T.LazyValue Type))+ -> (BL.ByteString, [[(Text, T.LazyValue Type)]])+getKVPairs bs getElement =+ case runGetOrFail (abs <$> getLong) bs of+ Left (bs', _, err) -> (bs', [[("", T.Error err)]])+ Right (bs', _, l) | l == 0 -> (bs', [])+ Right (bs', _, l) ->+ let (bs'', vs) = consumeN l (getKVPair getElement) bs'+ (rest, vs') = getKVPairs bs'' getElement+ in (rest, vs : vs')+{-# INLINE getKVPairs #-}+++getElements :: BL.ByteString+ -> (BL.ByteString -> (BL.ByteString, T.LazyValue Type))+ -> (BL.ByteString, [[T.LazyValue Type]])+getElements bs getElement =+ case runGetOrFail (abs <$> getLong) bs of+ Left (bs', _, err) -> (bs', [[T.Error err]])+ Right (bs', _, l) | l == 0 -> (bs', [])+ Right (bs', _, l) ->+ let (bs'', vs) = consumeN l getElement bs'+ (rest, vs') = getElements bs'' getElement+ in (rest, vs : vs')+{-# INLINE getElements #-}++--+takeWhileInclusive :: (a -> Bool) -> [a] -> [a]+takeWhileInclusive _ [] = []+takeWhileInclusive p (x:xs) =+ x : if p x then takeWhileInclusive p xs else []+{-# INLINE takeWhileInclusive #-}
+ src/Data/Avro/Decode/Lazy/Convert.hs view
@@ -0,0 +1,45 @@+module Data.Avro.Decode.Lazy.Convert+where++import Data.Avro.Decode.Lazy.LazyValue (LazyValue)+import qualified Data.Avro.Decode.Lazy.LazyValue as D+import Data.Avro.Types.Value (Value)+import qualified Data.Avro.Types.Value as V+import Data.Text (Text)++toStrictValue :: LazyValue f -> Either String (Value f)+toStrictValue d = case d of+ D.Null -> Right V.Null+ D.Boolean v -> Right $ V.Boolean v+ D.Int v -> Right $ V.Int v+ D.Long v -> Right $ V.Long v+ D.Float v -> Right $ V.Float v+ D.Double v -> Right $ V.Double v+ D.Bytes v -> Right $ V.Bytes v+ D.String v -> Right $ V.String v+ D.Array vs -> V.Array <$> traverse toStrictValue vs+ D.Map vs -> V.Map <$> traverse toStrictValue vs+ D.Record f vs -> V.Record f <$> traverse toStrictValue vs+ D.Union fs f v -> V.Union fs f <$> toStrictValue v+ D.Fixed f v -> Right $ V.Fixed f v+ D.Enum f i v -> Right $ V.Enum f i v+ D.Error v -> Left v+{-# INLINE toStrictValue #-}++fromStrictValue :: Value f -> LazyValue f+fromStrictValue d = case d of+ V.Null -> D.Null+ V.Boolean v -> D.Boolean v+ V.Int v -> D.Int v+ V.Long v -> D.Long v+ V.Float v -> D.Float v+ V.Double v -> D.Double v+ V.Bytes v -> D.Bytes v+ V.String v -> D.String v+ V.Array vs -> D.Array $ fromStrictValue <$> vs+ V.Map vs -> D.Map $ fromStrictValue <$> vs+ V.Record f vs -> D.Record f $ fromStrictValue <$> vs+ V.Union fs f v -> D.Union fs f $ fromStrictValue v+ V.Fixed f v -> D.Fixed f v+ V.Enum f i v -> D.Enum f i v+{-# INLINE fromStrictValue #-}
+ src/Data/Avro/Decode/Lazy/Deconflict.hs view
@@ -0,0 +1,117 @@+module Data.Avro.Decode.Lazy.Deconflict+ ( deconflict+ ) where++import Control.Applicative ((<|>))+import Data.Avro.Decode.Lazy.Convert (fromStrictValue)+import Data.Avro.Decode.Lazy.LazyValue as T+import Data.Avro.Schema as S+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap+import Data.List (find)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.List.NonEmpty as NE+import qualified Data.Map as M+import Data.Semigroup ((<>))+import qualified Data.Set as Set+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text++-- | @deconflict writer reader val@ will convert a value that was+-- encoded/decoded with the writer's schema into the form specified by the+-- reader's schema.+deconflict :: Schema -- ^ Writer schema+ -> Schema -- ^ Reader schema+ -> T.LazyValue Type+ -> T.LazyValue Type+deconflict = resolveSchema++resolveSchema :: Type -> Type -> T.LazyValue Type -> T.LazyValue Type+resolveSchema writerSchema readerSchema v+ | writerSchema == readerSchema = v+ | otherwise = go writerSchema readerSchema v+ where+ go :: Type -> Type -> T.LazyValue Type -> T.LazyValue Type+ go _ _ val@(T.Error _) = val+ go (S.Array aTy) (S.Array bTy) (T.Array vec) =+ T.Array $ fmap (go aTy bTy) vec+ go (S.Map aTy) (S.Map bTy) (T.Map mp) =+ T.Map $ fmap (go aTy bTy) mp+ go a@S.Enum {} b@S.Enum {} val+ | name a == name b = resolveEnum a b val+ go a@S.Fixed {} b@S.Fixed {} val+ | name a == name b && size a == size b = val+ go a@S.Record {} b@S.Record {} val+ | name a == name b = resolveRecord a b val+ go (S.Union _ _) (S.Union ys _) val =+ resolveTwoUnions ys val+ go nonUnion (S.Union ys _) val =+ resolveReaderUnion nonUnion ys val+ go (S.Union _xs _) nonUnion val =+ resolveWriterUnion nonUnion val+ go eTy dTy val =+ case val of+ T.Int i32 | dTy == S.Long -> T.Long (fromIntegral i32)+ | dTy == S.Float -> T.Float (fromIntegral i32)+ | dTy == S.Double -> T.Double (fromIntegral i32)+ T.Long i64 | dTy == S.Float -> T.Float (fromIntegral i64)+ | dTy == S.Double -> T.Double (fromIntegral i64)+ T.Float f | dTy == S.Double -> T.Double (realToFrac f)+ T.String s | dTy == S.Bytes -> T.Bytes (Text.encodeUtf8 s)+ T.Bytes bs | dTy == S.String -> T.String (Text.decodeUtf8 bs)+ _ -> T.Error $ "Can not resolve differing writer and reader schemas: " ++ show (eTy, dTy)++-- The writer's symbol must be present in the reader's enum+resolveEnum :: Type -> Type -> T.LazyValue Type -> T.LazyValue Type+resolveEnum e d val@(T.Enum _ _ _txt) = val+ -- -- | txt `elem` symbols d = Right val+ -- -- | otherwise = Left "Decoded enum does not appear in reader's symbol list."++resolveTwoUnions :: NonEmpty Type -> T.LazyValue Type -> T.LazyValue Type+resolveTwoUnions ds (T.Union _ eTy val) =+ resolveReaderUnion eTy ds val++resolveReaderUnion :: Type -> NonEmpty Type -> T.LazyValue Type -> T.LazyValue Type+resolveReaderUnion e ds val =+ let hdl [] = T.Error $ "No corresponding union value for " <> Text.unpack (typeName e)+ hdl (d:rest) =+ case resolveSchema e d val of+ T.Error _ -> hdl rest+ -- Right (T.Union ds d v)+ v -> T.Union ds d v+ in hdl (NE.toList ds)++resolveWriterUnion :: Type -> T.LazyValue Type -> T.LazyValue Type+resolveWriterUnion reader (T.Union _ ty val) = resolveSchema ty reader val++resolveRecord :: Type -> Type -> T.LazyValue Type -> T.LazyValue Type+resolveRecord writerSchema readerSchema (T.Record ty fldVals) =+ T.Record ty . HashMap.fromList $ fmap (resolveFields fldVals (fields writerSchema)) (fields readerSchema)++-- For each field of the decoders, lookup the field in the hash map+-- 1) If the field exists, call 'resolveSchema'+-- 2) If the field is missing use the reader's default+-- 3) If there is no default, fail.+--+-- XXX: Consider aliases in the writer schema, use those to retry on failed lookup.+resolveFields :: HashMap Text (T.LazyValue Type) -> [Field] -> Field -> (Text,T.LazyValue Type)+resolveFields hm writerFields readerField =+ let+ mbWriterField = findField readerField writerFields+ mbValue = HashMap.lookup (fldName readerField) hm+ in case (mbWriterField, mbValue, fldDefault readerField) of+ (Just w, Just x,_) -> (fldName readerField, resolveSchema (fldType w) (fldType readerField) x)+ (_, Just x,_) -> (fldName readerField, x)+ (_, _,Just def) -> (fldName readerField, fromStrictValue def)+ (_,Nothing,Nothing) -> (fldName readerField, T.Error ("No field and no default for " ++ show (fldName readerField)))++findField :: Field -> [Field] -> Maybe Field+findField f fs =+ let+ byName = find (\x -> fldName x == fldName f) fs+ allNames fld = Set.fromList (fldName fld : fldAliases fld)+ fNames = allNames f+ sameField = not . Set.null . Set.intersection fNames . allNames+ byAliases = find sameField fs+ in byName <|> byAliases
+ src/Data/Avro/Decode/Lazy/LazyValue.hs view
@@ -0,0 +1,27 @@+module Data.Avro.Decode.Lazy.LazyValue+where++import Data.ByteString+import Data.HashMap.Strict (HashMap)+import Data.Int+import Data.List.NonEmpty (NonEmpty)+import Data.Text+import Data.Vector++data LazyValue f+ = Null+ | Boolean Bool+ | Int Int32+ | Long Int64+ | Float Float+ | Double Double+ | Bytes ByteString+ | String Text+ | Array (Vector (LazyValue f)) -- ^ Dynamically enforced monomorphic type.+ | Map (HashMap Text (LazyValue f)) -- ^ Dynamically enforced monomorphic type+ | Record f (HashMap Text (LazyValue f)) -- Order and a map+ | Union (NonEmpty f) f (LazyValue f) -- ^ Set of union options, schema for selected option, and the actual value.+ | Fixed f ByteString+ | Enum f Int Text -- ^ An enum is a set of the possible symbols (the schema) and the selected symbol+ | Error !String+ deriving (Eq, Show)
src/Data/Avro/Deriving.hs view
@@ -45,6 +45,7 @@ import Language.Haskell.TH.Syntax import Data.Avro.Deriving.NormSchema+import Data.Avro.EitherN import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LBS@@ -233,6 +234,7 @@ genFromAvro _ = pure [] genFromAvroFieldsExp :: Name -> [Field] -> Q Exp+genFromAvroFieldsExp n [] = [| (return . return) $(conE n) |] genFromAvroFieldsExp n (x:xs) = [| \r -> $(let extract fld = [| r .: T.pack $(mkTextLit (fldName fld))|]@@ -424,7 +426,10 @@ S.Union (Null :| [x]) _ -> [t| Maybe $(mkFieldTypeName x) |] -- AppT (ConT $ mkName "Maybe") (mkFieldTypeName x) S.Union (x :| [Null]) _ -> [t| Maybe $(mkFieldTypeName x) |] --AppT (ConT $ mkName "Maybe") (mkFieldTypeName x) S.Union (x :| [y]) _ -> [t| Either $(mkFieldTypeName x) $(mkFieldTypeName y) |] -- AppT (AppT (ConT (mkName "Either")) (mkFieldTypeName x)) (mkFieldTypeName y)- S.Union (_ :| _) _ -> error "Unions with more than 2 elements are not yet supported"+ S.Union (a :| [b, c]) _ -> [t| Either3 $(mkFieldTypeName a) $(mkFieldTypeName b) $(mkFieldTypeName c) |]+ S.Union (a :| [b, c, d]) _ -> [t| Either4 $(mkFieldTypeName a) $(mkFieldTypeName b) $(mkFieldTypeName c) $(mkFieldTypeName d) |]+ S.Union (a :| [b, c, d, e]) _ -> [t| Either5 $(mkFieldTypeName a) $(mkFieldTypeName b) $(mkFieldTypeName c) $(mkFieldTypeName d) $(mkFieldTypeName e) |]+ S.Union _ _ -> error "Unions with more than 5 elements are not yet supported" S.Record n _ _ _ _ _ -> [t| $(conT $ mkDataTypeName n) |] S.Map x -> [t| Map Text $(mkFieldTypeName x) |] --AppT (AppT (ConT (mkName "Map")) (ConT $ mkName "Text")) (mkFieldTypeName x) S.Array x -> [t| [$(mkFieldTypeName x)] |]--AppT (ConT $ Text "[]") (mkFieldTypeName x)
+ src/Data/Avro/EitherN.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Data.Avro.EitherN where++import Data.Avro+import Data.Avro.Schema+import qualified Data.Avro.Types as T+import Data.Tagged+import Data.List.NonEmpty+import GHC.Generics (Generic)++data Either3 a b c = E3_1 a | E3_2 b | E3_3 c deriving (Eq, Ord, Show, Generic)++data Either4 a b c d = E4_1 a | E4_2 b | E4_3 c | E4_4 d deriving (Eq, Ord, Show, Generic)++data Either5 a b c d e = E5_1 a | E5_2 b | E5_3 c | E5_4 d | E5_5 e deriving (Eq, Ord, Show, Generic)++instance (HasAvroSchema a, HasAvroSchema b, HasAvroSchema c) => HasAvroSchema (Either3 a b c) where+ schema = Tagged $ mkUnion (untag (schema :: Tagged a Type) :| [+ untag (schema :: Tagged b Type),+ untag (schema :: Tagged c Type)+ ])++instance (HasAvroSchema a, HasAvroSchema b, HasAvroSchema c, HasAvroSchema d) => HasAvroSchema (Either4 a b c d) where+ schema = Tagged $ mkUnion (untag (schema :: Tagged a Type) :| [+ untag (schema :: Tagged b Type),+ untag (schema :: Tagged c Type),+ untag (schema :: Tagged d Type)+ ])++instance (HasAvroSchema a, HasAvroSchema b, HasAvroSchema c, HasAvroSchema d, HasAvroSchema e) => HasAvroSchema (Either5 a b c d e) where+ schema = Tagged $ mkUnion (untag (schema :: Tagged a Type) :| [+ untag (schema :: Tagged b Type),+ untag (schema :: Tagged c Type),+ untag (schema :: Tagged d Type),+ untag (schema :: Tagged e Type)+ ])++instance (FromAvro a, FromAvro b, FromAvro c) => FromAvro (Either3 a b c) where+ fromAvro e@(T.Union _ branch x)+ | matches branch schemaA = E3_1 <$> fromAvro x+ | matches branch schemaB = E3_2 <$> fromAvro x+ | matches branch schemaC = E3_3 <$> fromAvro x+ | otherwise = badValue e "either3"+ where Tagged schemaA = schema :: Tagged a Type+ Tagged schemaB = schema :: Tagged b Type+ Tagged schemaC = schema :: Tagged c Type+ fromAvro x = badValue x "either3"++instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d) => FromAvro (Either4 a b c d) where+ fromAvro e@(T.Union _ branch x)+ | matches branch schemaA = E4_1 <$> fromAvro x+ | matches branch schemaB = E4_2 <$> fromAvro x+ | matches branch schemaC = E4_3 <$> fromAvro x+ | matches branch schemaD = E4_4 <$> fromAvro x+ | otherwise = badValue e "either4"+ where Tagged schemaA = schema :: Tagged a Type+ Tagged schemaB = schema :: Tagged b Type+ Tagged schemaC = schema :: Tagged c Type+ Tagged schemaD = schema :: Tagged d Type+ fromAvro x = badValue x "either4"++instance (FromAvro a, FromAvro b, FromAvro c, FromAvro d, FromAvro e) => FromAvro (Either5 a b c d e) where+ fromAvro e@(T.Union _ branch x)+ | matches branch schemaA = E5_1 <$> fromAvro x+ | matches branch schemaB = E5_2 <$> fromAvro x+ | matches branch schemaC = E5_3 <$> fromAvro x+ | matches branch schemaD = E5_4 <$> fromAvro x+ | matches branch schemaE = E5_5 <$> fromAvro x+ | otherwise = badValue e "either5"+ where Tagged schemaA = schema :: Tagged a Type+ Tagged schemaB = schema :: Tagged b Type+ Tagged schemaC = schema :: Tagged c Type+ Tagged schemaD = schema :: Tagged d Type+ Tagged schemaE = schema :: Tagged e Type+ fromAvro x = badValue x "either5"++instance (ToAvro a, ToAvro b, ToAvro c) => ToAvro (Either3 a b c) where+ toAvro e =+ let sch@(one :| [two, three]) = options (schemaOf e)+ in case e of+ E3_1 a -> T.Union sch one (toAvro a)+ E3_2 b -> T.Union sch two (toAvro b)+ E3_3 c -> T.Union sch three (toAvro c)++instance (ToAvro a, ToAvro b, ToAvro c, ToAvro d) => ToAvro (Either4 a b c d) where+ toAvro e =+ let sch@(one :| [two, three, four]) = options (schemaOf e)+ in case e of+ E4_1 a -> T.Union sch one (toAvro a)+ E4_2 b -> T.Union sch two (toAvro b)+ E4_3 c -> T.Union sch three (toAvro c)+ E4_4 d -> T.Union sch four (toAvro d)++instance (ToAvro a, ToAvro b, ToAvro c, ToAvro d, ToAvro e) => ToAvro (Either5 a b c d e) where+ toAvro e =+ let sch@(one :| [two, three, four, five]) = options (schemaOf e)+ in case e of+ E5_1 a -> T.Union sch one (toAvro a)+ E5_2 b -> T.Union sch two (toAvro b)+ E5_3 c -> T.Union sch three (toAvro c)+ E5_4 d -> T.Union sch four (toAvro d)+ E5_5 e -> T.Union sch five (toAvro e)
src/Data/Avro/Schema.hs view
@@ -25,6 +25,7 @@ , typeName , buildTypeEnvironment , Result(..)+ , resultToEither , matches @@ -349,7 +350,14 @@ Ty.Enum _ _ txt -> A.String txt data Result a = Success a | Error String- deriving (Eq,Ord,Show)+ deriving (Eq, Ord, Show)++resultToEither :: Result b -> Either String b+resultToEither r =+ case r of+ Success v -> Right v+ Error err -> Left err+{-# INLINE resultToEither #-} instance Monad Result where return = pure
src/Data/Avro/Types.hs view
@@ -1,25 +1,6 @@-module Data.Avro.Types where--import Data.ByteString-import Data.HashMap.Strict (HashMap)-import Data.Int-import Data.List.NonEmpty (NonEmpty)-import Data.Text-import Data.Vector+module Data.Avro.Types+( module X+)+where -data Value f- = Null- | Boolean !Bool- | Int {-# UNPACK #-} !Int32- | Long {-# UNPACK #-} !Int64- | Float {-# UNPACK #-} !Float- | Double {-# UNPACK #-} !Double- | Bytes {-# UNPACK #-} !ByteString- | String {-# UNPACK #-} !Text- | Array (Vector (Value f)) -- ^ Dynamically enforced monomorphic type.- | Map (HashMap Text (Value f)) -- ^ Dynamically enforced monomorphic type- | Record f (HashMap Text (Value f)) -- Order and a map- | Union (NonEmpty f) f (Value f) -- ^ Set of union options, schema for selected option, and the actual value.- | Fixed f {-# UNPACK #-} !ByteString- | Enum f {-# UNPACK #-} !Int Text -- ^ An enum is a set of the possible symbols (the schema) and the selected symbol- deriving (Eq, Show)+import Data.Avro.Types.Value as X
+ src/Data/Avro/Types/Value.hs view
@@ -0,0 +1,25 @@+module Data.Avro.Types.Value where++import Data.ByteString+import Data.HashMap.Strict (HashMap)+import Data.Int+import Data.List.NonEmpty (NonEmpty)+import Data.Text+import Data.Vector++data Value f+ = Null+ | Boolean !Bool+ | Int {-# UNPACK #-} !Int32+ | Long {-# UNPACK #-} !Int64+ | Float {-# UNPACK #-} !Float+ | Double {-# UNPACK #-} !Double+ | Bytes {-# UNPACK #-} !ByteString+ | String {-# UNPACK #-} !Text+ | Array (Vector (Value f)) -- ^ Dynamically enforced monomorphic type.+ | Map (HashMap Text (Value f)) -- ^ Dynamically enforced monomorphic type+ | Record f (HashMap Text (Value f)) -- Order and a map+ | Union (NonEmpty f) f (Value f) -- ^ Set of union options, schema for selected option, and the actual value.+ | Fixed f {-# UNPACK #-} !ByteString+ | Enum f {-# UNPACK #-} !Int Text -- ^ An enum is a set of the possible symbols (the schema) and the selected symbol+ deriving (Eq, Show)
+ test/Avro/Decode/Lazy/ValuesSpec.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+module Avro.Decode.Lazy.ValuesSpec+where++import Data.Avro+import Data.Avro.Decode.Lazy+import Data.Avro.Decode.Lazy.Convert as TC+import Data.Avro.Deriving+import Data.Either (isLeft)++import Test.Hspec++{-# ANN module ("HLint: ignore Redundant do" :: String) #-}++deriveAvro "test/data/small.avsc"++spec :: Spec+spec = describe "Avro.Decode.Lazy.ValueSpec" $ do+ let msg = Endpoint+ { endpointIps = ["192.168.1.1", "127.0.0.1"]+ , endpointPorts = [PortRange 1 10, PortRange 11 20]+ , endpointOpaque = Opaque "16-b-long-string"+ , endpointCorrelation = Opaque "opaq-correlation"+ , endpointTag = Left 14+ }++ it "should lazily decode correct value" $ do+ let lazyValue = decodeAvro schema'Endpoint (encode msg)+ TC.toStrictValue lazyValue `shouldBe` Right (toAvro msg)++ it "should return an error for a wrong content" $ do+ let lazyValue = decodeAvro schema'Endpoint "nonsense lives here"+ TC.toStrictValue lazyValue `shouldSatisfy` isLeft