{-# Language OverloadedStrings , ExistentialQuantification , MultiParamTypeClasses , FlexibleInstances , CPP #-} module Network.HTTP.ClientExtra.Types where import Prelude -- import Blaze.ByteString.Builder (toByteString) import qualified Network.HTTP.Types.URI as HU import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy.Char8 as BSL8 import Data.CaseInsensitive (CI(..), mk) import qualified Data.Text.Encoding as DTE import Control.Arrow ((***)) import qualified Data.Aeson as DA import Data.Text #if !(MIN_VERSION_base(4,8,0)) import Data.Monoid (Monoid(..)) #endif #if !(MIN_VERSION_base(4,11,0)) import Data.Semigroup (Semigroup(..)) #endif import Data.Default import qualified Data.ByteString.Base64.Lazy as B64 import Control.Monad.IO.Class (MonadIO) import Network.HTTP.Client import Blaze.ByteString.Builder data RawL = RawL RequestHeadersE BSL.ByteString data JSON a = JSON RequestHeadersE a data UrlEncode = UrlEncode RequestHeadersE QueryE data UriDataEncode = UriDataEncode RequestHeadersE BSL.ByteString data EmptyBody = EmptyBody RequestHeadersE newtype RequestHeadersE = RequestHeadersE [(Text,Text)] deriving Show newtype QueryE = QueryE { unQueryE :: HU.QueryText } deriving Show unRequestHeaders :: RequestHeadersE -> [(CI BS.ByteString, BS.ByteString)] unRequestHeaders (RequestHeadersE a)= Prelude.map ( (mk . DTE.encodeUtf8) *** DTE.encodeUtf8) a instance Default RequestHeadersE where def = RequestHeadersE [] instance Semigroup RequestHeadersE where a@(RequestHeadersE _) <> (RequestHeadersE []) = a (RequestHeadersE a) <> (RequestHeadersE ((bh,bc):bs)) = RequestHeadersE ((bh,bc) : Prelude.filter (\(x, _) -> x /= bh) a) `mappend` RequestHeadersE bs instance Monoid RequestHeadersE where mempty = def class ToQueryE a where toQueryE :: a -> QueryE instance Default QueryE where def = QueryE [] instance Semigroup QueryE where (QueryE a) <> (QueryE b) = QueryE (a ++ b) instance Monoid QueryE where mempty = def instance ToQueryE BS.ByteString where toQueryE = QueryE . HU.parseQueryText fromQueryE :: QueryE -> BS.ByteString fromQueryE = toByteString . HU.renderQueryText True . unQueryE fromQueryE' :: QueryE -> BS.ByteString fromQueryE' = toByteString . HU.renderQueryText False . unQueryE class (MonadIO m) => ContentEncoder m a where buildBody :: a -> m (RequestBody , RequestHeadersE) renderPart :: BS.ByteString -> a -> m RequestBody renderPart b part = do (body,eh) <- buildBody part return $ renderHeader eh <> body <> renderBoundary b where renderBoundary b1 = cp "\r\n--" <> cp b1 renderHeader (RequestHeadersE a) = go a where go [] = cp "\r\n\r\n" go ((t,c):as) = cp "\r\n" <> RequestBodyBS (DTE.encodeUtf8 t) <> cp ": " <> RequestBodyBS (DTE.encodeUtf8 c) <> go as {-# INLINE cp #-} cp :: BS.ByteString -> RequestBody cp bs = RequestBodyBuilder (fromIntegral $ BS.length bs) $ copyByteString bs instance (MonadIO m) => ContentEncoder m RawL where buildBody (RawL eh a) = return (RequestBodyLBS a , eh ) instance (MonadIO m) => ContentEncoder m UriDataEncode where buildBody (UriDataEncode eh ct) = buildBody $ RawL (eh <> RequestHeadersE [("Content-Type", dte)] ) b64 where (dte,b64) = ( DTE.decodeUtf8 . BSL.toStrict . fst . BSL8.break (==';') . BSL.drop 1 . snd . BSL8.break (==':') *** B64.decodeLenient . BSL.drop 1) . BSL8.break (== ',') $ ct instance (MonadIO m) => ContentEncoder m EmptyBody where buildBody (EmptyBody eh) = return (RequestBodyLBS BSL.empty , eh ) instance (MonadIO m, DA.ToJSON a) => ContentEncoder m (JSON a) where buildBody (JSON eh a) = return (RequestBodyLBS $ DA.encode a , eh <> RequestHeadersE [("Content-Type", "application/json")]) instance (MonadIO m) => ContentEncoder m UrlEncode where buildBody (UrlEncode eh q) = return (RequestBodyBS $ fromQueryE' q, eh <> RequestHeadersE [("Content-Type", "application/x-www-form-urlencoded")])