packages feed

antiope-sqs 6.4.0 → 7.0.0

raw patch · 6 files changed

+165/−71 lines, 6 filesdep +bytestringdep +hedgehogdep +hspecdep −amazonka-s3dep −antiope-messagesdep −antiope-s3PVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, hedgehog, hspec, hw-hspec-hedgehog, time, unordered-containers

Dependencies removed: amazonka-s3, antiope-messages, antiope-s3

API changes (from Hackage documentation)

- Antiope.SQS.Messages: Many :: [a] -> Many a
- Antiope.SQS.Messages: [records] :: Many a -> [a]
- Antiope.SQS.Messages: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Antiope.SQS.Messages.Many a)
- Antiope.SQS.Messages: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Antiope.SQS.Messages.SqsMessage a)
- Antiope.SQS.Messages: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Antiope.SQS.Messages.Many a)
- Antiope.SQS.Messages: instance GHC.Classes.Eq a => GHC.Classes.Eq (Antiope.SQS.Messages.Many a)
- Antiope.SQS.Messages: instance GHC.Generics.Generic (Antiope.SQS.Messages.Many a)
- Antiope.SQS.Messages: instance GHC.Show.Show a => GHC.Show.Show (Antiope.SQS.Messages.Many a)
- Antiope.SQS.Messages: newtype Many a
+ Antiope.SQS: Ack :: ConsumerResult
+ Antiope.SQS: Drain :: ConsumerMode
+ Antiope.SQS: Forever :: ConsumerMode
+ Antiope.SQS: Nack :: ConsumerResult
+ Antiope.SQS: class HasReceiptHandle a
+ Antiope.SQS: data ConsumerMode
+ Antiope.SQS: data ConsumerResult
+ Antiope.SQS: data Message
+ Antiope.SQS: forAllMessages :: (MonadUnliftIO m, HasEnv env) => env -> QueueUrl -> ConsumerMode -> (Message -> m ConsumerResult) -> m ()
+ Antiope.SQS: mAttributes :: Lens' Message (HashMap MessageAttribute Text)
+ Antiope.SQS: mMD5OfBody :: Lens' Message (Maybe Text)
+ Antiope.SQS: mMessageId :: Lens' Message (Maybe Text)
+ Antiope.SQS: mReceiptHandle :: Lens' Message (Maybe Text)
+ Antiope.SQS.Messages: [approximateFirstReceiveTimestamp] :: SqsMessage a -> !Maybe Text
+ Antiope.SQS.Messages: [approximateReceiveCount] :: SqsMessage a -> !Maybe Text
+ Antiope.SQS.Messages: [sendTimestamp] :: SqsMessage a -> !Maybe Text
+ Antiope.SQS.Messages: data Message
+ Antiope.SQS.Messages: data QueueUrl
+ Antiope.SQS.Messages: decodeBody :: FromJSON a => Message -> Maybe a
+ Antiope.SQS.Messages: fromMessage :: FromJSON a => Message -> SqsMessage (Maybe a)
+ Antiope.SQS.Messages: fromMessageMaybe :: FromJSON a => Message -> Maybe (SqsMessage a)
+ Antiope.SQS.Messages: instance Antiope.SQS.Types.HasReceiptHandle (Antiope.SQS.Messages.SqsMessage a)
+ Antiope.SQS.Messages: instance GHC.Base.Functor Antiope.SQS.Messages.SqsMessage
+ Antiope.SQS.Messages: mBody :: Lens' Message (Maybe Text)
+ Antiope.SQS.Types: Ack :: ConsumerResult
+ Antiope.SQS.Types: DeleteMessageBatchError :: SQSError
+ Antiope.SQS.Types: Drain :: ConsumerMode
+ Antiope.SQS.Types: Forever :: ConsumerMode
+ Antiope.SQS.Types: Nack :: ConsumerResult
+ Antiope.SQS.Types: QueueUrl :: Text -> QueueUrl
+ Antiope.SQS.Types: ReceiptHandle :: Text -> ReceiptHandle
+ Antiope.SQS.Types: class HasReceiptHandle a
+ Antiope.SQS.Types: data ConsumerMode
+ Antiope.SQS.Types: data ConsumerResult
+ Antiope.SQS.Types: data SQSError
+ Antiope.SQS.Types: getReceiptHandle :: HasReceiptHandle a => a -> Maybe ReceiptHandle
+ Antiope.SQS.Types: instance Antiope.SQS.Types.HasReceiptHandle Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: instance Antiope.SQS.Types.HasReceiptHandle Network.AWS.SQS.Types.Product.Message
+ Antiope.SQS.Types: instance Data.String.IsString Antiope.SQS.Types.QueueUrl
+ Antiope.SQS.Types: instance GHC.Classes.Eq Antiope.SQS.Types.ConsumerMode
+ Antiope.SQS.Types: instance GHC.Classes.Eq Antiope.SQS.Types.ConsumerResult
+ Antiope.SQS.Types: instance GHC.Classes.Eq Antiope.SQS.Types.QueueUrl
+ Antiope.SQS.Types: instance GHC.Classes.Eq Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: instance GHC.Classes.Eq Antiope.SQS.Types.SQSError
+ Antiope.SQS.Types: instance GHC.Classes.Ord Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: instance GHC.Generics.Generic Antiope.SQS.Types.ConsumerMode
+ Antiope.SQS.Types: instance GHC.Generics.Generic Antiope.SQS.Types.ConsumerResult
+ Antiope.SQS.Types: instance GHC.Generics.Generic Antiope.SQS.Types.QueueUrl
+ Antiope.SQS.Types: instance GHC.Generics.Generic Antiope.SQS.Types.SQSError
+ Antiope.SQS.Types: instance GHC.Read.Read Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: instance GHC.Show.Show Antiope.SQS.Types.ConsumerMode
+ Antiope.SQS.Types: instance GHC.Show.Show Antiope.SQS.Types.ConsumerResult
+ Antiope.SQS.Types: instance GHC.Show.Show Antiope.SQS.Types.QueueUrl
+ Antiope.SQS.Types: instance GHC.Show.Show Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: instance GHC.Show.Show Antiope.SQS.Types.SQSError
+ Antiope.SQS.Types: instance Network.AWS.Data.Text.FromText Antiope.SQS.Types.QueueUrl
+ Antiope.SQS.Types: instance Network.AWS.Data.Text.FromText Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: instance Network.AWS.Data.Text.ToText Antiope.SQS.Types.QueueUrl
+ Antiope.SQS.Types: instance Network.AWS.Data.Text.ToText Antiope.SQS.Types.ReceiptHandle
+ Antiope.SQS.Types: newtype QueueUrl
+ Antiope.SQS.Types: newtype ReceiptHandle
- Antiope.SQS: ackMessage :: MonadAWS m => QueueUrl -> Message -> m (Either SQSError ())
+ Antiope.SQS: ackMessage :: (MonadAWS m, HasReceiptHandle msg) => QueueUrl -> msg -> m (Either SQSError ())
- Antiope.SQS: ackMessages :: MonadAWS m => QueueUrl -> [Message] -> m (Either SQSError ())
+ Antiope.SQS: ackMessages :: (MonadAWS m, HasReceiptHandle msg) => QueueUrl -> [msg] -> m (Either SQSError ())
- Antiope.SQS.Messages: SqsMessage :: !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe a -> SqsMessage a
+ Antiope.SQS.Messages: SqsMessage :: !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe ReceiptHandle -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !a -> SqsMessage a
- Antiope.SQS.Messages: [body] :: SqsMessage a -> !Maybe a
+ Antiope.SQS.Messages: [body] :: SqsMessage a -> !a
- Antiope.SQS.Messages: [receiptHandle] :: SqsMessage a -> !Maybe Text
+ Antiope.SQS.Messages: [receiptHandle] :: SqsMessage a -> !Maybe ReceiptHandle

