{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Data.Avro.Encoding.ToAvro
where
import Control.Monad.Identity (Identity (..))
import qualified Data.Array as Ar
import Data.Avro.Internal.EncodeRaw
import Data.Avro.Internal.Time
import Data.Avro.Schema.Decimal as D
import Data.Avro.Schema.Schema as S
import qualified Data.Binary.IEEE754 as IEEE
import qualified Data.ByteString as B
import Data.ByteString.Builder
import Data.ByteString.Lazy as BL
import qualified Data.Foldable as F
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.Int
import Data.Ix (Ix)
import Data.List as DL
import qualified Data.Map.Strict as Map
import Data.Maybe (fromJust)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import qualified Data.Time as Time
import qualified Data.UUID as UUID
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as U
import Data.Word
import GHC.TypeLits
newtype Encoder = Encoder { runEncoder :: Schema -> Builder }
(.=) :: forall a. ToAvro a => Text -> a -> (Text, Encoder)
(.=) fieldName fieldValue = (fieldName, Encoder (flip toAvro fieldValue))
record :: Schema -> [(Text, Encoder)] -> Builder
record (S.Record _ _ _ fs) vs =
foldMap (mapField provided) fs
where
provided :: HashMap Text Encoder
provided = HashMap.fromList vs
providedNames = fst <$> vs
failField :: S.Field -> Builder
failField fld = error $ "Field '" <> show (S.fldName fld) <> "' is missing from the provided list of fields: " <> show providedNames
mapField :: HashMap Text Encoder -> S.Field -> Builder
mapField env fld =
maybe (failField fld) (flip runEncoder (S.fldType fld)) (HashMap.lookup (S.fldName fld) env)
class ToAvro a where
toAvro :: Schema -> a -> Builder
instance ToAvro Int where
toAvro (S.Long _) i = encodeRaw @Int64 (fromIntegral i)
toAvro (S.Int _) i = encodeRaw @Int32 (fromIntegral i)
toAvro s _ = error ("Unable to encode Int as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Int32 where
toAvro (S.Long _) i = encodeRaw @Int64 (fromIntegral i)
toAvro (S.Int _) i = encodeRaw @Int32 i
toAvro S.Double i = toAvro @Double (S.Double) (fromIntegral i)
toAvro S.Float i = toAvro @Float (S.Float) (fromIntegral i)
toAvro s _ = error ("Unable to encode Int32 as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Int64 where
toAvro (S.Long _) i = encodeRaw @Int64 i
toAvro S.Double i = toAvro @Double (S.Double) (fromIntegral i)
toAvro S.Float i = toAvro @Float (S.Float) (fromIntegral i)
toAvro s _ = error ("Unable to encode Int64 as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Word8 where
toAvro (S.Int _) i = encodeRaw @Word8 i
toAvro (S.Long _) i = encodeRaw @Word64 (fromIntegral i)
toAvro S.Double i = toAvro @Double (S.Double) (fromIntegral i)
toAvro S.Float i = toAvro @Float (S.Float) (fromIntegral i)
toAvro s _ = error ("Unable to encode Word8 as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Word16 where
toAvro (S.Int _) i = encodeRaw @Word16 i
toAvro (S.Long _) i = encodeRaw @Word64 (fromIntegral i)
toAvro S.Double i = toAvro @Double (S.Double) (fromIntegral i)
toAvro S.Float i = toAvro @Float (S.Float) (fromIntegral i)
toAvro s _ = error ("Unable to encode Word16 as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Word32 where
toAvro (S.Int _) i = encodeRaw @Word32 i
toAvro (S.Long _) i = encodeRaw @Word64 (fromIntegral i)
toAvro S.Double i = toAvro @Double (S.Double) (fromIntegral i)
toAvro S.Float i = toAvro @Float (S.Float) (fromIntegral i)
toAvro s _ = error ("Unable to encode Word32 as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Word64 where
toAvro (S.Long _) i = encodeRaw @Word64 i
toAvro S.Double i = toAvro @Double (S.Double) (fromIntegral i)
toAvro s _ = error ("Unable to encode Word64 as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Double where
toAvro S.Double i = word64LE (IEEE.doubleToWord i)
toAvro s _ = error ("Unable to encode Double as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Float where
toAvro S.Float i = word32LE (IEEE.floatToWord i)
toAvro S.Double i = word64LE (IEEE.doubleToWord $ realToFrac i)
toAvro s _ = error ("Unable to encode Float as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro Bool where
toAvro S.Boolean v = word8 $ fromIntegral (fromEnum v)
toAvro s _ = error ("Unable to encode Bool as: " <> show s)
{-# INLINE toAvro #-}
instance (KnownNat p, KnownNat s) => ToAvro (D.Decimal p s) where
toAvro s = toAvro @Int64 s . fromIntegral . fromJust . D.underlyingValue
instance ToAvro UUID.UUID where
toAvro s = toAvro s . UUID.toText
{-# INLINE toAvro #-}
instance ToAvro Time.Day where
toAvro s = toAvro @Int32 s . fromIntegral . daysSinceEpoch
{-# INLINE toAvro #-}
instance ToAvro Time.DiffTime where
toAvro s@(S.Long (Just S.TimeMicros)) = toAvro @Int64 s . fromIntegral . diffTimeToMicros
toAvro s@(S.Long (Just S.TimestampMicros)) = toAvro @Int64 s . fromIntegral . diffTimeToMicros
toAvro s@(S.Long (Just S.TimestampMillis)) = toAvro @Int64 s . fromIntegral . diffTimeToMillis
toAvro s@(S.Int (Just S.TimeMillis)) = toAvro @Int32 s . fromIntegral . diffTimeToMillis
toAvro s = error ("Unble to decode DiffTime from " <> show s)
instance ToAvro Time.UTCTime where
toAvro s@(S.Long (Just S.TimestampMicros)) = toAvro @Int64 s . fromIntegral . utcTimeToMicros
toAvro s@(S.Long (Just S.TimestampMillis)) = toAvro @Int64 s . fromIntegral . utcTimeToMillis
instance ToAvro B.ByteString where
toAvro s bs = case s of
(S.Bytes _) -> encodeRaw (B.length bs) <> byteString bs
(S.String _) -> encodeRaw (B.length bs) <> byteString bs
S.Fixed _ _ l _ | l == B.length bs -> byteString bs
S.Fixed _ _ l _ -> error ("Unable to encode ByteString as Fixed(" <> show l <> ") because its length is " <> show (B.length bs))
_ -> error ("Unable to encode ByteString as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro BL.ByteString where
toAvro s bs = toAvro s (BL.toStrict bs)
{-# INLINE toAvro #-}
instance ToAvro Text where
toAvro s v =
let
bs = T.encodeUtf8 v
res = encodeRaw (B.length bs) <> byteString bs
in case s of
(S.Bytes _) -> res
(S.String _) -> res
_ -> error ("Unable to encode Text as: " <> show s)
{-# INLINE toAvro #-}
instance ToAvro TL.Text where
toAvro s v = toAvro s (TL.toStrict v)
{-# INLINE toAvro #-}
instance ToAvro a => ToAvro [a] where
toAvro (S.Array s) as =
if DL.null as then long0 else encodeRaw (F.length as) <> foldMap (toAvro s) as <> long0
toAvro s _ = error ("Unable to encode Haskell list as: " <> show s)
instance ToAvro a => ToAvro (V.Vector a) where
toAvro (S.Array s) as =
if V.null as then long0 else encodeRaw (V.length as) <> foldMap (toAvro s) as <> long0
toAvro s _ = error ("Unable to encode Vector list as: " <> show s)
instance (Ix i, ToAvro a) => ToAvro (Ar.Array i a) where
toAvro (S.Array s) as =
if F.length as == 0 then long0 else encodeRaw (F.length as) <> foldMap (toAvro s) as <> long0
toAvro s _ = error ("Unable to encode indexed Array list as: " <> show s)
instance (U.Unbox a, ToAvro a) => ToAvro (U.Vector a) where
toAvro (S.Array s) as =
if U.null as then long0 else encodeRaw (U.length as) <> foldMap (toAvro s) (U.toList as) <> long0
toAvro s _ = error ("Unable to encode Vector list as: " <> show s)
instance ToAvro a => ToAvro (Map.Map Text a) where
toAvro (S.Map s) hm =
if Map.null hm then long0 else putI (F.length hm) <> foldMap putKV (Map.toList hm) <> long0
where putKV (k,v) = toAvro S.String' k <> toAvro s v
toAvro s _ = error ("Unable to encode HashMap as: " <> show s)
instance ToAvro a => ToAvro (HashMap Text a) where
toAvro (S.Map s) hm =
if HashMap.null hm then long0 else putI (F.length hm) <> foldMap putKV (HashMap.toList hm) <> long0
where putKV (k,v) = toAvro S.String' k <> toAvro s v
toAvro s _ = error ("Unable to encode HashMap as: " <> show s)
instance ToAvro a => ToAvro (Maybe a) where
toAvro (S.Union opts) v =
case F.toList opts of
[S.Null, s] -> maybe (putI 0) (\a -> putI 1 <> toAvro s a) v
wrongOpts -> error ("Unable to encode Maybe as " <> show wrongOpts)
toAvro s _ = error ("Unable to encode Maybe as " <> show s)
instance (ToAvro a) => ToAvro (Identity a) where
toAvro (S.Union opts) e@(Identity a) =
if (V.length opts == 1)
then putI 0 <> toAvro (V.unsafeIndex opts 0) a
else error ("Unable to encode Identity as a single-value union: " <> show opts)
toAvro s _ = error ("Unable to encode Identity value as " <> show s)
instance (ToAvro a, ToAvro b) => ToAvro (Either a b) where
toAvro (S.Union opts) v =
if (V.length opts == 2)
then case v of
Left a -> putI 0 <> toAvro (V.unsafeIndex opts 0) a
Right b -> putI 1 <> toAvro (V.unsafeIndex opts 1) b
else error ("Unable to encode Either as " <> show opts)
toAvro s _ = error ("Unable to encode Either as " <> show s)