{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -- | -- Module : Network.DO.Spaces.Actions -- Copyright : (c) 2021 Rory Tyler Hayford -- License : BSD-3-Clause -- Maintainer : rory.hayford@protonmail.com -- Stability : experimental -- Portability : GHC -- -- This module re-exports the 'Action' instances in the modules below it -- module Network.DO.Spaces.Actions ( runAction , parseErrorResponse -- * Re-exports , module M ) where import Conduit ( (.|) , runConduit ) import Control.Monad ( when ) import Control.Monad.Catch ( MonadThrow , throwM ) import Control.Monad.IO.Class ( MonadIO(liftIO) ) import Data.Conduit.Binary ( sinkLbs ) import Data.Function ( (&) ) import Data.Time ( getCurrentTime ) import Network.DO.Spaces.Actions.CopyObject as M import Network.DO.Spaces.Actions.CreateBucket as M import Network.DO.Spaces.Actions.DeleteBucket as M import Network.DO.Spaces.Actions.DeleteBucketCORS as M import Network.DO.Spaces.Actions.DeleteBucketLifecycle as M import Network.DO.Spaces.Actions.DeleteObject as M import Network.DO.Spaces.Actions.GetBucketACLs as M import Network.DO.Spaces.Actions.GetBucketCORS as M import Network.DO.Spaces.Actions.GetBucketLifecycle as M import Network.DO.Spaces.Actions.GetBucketLocation as M import Network.DO.Spaces.Actions.GetObject as M import Network.DO.Spaces.Actions.GetObjectACLs as M import Network.DO.Spaces.Actions.GetObjectInfo as M import Network.DO.Spaces.Actions.ListAllBuckets as M import Network.DO.Spaces.Actions.ListBucket as M import Network.DO.Spaces.Actions.SetBucketACLs as M import Network.DO.Spaces.Actions.SetBucketCORS as M import Network.DO.Spaces.Actions.SetBucketLifecycle as M import Network.DO.Spaces.Actions.SetObjectACLs as M import Network.DO.Spaces.Actions.UploadMultipart as M import Network.DO.Spaces.Actions.UploadObject as M import Network.DO.Spaces.Request ( finalize , mkAuthorization , mkStringToSign , newSpacesRequest ) import Network.DO.Spaces.Types ( APIException(..) , Action(..) , ClientException(HTTPStatus) , MonadSpaces , RawResponse(..) , SpacesResponse(..) , WithMetadata(NoMetadata, KeepMetadata) ) import Network.DO.Spaces.Utils ( getResponseMetadata , handleMaybe , xmlDocCursor , xmlElemError ) import Network.HTTP.Client.Conduit ( withResponse ) import qualified Network.HTTP.Conduit as H import qualified Network.HTTP.Types as H import Network.HTTP.Types ( Status ) import qualified Text.XML.Cursor as X import Text.XML.Cursor ( ($/) , (&/) ) -- | Run an instance of 'Action', receiving a 'ConsumedResponse'. The retention -- of 'Network.DO.Spaces.Types.SpacesMetadata' can be controlled by passing a -- 'Network.DO.Spaces.Types.WithMetadata' constructor runAction :: forall a m. (MonadSpaces m, Action m a) => WithMetadata -> a -> m (SpacesResponse a) runAction withMD action = do now <- liftIO getCurrentTime reqBuilder <- buildRequest action req <- newSpacesRequest reqBuilder now let stringToSign = mkStringToSign req auth = mkAuthorization req stringToSign finalized = finalize req auth withResponse @_ @m finalized $ \resp -> do let status = resp & H.responseStatus body = resp & H.responseBody headers = resp & H.responseHeaders metadata = case withMD of NoMetadata -> Nothing KeepMetadata -> Just $ getResponseMetadata status raw raw = RawResponse { .. } when ((status & H.statusCode) >= 300) $ handleMaybe (parseErrorResponse status) raw >>= \case Just apiErr -> throwM apiErr Nothing -> throwM . HTTPStatus status =<< runConduit (body .| sinkLbs) result <- consumeResponse @_ @a raw pure SpacesResponse { .. } parseErrorResponse :: (MonadThrow m, MonadIO m) => Status -> RawResponse m -> m APIException parseErrorResponse status raw = do cursor <- xmlDocCursor raw code <- X.force (xmlElemError "Code") $ cursor $/ X.laxElement "Code" &/ X.content requestID <- X.force (xmlElemError "RequestId") $ cursor $/ X.laxElement "RequestId" &/ X.content hostID <- X.force (xmlElemError "HostId") $ cursor $/ X.laxElement "HostId" &/ X.content pure APIException { .. }