{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TypeOperators #-} -- | Functions and types for creating a Serv response to match a 'Api.Api'. -- A value of @'Response' (s, 'Api.Respond' headers body)@ is a response -- with status code @s :: 'Status'@, response headers @headers :: -- [(Network.HTTP.Kinder.Header.HeaderName, *)]@ and a body described by -- @body :: 'Api.Body' *@. module Serv.Wai.Response ( -- * Responses Response (..) , SomeResponse -- ** Construction , emptyResponse , withBody , withoutBody , withHeader , withHeaderQuiet -- ** Finalization -- | When constructing a response in our server implementation we do not -- build specific responses but instead responses which may be one of -- many possible server result types (parameterized by status codes). To -- represent this we use the 'SomeResponse' type and use 'respond' to -- convert from a normal 'Response' to 'SomeResponse'. , respond ) where import Data.Singletons import Network.HTTP.Kinder.Header (HeaderEncode, headerEncodePair) import Network.HTTP.Kinder.Status (Status) import qualified Network.HTTP.Types as HTTP import qualified Serv.Api as Api import Serv.Wai.Corec import Serv.Wai.Rec -- | A value of type @'Response (status, 'Api.Respond' headers body)@ -- fully describes one response a Serv server might emit. data Response (x :: (Status, Api.Output *)) where ContentResponse :: [HTTP.Header] -> FieldRec hs -> a -> Response '(s, Api.Respond hs (Api.HasBody ts a)) EmptyResponse :: [HTTP.Header] -> FieldRec hs -> Response '(s, Api.Respond hs Api.Empty) -- | A value of type @'SomeResponse rs'@ is a value of @'Response' (s, r)@ -- such that @(s, r)@ is an element of @rs@. type SomeResponse rs = Corec Response rs -- | Forget the details of a specific response making it an approprate -- response at a given 'Api.Endpoint' respond :: ElemOf rs '(s, r) => Response '(s, r) -> SomeResponse rs respond = inject -- | The empty response at a given status code: no headers, no body. emptyResponse :: sing s -> Response '(s, Api.Respond '[] Api.Empty) emptyResponse _ = EmptyResponse [] RNil -- | Attach a body to an empty 'Response'. withBody :: a -> Response '(s, Api.Respond hs Api.Empty) -> Response '(s, Api.Respond hs (Api.HasBody ts a)) withBody a (EmptyResponse secretHeaders headers) = ContentResponse secretHeaders headers a -- | Eliminate a body in a 'Response', returning it to 'Api.Empty'. withoutBody :: Response '(s, Api.Respond hs (Api.HasBody ts a)) -> Response '(s, Api.Respond hs Api.Empty) withoutBody (ContentResponse secretHeaders headers _) = EmptyResponse secretHeaders headers -- | Adds a header to a 'Response' withHeader :: Sing name -> value -> Response '(s, Api.Respond headers body) -> Response '(s, Api.Respond ( '(name, value) ': headers) body) withHeader s val r = case r of ContentResponse secretHeaders headers body -> ContentResponse secretHeaders (s =: val <+> headers) body EmptyResponse secretHeaders headers -> EmptyResponse secretHeaders (s =: val <+> headers) -- | Unlike 'addHeader', 'addHeaderQuiet' allows you to add headers not -- explicitly specified in the api specification. withHeaderQuiet :: HeaderEncode name value => Sing name -> value -> Response '(s, Api.Respond headers body) -> Response '(s, Api.Respond headers body) withHeaderQuiet s value r = case headerEncodePair s value of Nothing -> r Just newHeader -> case r of ContentResponse secretHeaders headers body -> ContentResponse (newHeader : secretHeaders) headers body EmptyResponse secretHeaders headers -> EmptyResponse (newHeader : secretHeaders) headers