grpc-spec-1.0.0: src/Network/GRPC/Spec/CustomMetadata/Map.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Map of t'CustomMetadata', handling joining values
module Network.GRPC.Spec.CustomMetadata.Map (
CustomMetadataMap -- opaque
-- * Conversion
, customMetadataMapFromList
, customMetadataMapToList
-- * Construction
, customMetadataMapInsert
) where
import Data.ByteString qualified as Strict (ByteString)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Maybe (fromMaybe)
import GHC.Generics (Generic)
import Network.GRPC.Spec.CustomMetadata.Raw
{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}
-- | Map from header names to values
--
-- The gRPC spec mandates
--
-- > Custom-Metadata header order is not guaranteed to be preserved except for
-- > values with duplicate header names. Duplicate header names may have their
-- > values joined with "," as the delimiter and be considered semantically
-- > equivalent.
--
-- Internally we don't allow for these duplicates, but instead join the headers
-- as mandated by the spec.
newtype CustomMetadataMap = CustomMetadataMap {
getCustomMetadataMap :: Map HeaderName Strict.ByteString
}
deriving stock (Show, Eq, Generic)
{-------------------------------------------------------------------------------
Dealing with duplicates
-------------------------------------------------------------------------------}
instance Monoid CustomMetadataMap where
mempty = CustomMetadataMap Map.empty
instance Semigroup CustomMetadataMap where
CustomMetadataMap x <> CustomMetadataMap y = CustomMetadataMap $
Map.unionWith joinHeaderValue x y
{-------------------------------------------------------------------------------
Conversion
-------------------------------------------------------------------------------}
-- | Construct t'CustomMetadataMap', joining duplicates
customMetadataMapFromList :: [CustomMetadata] -> CustomMetadataMap
customMetadataMapFromList =
CustomMetadataMap
. Map.fromListWith joinHeaderValue
. map unpairCustomMetadata
-- | Flatten t'CustomMetadataMap' to a list
--
-- Precondition: the map must be valid.
customMetadataMapToList :: CustomMetadataMap -> [CustomMetadata]
customMetadataMapToList mds =
map pairCustomMetadata
. Map.toList
. getCustomMetadataMap
$ mds
{-------------------------------------------------------------------------------
Construction
-------------------------------------------------------------------------------}
-- | Insert value into t'CustomMetadataMap'
--
-- If a header with the same name already exists, the value is appended to
-- (the end of) the existing value.
customMetadataMapInsert ::
CustomMetadata
-> CustomMetadataMap -> CustomMetadataMap
customMetadataMapInsert (CustomMetadata name value) (CustomMetadataMap mds) =
CustomMetadataMap $
Map.insertWith (flip joinHeaderValue) name value mds
{-------------------------------------------------------------------------------
Internal auxiliary
-------------------------------------------------------------------------------}
joinHeaderValue :: Strict.ByteString -> Strict.ByteString -> Strict.ByteString
joinHeaderValue x y = x <> "," <> y
unpairCustomMetadata :: CustomMetadata -> (HeaderName, Strict.ByteString)
unpairCustomMetadata (CustomMetadata name value) = (name, value)
pairCustomMetadata :: (HeaderName, Strict.ByteString) -> CustomMetadata
pairCustomMetadata md =
fromMaybe (error $ "invalid " ++ show md) $
uncurry safeCustomMetadata md