{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_HADDOCK not-home #-}
module Servant.API.ResponseHeaders
( Headers(..)
, ResponseHeader (..)
, AddHeader
, addHeader
, noHeader
, HasResponseHeader
, lookupResponseHeader
, BuildHeadersTo(buildHeadersTo)
, GetHeaders(getHeaders)
, GetHeaders'
, HeaderValMap
, HList(..)
) where
import Control.DeepSeq
(NFData (..))
import Data.ByteString.Char8 as BS
(ByteString, init, pack, unlines)
import qualified Data.CaseInsensitive as CI
import Data.Proxy
import Data.Typeable
(Typeable)
import GHC.TypeLits
(KnownSymbol, Symbol, symbolVal)
import qualified Network.HTTP.Types.Header as HTTP
import Web.HttpApiData
(FromHttpApiData, ToHttpApiData, parseHeader, toHeader)
import Prelude ()
import Prelude.Compat
import Servant.API.Header
(Header)
data Headers ls a = Headers { getResponse :: a
, getHeadersHList :: HList ls
} deriving (Functor)
instance (NFDataHList ls, NFData a) => NFData (Headers ls a) where
rnf (Headers x hdrs) = rnf x `seq` rnf hdrs
data ResponseHeader (sym :: Symbol) a
= Header a
| MissingHeader
| UndecodableHeader ByteString
deriving (Typeable, Eq, Show, Functor)
instance NFData a => NFData (ResponseHeader sym a) where
rnf MissingHeader = ()
rnf (UndecodableHeader bs) = rnf bs
rnf (Header x) = rnf x
data HList a where
HNil :: HList '[]
HCons :: ResponseHeader h x -> HList xs -> HList (Header h x ': xs)
class NFDataHList xs where rnfHList :: HList xs -> ()
instance NFDataHList '[] where rnfHList HNil = ()
instance (y ~ Header h x, NFData x, NFDataHList xs) => NFDataHList (y ': xs) where
rnfHList (HCons h xs) = rnf h `seq` rnfHList xs
instance NFDataHList xs => NFData (HList xs) where
rnf = rnfHList
type family HeaderValMap (f :: * -> *) (xs :: [*]) where
HeaderValMap f '[] = '[]
HeaderValMap f (Header h x ': xs) = Header h (f x) ': HeaderValMap f xs
class BuildHeadersTo hs where
buildHeadersTo :: [HTTP.Header] -> HList hs
instance {-# OVERLAPPING #-} BuildHeadersTo '[] where
buildHeadersTo _ = HNil
instance {-# OVERLAPPABLE #-} ( FromHttpApiData v, BuildHeadersTo xs, KnownSymbol h )
=> BuildHeadersTo (Header h v ': xs) where
buildHeadersTo headers =
let wantedHeader = CI.mk . pack $ symbolVal (Proxy :: Proxy h)
matching = snd <$> filter (\(h, _) -> h == wantedHeader) headers
in case matching of
[] -> MissingHeader `HCons` buildHeadersTo headers
xs -> case parseHeader (BS.init $ BS.unlines xs) of
Left _err -> UndecodableHeader (BS.init $ BS.unlines xs)
`HCons` buildHeadersTo headers
Right h -> Header h `HCons` buildHeadersTo headers
class GetHeaders ls where
getHeaders :: ls -> [HTTP.Header]
class GetHeadersFromHList hs where
getHeadersFromHList :: HList hs -> [HTTP.Header]
instance GetHeadersFromHList hs => GetHeaders (HList hs) where
getHeaders = getHeadersFromHList
instance GetHeadersFromHList '[] where
getHeadersFromHList _ = []
instance (KnownSymbol h, ToHttpApiData x, GetHeadersFromHList xs)
=> GetHeadersFromHList (Header h x ': xs)
where
getHeadersFromHList hdrs = case hdrs of
Header val `HCons` rest -> (headerName , toHeader val) : getHeadersFromHList rest
UndecodableHeader h `HCons` rest -> (headerName, h) : getHeadersFromHList rest
MissingHeader `HCons` rest -> getHeadersFromHList rest
where
headerName = CI.mk . pack $ symbolVal (Proxy :: Proxy h)
class GetHeaders' hs where
getHeaders' :: Headers hs a -> [HTTP.Header]
instance GetHeaders' hs => GetHeaders (Headers hs a) where
getHeaders = getHeaders'
instance GetHeaders' '[] where
getHeaders' _ = []
instance (KnownSymbol h, GetHeadersFromHList rest, ToHttpApiData v)
=> GetHeaders' (Header h v ': rest)
where
getHeaders' hs = getHeadersFromHList $ getHeadersHList hs
class AddHeader h v orig new
| h v orig -> new, new -> h, new -> v, new -> orig where
addOptionalHeader :: ResponseHeader h v -> orig -> new
instance {-# OVERLAPPING #-} ( KnownSymbol h, ToHttpApiData v )
=> AddHeader h v (Headers (fst ': rest) a) (Headers (Header h v ': fst ': rest) a) where
addOptionalHeader hdr (Headers resp heads) = Headers resp (HCons hdr heads)
instance {-# OVERLAPPABLE #-} ( KnownSymbol h, ToHttpApiData v
, new ~ (Headers '[Header h v] a) )
=> AddHeader h v a new where
addOptionalHeader hdr resp = Headers resp (HCons hdr HNil)
addHeader :: AddHeader h v orig new => v -> orig -> new
addHeader = addOptionalHeader . Header
noHeader :: AddHeader h v orig new => orig -> new
noHeader = addOptionalHeader MissingHeader
class HasResponseHeader h a headers where
hlistLookupHeader :: HList headers -> ResponseHeader h a
instance {-# OVERLAPPING #-} HasResponseHeader h a (Header h a ': rest) where
hlistLookupHeader (HCons ha _) = ha
instance {-# OVERLAPPABLE #-} (HasResponseHeader h a rest) => HasResponseHeader h a (first ': rest) where
hlistLookupHeader (HCons _ hs) = hlistLookupHeader hs
lookupResponseHeader :: (HasResponseHeader h a headers)
=> Headers headers r -> ResponseHeader h a
lookupResponseHeader = hlistLookupHeader . getHeadersHList