-- | Protobuf utilities
--
-- Intended for qualified import.
--
-- > import Network.GRPC.Spec.Util.Protobuf qualified as Protobuf
module Network.GRPC.Spec.Util.Protobuf (
    -- * Serialization
    parseStrict
  , parseLazy
  , buildStrict
  , buildLazy
  ) where

import Data.Binary.Builder qualified as Builder
import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString.Lazy qualified as BS.Lazy
import Data.ByteString.Lazy qualified as Lazy (ByteString)
import Data.ProtoLens (Message)
import Data.ProtoLens qualified as Protobuf
import Data.ProtoLens.Encoding.Parser (Parser)
import Data.ProtoLens.Encoding.Parser qualified as Protobuf
import Data.Proxy
import Data.Text qualified as Text

{-------------------------------------------------------------------------------
  Serialization
-------------------------------------------------------------------------------}

parseStrict :: Message msg => Strict.ByteString -> Either String msg
parseStrict :: forall msg. Message msg => ByteString -> Either String msg
parseStrict = Parser msg -> ByteString -> Either String msg
forall a. Parser a -> ByteString -> Either String a
Protobuf.runParser Parser msg
forall msg. Message msg => Parser msg
parseMessage

-- | Parse lazy bytestring
--
-- TODO: <https://github.com/well-typed/grapesy/issues/119>.
-- We currently turn this into a strict bytestring before parsing.
parseLazy :: Message msg => Lazy.ByteString -> Either String msg
parseLazy :: forall msg. Message msg => ByteString -> Either String msg
parseLazy = ByteString -> Either String msg
forall msg. Message msg => ByteString -> Either String msg
parseStrict (ByteString -> Either String msg)
-> (ByteString -> ByteString) -> ByteString -> Either String msg
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
BS.Lazy.toStrict

buildStrict :: Message msg => msg -> Strict.ByteString
buildStrict :: forall msg. Message msg => msg -> ByteString
buildStrict = ByteString -> ByteString
BS.Lazy.toStrict (ByteString -> ByteString)
-> (msg -> ByteString) -> msg -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. msg -> ByteString
forall msg. Message msg => msg -> ByteString
buildLazy

buildLazy :: Message msg => msg -> Lazy.ByteString
buildLazy :: forall msg. Message msg => msg -> ByteString
buildLazy = Builder -> ByteString
Builder.toLazyByteString (Builder -> ByteString) -> (msg -> Builder) -> msg -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. msg -> Builder
forall msg. Message msg => msg -> Builder
Protobuf.buildMessage

{-------------------------------------------------------------------------------
  Internal auxilairy
-------------------------------------------------------------------------------}

parseMessage :: forall msg. Protobuf.Message msg => Parser msg
parseMessage :: forall msg. Message msg => Parser msg
parseMessage = do
    msg   <- Parser msg
forall msg. Message msg => Parser msg
Protobuf.parseMessage
    atEnd <- Protobuf.atEnd
    if atEnd then
      return msg
    else
      fail $ concat [
          Text.unpack $ Protobuf.messageName $ Proxy @msg
        , ": unconsumed bytes"
        ]