packages feed

grapesy-1.0.0: proto/Proto/API/Interop.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Proto.API.Interop (
    -- * TestService

    -- ** Endpoints
    EmptyCall
  , UnaryCall
  , StreamingInputCall
  , StreamingOutputCall
  , FullDuplexCall
  , UnimplementedCall

    -- ** Metadata
  , InteropReqMeta(..)
  , InteropRespInitMeta(..)
  , InteropRespTrailMeta(..)

    -- * UnimplementedService
  , UnimplementedServiceCall

    -- * Re-exports
  , module Proto.Empty
  , module Proto.Messages
  , module Proto.Test
  ) where

import Data.ByteString qualified as Strict (ByteString)
import Control.Monad.Catch (MonadThrow(throwM))
import Control.Monad.State (StateT, execStateT, modify)

import Network.GRPC.Common
import Network.GRPC.Common.Protobuf

import Proto.Empty
import Proto.Messages
import Proto.Test

{-------------------------------------------------------------------------------
  Endpoints
-------------------------------------------------------------------------------}

type EmptyCall           = Protobuf TestService "emptyCall"
type UnaryCall           = Protobuf TestService "unaryCall"
type StreamingInputCall  = Protobuf TestService "streamingInputCall"
type StreamingOutputCall = Protobuf TestService "streamingOutputCall"
type FullDuplexCall      = Protobuf TestService "fullDuplexCall"
type UnimplementedCall   = Protobuf TestService "unimplementedCall"

{-------------------------------------------------------------------------------
  Metadata
-------------------------------------------------------------------------------}

type instance RequestMetadata          (Protobuf TestService meth) = InteropReqMeta
type instance ResponseInitialMetadata  (Protobuf TestService meth) = InteropRespInitMeta
type instance ResponseTrailingMetadata (Protobuf TestService meth) = InteropRespTrailMeta

data InteropReqMeta = InteropReqMeta {
      -- | Header we expect the server to include in the initial metadata
      interopExpectInit :: Maybe Strict.ByteString

      -- | Header we expect the server to include in the trailng metadata
    , interopExpectTrail :: Maybe Strict.ByteString
    }
  deriving (Show, Eq)

newtype InteropRespInitMeta = InteropRespInitMeta {
      -- | Metadata the server /actually/ included in the initial metadata
      --
      -- See also 'interopExpectInit'
      interopActualInit :: Maybe Strict.ByteString
    }
  deriving (Show, Eq)

newtype InteropRespTrailMeta = InteropRespTrailMeta {
      -- | Metadata the server /actually/ included in the trailing metadata
      --
      -- See also 'interopExpectTrail'
      interopActualTrail :: Maybe Strict.ByteString
    }
  deriving (Show, Eq)

grpcTestEchoInitial :: HeaderName
grpcTestEchoInitial = "x-grpc-test-echo-initial"

grpcTestEchoTrailingBin :: HeaderName
grpcTestEchoTrailingBin = "x-grpc-test-echo-trailing-bin"

{-------------------------------------------------------------------------------
  Client instances
-------------------------------------------------------------------------------}

instance Default InteropReqMeta where
  def = InteropReqMeta {
        interopExpectInit  = Nothing
      , interopExpectTrail = Nothing
      }

instance BuildMetadata InteropReqMeta where
  buildMetadata md = concat [
        [ CustomMetadata grpcTestEchoInitial val
        | Just val <- [interopExpectInit md]
        ]
      , [ CustomMetadata grpcTestEchoTrailingBin val
        | Just val <- [interopExpectTrail md]
        ]
      ]

instance ParseMetadata InteropRespInitMeta where
  parseMetadata headers =
      case headers of
        [] ->
          return $ InteropRespInitMeta $ Nothing
        [md] | customMetadataName md == grpcTestEchoInitial ->
          return $ InteropRespInitMeta $ Just (customMetadataValue md)
        _otherwise ->
          throwM $ UnexpectedMetadata headers

instance ParseMetadata InteropRespTrailMeta where
  parseMetadata headers =
      case headers of
        [] ->
          return $ InteropRespTrailMeta $ Nothing
        [md] | customMetadataName md == grpcTestEchoTrailingBin ->
          return $ InteropRespTrailMeta $ Just (customMetadataValue md)
        _otherwise ->
          throwM $ UnexpectedMetadata headers

{-------------------------------------------------------------------------------
  Server instances
-------------------------------------------------------------------------------}

instance Default InteropRespInitMeta where
  def = InteropRespInitMeta Nothing

instance Default InteropRespTrailMeta where
  def = InteropRespTrailMeta Nothing

instance ParseMetadata InteropReqMeta where
  parseMetadata = flip execStateT def . mapM go
    where
      go :: MonadThrow m => CustomMetadata -> StateT InteropReqMeta m ()
      go md
        | customMetadataName md == grpcTestEchoInitial
        = modify $ \x -> x{interopExpectInit = Just $ customMetadataValue md}

        | customMetadataName md == grpcTestEchoTrailingBin
        = modify $ \x -> x{interopExpectTrail = Just $ customMetadataValue md}

        | otherwise
        = throwM $ UnexpectedMetadata [md]

instance BuildMetadata InteropRespInitMeta where
  buildMetadata md = concat [
        [ CustomMetadata grpcTestEchoInitial val
        | Just val <- [interopActualInit md]
        ]
      ]

instance BuildMetadata InteropRespTrailMeta where
  buildMetadata md = concat [
        [ CustomMetadata grpcTestEchoTrailingBin val
        | Just val <- [interopActualTrail md]
        ]
      ]

instance StaticMetadata InteropRespTrailMeta where
  metadataHeaderNames _ = [grpcTestEchoTrailingBin]

{-------------------------------------------------------------------------------
  UnimplementedService service
-------------------------------------------------------------------------------}

type UnimplementedServiceCall = Protobuf UnimplementedService "unimplementedCall"

type instance RequestMetadata          (Protobuf UnimplementedService meth) = NoMetadata
type instance ResponseInitialMetadata  (Protobuf UnimplementedService meth) = NoMetadata
type instance ResponseTrailingMetadata (Protobuf UnimplementedService meth) = NoMetadata