content: module Test.Util.RawTestServer
    ( -- * Raw test server
      respondWith
    , respondWithIO
  
      -- * Abstract response type
    , Response(..)
    , asciiHeader
    , utf8Header
    ) where
  
  import Data.ByteString qualified as BS.Strict
  import Data.ByteString qualified as Strict (ByteString)
  import Data.ByteString.Builder qualified as BS.Builder
  import Data.ByteString.Char8 qualified as BS.Strict.Char8
  import Data.ByteString.UTF8 qualified as BS.Strict.UTF8
  import Data.String (fromString)
  import Network.HTTP2.Server qualified as HTTP2
  
  import Network.GRPC.Client qualified as Client
  import Network.GRPC.Common
  import Network.GRPC.Server.Run
  import Network.HTTP.Types qualified as HTTP
  
  {-------------------------------------------------------------------------------
    Raw test server
  
    This allows us to simulate broken /servers/.
  -------------------------------------------------------------------------------}
  
  -- | Run the server and apply the continuation to an 'Client.Address' holding
  -- the running server's host and port.
  withTestServer :: HTTP2.Server -> (Client.Address -> IO a) -> IO a
  withTestServer server k = do
      let serverConfig =
            ServerConfig {
              serverInsecure = Just $ InsecureConfig {
                  insecureHost = Just "127.0.0.1"
                , insecurePort = 0
                }
              , serverSecure = Nothing
              }
      forkServer def serverConfig server $ \runningServer -> do
        port <- getServerPort runningServer
        let addr :: Client.Address
            addr = Client.Address {
                  addressHost      = "127.0.0.1"
                , addressPort      = port
                , addressAuthority = Nothing
                }
        k addr
  
  -- | Server that responds with the given 'Response', independent of the request
  respondWith :: Response -> (Client.Address -> IO a) -> IO a
  respondWith resp = respondWithIO (return resp)
  
  -- | Version of 'respondWith' that constructs the response
  respondWithIO :: IO Response -> (Client.Address -> IO a) -> IO a
  respondWithIO mkResponse = withTestServer $ \_req _aux respond -> do
      response <- mkResponse
      respond (toHTTP2Response response) []
  
  data Response = Response {
        responseStatus   :: HTTP.Status
      , responseHeaders  :: [HTTP.Header]
      , responseBody     :: Strict.ByteString
      , responseTrailers :: [HTTP.Header]
      }
  
  instance Default Response where
    def = Response {
          responseStatus   = HTTP.ok200
        , responseHeaders  = [ asciiHeader "content-type" "application/grpc" ]
        , responseBody     = BS.Strict.empty
        , responseTrailers = [ asciiHeader "grpc-status" "0" ]
        }
  
  toHTTP2Response :: Response -> HTTP2.Response
  toHTTP2Response response =
      flip HTTP2.setResponseTrailersMaker trailersMaker $
        HTTP2.responseBuilder
          (responseStatus  response)
          (responseHeaders response)
          (BS.Builder.byteString $ responseBody response)
    where
      trailersMaker :: HTTP2.TrailersMaker
      trailersMaker Nothing  = return $ HTTP2.Trailers (responseTrailers response)
      trailersMaker (Just _) = return $ HTTP2.NextTrailersMaker trailersMaker
  
  -- | Header with ASCII value
  --
  -- (Header /names/ are always ASCII.)
  asciiHeader :: String -> String -> HTTP.Header
  asciiHeader name value = (fromString name, BS.Strict.Char8.pack value)
  
  -- | Header with UTF-8 encoded value
  utf8Header :: String -> String -> HTTP.Header
  utf8Header name value = (fromString name, BS.Strict.UTF8.fromString value)
  