packages feed

grapesy-1.2.0: src/Network/GRPC/Server/Context.hs

-- | Server context
--
-- Intended for unqualified import.
module Network.GRPC.Server.Context (
    -- * Context
    ServerContext(..)
  , newServerContext
    -- * Configuration
  , ServerParams(..)
  ) where

import Data.Text qualified as Text
import System.IO (stderr, hPrint)

import Network.GRPC.Common.Compression qualified as Compr
import Network.GRPC.Common.Exception
import Network.GRPC.Server.RequestHandler.API
import Network.GRPC.Util.Imports

{-------------------------------------------------------------------------------
  Context

  TODO: <https://github.com/well-typed/grapesy/issues/130>
  The server context is more of a placeholder at the moment. The plan is to use
  it to keep things like server usage statistics etc.
-------------------------------------------------------------------------------}

data ServerContext = ServerContext {
      serverParams :: ServerParams
    }

newServerContext :: ServerParams -> IO ServerContext
newServerContext serverParams = do
    return ServerContext{
        serverParams
      }

{-------------------------------------------------------------------------------
  Configuration
-------------------------------------------------------------------------------}

data ServerParams = ServerParams {
      -- | Server compression preferences
      serverCompression :: Compr.Negotation

      -- | Top-level hook for request handlers
      --
      -- The most important responsibility of this function is to deal with
      -- any exceptions that the handler might throw, but in principle it has
      -- full control over how requests are handled.
      --
      -- The default merely logs any exceptions to 'stderr'.
    , serverTopLevel :: RequestHandler () -> RequestHandler ()

      -- | Render handler-side exceptions for the client
      --
      -- When a handler throws an exception other than a 'GrpcException', we use
      -- this function to render that exception for the client (server-side
      -- logging is taken care of by 'serverTopLevel'). The default
      -- implementation simply calls 'displayException' on the exception, which
      -- means the full context is visible on the client, which is most useful
      -- for debugging. However, it is a potential security concern: if the
      -- exception happens to contain sensitive information, this information
      -- will also be visible on the client. You may therefore wish to override
      -- the default behaviour.
    , serverExceptionToClient :: ExactException -> IO (Maybe Text)

      -- | Override content-type for response to client.
      --
      -- Set to 'Nothing' to omit the content-type header completely
      -- (this is not conform the gRPC spec).
    , serverContentType :: Maybe ContentType

      -- | Verify that all request headers can be parsed
      --
      -- When enabled, we verify at the start of each request that all request
      -- headers are valid. By default we do /not/ do this, throwing an error
      -- only in scenarios where we really cannot continue.
      --
      -- Even if enabled, we will not attempt to parse @rpc@-specific metadata
      -- (merely that the metadata is syntactically correct). See
      -- 'Network.GRPC.Server.getRequestMetadata' for detailed discussion.
    , serverVerifyHeaders :: Bool
    }

instance Default ServerParams where
  def = ServerParams {
        serverCompression       = def
      , serverTopLevel          = defaultServerTopLevel
      , serverExceptionToClient = defaultServerExceptionToClient
      , serverContentType       = Just ContentTypeDefault
      , serverVerifyHeaders     = False
      }

defaultServerTopLevel :: RequestHandler () -> RequestHandler ()
defaultServerTopLevel h unmask req resp =
    h unmask req resp `catch` handler
  where
    handler :: ExactException -> IO ()
    handler = hPrint stderr

-- | Default implementation for 'serverExceptionToClient'
--
-- Exception annotations are not included: these reveal server implementation
-- details and are not typicall relevant or even meaningful to clients.
defaultServerExceptionToClient :: ExactException -> IO (Maybe Text)
defaultServerExceptionToClient exact = return $
    withoutAnnotations exact $ \e -> Just . Text.pack $ concat [
        "Server-side exception: "
      ,  displayException e
      ]