packages feed

azure-functions-worker-0.0.0.0: src/Azure/Functions/Bindings/ServiceBus.hs

{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase                 #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE StrictData                 #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeFamilies               #-}
module Azure.Functions.Bindings.ServiceBus
( QueueName(..)
, ConnectionName(..)
, ServiceBusBinding(..)
, ReceivedMessage(..)
)
where

import           Azure.Functions.Bindings.Class
import           Azure.Functions.Bindings.Shared
import           Azure.Functions.Internal.Lens   (orError)
import           Control.Applicative             (Alternative, (<|>))
import           Control.Arrow                   ((&&&))
import           Data.Aeson                      (FromJSON, ToJSON (..), Value (Null), decodeStrict', object, (.=))
import           Data.ByteString                 (ByteString)
import           Data.Coerce                     (coerce)
import           Data.Functor                    ((<&>))
import           Data.Int                        (Int32, Int64)
import qualified Data.List                       as List
import           Data.Map.Strict                 (Map)
import qualified Data.Map.Strict                 as Map
import           Data.Maybe                      (fromMaybe)
import           Data.String                     (IsString)
import           Data.Text                       (Text)
import qualified Data.Text                       as Text
import qualified Data.Text.Encoding              as Text
import           Data.Time                       (UTCTime)
import           GHC.Generics                    (Generic)
import           Lens.Family                     (view, (&), (^.))
import           Lens.Family.Stock               (at)
import           Proto.FunctionRpc
import           Proto.FunctionRpc_Fields
import           Text.Read                       (readMaybe)

deliveryCountKey    = "DeliveryCount"
deadLetterSourceKey = "DeadLetterSource"
expirationTimeKey   = "ExpiresAtUtc"
enqueuedTimeKey     = "EnqueuedTimeUtc"
messageIdKey        = "MessageId"
contentTypeKey      = "ContentType"
replyToKey          = "ReplyTo"
sequenceNumberKey   = "SequenceNumber"
toKey               = "To"
labelKey            = "Label"
correlationIdKey    = "CorrelationId"
userPropertiesKey   = "UserProperties"

newtype QueueName       = QueueName Text deriving (Show, Eq, IsString, Generic)

data ServiceBusBinding = ServiceBusBinding
  { serviceBusConnectionName :: ConnectionName
  , serviceBusQueueName      :: QueueName
  }

instance ToInBinding ServiceBusBinding where
  toInBindingJSON v = object
    [ "name"          .= ("queueTrigger" :: Text)
    , "type"          .= ("serviceBusTrigger" :: Text)
    , "direction"     .= ("in" :: Text)
    , "queueName"     .= coerce @_ @Text (serviceBusQueueName v)
    , "connection"    .= coerce @_ @Text (serviceBusConnectionName v)
    , "accessRights"  .= ("Listen" :: Text)
    ]

data ReceivedMessage = ReceivedMessage
  { receivedMessageId               :: Text            -- ^ The user-defined value that Service Bus can use to identify duplicate messages, if enabled.
  , receivedMessageBody             :: ByteString      -- ^ The message that triggered the function.
  , receivedMessageDeliveryCount    :: Int32           -- ^ The number of deliveries.
  , receivedMessageDeadLetterSource :: Maybe Text      -- ^ The dead letter source.
  , receivedMessageEnqueuedTime     :: UTCTime         -- ^ The time that the message was enqueued.
  , receivedMessageExpirationTime   :: UTCTime         -- ^ The time that the message expires.
  , receivedMessageContentType      :: Maybe Text      -- ^ The content type identifier utilized by the sender and receiver for application specific logic.
  , receivedMessageReplyTo          :: Maybe Text      -- ^ The reply to queue address.
  , receivedMessageSequenceNumber   :: Int64           -- ^ The unique number assigned to a message by the Service Bus.
  , receivedMessageTo               :: Maybe Text      -- ^ The send to address.
  , receivedMessageLabel            :: Maybe Text      -- ^ The application specific label.
  , receivedMessageCorrelationId    :: Maybe Text      -- ^ The correlation ID.
  , receivedMessageUserProperties   :: Map Text Text   -- ^ The application specific message properties.
  } deriving (Show, Eq, Generic)

instance InMessage ReceivedMessage where
  type InBinding ReceivedMessage = ServiceBusBinding
  fromInvocationRequest req = do
    let idata = req ^. inputData <&> (view name &&& view data') & Map.fromList
    let metadata = req ^. triggerMetadata

    let orMissing fld = orError ("Unable to parse " <> fld)

    mid    <- metadata ^. at messageIdKey       >>= view maybe'string & orMissing messageIdKey
    seq    <- metadata ^. at sequenceNumberKey  >>= decodeJson & orMissing sequenceNumberKey
    body   <- idata    ^. at "queueTrigger"     >>= getText    & orMissing "queueTrigger"
    delCnt <- metadata ^. at deliveryCountKey   >>= decodeJson & orMissing deliveryCountKey
    queued <- metadata ^. at enqueuedTimeKey    >>= decodeJson & orMissing enqueuedTimeKey
    expire <- metadata ^. at expirationTimeKey  >>= decodeJson & orMissing expirationTimeKey

    pure ReceivedMessage
          { receivedMessageId               = mid
          , receivedMessageBody             = Text.encodeUtf8 body
          , receivedMessageDeliveryCount    = delCnt
          , receivedMessageDeadLetterSource = metadata ^. at deadLetterSourceKey >>= getText
          , receivedMessageExpirationTime   = expire
          , receivedMessageEnqueuedTime     = queued
          , receivedMessageContentType      = metadata ^. at contentTypeKey >>= getText
          , receivedMessageReplyTo          = metadata ^. at replyToKey >>= getText
          , receivedMessageSequenceNumber   = seq
          , receivedMessageTo               = metadata ^. at toKey >>= getText
          , receivedMessageLabel            = metadata ^. at labelKey >>= getText
          , receivedMessageCorrelationId    = metadata ^. at correlationIdKey >>= getText
          , receivedMessageUserProperties   = metadata ^. at userPropertiesKey >>= decodeJson & fromMaybe mempty
          }