packages feed

http-core-0.1.0.0: src/Http/Core/Request.hs

-- | Backend-agnostic HTTP request types.
--
-- 'Request' is parameterized by body type, enabling both strict
-- (@ByteString@) and streaming request bodies. Backend adapters
-- (spire-wai, etc.) convert from their native request types to this.
module Http.Core.Request
  ( -- * Request type
    Request (..)
    -- * Request parts (for extractors)
  , RequestParts (..)
  , splitRequest
    -- * Convenience constructors
  , defaultRequest
  ) where

import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Text (Text)
import Network.HTTP.Types (Method, Query, RequestHeaders, methodGet)

import Http.Core.Extensions (Extensions, emptyExtensions)


-- | An HTTP request, parameterized by body type.
--
-- The body type parameter enables both strict (@ByteString@) and
-- streaming bodies. For the default server path, the body is strict
-- bytes (collected by the backend before dispatch).
data Request body = Request
  { requestMethod     :: !Method
  , requestPathRaw    :: !ByteString
  , requestPath       :: ![Text]
  , requestQuery      :: !Query
  , requestHeaders    :: !RequestHeaders
  , requestBody       :: !body
  , requestExtensions :: !Extensions
  }


-- | The non-body parts of a request, used by 'FromRequestParts' extractors.
--
-- Extractors that don't need the body (path captures, query params,
-- headers, state, auth) work on 'RequestParts'. This allows multiple
-- extractors to read from the same request without consuming the body.
--
-- The body is extracted separately by 'FromRequest' (at most once per
-- handler).
data RequestParts = RequestParts
  { rpMethod     :: !Method
  , rpPathRaw    :: !ByteString
  , rpPath       :: ![Text]
  , rpQuery      :: !Query
  , rpHeaders    :: !RequestHeaders
  , rpExtensions :: !Extensions
  }


-- | Split a strict-body request into parts + body.
--
-- This is the boundary between the router (which has the full request)
-- and the extractor system (which separates parts from body).
splitRequest :: Request ByteString -> (RequestParts, ByteString)
splitRequest req =
  ( RequestParts
      { rpMethod     = requestMethod req
      , rpPathRaw    = requestPathRaw req
      , rpPath       = requestPath req
      , rpQuery      = requestQuery req
      , rpHeaders    = requestHeaders req
      , rpExtensions = requestExtensions req
      }
  , requestBody req
  )


-- | A minimal default request for testing.
--
-- Method: GET, path: "/", empty query/headers/body.
defaultRequest :: IO (Request ByteString)
defaultRequest = do
  exts <- emptyExtensions
  pure Request
    { requestMethod     = methodGet
    , requestPathRaw    = "/"
    , requestPath       = []
    , requestQuery      = []
    , requestHeaders    = []
    , requestBody       = BS.empty
    , requestExtensions = exts
    }