servant-server-0.20.3.0: src/Servant/Server/Internal/ResponseRender.hs
{-# LANGUAGE EmptyCase #-}
module Servant.Server.Internal.ResponseRender where
import Data.ByteString (ByteString)
import Data.Kind (Type)
import Data.Typeable
import GHC.TypeLits
import qualified Data.ByteString.Lazy as BSL
import qualified Network.Wai as Wai
import Network.HTTP.Types (Status, hContentType)
import Data.SOP
import qualified Servant.Types.SourceT as S
import qualified Data.ByteString.Builder as BB
import qualified Data.Sequence as Seq
import Servant.API.ContentTypes (AcceptHeader (..), AllMimeRender, MimeRender, Accept, allMimeRender, mimeRender, contentType)
import Servant.API.MultiVerb
import Servant.API.Status
import Servant.API.Stream (SourceIO)
import Servant.API.UVerb.Union
import Servant.Types.Internal.Response
import qualified Network.HTTP.Media as M
import Data.Foldable (toList)
import Data.Sequence ((<|))
class (Typeable a) => IsWaiBody a where
responseToWai :: InternalResponse a -> Wai.Response
instance IsWaiBody BSL.ByteString where
responseToWai r =
Wai.responseLBS
(statusCode r)
(toList (headers r))
(responseBody r)
instance IsWaiBody () where
responseToWai r =
Wai.responseLBS
(statusCode r)
(toList (headers r))
mempty
instance IsWaiBody (SourceIO ByteString) where
responseToWai r =
Wai.responseStream
(statusCode r)
(toList (headers r))
$ \output flush -> do
S.foreach
(const (pure ()))
(\chunk -> output (BB.byteString chunk) *> flush)
(responseBody r)
data SomeResponse = forall a. (IsWaiBody a) => SomeResponse (InternalResponse a)
class ResponseListRender cs as where
responseListRender
:: AcceptHeader
-> Union (ResponseTypes as)
-> Maybe SomeResponse
responseListStatuses :: [Status]
instance ResponseListRender cs '[] where
responseListRender _ x = case x of {}
responseListStatuses = []
class (IsWaiBody (ResponseBody a)) => ResponseRender cs a where
type ResponseStatus a :: Nat
type ResponseBody a :: Type
responseRender
:: AcceptHeader
-> ResponseType a
-> Maybe (InternalResponse (ResponseBody a))
instance
( ResponseRender cs a,
ResponseListRender cs as,
KnownStatus (ResponseStatus a)
) =>
ResponseListRender cs (a ': as)
where
responseListRender acc (Z (I x)) = fmap SomeResponse (responseRender @cs @a acc x)
responseListRender acc (S x) = responseListRender @cs @as acc x
responseListStatuses = statusVal (Proxy @(ResponseStatus a)) : responseListStatuses @cs @as
instance
( AsHeaders xs (ResponseType r) a,
ServantHeaders hs xs,
ResponseRender cs r
) =>
ResponseRender cs (WithHeaders hs a r)
where
type ResponseStatus (WithHeaders hs a r) = ResponseStatus r
type ResponseBody (WithHeaders hs a r) = ResponseBody r
responseRender acc x = addHeaders <$> responseRender @cs @r acc y
where
(hs, y) = toHeaders @xs x
addHeaders r =
r
{ headers = headers r <> Seq.fromList (constructHeaders @hs hs)
}
instance
( KnownStatus s,
MimeRender ct a
) =>
ResponseRender cs (RespondAs (ct :: Type) s desc a)
where
type ResponseStatus (RespondAs ct s desc a) = s
type ResponseBody (RespondAs ct s desc a) = BSL.ByteString
responseRender _ x =
pure . addContentType @ct $
InternalResponse
{ statusCode = statusVal (Proxy @s),
responseBody = mimeRender (Proxy @ct) x,
headers = mempty
}
instance (KnownStatus s) => ResponseRender cs (RespondAs '() s desc ()) where
type ResponseStatus (RespondAs '() s desc ()) = s
type ResponseBody (RespondAs '() s desc ()) = ()
responseRender _ _ =
pure $
InternalResponse
{ statusCode = statusVal (Proxy @s),
responseBody = (),
headers = mempty
}
instance
(Accept ct, KnownStatus s)
=> ResponseRender cs (RespondStreaming s desc framing ct)
where
type ResponseStatus (RespondStreaming s desc framing ct) = s
type ResponseBody (RespondStreaming s desc framing ct) = SourceIO ByteString
responseRender _ x =
pure . addContentType @ct $
InternalResponse
{ statusCode = statusVal (Proxy @s),
responseBody = x,
headers = mempty
}
instance
(AllMimeRender cs a, KnownStatus s)
=> ResponseRender cs (Respond s desc a) where
type ResponseStatus (Respond s desc a) = s
type ResponseBody (Respond s desc a) = BSL.ByteString
-- Note: here it seems like we are rendering for all possible content types,
-- only to choose the correct one afterwards. However, render results besides the
-- one picked by 'M.mapAcceptMedia' are not evaluated, and therefore nor are the
-- corresponding rendering functions.
responseRender (AcceptHeader acc) x =
M.mapAcceptMedia (map (uncurry mkRenderOutput) (allMimeRender (Proxy @cs) x)) acc
where
mkRenderOutput :: M.MediaType -> BSL.ByteString -> (M.MediaType, InternalResponse BSL.ByteString)
mkRenderOutput c body =
(c,) . addContentType' c $
InternalResponse
{ statusCode = statusVal (Proxy @s),
responseBody = body,
headers = mempty
}
addContentType :: forall ct a. (Accept ct) => InternalResponse a -> InternalResponse a
addContentType = addContentType' (contentType (Proxy @ct))
addContentType' :: M.MediaType -> InternalResponse a -> InternalResponse a
addContentType' c r = r {headers = (hContentType, M.renderHeader c) <| headers r}
setEmptyBody :: SomeResponse -> SomeResponse
setEmptyBody (SomeResponse r) = SomeResponse (go r)
where
go :: InternalResponse a -> InternalResponse BSL.ByteString
go InternalResponse {..} = InternalResponse {responseBody = mempty, ..}
someResponseToWai :: SomeResponse -> Wai.Response
someResponseToWai (SomeResponse r) = responseToWai r