packages feed

bolty-0.1.0.0: src/Database/Bolty/Util.hs

-- | Internal module. Not part of the public API.
module Database.Bolty.Util
  ( chunksOfBSL
  , chunksOfBS
  , putTextUtf8
  , decodeStrict
  , encodeStrict
  , lazyByteStringToHex
  , whenInvalid
  ) where

import           Control.Exception.Extra          (Partial)
import           Data.ByteString.Builder          (toLazyByteString, lazyByteStringHex)
import           Data.Int                         (Int64)
import           Data.List.Split                  (chunksOf)
import           Data.Persist                     (Put, HasEndianness, putByteString, runPut, runGet, putBE, getBE)
import           Data.Word8                       (toUpper)
import qualified Data.ByteString                  as BS
import qualified Data.ByteString.Lazy             as BSL
import qualified Data.Text                        as T
import qualified Data.Text.Encoding               as T
import qualified Validation                       as V


-- | Split a lazy 'BSL.ByteString' into chunks of at most @n@ bytes.
chunksOfBSL :: Partial => Int64 -> BSL.ByteString -> [BSL.ByteString]
chunksOfBSL i _ | i <= 0 = error $ "chunksOf, number must be positive, got " ++ show i
chunksOfBSL i xs = repeatedlyBSL (BSL.splitAt i) xs

-- | Split a strict 'BS.ByteString' into chunks of at most @n@ bytes.
chunksOfBS :: Partial => Int -> BS.ByteString -> [BS.ByteString]
chunksOfBS i _ | i <= 0 = error $ "chunksOf, number must be positive, got " ++ show i
chunksOfBS i xs = repeatedlyBS (BS.splitAt i) xs

repeatedlyBSL :: (BSL.ByteString -> (b, BSL.ByteString)) -> BSL.ByteString -> [b]
repeatedlyBSL f bs
    | BSL.null bs = []
    | otherwise   = b : repeatedlyBSL f bs'
  where (b, bs') = f bs

repeatedlyBS :: (BS.ByteString -> (b, BS.ByteString)) -> BS.ByteString -> [b]
repeatedlyBS f bs
    | BS.null bs = []
    | otherwise  = b : repeatedlyBS f bs'
  where (b, bs') = f bs


-- | Encode 'T.Text' as raw UTF-8 bytes into the 'Put' monad.
putTextUtf8 :: T.Text -> Put ()
putTextUtf8 = putByteString . T.encodeUtf8

-- | Decode a big-endian value from a strict 'BS.ByteString', throwing on failure.
decodeStrict :: forall a. HasEndianness a => BS.ByteString -> a
decodeStrict bs = case runGet (getBE @a) bs of
  Left e  -> error $ "decodeStrict: " ++ e
  Right v -> v

-- | Encode a value as a big-endian strict 'BS.ByteString'.
encodeStrict :: HasEndianness a => a -> BS.ByteString
encodeStrict = runPut . putBE


-- | Convert a lazy 'BSL.ByteString' to an uppercase hex-encoded strict 'BS.ByteString'.
lazyByteStringToHex :: BSL.ByteString -> BS.ByteString
lazyByteStringToHex bs = BS.intercalate " " $ map BS.pack $ Data.List.Split.chunksOf 2 $ map toUpper $ BS.unpack $ BSL.toStrict $ toLazyByteString $ lazyByteStringHex bs


-- | Return 'V.Failure' with the given error when the condition is 'True', otherwise 'V.Success'.
whenInvalid :: a -> Bool -> T.Text -> V.Validation [T.Text] a
whenInvalid x invalid err =
  if invalid then
    V.Failure [err]
  else
    V.Success x