packages feed

borsh-0.2.0: src/Codec/Borsh/Decoding.hs

module Codec.Borsh.Decoding (
    -- * Decoders for non-composite types mandated by the Borsh spec
    decodeU8
  , decodeU16
  , decodeU32
  , decodeU64
  , decodeU128
  , decodeI8
  , decodeI16
  , decodeI32
  , decodeI64
  , decodeI128
  , decodeF32
  , decodeF64
  , decodeString
    -- * Decoders for composite types mandated by the Borsh spec
  , decodeArray
  , decodeVec
  , decodeOption
  , decodeHashSet
  , decodeHashMap
  , decodeStruct
  , decodeEnum
    -- * Decoders for Haskell types not mandated by the Borsh spec
  , decodeLazyByteString
  , decodeStrictByteString
  , decodeChar
  , decodeBool
  ) where

import Data.Char (chr)
import Data.Int
import Data.Map (Map)
import Data.Maybe
import Data.Proxy
import Data.Set (Set)
import Data.STRef
import Data.Text (Text)
import Data.WideWord.Word128
import Data.WideWord.Int128
import Data.Word
import Generics.SOP
import GHC.TypeLits

import qualified Data.ByteString             as S
import qualified Data.ByteString.Lazy        as L
import qualified Data.Map.Strict             as Map
import qualified Data.Set                    as Set
import qualified Data.Text.Encoding          as Text
import qualified Data.Vector.Generic         as G
import qualified Data.Vector.Generic.Mutable as GM

import Codec.Borsh.Incremental
import Codec.Borsh.Incremental.Decoder (decodeLittleEndian)
import Codec.Borsh.Internal.Util.BitwiseCast
import Codec.Borsh.Internal.Util.SOP
import Data.FixedSizeArray (FixedSizeArray, MFixedSizeArray)

import qualified Data.FixedSizeArray as FSA

{-------------------------------------------------------------------------------
  Decoders for the non-composite types mandated by the Borsh spec
-------------------------------------------------------------------------------}

decodeU8   :: Decoder s Word8
decodeU16  :: Decoder s Word16
decodeU32  :: Decoder s Word32
decodeU64  :: Decoder s Word64
decodeU128 :: Decoder s Word128

decodeU8   = decodeLittleEndian
decodeU16  = decodeLittleEndian
decodeU32  = decodeLittleEndian
decodeU64  = decodeLittleEndian
decodeU128 = decodeLittleEndian

decodeI8   :: Decoder s Int8
decodeI16  :: Decoder s Int16
decodeI32  :: Decoder s Int32
decodeI64  :: Decoder s Int64
decodeI128 :: Decoder s Int128

decodeI8   = (castBits @Word8  ) <$> decodeLittleEndian
decodeI16  = (castBits @Word16 ) <$> decodeLittleEndian
decodeI32  = (castBits @Word32 ) <$> decodeLittleEndian
decodeI64  = (castBits @Word64 ) <$> decodeLittleEndian
decodeI128 = (castBits @Word128) <$> decodeLittleEndian

decodeF32  :: Decoder s Float
decodeF64  :: Decoder s Double

decodeF32  = (castBits @Word32) <$> decodeLittleEndian
decodeF64  = (castBits @Word64) <$> decodeLittleEndian

decodeString :: Decoder s Text
decodeString = do
    len <- decodeU32
    lbs <- decodeLargeToken len
    case Text.decodeUtf8' $ L.toStrict lbs of
      Right txt -> return txt
      Left  err -> fail (show err)

{-------------------------------------------------------------------------------
  Decoders for composite types mandated by the Borsh spec
-------------------------------------------------------------------------------}

decodeArray :: forall n s a.
     KnownNat n
  => Decoder s a -> Decoder s (FixedSizeArray n a)
decodeArray d = do
    -- Construct mutable array before we start processing elements,
    -- along with a counter for the next element
    mArr :: MFixedSizeArray n s a <- liftDecoder $ FSA.new
    next :: STRef s Int           <- liftDecoder $ newSTRef 0

    let d' :: Decoder s ()
        d' = d >>= \b -> liftDecoder $ do
                i <- readSTRef next
                modifySTRef next (+ 1)
                GM.write mArr i b

    decodeIncremental_ count d'
    liftDecoder $ G.freeze mArr
  where
    count :: Word32
    count = fromIntegral $ natVal (Proxy @n)

decodeVec :: Decoder s a -> Decoder s [a]
decodeVec d = decodeU32 >>= \count -> decodeIncremental count d

decodeOption :: Decoder s a -> Decoder s (Maybe a)
decodeOption d = do
    present <- decodeU8
    case present of
      0 -> return Nothing
      1 -> Just <$> d
      _ -> fail "Expected 0 or 1 for option prefix"

decodeHashSet :: Ord a => Decoder s a -> Decoder s (Set a)
decodeHashSet d = do
    count <- decodeU32
    Set.fromList <$> decodeIncremental count d

decodeHashMap :: Ord k => Decoder s k -> Decoder s a -> Decoder s (Map k a)
decodeHashMap dk dv = do
    count <- decodeU32
    Map.fromList <$> decodeIncremental count dPair
  where
    dPair = (,) <$> dk <*> dv

decodeStruct :: All Top xs => NP (Decoder s) xs -> Decoder s (NP I xs)
decodeStruct = hsequence

decodeEnum :: forall s xss.
     All SListI xss
  => POP (Decoder s) xss -> Decoder s (SOP I xss)
decodeEnum =
      selectDecoder
    . hcollapse
    . hczipWith3
        (Proxy @SListI)
        (\(K ix) (Fn inj) ds -> K $ (ix, SOP . unK . inj <$> hsequence ds))
        indices
        (injections :: NP (Injection (NP I) xss) xss)
    . unPOP
  where
    selectDecoder :: [(Word8, Decoder s (SOP I xss))] -> Decoder s (SOP I xss)
    selectDecoder decs = do
        n <- fromIntegral <$> decodeU8
        fromMaybe (fail err) $ lookup n decs
      where
        err :: String
        err = "Expected index < " ++ show (length decs)

{-------------------------------------------------------------------------------
  Decoders for Haskell types not mandated by the Borsh spec
-------------------------------------------------------------------------------}

-- ByteStrings

decodeLazyByteString :: Decoder s L.ByteString
decodeLazyByteString = do
    len <- decodeU32
    decodeLargeToken len

decodeStrictByteString :: Decoder s S.ByteString
decodeStrictByteString = do
    len <- decodeU32
    L.toStrict <$> decodeLargeToken len

-- Char, Bool

decodeChar :: Decoder s Char
decodeChar = chr . fromIntegral <$> decodeU32

decodeBool :: Decoder s Bool
decodeBool = decodeU8 >>= \case
    0 -> return False
    1 -> return True
    _ -> fail "Expected 0 or 1 while decoding Bool"