borsh-0.1.0: src/Codec/Borsh/Encoding.hs
module Codec.Borsh.Encoding (
-- * Encoder definition
Encoder (..)
-- * Encoders for non-composite types mandated by the Borsh spec
, encodeU8
, encodeU16
, encodeU32
, encodeU64
, encodeU128
, encodeI8
, encodeI16
, encodeI32
, encodeI64
, encodeI128
, encodeF32
, encodeF64
, encodeString
-- * Encoders for composite types mandated by the Borsh spec
, encodeArray
, encodeVec
, encodeOption
, encodeHashSet
, encodeHashMap
, encodeStruct
, encodeEnum
-- * Encoders for Haskell types not mandated by the Borsh spec
, encodeLazyByteString
, encodeStrictByteString
, encodeChar
, encodeBool
) where
import Data.Char (ord)
import Data.ByteString.Builder (Builder)
import Data.Foldable (toList)
import Data.Functor.Contravariant
import Data.Int
import Data.Map (Map)
import Data.Set (Set)
import Data.SOP
import Data.Text (Text)
import Data.Word
import qualified Data.ByteString as S
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as L
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.Text.Encoding as Text
import Data.FixedSizeArray (FixedSizeArray)
import Codec.Borsh.Internal.Util.ByteString
import Codec.Borsh.Internal.Util.SOP (indices)
import Data.Word128
import Data.Int128
{-------------------------------------------------------------------------------
Encoder definition
-------------------------------------------------------------------------------}
-- | Encoder
--
-- An encoder describes how to serialise a given value in BORSH format.
newtype Encoder a = Encoder {
runEncoder :: a -> Builder
}
instance Contravariant Encoder where
contramap f (Encoder e) = Encoder (e . f)
liftEncoder :: Encoder a -> (I -.-> K Builder) a
liftEncoder (Encoder e) = fn $ K . e . unI
{-------------------------------------------------------------------------------
Encoders for non-composite types mandated by the Borsh spec
-------------------------------------------------------------------------------}
encodeU8 :: Encoder Word8
encodeU16 :: Encoder Word16
encodeU32 :: Encoder Word32
encodeU64 :: Encoder Word64
encodeI8 :: Encoder Int8
encodeI16 :: Encoder Int16
encodeI32 :: Encoder Int32
encodeI64 :: Encoder Int64
encodeF32 :: Encoder Float
encodeF64 :: Encoder Double
encodeU8 = Encoder B.word8
encodeU16 = Encoder B.word16LE
encodeU32 = Encoder B.word32LE
encodeU64 = Encoder B.word64LE
encodeI8 = Encoder B.int8
encodeI16 = Encoder B.int16LE
encodeI32 = Encoder B.int32LE
encodeI64 = Encoder B.int64LE
encodeF32 = Encoder B.floatLE
encodeF64 = Encoder B.doubleLE
encodeU128 :: Encoder Word128
encodeU128 = Encoder $
\w128 -> B.word64LE (word128LS64 w128) <> B.word64LE (word128MS64 w128)
encodeI128 :: Encoder Int128
encodeI128 = Encoder $
\i128 -> B.word64LE (int128LS64 i128) <> B.word64LE (int128MS64 i128)
-- Encoding 'Text'
--
-- Borsh requires the length of the utf8-encoded string before the string, but
-- unfortunately we have no easy way to compute this without encoding the entire
-- string. This means that we are not streaming here: the entire utf8 encoding
-- is constructed in memory.
--
-- With text version 2.0 we can use @lengthWord8@ but that is not available most
-- of the time.
encodeString :: Encoder Text
encodeString = Encoder $ \txt ->
B.word32LE (lengthLazy $ utf8 txt) <> B.lazyByteString (utf8 txt)
where
utf8 :: Text -> L.ByteString
utf8 txt = B.toLazyByteString $ Text.encodeUtf8Builder txt
{-------------------------------------------------------------------------------
Encoders for composite types mandated by the Borsh spec
-------------------------------------------------------------------------------}
encodeArray :: Encoder a -> Encoder (FixedSizeArray n a)
encodeArray e = Encoder $ mconcat . map (runEncoder e) . toList
encodeVec :: Encoder a -> Encoder [a]
encodeVec e = Encoder $ \xs -> mconcat $
runEncoder encodeU32 (fromIntegral $ length xs)
: map (runEncoder e) xs
encodeOption :: Encoder a -> Encoder (Maybe a)
encodeOption e = Encoder $ \case
Nothing -> runEncoder encodeU8 0
Just x -> runEncoder encodeU8 1 <> runEncoder e x
encodeHashSet :: Encoder a -> Encoder (Set a)
encodeHashSet e = Encoder $ \xs -> mconcat $
runEncoder encodeU32 (fromIntegral $ Set.size xs)
: (map (runEncoder e) $ Set.toList xs)
encodeHashMap :: Encoder k -> Encoder a -> Encoder (Map k a)
encodeHashMap ek ev = Encoder $ \xs -> mconcat $
runEncoder encodeU32 (fromIntegral $ Map.size xs)
: (map ePair $ Map.toList xs)
where
ePair (k,v) = runEncoder ek k <> runEncoder ev v
encodeStruct :: SListI xs => NP Encoder xs -> Encoder (NP I xs)
encodeStruct es = Encoder $
mconcat
. hcollapse
. hap (hliftA liftEncoder es)
encodeEnum :: All SListI xss => POP Encoder xss -> Encoder (SOP I xss)
encodeEnum es = Encoder $
hcollapse
. hczipWith (Proxy @SListI) aux indices
. unSOP
. hap (hliftA liftEncoder es)
where
aux :: SListI xs => K Word8 xs -> NP (K Builder) xs -> K Builder xs
aux (K ix) xs = K $ runEncoder encodeU8 ix <> mconcat (hcollapse xs)
{-------------------------------------------------------------------------------
Encoders for Haskell types not mandated by the Borsh spec
-------------------------------------------------------------------------------}
-- ByteStrings
encodeLazyByteString :: Encoder L.ByteString
encodeLazyByteString = Encoder $ \bs ->
runEncoder encodeU32 (fromIntegral $ L.length bs)
<> B.lazyByteString bs
encodeStrictByteString :: Encoder S.ByteString
encodeStrictByteString = Encoder $ \bs ->
runEncoder encodeU32 (fromIntegral $ S.length bs)
<> B.byteString bs
-- Char, Bool
encodeChar :: Encoder Char
encodeChar = Encoder $ runEncoder encodeU32 . fromIntegral . ord
encodeBool :: Encoder Bool
encodeBool = Encoder $ runEncoder encodeU8 . fromIntegral . fromEnum