webgear-server-1.1.0: src/WebGear/Server/MIMETypes.hs
module WebGear.Server.MIMETypes (
-- * Parsing and rendering MIME types
BodyUnrender (..),
BodyRender (..),
-- * FormData utils
inMemoryBackend,
tempFileBackend,
) where
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Trans.Resource (MonadResource, getInternalState, liftResourceT)
import qualified Data.Aeson as Aeson
import Data.Bifunctor (Bifunctor (first))
import qualified Data.Binary.Builder as B
import Data.ByteString.Conversion (FromByteString (..), ToByteString (..), runParser')
import qualified Data.ByteString.Lazy as LBS
import Data.Text (Text, pack)
import Data.Text.Conversions (FromText (..), ToText (..))
import qualified Data.Text.Encoding as Text
import qualified Data.Text.Lazy as LText
import qualified Data.Text.Lazy.Encoding as LText
import qualified Network.HTTP.Media as HTTP
import Network.Wai.Parse (BackEnd, lbsBackEnd, parseRequestBodyEx, tempFileBackEnd)
import Web.FormUrlEncoded (
FromForm (..),
ToForm (..),
urlDecodeForm,
urlEncodeFormStable,
)
import WebGear.Core.MIMETypes (
FormData (..),
FormDataResult (..),
FormURLEncoded (..),
HTML,
JSON,
MIMEType (..),
OctetStream,
PlainText,
)
import WebGear.Core.Request (Request (..), getRequestBody)
import WebGear.Core.Response (Response, ResponseBody (..))
class (MIMEType mt) => BodyUnrender m mt a where
-- | Parse a request body. Return a 'Left' value with error messages
-- in case of failure.
bodyUnrender :: mt -> Request -> m (Either Text a)
class (MIMEType mt) => BodyRender m mt a where
-- | Render a value in the format specified by the media type.
--
-- Returns the response body and the media type to be used in the
-- "Content-Type" header. This could be a variant of the original
-- media type with additional parameters.
bodyRender :: mt -> Response -> a -> m (HTTP.MediaType, ResponseBody)
--------------------------------------------------------------------------------
instance (MonadIO m, FromForm a) => BodyUnrender m FormURLEncoded a where
bodyUnrender :: FormURLEncoded -> Request -> m (Either Text a)
bodyUnrender FormURLEncoded request = do
body <- liftIO $ getRequestBody request
pure $ urlDecodeForm body >>= fromForm
instance (Monad m, ToForm a) => BodyRender m FormURLEncoded a where
bodyRender :: FormURLEncoded -> Response -> a -> m (HTTP.MediaType, ResponseBody)
bodyRender FormURLEncoded _response a = do
let body = ResponseBodyBuilder $ B.fromLazyByteString $ urlEncodeFormStable $ toForm a
pure (mimeType FormURLEncoded, body)
--------------------------------------------------------------------------------
instance (MonadIO m, FromByteString a) => BodyUnrender m HTML a where
bodyUnrender :: HTML -> Request -> m (Either Text a)
bodyUnrender _ request = do
body <- liftIO $ getRequestBody request
pure $ first pack $ runParser' parser body
instance (Monad m, ToByteString a) => BodyRender m HTML a where
bodyRender :: HTML -> Response -> a -> m (HTTP.MediaType, ResponseBody)
bodyRender html _response a = do
let body = ResponseBodyBuilder $ builder a
pure (mimeType html, body)
--------------------------------------------------------------------------------
instance (MonadIO m, Aeson.FromJSON a) => BodyUnrender m JSON a where
bodyUnrender :: JSON -> Request -> m (Either Text a)
bodyUnrender _ request = do
s <- liftIO $ getRequestBody request
pure $ first pack $ Aeson.eitherDecode s
instance (Monad m, Aeson.ToJSON a) => BodyRender m JSON a where
bodyRender :: JSON -> Response -> a -> m (HTTP.MediaType, ResponseBody)
bodyRender json _response a = do
let body = ResponseBodyBuilder $ Aeson.fromEncoding $ Aeson.toEncoding a
pure (mimeType json, body)
--------------------------------------------------------------------------------
-- | A backend that stores all files in memory
inMemoryBackend :: BackEnd LBS.ByteString
inMemoryBackend = lbsBackEnd
-- | A backend that stores files in a temp directory.
tempFileBackend :: (MonadResource m) => m (BackEnd FilePath)
tempFileBackend = do
st <- liftResourceT getInternalState
pure $ tempFileBackEnd st
instance (MonadIO m) => BodyUnrender m (FormData a) (FormDataResult a) where
bodyUnrender :: FormData a -> Request -> m (Either Text (FormDataResult a))
bodyUnrender FormData{parseOptions, backendOptions} request = do
(formDataParams, formDataFiles) <-
liftIO $ parseRequestBodyEx parseOptions backendOptions $ toWaiRequest request
pure $ Right FormDataResult{formDataParams, formDataFiles}
--------------------------------------------------------------------------------
instance (MonadIO m, FromByteString a) => BodyUnrender m OctetStream a where
bodyUnrender :: OctetStream -> Request -> m (Either Text a)
bodyUnrender _ request = do
body <- liftIO $ getRequestBody request
pure $ first pack $ runParser' parser body
instance (Monad m, ToByteString a) => BodyRender m OctetStream a where
bodyRender :: OctetStream -> Response -> a -> m (HTTP.MediaType, ResponseBody)
bodyRender os _response a = do
let body = ResponseBodyBuilder $ builder a
pure (mimeType os, body)
--------------------------------------------------------------------------------
instance (MonadIO m, FromText a) => BodyUnrender m PlainText a where
bodyUnrender :: PlainText -> Request -> m (Either Text a)
bodyUnrender _ request = do
body <- liftIO $ getRequestBody request
pure $ case LText.decodeUtf8' body of
Left e -> Left $ pack $ show e
Right t -> Right $ fromText $ LText.toStrict t
instance (Monad m, ToText a) => BodyRender m PlainText a where
bodyRender :: PlainText -> Response -> a -> m (HTTP.MediaType, ResponseBody)
bodyRender txt _response a = do
let body = ResponseBodyBuilder $ Text.encodeUtf8Builder $ toText a
pure (mimeType txt, body)