packages feed

haskoin-core-1.6.0: src/Haskoin/Util/Marshal.hs

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ImportQualifiedPost #-}

module Haskoin.Util.Marshal where

import Control.Monad
import Crypto.Secp256k1
import Data.Aeson
import Data.Aeson.Encoding
import Data.Aeson.Types
import Data.ByteString
import Data.ByteString.Builder
import Data.ByteString.Lazy qualified as Lazy
import Data.Bytes.Get
import Data.Bytes.Put
import Data.Bytes.Serial

class Marshal s a | a -> s where
  marshalPut :: (MonadPut m) => s -> a -> m ()
  marshalGet :: (MonadGet m) => s -> m a

marshal :: (Marshal s a) => s -> a -> ByteString
marshal s = runPutS . marshalPut s

marshalLazy :: (Marshal s a) => s -> a -> Lazy.ByteString
marshalLazy s = runPutL . marshalPut s

-- | Read a value from bytes that hold the value and nothing else. Bytes left
-- over after the value are an error, so that two different byte strings never
-- read as the same value. To read a value from the start of a longer stream,
-- run 'marshalGet' yourself.
unmarshal :: (Marshal s a) => s -> ByteString -> Either String a
unmarshal s = runGetS (marshalGetAll s)

-- | Like 'unmarshal', for lazy bytes.
unmarshalLazy :: (Marshal s a) => s -> Lazy.ByteString -> Either String a
unmarshalLazy s = runGetS (marshalGetAll s) . Lazy.toStrict

-- | Read a value and require that the input ends there.
marshalGetAll :: (Marshal s a, MonadGet m) => s -> m a
marshalGetAll s = do
  value <- marshalGet s
  done <- isEmpty
  unless done $ fail "unmarshal: bytes left over after the value"
  return value

class MarshalJSON s a | a -> s where
  marshalValue :: s -> a -> Value
  marshalEncoding :: s -> a -> Encoding
  marshalEncoding x = value . marshalValue x
  unmarshalValue :: s -> Value -> Parser a

marshalJSON :: (MarshalJSON s a) => s -> a -> Lazy.ByteString
marshalJSON s = toLazyByteString . fromEncoding . marshalEncoding s

unmarshalJSON :: (MarshalJSON s a) => s -> Lazy.ByteString -> Maybe a
unmarshalJSON s = parseMaybe (unmarshalValue s) <=< decode