{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} module Servant.Client.MultipartFormData ( ToMultipartFormData (..) , MultipartFormDataReqBody ) where --import Control.Exception import Control.Monad import Control.Monad.Error.Class import Control.Monad.IO.Class import Control.Monad.Reader.Class import Data.ByteString.Lazy hiding (any, elem, filter, map, null, pack) import Data.Proxy --import Data.Foldable (toList) import qualified Data.List.NonEmpty as NonEmpty --import Data.String.Conversions import qualified Data.Sequence as Sequence import Data.Text (pack) 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.Client import qualified Servant.Client.Core as Core import Servant.Client.Internal.HttpClient (catchConnectionError, clientResponseToReponse, requestToClientRequest) -- | 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 (Core.RunClient m, ToMultipartFormData b, MimeUnrender ct a, cts' ~ (ct ': cts) ) => HasClient m (MultipartFormDataReqBody b :> Post cts' a) where type Client m (MultipartFormDataReqBody b :> Post cts' a) = b-> ClientM a clientWithRoute _pm Proxy req reqData = let requestToClientRequest' req' baseurl' = do let requestWithoutBody = requestToClientRequest baseurl' req' formDataBody (toMultipartFormData reqData) requestWithoutBody in snd <$> performRequestCT' requestToClientRequest' (Proxy :: Proxy ct) H.methodPost req -- copied `performRequest` from servant-0.11, then modified so it takes a variant of `requestToClientRequest` -- as an argument. performRequest' :: (Core.Request -> BaseUrl -> IO Request) -> Method -> Core.Request -> ClientM ( Int, ByteString, MediaType , [HTTP.Header], Client.Response ByteString) performRequest' requestToClientRequest' reqMethod req = do m <- asks manager reqHost <- asks baseUrl partialRequest <- liftIO $ requestToClientRequest' req reqHost let request = partialRequest { Client.method = reqMethod } eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m case eResponse of Left err -> throwError . ConnectionError $ pack $ show err Right response -> do let status = Client.responseStatus response body = Client.responseBody response hdrs = Client.responseHeaders response status_code = statusCode status coreResponse = clientResponseToReponse response ct <- case lookup "Content-Type" $ Client.responseHeaders response of Nothing -> pure $ "application"//"octet-stream" Just t -> case parseAccept t of Nothing -> throwError $ InvalidContentTypeHeader coreResponse Just t' -> pure t' unless (status_code >= 200 && status_code < 300) $ throwError $ FailureResponse coreResponse return (status_code, body, ct, hdrs, response) -- copied `performRequestCT` from servant-0.11, then modified so it takes a variant of `requestToClientRequest` -- as an argument. performRequestCT' :: MimeUnrender ct result => (Core.Request -> BaseUrl -> IO Request) -> Proxy ct -> Method -> Core.Request -> ClientM ([HTTP.Header], result) performRequestCT' requestToClientRequest' ct reqMethod req = do let acceptCTS = contentTypes ct (_status, respBody, respCT, hdrs, _response) <- performRequest' requestToClientRequest' reqMethod (req { Core.requestAccept = Sequence.fromList $ NonEmpty.toList acceptCTS }) let coreResponse = clientResponseToReponse _response unless (any (matches respCT) acceptCTS) $ throwError $ UnsupportedContentType respCT coreResponse case mimeUnrender ct respBody of Left err -> throwError $ DecodeFailure (pack err) coreResponse Right val -> return (hdrs, val)