content: {-# 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
  