packages feed

servant-benchmark-0.1.0.0: src/Servant/Benchmark/Endpoint.hs

{-# LANGUAGE OverloadedStrings #-}

module Servant.Benchmark.Endpoint where

import Control.Applicative ((<|>))
import qualified Data.ByteString as BS
import Data.CaseInsensitive (mk)
import Data.Maybe (maybeToList)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Network.HTTP.Media (MediaType, RenderHeader (renderHeader), (//), (/:))
import Network.HTTP.Types (Method, hContentType)
import Network.HTTP.Types.Header (Header)

{- | An API endpoint.
-
-}
data Endpoint = MkEndpoint
    { name :: T.Text
    , -- All endpoint request paths
      path :: T.Text
    , -- The endpoint request method
      method :: Maybe Method
    , -- | The request value, where applicable.
      -- Only the first encountered request value is taken into consideration
      -- eg. "user" :> ReqBody '[JSON] Text :> ReqBody '[JSON] Int :> Get '[JSON] User
      -- will produce only a `Text` based request value
      body :: Maybe BS.ByteString
    , -- | The requests content type.
      -- Only the first encountered content type is taken into consideration.
      -- If you're building an endpoint manually, you should enter the media type here
      -- rather than directly in headers. All implementations automatically include the
      -- content type header during benchmark configuration output.
      contentType :: Maybe MediaType
    , -- | The request headers
      headers :: [Header]
    }
    deriving (Show, Eq)

instance Semigroup Endpoint where
    a <> b =
        MkEndpoint
            (name a <> name b)
            (path a <> path b)
            (method a <> method b)
            (body a <|> body b)
            (contentType a <|> contentType b)
            (headers a <> headers b)

instance Monoid Endpoint where
    mempty = MkEndpoint mempty mempty mempty Nothing Nothing []

{- | Pack an endpoint created from an API interpretation in a form
 ready to be serialized.
 - This is only useful if your are building your own output.
-}
pack :: Endpoint -> Endpoint
pack endpoint =
    endpoint
        { contentType = Nothing
        , headers = ct ++ headers endpoint
        }
  where
    ct = maybeToList ctHeader
    ctHeader = (,) hContentType <$> fmap renderHeader (contentType endpoint)

-- | Create a `Header` from two `Text` inputs
mkHeader :: T.Text -> T.Text -> Header
mkHeader ciName value =
    (mk $ T.encodeUtf8 ciName, T.encodeUtf8 value)

-- * Content Types

-- | application/json
ctJSON :: MediaType
ctJSON = hApplication // hJSON

-- | text/plain ; charset=utf-8
ctPlainText :: MediaType
ctPlainText = hText // hPlain /: (hCharset, hUTF8)

-- | application
hApplication :: BS.ByteString
hApplication = "application"

-- | json
hJSON :: BS.ByteString
hJSON = "json"

-- | text
hText :: BS.ByteString
hText = "text"

-- | plain
hPlain :: BS.ByteString
hPlain = "plain"

-- | charset
hCharset :: BS.ByteString
hCharset = "charset"

-- | utf-8
hUTF8 :: BS.ByteString
hUTF8 = "utf-8"