packages feed

packstream-bolt-0.1.0.0: src/Data/PackStream/Put.hs

-- | Internal module. Not part of the public API.
module Data.PackStream.Put (
  putNull, putBoolean, putFloat,
  putInteger, putInteger64,
  putString, putBytes, putList, putList', putDictionary
  ) where

import           Compat.Prelude
import           Prelude

import qualified Data.ByteString          as S
import qualified Data.Text                as T
import qualified Data.Text.Encoding       as T
import qualified Data.Vector              as V
import qualified Data.HashMap.Lazy        as H

import           Compat.Binary
import           Data.PackStream.Integer
import           Data.PackStream.Tags

-- | Encode a PackStream null value.
putNull :: Put
putNull = putWord8 TAG_Null

-- | Encode a PackStream boolean value.
putBoolean :: Bool -> Put
putBoolean False = putWord8 TAG_false
putBoolean True  = putWord8 TAG_true

-- | Encodes an 'Int' to PackStream
--
-- See also 'PSInteger' and its 'Binary' instance.
putInteger :: Int -> Put
putInteger = put . toPSInteger

-- putWord :: Word -> Put
-- putWord = put . toPSInteger

-- | Encode an 'Int64' to PackStream.
putInteger64 :: Int64 -> Put
putInteger64 = put . toPSInteger

-- putWord64 :: Word64 -> Put
-- putWord64 = put . toPSInteger

-- | Encode a 'Double' as a PackStream 64-bit float.
putFloat :: Double -> Put
putFloat d = putWord8 TAG_Float >> putFloat64be d

-- | Encode a 'T.Text' as a PackStream UTF-8 string.
putString :: T.Text -> Put
putString t = do
  let bs = T.encodeUtf8 t
  toSizeM ("putString: data exceeds 2^32-1 byte limit of PackStream") (S.length bs) >>= \case
    len | len <= 15     -> putWord8 (TAG_STRING_SHORT .|. fromIntegral len)
        | len < 0x100   -> putWord8 TAG_STRING_8  >> putWord8    (fromIntegral len)
        | len < 0x10000 -> putWord8 TAG_STRING_16 >> putWord16be (fromIntegral len)
        | otherwise     -> putWord8 TAG_STRING_32 >> putWord32be len
  putByteString bs

-- | Encode a 'S.ByteString' as a PackStream byte array.
putBytes :: S.ByteString -> Put
putBytes bs = do
  toSizeM ("putBytes: data exceeds 2^32-1 byte limit of PackStream") (S.length bs) >>= \case
    len | len < 0x100   -> putWord8 TAG_BYTE_ARRAY_8  >> putWord8    (fromIntegral len)
        | len < 0x10000 -> putWord8 TAG_BYTE_ARRAY_16 >> putWord16be (fromIntegral len)
        | otherwise     -> putWord8 TAG_BYTE_ARRAY_32 >> putWord32be len
  putByteString bs

-- | Encode a 'V.Vector' as a PackStream list using the given element encoder.
putList :: (a -> Put) -> V.Vector a -> Put
putList p xs = do
  len <- toSizeM ("putList: data exceeds 2^32-1 element limit of PackStream") (V.length xs)
  putList' len (V.mapM_ p xs)

putList' :: Word32 -- ^ number of array elements
          -> Put    -- ^ 'Put' action emitting array elements (__NOTE__: it's the responsibility of the caller to ensure that the declared array length matches exactly the data generated by the 'Put' action)
          -> Put
putList' len putter = do
  case () of
    _   | len <= 15     -> putWord8 (TAG_LIST_SHORT .|. fromIntegral len)
        | len < 0x100   -> putWord8 TAG_LIST_8  >> putWord8    (fromIntegral len)
        | len < 0x10000 -> putWord8 TAG_LIST_16 >> putWord16be (fromIntegral len)
        | otherwise     -> putWord8 TAG_LIST_32 >> putWord32be len
  putter

-- | Encode a 'H.HashMap' as a PackStream dictionary using the given key and value encoders.
putDictionary :: (a -> Put) -> (b -> Put) -> H.HashMap a b -> Put
putDictionary p q xs = do
  let len' = H.size xs
  if len' > 2_147_483_647 then
    error "putDictionary: data exceeds 2^32-1 element limit of PackStream"
  else case len' of
    len | len <= 15     -> putWord8 (TAG_DICTIONARY_SHORT .|. fromIntegral len)
        | len < 0x100   -> putWord8 TAG_DICTIONARY_8  >> putWord8    (fromIntegral len)
        | len < 0x10000 -> putWord8 TAG_DICTIONARY_16 >> putWord16be (fromIntegral len)
        | otherwise     -> putWord8 TAG_DICTIONARY_32 >> putWord32be (fromIntegral len)
  mapM_ (\(a, b) -> p a >> q b) $ H.toList xs


toSizeM :: String -> Int -> PutM Word32
toSizeM label len0 = maybe (error label) pure (intCastMaybe len0)