packages feed

wireform-proto-0.2.0.0: src/Proto/Google/Protobuf/Any/Util.hs

{-# LANGUAGE ScopedTypeVariables #-}

{- | Utility functions for @google.protobuf.Any@ — type-safe packing,
unpacking, and runtime dispatch via 'TypeRegistry'.
-}
module Proto.Google.Protobuf.Any.Util (
  -- * Type-safe packing / unpacking
  packAny,
  packAnyWithPrefix,
  unpackAny,
  isMessageType,

  -- * Utilities
  typeUrlPrefix,
  typeUrlOf,
  typeNameFromUrl,
) where

import Data.Proxy (Proxy (..))
import Data.Text (Text)
import Data.Text qualified as T
import Proto.Decode (DecodeError, decodeMessage)
import Proto.Encode (encodeMessage)
import Proto.Google.Protobuf.Any (Any (..))
import Proto.Registry (IsMessage)
import Proto.Schema (ProtoMessage (..))


typeUrlPrefix :: Text
typeUrlPrefix = "type.googleapis.com/"


typeUrlOf :: forall a. IsMessage a => Proxy a -> Text
typeUrlOf p = typeUrlPrefix <> protoMessageName p


packAny :: forall a. IsMessage a => a -> Any
packAny msg =
  Any
    { anyTypeUrl = typeUrlOf (Proxy :: Proxy a)
    , anyValue = encodeMessage msg
    , anyUnknownFields = []
    }


packAnyWithPrefix :: forall a. IsMessage a => Text -> a -> Any
packAnyWithPrefix prefix msg =
  Any
    { anyTypeUrl = prefix <> protoMessageName (Proxy :: Proxy a)
    , anyValue = encodeMessage msg
    , anyUnknownFields = []
    }


unpackAny :: forall a. IsMessage a => Any -> Maybe (Either DecodeError a)
unpackAny any'
  | isTypeMatch (anyTypeUrl any') (Proxy :: Proxy a) = Just (decodeMessage (anyValue any'))
  | otherwise = Nothing


isMessageType :: forall a. IsMessage a => Proxy a -> Any -> Bool
isMessageType p any' = isTypeMatch (anyTypeUrl any') p


isTypeMatch :: forall a. IsMessage a => Text -> Proxy a -> Bool
isTypeMatch tu p =
  let expected = protoMessageName p
  in typeNameFromUrl tu == expected


typeNameFromUrl :: Text -> Text
typeNameFromUrl tu = case T.breakOnEnd "/" tu of
  ("", name) -> name
  (_, name) -> name