Add Response Headers

This commit is contained in:
Timo von Holtz 2015-03-18 15:23:09 +11:00
parent 642910b3b3
commit e9797732b1
2 changed files with 29 additions and 8 deletions

View file

@ -47,6 +47,7 @@ library
, aeson >= 0.7 , aeson >= 0.7
, attoparsec >= 0.12 , attoparsec >= 0.12
, bytestring == 0.10.* , bytestring == 0.10.*
, case-insensitive
, http-media >= 0.4 && < 0.7 , http-media >= 0.4 && < 0.7
, http-types == 0.8.* , http-types == 0.8.*
, text >= 1 && < 2 , text >= 1 && < 2

View file

@ -46,6 +46,7 @@ module Servant.API.ContentTypes
, PlainText , PlainText
, FormUrlEncoded , FormUrlEncoded
, OctetStream , OctetStream
, ResponseHeaders
-- * Building your own Content-Type -- * Building your own Content-Type
, Accept(..) , Accept(..)
@ -75,6 +76,7 @@ import Data.Attoparsec.ByteString (endOfInput, parseOnly)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Lazy (ByteString, fromStrict, toStrict) import Data.ByteString.Lazy (ByteString, fromStrict, toStrict)
import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy as B
import qualified Data.CaseInsensitive as CI
import Data.Monoid import Data.Monoid
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import qualified Data.Text as TextS import qualified Data.Text as TextS
@ -83,8 +85,9 @@ import qualified Data.Text.Lazy as TextL
import qualified Data.Text.Lazy.Encoding as TextL import qualified Data.Text.Lazy.Encoding as TextL
import Data.Typeable import Data.Typeable
import GHC.Exts (Constraint) import GHC.Exts (Constraint)
import GHC.TypeLits
import qualified Network.HTTP.Media as M import qualified Network.HTTP.Media as M
import Network.HTTP.Types.Header import qualified Network.HTTP.Types.Header as H
import Network.URI (escapeURIString, isUnreserved, import Network.URI (escapeURIString, isUnreserved,
unEscapeString) unEscapeString)
@ -94,6 +97,8 @@ data PlainText deriving Typeable
data FormUrlEncoded deriving Typeable data FormUrlEncoded deriving Typeable
data OctetStream deriving Typeable data OctetStream deriving Typeable
data ResponseHeaders (hs :: [Symbol]) (ct :: *)
-- * Accept class -- * Accept class
-- | Instances of 'Accept' represent mimetypes. They are used for matching -- | Instances of 'Accept' represent mimetypes. They are used for matching
@ -128,6 +133,9 @@ instance Accept PlainText where
instance Accept OctetStream where instance Accept OctetStream where
contentType _ = "application" M.// "octet-stream" contentType _ = "application" M.// "octet-stream"
instance Accept ct => Accept (ResponseHeaders hs ct) where
contentType _ = contentType (Proxy :: Proxy ct)
newtype AcceptHeader = AcceptHeader BS.ByteString newtype AcceptHeader = AcceptHeader BS.ByteString
deriving (Eq, Show) deriving (Eq, Show)
@ -149,13 +157,13 @@ newtype AcceptHeader = AcceptHeader BS.ByteString
-- > type MyAPI = "path" :> Get '[MyContentType] Int -- > type MyAPI = "path" :> Get '[MyContentType] Int
-- --
class Accept ctype => MimeRender ctype a where class Accept ctype => MimeRender ctype a where
toByteString :: Proxy ctype -> a -> (ResponseHeaders, ByteString) toByteString :: Proxy ctype -> a -> ([H.Header], ByteString)
class AllCTRender (list :: [*]) a where class AllCTRender (list :: [*]) a where
-- If the Accept header can be matched, returns (Just) a tuple of the -- If the Accept header can be matched, returns (Just) a tuple of the
-- Content-Type and response (serialization of @a@ into the appropriate -- Content-Type and response (serialization of @a@ into the appropriate
-- mimetype). -- mimetype).
handleAcceptH :: Proxy list -> AcceptHeader -> a -> Maybe (ByteString, (ResponseHeaders, ByteString)) handleAcceptH :: Proxy list -> AcceptHeader -> a -> Maybe (ByteString, ([H.Header], ByteString))
instance ( AllMimeRender ctyps a, IsNonEmpty ctyps instance ( AllMimeRender ctyps a, IsNonEmpty ctyps
) => AllCTRender ctyps a where ) => AllCTRender ctyps a where
@ -190,11 +198,11 @@ instance ( AllMimeRender ctyps a, IsNonEmpty ctyps
-- >>> type MyAPI = "path" :> ReqBody '[MyContentType] Int :> Get '[JSON] Int -- >>> type MyAPI = "path" :> ReqBody '[MyContentType] Int :> Get '[JSON] Int
-- --
class Accept ctype => MimeUnrender ctype a where class Accept ctype => MimeUnrender ctype a where
fromByteString :: Proxy ctype -> ResponseHeaders -> ByteString -> Either String a fromByteString :: Proxy ctype -> [H.Header] -> ByteString -> Either String a
class (IsNonEmpty list) => AllCTUnrender (list :: [*]) a where class (IsNonEmpty list) => AllCTUnrender (list :: [*]) a where
handleCTypeH :: Proxy list handleCTypeH :: Proxy list
-> ResponseHeaders -- Headers -> [H.Header] -- Headers
-> ByteString -- Content-Type header -> ByteString -- Content-Type header
-> ByteString -- Request body -> ByteString -- Request body
-> Maybe (Either String a) -> Maybe (Either String a)
@ -214,7 +222,7 @@ instance ( AllMimeUnrender ctyps a, IsNonEmpty ctyps
class AllMimeRender (list :: [*]) a where class AllMimeRender (list :: [*]) a where
allMimeRender :: Proxy list allMimeRender :: Proxy list
-> a -- value to serialize -> a -- value to serialize
-> [(M.MediaType, (ResponseHeaders, ByteString))] -- content-types/response pairs -> [(M.MediaType, ([H.Header], ByteString))] -- content-types/response pairs
instance ( MimeRender ctyp a ) => AllMimeRender '[ctyp] a where instance ( MimeRender ctyp a ) => AllMimeRender '[ctyp] a where
allMimeRender _ a = [(contentType pctyp, toByteString pctyp a)] allMimeRender _ a = [(contentType pctyp, toByteString pctyp a)]
@ -237,7 +245,7 @@ instance AllMimeRender '[] a where
-------------------------------------------------------------------------- --------------------------------------------------------------------------
class AllMimeUnrender (list :: [*]) a where class AllMimeUnrender (list :: [*]) a where
allMimeUnrender :: Proxy list allMimeUnrender :: Proxy list
-> ResponseHeaders -> [H.Header]
-> ByteString -> ByteString
-> [(M.MediaType, Either String a)] -> [(M.MediaType, Either String a)]
@ -285,7 +293,6 @@ instance MimeRender OctetStream ByteString where
instance MimeRender OctetStream BS.ByteString where instance MimeRender OctetStream BS.ByteString where
toByteString _ = ([],) . fromStrict toByteString _ = ([],) . fromStrict
-------------------------------------------------------------------------- --------------------------------------------------------------------------
-- * MimeUnrender Instances -- * MimeUnrender Instances
@ -322,6 +329,19 @@ instance MimeUnrender OctetStream ByteString where
instance MimeUnrender OctetStream BS.ByteString where instance MimeUnrender OctetStream BS.ByteString where
fromByteString _ _ = Right . toStrict fromByteString _ _ = Right . toStrict
class KnownSymbols a where
symbolVals :: Proxy a -> [String]
instance (KnownSymbols hs, MimeUnrender ct a)
=> MimeUnrender (ResponseHeaders hs ct) ([H.Header], a) where
fromByteString _ hs body = do
let required = map (CI.mk . cs) . symbolVals $ (Proxy :: Proxy hs) :: [H.HeaderName]
res <- fromByteString (Proxy :: Proxy ct) hs body
hs' <- forM required $ \r -> case lookup r hs of
Nothing -> Left $ "Required header not present: " <> show r
Just x -> return (r,x)
return (hs', res)
-------------------------------------------------------------------------- --------------------------------------------------------------------------
-- * FormUrlEncoded -- * FormUrlEncoded