telegram-api-0.3.1.0: src/Servant/Client/MultipartFormData.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Servant.Client.MultipartFormData
( ToMultipartFormData (..)
, MultipartFormDataReqBody
) where
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Either
import Data.ByteString.Lazy hiding (pack, filter, map, null, elem)
import Data.Proxy
import Data.String.Conversions
import Data.Typeable (Typeable)
import Network.HTTP.Client hiding (Proxy, path)
import qualified Network.HTTP.Client as Client
import Network.HTTP.Client.MultipartFormData
import Network.HTTP.Media
import Network.HTTP.Types
import qualified Network.HTTP.Types as H
import qualified Network.HTTP.Types.Header as HTTP
import Servant.API
import Servant.API.Post
import Servant.Client
import Servant.Common.Req
-- | A type that can be converted to a multipart/form-data value.
class ToMultipartFormData a where
-- | Convert a Haskell value to a multipart/form-data-friendly intermediate type.
toMultipartFormData :: a -> [Part]
-- | Extract the request body as a value of type @a@.
data MultipartFormDataReqBody a
deriving (Typeable)
instance (ToMultipartFormData b, MimeUnrender ct a)
=> HasClient (MultipartFormDataReqBody b :> Post (ct ': cts) a) where
type Client (MultipartFormDataReqBody b :> Post (ct ': cts) a)
= b -> EitherT ServantError IO a
clientWithRoute Proxy req baseurl reqData =
let reqToRequest' req' baseurl' = do
requestWithoutBody <- reqToRequest req' baseurl'
formDataBody (toMultipartFormData reqData) requestWithoutBody
in snd <$> performRequestCT' reqToRequest' (Proxy :: Proxy ct) H.methodPost req [200, 201, 202] baseurl
-- copied `performRequest` from servant-0.4.4.7, then modified so it takes a variant of `reqToRequest`
-- as an argument.
performRequest' :: (Req -> BaseUrl -> IO Request)
-> Method -> Req -> (Int -> Bool) -> BaseUrl
-> EitherT ServantError IO ( Int, ByteString, MediaType
, [HTTP.Header], Response ByteString)
performRequest' reqToRequest' reqMethod req isWantedStatus reqHost = do
partialRequest <- liftIO $ reqToRequest' req reqHost
let request = partialRequest { Client.method = reqMethod
, checkStatus = \ _status _headers _cookies -> Nothing
}
eResponse <- liftIO $ __withGlobalManager $ \ manager ->
catchHttpException $
Client.httpLbs request manager
case eResponse of
Left err ->
left $ ConnectionError err
Right response -> do
let status = Client.responseStatus response
body = Client.responseBody response
hrds = Client.responseHeaders response
status_code = statusCode status
ct <- case lookup "Content-Type" $ Client.responseHeaders response of
Nothing -> pure $ "application"//"octet-stream"
Just t -> case parseAccept t of
Nothing -> left $ InvalidContentTypeHeader (cs t) body
Just t' -> pure t'
unless (isWantedStatus status_code) $
left $ FailureResponse status ct body
return (status_code, body, ct, hrds, response)
-- copied `performRequestCT` from servant-0.4.4.7, then modified so it takes a variant of `reqToRequest`
-- as an argument.
performRequestCT' :: MimeUnrender ct result =>
(Req -> BaseUrl -> IO Request) ->
Proxy ct -> Method -> Req -> [Int] -> BaseUrl -> EitherT ServantError IO ([HTTP.Header], result)
performRequestCT' reqToRequest' ct reqMethod req wantedStatus reqHost = do
let acceptCT = contentType ct
(_status, respBody, respCT, hrds, _response) <-
performRequest' reqToRequest' reqMethod (req { reqAccept = [acceptCT] }) (`elem` wantedStatus) reqHost
unless (matches respCT (acceptCT)) $ left $ UnsupportedContentType respCT respBody
case mimeUnrender ct respBody of
Left err -> left $ DecodeFailure err respCT respBody
Right val -> return (hrds, val)