packages feed

amazonka-core-2.0: src/Amazonka/Data/Headers.hs

-- |
-- Module      : Amazonka.Data.Headers
-- Copyright   : (c) 2013-2023 Brendan Hay
-- License     : Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay+amazonka@gmail.com>
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
module Amazonka.Data.Headers
  ( module Amazonka.Data.Headers,
    HeaderName,
    Header,
    HTTP.hContentType,
  )
where

import Amazonka.Data.ByteString
import Amazonka.Data.Text
import Amazonka.Prelude
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Data.CaseInsensitive as CI
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Network.HTTP.Types (Header, HeaderName, ResponseHeaders)
import qualified Network.HTTP.Types as HTTP

infixl 7 .#, .#?

-- FIXME: This whole toText/fromText shit is just stupid.
(.#) :: FromText a => ResponseHeaders -> HeaderName -> Either String a
hs .# k = hs .#? k >>= note
  where
    note Nothing = Left (BS8.unpack $ "Unable to find header: " <> CI.original k)
    note (Just x) = Right x

(.#?) :: FromText a => ResponseHeaders -> HeaderName -> Either String (Maybe a)
hs .#? k =
  maybe
    (Right Nothing)
    (fmap Just . fromText . Text.decodeUtf8)
    (k `lookup` hs)

infixr 7 =#

(=#) :: ToHeader a => HeaderName -> a -> [Header]
(=#) = toHeader

hdr :: HeaderName -> ByteString -> [Header] -> [Header]
hdr k v hs = (k, v) : filter ((/= k) . fst) hs

class ToHeaders a where
  toHeaders :: a -> [Header]

instance (ToByteString k, ToByteString v) => ToHeaders (HashMap k v) where
  toHeaders = map (bimap (CI.mk . toBS) toBS) . HashMap.toList

class ToHeader a where
  toHeader :: HeaderName -> a -> [Header]
  default toHeader :: ToText a => HeaderName -> a -> [Header]
  toHeader k = toHeader k . toText

instance ToHeader Int

instance ToHeader Integer

instance ToHeader Natural

instance ToHeader Text where
  toHeader k v = [(k, Text.encodeUtf8 v)]

instance ToHeader ByteString where
  toHeader k v = [(k, v)]

instance ToText a => ToHeader (Maybe a) where
  toHeader k = maybe [] (toHeader k . toText)

instance ToText a => ToHeader [a] where
  toHeader k vs = case vs of
    [] -> []
    _ -> toHeader k . Text.intercalate "," $ map toText vs

instance (ToByteString k, ToByteString v) => ToHeader (HashMap k v) where
  toHeader p = map (bimap k v) . HashMap.toList
    where
      k = mappend p . CI.mk . toBS
      v = toBS

parseHeadersMap ::
  FromText a =>
  ByteString ->
  ResponseHeaders ->
  Either String (HashMap Text a)
parseHeadersMap p = fmap HashMap.fromList . traverse g . filter f
  where
    f = BS.isPrefixOf p . CI.foldedCase . fst

    g (k, v) =
      (Text.decodeUtf8 . BS.drop n $ CI.original k,)
        <$> fromText (Text.decodeUtf8 v)

    n = BS.length p

hHost :: HeaderName
hHost = "Host"

hExpect :: HeaderName
hExpect = "Expect"

hAMZToken :: HeaderName
hAMZToken = "X-Amz-Security-Token"

hAMZTarget :: HeaderName
hAMZTarget = "X-Amz-Target"

hAMZAlgorithm :: HeaderName
hAMZAlgorithm = "X-Amz-Algorithm"

hAMZCredential :: HeaderName
hAMZCredential = "X-Amz-Credential"

hAMZExpires :: HeaderName
hAMZExpires = "X-Amz-Expires"

hAMZSignedHeaders :: HeaderName
hAMZSignedHeaders = "X-Amz-SignedHeaders"

hAMZContentSHA256 :: HeaderName
hAMZContentSHA256 = "X-Amz-Content-SHA256"

hAMZDate :: HeaderName
hAMZDate = "X-Amz-Date"

hMetaPrefix :: HeaderName
hMetaPrefix = "X-Amz-"

hAMZRequestId :: HeaderName
hAMZRequestId = "X-Amz-Request-Id"

hAMZNRequestId :: HeaderName
hAMZNRequestId = "X-Amzn-RequestId"

hAMZNErrorType :: HeaderName
hAMZNErrorType = "X-Amzn-ErrorType"

hAMZNAuth :: HeaderName
hAMZNAuth = "X-Amzn-Authorization"

hAMZDecodedContentLength :: HeaderName
hAMZDecodedContentLength = "X-Amz-Decoded-Content-Length"

hTransferEncoding :: HeaderName
hTransferEncoding = "Transfer-Encoding"

hFormEncoded :: ByteString
hFormEncoded = "application/x-www-form-urlencoded; charset=utf-8"