Files

antiope-sqs.cabal view
@@ -1,14 +1,9 @@-cabal-version: 1.12---- This file has been generated from package.yaml by hpack version 0.31.1.------ see: https://github.com/sol/hpack------ hash: 443e48287c77512b5c25fec64ac9d6cf85fec8c2c04eaf787c0ac373596918bd+cabal-version: 2.2  name:           antiope-sqs-version:        6.4.0-description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>+version:        7.0.0+synopsis:       Please see the README on Github at <https://github.com/arbor/antiope#readme>+description:    Please see the README on Github at <https://github.com/arbor/antiope#readme>. category:       Services homepage:       https://github.com/arbor/antiope#readme bug-reports:    https://github.com/arbor/antiope/issues@@ -27,24 +22,16 @@   location: https://github.com/arbor/antiope  library-  exposed-modules:-      Antiope.SQS-      Antiope.SQS.Messages-  other-modules:-      Paths_antiope_sqs-  hs-source-dirs:-      src+  hs-source-dirs: src   default-extensions: BangPatterns GeneralizedNewtypeDeriving OverloadedStrings TupleSections   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -msse4.2   build-depends:       aeson     , amazonka     , amazonka-core-    , amazonka-s3     , amazonka-sqs-    , antiope-messages-    , antiope-s3     , base >=4.7 && <5+    , bytestring     , conduit     , generic-lens     , lens@@ -54,34 +41,40 @@     , network-uri     , text     , unliftio-core+    , unordered-containers+  exposed-modules:+      Antiope.SQS+      Antiope.SQS.Messages+      Antiope.SQS.Types   default-language: Haskell2010  test-suite antiope-sqs-test   type: exitcode-stdio-1.0   main-is: Spec.hs-  other-modules:-      Paths_antiope_sqs-  hs-source-dirs:-      test+  hs-source-dirs: test   default-extensions: BangPatterns GeneralizedNewtypeDeriving OverloadedStrings TupleSections   ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -msse4.2 -threaded -rtsopts -with-rtsopts=-N   build-depends:       aeson     , amazonka     , amazonka-core-    , amazonka-s3     , amazonka-sqs-    , antiope-messages-    , antiope-s3     , antiope-sqs     , base >=4.7 && <5+    , bytestring     , conduit     , generic-lens+    , hedgehog >=0.5 && <0.7+    , hspec >=2.4 && <2.7+    , hw-hspec-hedgehog >=0.1 && <0.3     , lens     , lens-aeson     , monad-loops     , mtl     , network-uri     , text+    , time     , unliftio-core+  other-modules:+      Antiope.SQS.MessagesSpec   default-language: Haskell2010
src/Antiope/SQS.hs view
@@ -1,26 +1,36 @@ module Antiope.SQS   ( QueueUrl(..)   , SQSError(..)+  , HasReceiptHandle+  , ConsumerMode(..)+  , ConsumerResult(..)   , readQueue   , drainQueue   , ackMessage   , ackMessages-  , mBody   , queueSource+  , forAllMessages++  -- * Re-exports+  , Message+  , mBody, mMD5OfBody, mMessageId, mReceiptHandle, mAttributes   ) where -import Antiope.Messages         (QueueUrl (QueueUrl), SQSError (DeleteMessageBatchError)) import Control.Lens-import Control.Monad            (join)+import Control.Monad            (forM_, join, void)+import Control.Monad.IO.Unlift  (MonadUnliftIO) import Control.Monad.Loops      (unfoldWhileM) import Control.Monad.Trans      (lift)+import Data.Coerce              (coerce) import Data.Conduit import Data.Conduit.Combinators (yieldMany) import Data.Maybe               (catMaybes) import Data.Text                (pack)-import Network.AWS              (MonadAWS)+import Network.AWS              (HasEnv, MonadAWS, runAWS, runResourceT) import Network.AWS.SQS +import Antiope.SQS.Types+ import qualified Network.AWS as AWS  -- | Reads the specified SQS queue once returning a bath of messages@@ -39,21 +49,21 @@ drainQueue queueUrl = join <$> unfoldWhileM (not . null) (readQueue queueUrl)  -- | Acknowledges a single SQS message-ackMessage :: MonadAWS m+ackMessage :: (MonadAWS m, HasReceiptHandle msg)   => QueueUrl-  -> Message+  -> msg   -> m (Either SQSError ()) ackMessage q msg = ackMessages q [msg]  -- | Acknowledges a group of SQS messages-ackMessages :: MonadAWS m+ackMessages :: (MonadAWS m, HasReceiptHandle msg)   => QueueUrl-  -> [Message]+  -> [msg]   -> m (Either SQSError ()) ackMessages (QueueUrl queueUrl) msgs = do-  let receipts = catMaybes $ msgs ^.. each . mReceiptHandle+  let receipts = msgs ^.. each . to getReceiptHandle & catMaybes   -- each dmbr needs an ID. just use the list index.-  let dmbres = (\(r, i) -> deleteMessageBatchRequestEntry (pack (show i)) r) <$> zip receipts ([0..] :: [Int])+  let dmbres = (\(r, i) -> deleteMessageBatchRequestEntry (pack (show i)) r) <$> zip (coerce receipts) ([0..] :: [Int])   resp <- AWS.send $ deleteMessageBatch queueUrl & dmbEntries .~ dmbres   -- only acceptable if no errors.   if resp ^. dmbrsResponseStatus == 200@@ -69,4 +79,22 @@   yieldMany (m ^. rmrsMessages)   queueSource (QueueUrl queueUrl) -+forAllMessages :: (MonadUnliftIO m, HasEnv env)+  => env+  -> QueueUrl+  -> ConsumerMode+  -> (Message -> m ConsumerResult)+  -> m ()+forAllMessages env queue mode process = go+  where+    go = do+      msgs <- runResourceT $ runAWS env (readQueue queue)+      case (mode, msgs) of+        (Drain, []) -> pure ()+        _           -> processBatch msgs >> go+    processBatch msgs =+      forM_ msgs $ \msg -> do+        res <- process msg+        case res of+          Ack  -> void . runResourceT . runAWS env $ (ackMessage queue msg)+          Nack -> pure ()
src/Antiope/SQS/Messages.hs view
@@ -1,45 +1,62 @@+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-}  module Antiope.SQS.Messages-  ( Many (..)-  , SqsMessage (..)+  ( SqsMessage (..)+  , Message, mBody+  , QueueUrl+  , fromMessage, fromMessageMaybe+  , decodeBody   ) where -import Data.Aeson   as Aeson-import Data.Text    (Text)-import GHC.Generics (Generic)--import qualified Data.Aeson.Types   as Aeson-import qualified Data.Text.Encoding as T+import Antiope.SQS.Types+import Control.Lens      ((&), (<&>), (^.))+import Data.Aeson        as Aeson+import Data.Text         (Text)+import GHC.Generics      (Generic)+import Network.AWS.SQS   (Message, mAttributes, mBody, mMD5OfBody, mMessageId, mReceiptHandle) -newtype Many a = Many { records :: [a] } deriving (Show, Eq, Generic)+import qualified Data.HashMap.Strict as Hash+import qualified Data.Text.Encoding  as Text+import qualified Network.AWS.SQS     as SQS -instance FromJSON a => FromJSON (Many a) where-  parseJSON =-    withObject "Many" $ \obj ->-      Many <$> obj .: "Records"+-- | Converts 'Message` into 'SqsMessage' if its body is decodable as 'a'+fromMessageMaybe :: FromJSON a => Message -> Maybe (SqsMessage a)+fromMessageMaybe msg =+  let smsg = fromMessage msg+  in body smsg <&> (\a -> smsg { body = a}) -instance ToJSON a => ToJSON (Many a) where-  toJSON (Many a) =-    object ["Records" Aeson..= a]+-- | Converts a 'Message' into 'SqsMessage'+fromMessage :: FromJSON a => Message -> SqsMessage (Maybe a)+fromMessage msg =+  let attr a = msg ^. mAttributes & Hash.lookup a+  in SqsMessage+        { messageId                         = msg ^. mMessageId+        , receiptHandle                     = msg ^. mReceiptHandle <&> ReceiptHandle+        , md5OfBody                         = msg ^. mMD5OfBody+        , senderId                          = attr SQS.SenderId+        , sendTimestamp                     = attr SQS.SentTimestamp+        , approximateReceiveCount           = attr SQS.ApproximateReceiveCount+        , approximateFirstReceiveTimestamp  = attr SQS.ApproximateFirstReceiveTimestamp+        , body                              = decodeBody msg+        }  data SqsMessage a = SqsMessage-  { senderId      :: !(Maybe Text)-  , messageId     :: !(Maybe Text)-  , md5OfBody     :: !(Maybe Text)-  , receiptHandle :: !(Maybe Text)-  , body          :: !(Maybe a)-  } deriving (Show, Eq, Generic)+  { senderId                         :: !(Maybe Text)+  , messageId                        :: !(Maybe Text)+  , md5OfBody                        :: !(Maybe Text)+  , receiptHandle                    :: !(Maybe ReceiptHandle)+  , sendTimestamp                    :: !(Maybe Text)+  , approximateReceiveCount          :: !(Maybe Text)+  , approximateFirstReceiveTimestamp :: !(Maybe Text)+  , body                             :: !a+  } deriving (Show, Eq, Generic, Functor) -instance FromJSON a => FromJSON (SqsMessage a) where-  parseJSON = withObject "SqsMessage" $ \obj -> SqsMessage-    <$> obj .:? "SenderId"-    <*> obj .:? "MessageId"-    <*> obj .:? "Md5OfBody"-    <*> obj .:? "ReceiptHandle"-    <*> decodeEscaped obj "Body"+instance HasReceiptHandle (SqsMessage a) where+  getReceiptHandle = receiptHandle -decodeEscaped :: FromJSON b => Object -> Text -> Aeson.Parser b-decodeEscaped o t =-  (o .: t) >>= (either fail pure . eitherDecodeStrict . T.encodeUtf8)+------------------------------------------------------------------------------- +decodeBody :: FromJSON a => Message -> Maybe a+decodeBody msg =+  msg ^. mBody >>= (Aeson.decodeStrict . Text.encodeUtf8)
+ src/Antiope/SQS/Types.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE DeriveGeneric #-}+module Antiope.SQS.Types+where++import Control.Lens          ((<&>), (^.))+import Data.String           (IsString)+import Data.Text             (Text)+import GHC.Generics          (Generic)+import Network.AWS.Data.Text (FromText (..), ToText (..))+import Network.AWS.SQS       (Message, mReceiptHandle)++-- | Queue consuming mode+data ConsumerMode+  = Drain   -- ^ Keep consuming the queue until there are no messages+  | Forever -- ^ Keep consuming the queue forever+  deriving (Show, Eq, Generic)++data ConsumerResult+  = Ack+  | Nack+  deriving (Show, Eq, Generic)++data SQSError = DeleteMessageBatchError+  deriving (Eq, Show, Generic)++newtype QueueUrl = QueueUrl Text+  deriving (Show, Eq, IsString, FromText, ToText, Generic)++newtype ReceiptHandle = ReceiptHandle Text+  deriving (Show, Read, Eq, Ord, FromText, ToText)++class HasReceiptHandle a where+  getReceiptHandle :: a -> Maybe ReceiptHandle++instance HasReceiptHandle Message where+  getReceiptHandle msg = msg ^. mReceiptHandle <&> ReceiptHandle++instance HasReceiptHandle ReceiptHandle where+  getReceiptHandle = Just . id
+ test/Antiope/SQS/MessagesSpec.hs view
@@ -0,0 +1,18 @@+module Antiope.SQS.MessagesSpec+where++import Antiope.SQS.Messages+import Data.Aeson++import HaskellWorks.Hspec.Hedgehog+import Hedgehog+import Hedgehog.Gen                as Gen+import Hedgehog.Range              as Range+import Test.Hspec++{-# ANN module ("HLint: Ignore Redundant do" :: String) #-}++spec :: Spec+spec = describe "Antiope.SQS.MessagesSpec" $ do+  it "Implement me" $ require $ property $ do+    True === True
test/Spec.hs view
@@ -1,2 +1,1 @@-main :: IO ()-main = putStrLn "Test suite not yet implemented"+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}