packages feed

webgear-server-0.1.0: src/WebGear/Trait/Header.hs

-- |
-- Copyright        : (c) Raghu Kaippully, 2020
-- License          : MPL-2.0
-- Maintainer       : rkaippully@gmail.com
--
-- Traits related to HTTP headers.
module WebGear.Trait.Header
  ( Header
  , HeaderFail (..)
  , HeaderMatch
  , HeaderMismatch (..)
  ) where

import Data.ByteString (ByteString)
import Data.Kind (Type)
import Data.Proxy (Proxy (..))
import Data.String (fromString)
import Data.Text (Text)
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import Web.HttpApiData (FromHttpApiData (..))

import WebGear.Trait (CheckResult (..), Trait (..))
import WebGear.Types (Request, requestHeader)


-- | A 'Trait' for capturing a header with name @s@ in a request or
-- response and convert it to some type @t@ via 'FromHttpApiData'.
data Header (s :: Symbol) (t :: Type)

-- | Failure in extracting a header value
data HeaderFail = HeaderNotFound | HeaderParseError Text
  deriving stock (Read, Show, Eq)

instance (KnownSymbol s, FromHttpApiData t, Monad m) => Trait (Header s t) Request m where
  type Val (Header s t) Request = t
  type Fail (Header s t) Request = HeaderFail

  check :: Request -> m (CheckResult (Header s t) Request)
  check r = pure $
    let s = fromString $ symbolVal (Proxy @s)
    in case parseHeader <$> requestHeader s r of
         Nothing        -> CheckFail HeaderNotFound
         Just (Left e)  -> CheckFail $ HeaderParseError e
         Just (Right x) -> CheckSuccess r x

-- | A 'Trait' for ensuring that a header named @s@ has value @t@.
data HeaderMatch (s :: Symbol) (t :: Symbol)

-- | Failure in extracting a header value
data HeaderMismatch = HeaderMismatch
  { expectedHeader :: ByteString
  , actualHeader   :: Maybe ByteString
  }
  deriving stock (Eq, Read, Show)

instance (KnownSymbol s, KnownSymbol t, Monad m) => Trait (HeaderMatch s t) Request m where
  type Val (HeaderMatch s t) Request = ByteString
  type Fail (HeaderMatch s t) Request = HeaderMismatch

  check :: Request -> m (CheckResult (HeaderMatch s t) Request)
  check r = pure $
    let
      name = fromString $ symbolVal (Proxy @s)
      expected = fromString $ symbolVal (Proxy @t)
    in
      case requestHeader name r of
        Nothing                  -> CheckFail HeaderMismatch
                                      {expectedHeader = expected, actualHeader = Nothing}
        Just hv | hv == expected -> CheckSuccess r hv
                | otherwise      -> CheckFail HeaderMismatch
                                      {expectedHeader = expected, actualHeader = Just hv}