{-# language FlexibleInstances #-} {-# language OverloadedStrings #-} {-# language UndecidableInstances #-} {-# language RankNTypes #-} module Web.Firefly.Response ( ToResponse(..) , Json(..) , respond , respondWith ) where import Data.Function ((&)) import Control.Monad.Except import qualified Data.Text as T import qualified Data.Text.Lazy as TL import Data.Aeson as Aeson import Network.Wai as W import Network.HTTP.Types.Status import Text.Blaze.Html (Html) import Text.Blaze.Html.Renderer.Text (renderHtml) import Web.Firefly.Internal.Utils import Web.Firefly.Types -- | A simple newtype wrapper you can use to wrap values, signifying -- they should be JSON encoded sent with the "application/json" -- Content-Type. newtype Json a = Json a deriving Show -- | This class represents all types which can be converted into a valid -- 'W.Response'. Feel free to implement additional instances for your own -- data-types. class ToResponse c where toResponse :: c -> W.Response instance ToResponse String where toResponse = mkResponse ok200 "text/plain" . T.pack instance ToResponse T.Text where toResponse = mkResponse ok200 "text/plain" instance ToResponse Html where toResponse = mkResponse ok200 "text/html" . TL.toStrict . renderHtml instance ToResponse Aeson.Value where toResponse = mkResponse ok200 "application/json" . fromLBS . encode instance (ToJSON a) => ToResponse (Json a) where toResponse (Json obj) = toResponse (toJSON obj) instance ToResponse W.Response where toResponse = id instance (ToResponse b) => ToResponse (b, Status) where toResponse (b, status) = toResponse (b, status, mempty :: HeaderMap) instance (ToResponse b) => ToResponse (b, Status, HeaderMap) where toResponse (b, status, hm) = toResponse b & mapResponseStatus (const status) & mapResponseHeaders (++ fromHeaderMap hm) mkResponse :: Status -> ContentType -> T.Text -> W.Response mkResponse status contentType body = W.responseLBS status [mkHeader "Content-Type" contentType] (toLBS body) -- | Respond to the client immediately. Any statements following this one -- in the App or Handler Monads will not be run. respond :: ToResponse r => r -> App () respond = throwError . toResponse -- | Use a given handler to respond to the request. -- Any handlers following this will not run. respondWith :: ToResponse r => Handler r -> App () respondWith handler = runHandler handler >>= respond