packages feed

gogol-0.0.1: src/Network/Google/Internal/HTTP.hs

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TypeFamilies      #-}

-- |
-- Module      : Network.Google.Internal.HTTP
-- Copyright   : (c) 2015 Brendan Hay
-- License     : Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay@gmail.com>
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
--
module Network.Google.Internal.HTTP where


import           Control.Lens
import           Control.Monad.Catch
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Resource
import           Data.Default.Class
import           Data.Monoid
import qualified Data.Text.Encoding                as Text
import qualified Data.Text.Lazy                    as LText
import qualified Data.Text.Lazy.Builder            as Build
import           GHC.Exts                          (toList)
import           Network.Google.Auth
import           Network.Google.Env                (Env (..))
import           Network.Google.Internal.Logger
import           Network.Google.Internal.Multipart
import           Network.Google.Types
import qualified Network.HTTP.Client.Conduit       as Client
import           Network.HTTP.Conduit
import           Network.HTTP.Media
import           Network.HTTP.Types

-- FIXME: "mediaType" param also comes/calculated from the request body?
--
-- Resumable endpoints - Not supported to begin with, need to figure
-- out how to return session id etc.
--   - Assume initially that every service supports multipart upload.
--
-- "resumable" or "multipart" needs to go into the "uploadType" param

perform :: (MonadCatch m, MonadResource m, GoogleRequest a)
        => Env
        -> a
        -> m (Either Error (Rs a))
perform Env{..} x = catches go handlers
  where
    Request {..} = _cliRequest
    Service {..} = _cliService

    Client  {..} = requestClient x
        & clientService %~ appEndo (getDual _envOverride)

    go = liftResourceT $ do
        (ct, b) <- getContent _rqBody
        rq      <- authorise _envLogger _envManager (request ct b) _envAuth

        logDebug _envLogger rq -- debug:ClientRequest

        rs      <- http rq _envManager

        logDebug _envLogger rs -- debug:ClientResponse

        r       <- _cliResponse (responseBody rs)

        pure $! case r of
            Right y       -> Right y
            Left  (e, bs) -> Left . SerializeError $ SerializeError'
                { _serializeId      = _svcId
                , _serializeHeaders = responseHeaders rs
                , _serializeStatus  = responseStatus rs
                , _serializeMessage = e
                , _serializeBody    = Just bs
                }

    request ct b = def
        { Client.host            = _svcHost
        , Client.port            = _svcPort
        , Client.secure          = _svcSecure
        , Client.checkStatus     = status
        , Client.responseTimeout = timeout
        , Client.method          = _cliMethod
        , Client.path            = path
        , Client.queryString     = renderQuery True (toList _rqQuery)
        , Client.requestHeaders  = accept (ct (toList _rqHeaders))
        , Client.requestBody     = b
        }

    accept
         | Just t <- _cliAccept   = ((hAccept, renderHeader t) :)
         | otherwise              = id

    path = Text.encodeUtf8
         . LText.toStrict
         $ Build.toLazyText (_svcPath <> _rqPath)

    status s hs _
         | _cliCheck s = Nothing
         | otherwise   = Just . toException . ServiceError $ ServiceError'
             { _serviceId      = _svcId
             , _serviceStatus  = s
             , _serviceHeaders = hs
             , _serviceBody    = Nothing
             }

    timeout = microseconds <$> _svcTimeout

    handlers =
        [ Handler $ err
        , Handler $ err . TransportError
        ]
      where
        err e = return (Left e)

getContent :: MonadIO m
           => Maybe Payload
           -> m ([Header] -> [Header], RequestBody)
getContent = \case
    Nothing           -> pure (id, mempty)
    Just (Body t s)   -> pure (((hContentType, renderHeader t) :), s)
    Just (Related ps) -> do
        b <- genBoundary
        pure ( (multipartHeader b :)
             , renderParts b ps
             )