packages feed

natskell-0.3.0.0: jetstream/JetStream/Consumer/Types.hs

{-# LANGUAGE OverloadedStrings #-}

module JetStream.Consumer.Types
  ( AckPolicy (..)
  , ConsumerConfig (..)
  , ConsumerAction (..)
  , ConsumerConfigOption
  , ConsumerConfigRequest
  , ConsumerFilter (..)
  , ConsumerKind (..)
  , ConsumerInfo (..)
  , ConsumerListOption
  , ConsumerListResponse (..)
  , ConsumerNamesResponse (..)
  , ConsumerPauseResponse (..)
  , ConsumerResetOption
  , ConsumerResetResponse (..)
  , ConsumerSequenceInfo (..)
  , ConsumerTarget (..)
  , DeleteConsumerResponse (..)
  , DeliverPolicy (..)
  , ReplayPolicy (..)
  , applyConsumerKind
  , applyConsumerTarget
  , consumerConfigRequest
  , consumerActionValue
  , consumerListRequest
  , consumerNamesRequest
  , consumerPauseRequest
  , consumerResetRequest
  , withConsumerAckPolicy
  , withConsumerAckWait
  , withConsumerDescription
  , withConsumerDeliverGroup
  , withConsumerDurableName
  , withConsumerDeliverSubject
  , withConsumerFilter
  , withConsumerHeadersOnly
  , withConsumerIdleHeartbeat
  , withConsumerInactiveThreshold
  , withConsumerListOffset
  , withConsumerMaxAckPending
  , withConsumerMaxDeliver
  , withConsumerMaxWaiting
  , withConsumerMemoryStorage
  , withConsumerName
  , withConsumerReplicas
  , withConsumerDeliverPolicy
  , withConsumerReplayPolicy
  , withConsumerResetSequence
  ) where

import           Data.Aeson
import           Data.Aeson.Types (Pair, Parser)
import qualified Data.ByteString  as BS
import           Data.Maybe       (catMaybes)
import qualified Data.Text        as T
import           Data.Time.Clock  (NominalDiffTime, UTCTime)
import           JetStream.Types
    ( AckPolicy (..)
    , CallOption
    , ConsumerName
    , DeliverPolicy (..)
    , ReplayPolicy (..)
    , StreamName
    , Subject
    , applyCallOptions
    , byteStringToJSON
    , diffTimeNanosToJSON
    , parseByteString
    )

data ConsumerConfigRequest = ConsumerConfigRequest
                               { consumerConfigRequestDurableName :: Maybe ConsumerName
                               , consumerConfigRequestName :: Maybe ConsumerName
                               , consumerConfigRequestDescription :: Maybe BS.ByteString
                               , consumerConfigRequestDeliverSubject :: Maybe Subject
                               , consumerConfigRequestDeliverGroup :: Maybe Subject
                               , consumerConfigRequestDeliverPolicy :: Maybe DeliverPolicy
                               , consumerConfigRequestAckPolicy :: Maybe AckPolicy
                               , consumerConfigRequestReplayPolicy :: Maybe ReplayPolicy
                               , consumerConfigRequestFilter :: Maybe ConsumerFilter
                               , consumerConfigRequestAckWait :: Maybe NominalDiffTime
                               , consumerConfigRequestMaxDeliver :: Maybe Int
                               , consumerConfigRequestMaxWaiting :: Maybe Int
                               , consumerConfigRequestMaxAckPending :: Maybe Int
                               , consumerConfigRequestInactiveThreshold :: Maybe NominalDiffTime
                               , consumerConfigRequestIdleHeartbeat :: Maybe NominalDiffTime
                               , consumerConfigRequestHeadersOnly :: Maybe Bool
                               , consumerConfigRequestReplicas :: Maybe Int
                               , consumerConfigRequestMemoryStorage :: Maybe Bool
                               }
  deriving (Eq, Show)

data ConsumerAction = ConsumerCreate | ConsumerCreateOrUpdate | ConsumerUpdate
  deriving (Eq, Show)

consumerActionValue :: ConsumerAction -> Maybe BS.ByteString
consumerActionValue ConsumerCreate         = Just "create"
consumerActionValue ConsumerCreateOrUpdate = Nothing
consumerActionValue ConsumerUpdate         = Just "update"

data ConsumerTarget = EphemeralConsumer
                    | NamedConsumer ConsumerName
                    | DurableConsumer ConsumerName
  deriving (Eq, Show)

data ConsumerKind = PullConsumer
                  | PushConsumer Subject
  deriving (Eq, Show)

type ConsumerConfigOption = CallOption ConsumerConfigRequest

consumerConfigRequest :: [ConsumerConfigOption] -> ConsumerConfigRequest
consumerConfigRequest options =
  applyCallOptions options emptyConsumerConfigRequest

emptyConsumerConfigRequest :: ConsumerConfigRequest
emptyConsumerConfigRequest =
  ConsumerConfigRequest
    { consumerConfigRequestDurableName = Nothing
    , consumerConfigRequestName = Nothing
    , consumerConfigRequestDescription = Nothing
    , consumerConfigRequestDeliverSubject = Nothing
    , consumerConfigRequestDeliverGroup = Nothing
    , consumerConfigRequestDeliverPolicy = Nothing
    , consumerConfigRequestAckPolicy = Nothing
    , consumerConfigRequestReplayPolicy = Nothing
    , consumerConfigRequestFilter = Nothing
    , consumerConfigRequestAckWait = Nothing
    , consumerConfigRequestMaxDeliver = Nothing
    , consumerConfigRequestMaxWaiting = Nothing
    , consumerConfigRequestMaxAckPending = Nothing
    , consumerConfigRequestInactiveThreshold = Nothing
    , consumerConfigRequestIdleHeartbeat = Nothing
    , consumerConfigRequestHeadersOnly = Nothing
    , consumerConfigRequestReplicas = Nothing
    , consumerConfigRequestMemoryStorage = Nothing
    }

applyConsumerTarget :: ConsumerTarget -> ConsumerConfigRequest -> ConsumerConfigRequest
applyConsumerTarget EphemeralConsumer config =
  config
    { consumerConfigRequestDurableName = Nothing
    , consumerConfigRequestName = Nothing
    }
applyConsumerTarget (NamedConsumer name) config =
  config
    { consumerConfigRequestDurableName = Nothing
    , consumerConfigRequestName = Just name
    }
applyConsumerTarget (DurableConsumer durable) config =
  config
    { consumerConfigRequestDurableName = Just durable
    , consumerConfigRequestName = Just durable
    }

applyConsumerKind :: ConsumerKind -> ConsumerConfigRequest -> ConsumerConfigRequest
applyConsumerKind PullConsumer config =
  config
    { consumerConfigRequestDeliverSubject = Nothing
    , consumerConfigRequestDeliverGroup = Nothing
    }
applyConsumerKind (PushConsumer deliverSubject) config =
  config { consumerConfigRequestDeliverSubject = Just deliverSubject }

withConsumerDurableName :: ConsumerName -> ConsumerConfigOption
withConsumerDurableName durable config =
  config { consumerConfigRequestDurableName = Just durable }

withConsumerName :: ConsumerName -> ConsumerConfigOption
withConsumerName name config =
  config { consumerConfigRequestName = Just name }

withConsumerDescription :: BS.ByteString -> ConsumerConfigOption
withConsumerDescription description config =
  config { consumerConfigRequestDescription = Just description }

withConsumerDeliverSubject :: Subject -> ConsumerConfigOption
withConsumerDeliverSubject subject config =
  config { consumerConfigRequestDeliverSubject = Just subject }

withConsumerDeliverGroup :: Subject -> ConsumerConfigOption
withConsumerDeliverGroup group config =
  config { consumerConfigRequestDeliverGroup = Just group }

withConsumerDeliverPolicy :: DeliverPolicy -> ConsumerConfigOption
withConsumerDeliverPolicy policy config =
  config { consumerConfigRequestDeliverPolicy = Just policy }

withConsumerAckPolicy :: AckPolicy -> ConsumerConfigOption
withConsumerAckPolicy policy config =
  config { consumerConfigRequestAckPolicy = Just policy }

withConsumerReplayPolicy :: ReplayPolicy -> ConsumerConfigOption
withConsumerReplayPolicy policy config =
  config { consumerConfigRequestReplayPolicy = Just policy }

data ConsumerFilter = ConsumerFilterSubject Subject
                    | ConsumerFilterSubjects [Subject]
  deriving (Eq, Show)

withConsumerFilter :: ConsumerFilter -> ConsumerConfigOption
withConsumerFilter consumerFilter config =
  config { consumerConfigRequestFilter = Just consumerFilter }

withConsumerAckWait :: NominalDiffTime -> ConsumerConfigOption
withConsumerAckWait ackWait config =
  config { consumerConfigRequestAckWait = Just ackWait }

withConsumerMaxDeliver :: Int -> ConsumerConfigOption
withConsumerMaxDeliver maxDeliver config =
  config { consumerConfigRequestMaxDeliver = Just maxDeliver }

withConsumerMaxWaiting :: Int -> ConsumerConfigOption
withConsumerMaxWaiting maxWaiting config =
  config { consumerConfigRequestMaxWaiting = Just maxWaiting }

withConsumerMaxAckPending :: Int -> ConsumerConfigOption
withConsumerMaxAckPending maxAckPending config =
  config { consumerConfigRequestMaxAckPending = Just maxAckPending }

withConsumerInactiveThreshold :: NominalDiffTime -> ConsumerConfigOption
withConsumerInactiveThreshold inactiveThreshold config =
  config { consumerConfigRequestInactiveThreshold = Just inactiveThreshold }

withConsumerIdleHeartbeat :: NominalDiffTime -> ConsumerConfigOption
withConsumerIdleHeartbeat idleHeartbeat config =
  config { consumerConfigRequestIdleHeartbeat = Just idleHeartbeat }

withConsumerHeadersOnly :: Bool -> ConsumerConfigOption
withConsumerHeadersOnly headersOnly config =
  config { consumerConfigRequestHeadersOnly = Just headersOnly }

withConsumerReplicas :: Int -> ConsumerConfigOption
withConsumerReplicas replicas config =
  config { consumerConfigRequestReplicas = Just replicas }

withConsumerMemoryStorage :: Bool -> ConsumerConfigOption
withConsumerMemoryStorage memoryStorage config =
  config { consumerConfigRequestMemoryStorage = Just memoryStorage }

data ConsumerConfig = ConsumerConfig
                        { consumerConfigDurableName :: Maybe ConsumerName
                        , consumerConfigName :: Maybe ConsumerName
                        , consumerConfigDescription :: Maybe BS.ByteString
                        , consumerConfigDeliverSubject :: Maybe Subject
                        , consumerConfigDeliverGroup :: Maybe Subject
                        , consumerConfigDeliverPolicy :: DeliverPolicy
                        , consumerConfigAckPolicy :: AckPolicy
                        , consumerConfigReplayPolicy :: ReplayPolicy
                        , consumerConfigFilterSubject :: Maybe Subject
                        , consumerConfigFilterSubjects :: Maybe [Subject]
                        , consumerConfigAckWait :: Maybe NominalDiffTime
                        , consumerConfigMaxDeliver :: Maybe Int
                        , consumerConfigMaxWaiting :: Maybe Int
                        , consumerConfigMaxAckPending :: Maybe Int
                        , consumerConfigInactiveThreshold :: Maybe NominalDiffTime
                        , consumerConfigIdleHeartbeat :: Maybe NominalDiffTime
                        , consumerConfigHeadersOnly :: Maybe Bool
                        , consumerConfigReplicas :: Maybe Int
                        , consumerConfigMemoryStorage :: Maybe Bool
                        }
  deriving (Eq, Show)

data ConsumerInfo = ConsumerInfo
                      { consumerInfoStreamName     :: StreamName
                      , consumerInfoName           :: ConsumerName
                      , consumerInfoCreated        :: UTCTime
                      , consumerInfoConfig         :: ConsumerConfig
                      , consumerInfoDelivered      :: ConsumerSequenceInfo
                      , consumerInfoAckFloor       :: ConsumerSequenceInfo
                      , consumerInfoNumAckPending  :: Int
                      , consumerInfoNumRedelivered :: Int
                      , consumerInfoNumWaiting     :: Int
                      , consumerInfoNumPending     :: Integer
                      }
  deriving (Eq, Show)

data ConsumerSequenceInfo = ConsumerSequenceInfo
                              { consumerSequenceConsumer :: Integer
                              , consumerSequenceStream   :: Integer
                              , consumerSequenceLast     :: Maybe UTCTime
                              }
  deriving (Eq, Show)

newtype DeleteConsumerResponse = DeleteConsumerResponse { deleteConsumerSuccess :: Bool }
  deriving (Eq, Show)

newtype ConsumerPauseRequest = ConsumerPauseRequest { consumerPauseUntil :: Maybe UTCTime }
  deriving (Eq, Show)

consumerPauseRequest :: Maybe UTCTime -> ConsumerPauseRequest
consumerPauseRequest =
  ConsumerPauseRequest

data ConsumerPauseResponse = ConsumerPauseResponse
                               { consumerPausePaused    :: Bool
                               , consumerPauseUntilTime :: Maybe UTCTime
                               , consumerPauseRemaining :: Maybe NominalDiffTime
                               }
  deriving (Eq, Show)

newtype ConsumerResetRequest = ConsumerResetRequest { consumerResetRequestSequence :: Maybe Integer }
  deriving (Eq, Show)

type ConsumerResetOption = CallOption ConsumerResetRequest

consumerResetRequest :: [ConsumerResetOption] -> ConsumerResetRequest
consumerResetRequest options =
  applyCallOptions options $
    ConsumerResetRequest
      { consumerResetRequestSequence = Nothing
      }

withConsumerResetSequence :: Integer -> ConsumerResetOption
withConsumerResetSequence sequenceNumber request =
  request { consumerResetRequestSequence = Just sequenceNumber }

data ConsumerResetResponse = ConsumerResetResponse
                               { consumerResetInfo             :: ConsumerInfo
                               , consumerResetResponseSequence :: Integer
                               }
  deriving (Eq, Show)

newtype ConsumerListRequest = ConsumerListRequest { consumerListRequestOffset :: Maybe Int }
  deriving (Eq, Show)

type ConsumerListOption = CallOption ConsumerListRequest

consumerListRequest :: [ConsumerListOption] -> ConsumerListRequest
consumerListRequest options =
  applyCallOptions options defaultConsumerListRequest

consumerNamesRequest :: [ConsumerListOption] -> ConsumerListRequest
consumerNamesRequest =
  consumerListRequest

defaultConsumerListRequest :: ConsumerListRequest
defaultConsumerListRequest =
  ConsumerListRequest
    { consumerListRequestOffset = Nothing
    }

withConsumerListOffset :: Int -> ConsumerListOption
withConsumerListOffset offset request =
  request { consumerListRequestOffset = Just offset }

data ConsumerListResponse = ConsumerListResponse
                              { consumerListTotal     :: Int
                              , consumerListOffset    :: Int
                              , consumerListLimit     :: Int
                              , consumerListConsumers :: [ConsumerInfo]
                              }
  deriving (Eq, Show)

data ConsumerNamesResponse = ConsumerNamesResponse
                               { consumerNamesTotal     :: Int
                               , consumerNamesOffset    :: Int
                               , consumerNamesLimit     :: Int
                               , consumerNamesConsumers :: [ConsumerName]
                               }
  deriving (Eq, Show)

instance ToJSON ConsumerConfigRequest where
  toJSON config =
    object . catMaybes $
      [ maybeByteStringPair "durable_name" (consumerConfigRequestDurableName config)
      , maybeByteStringPair "name" (consumerConfigRequestName config)
      , maybeByteStringPair "description" (consumerConfigRequestDescription config)
      , maybeByteStringPair "deliver_subject" (consumerConfigRequestDeliverSubject config)
      , maybeByteStringPair "deliver_group" (consumerConfigRequestDeliverGroup config)
      , maybePair "ack_policy" (consumerConfigRequestAckPolicy config)
      , maybePair "replay_policy" (consumerConfigRequestReplayPolicy config)
      , maybeJsonPair "ack_wait" (diffTimeNanosToJSON <$> consumerConfigRequestAckWait config)
      , maybePair "max_deliver" (consumerConfigRequestMaxDeliver config)
      , maybePair "max_waiting" (consumerConfigRequestMaxWaiting config)
      , maybePair "max_ack_pending" (consumerConfigRequestMaxAckPending config)
      , maybeJsonPair "inactive_threshold" (diffTimeNanosToJSON <$> consumerConfigRequestInactiveThreshold config)
      , maybeJsonPair "idle_heartbeat" (diffTimeNanosToJSON <$> consumerConfigRequestIdleHeartbeat config)
      , maybePair "headers_only" (consumerConfigRequestHeadersOnly config)
      , maybePair "num_replicas" (consumerConfigRequestReplicas config)
      , maybePair "mem_storage" (consumerConfigRequestMemoryStorage config)
      ] ++ maybe [] deliverPolicyRequestPairs (consumerConfigRequestDeliverPolicy config)
        ++ maybe [] consumerFilterPairs (consumerConfigRequestFilter config)

instance ToJSON ConsumerConfig where
  toJSON config =
    object . catMaybes $
      [ maybeByteStringPair "durable_name" (consumerConfigDurableName config)
      , maybeByteStringPair "name" (consumerConfigName config)
      , maybeByteStringPair "description" (consumerConfigDescription config)
      , maybeByteStringPair "deliver_subject" (consumerConfigDeliverSubject config)
      , maybeByteStringPair "deliver_group" (consumerConfigDeliverGroup config)
      , Just ("ack_policy" .= consumerConfigAckPolicy config)
      , Just ("replay_policy" .= consumerConfigReplayPolicy config)
      , maybeByteStringPair "filter_subject" (consumerConfigFilterSubject config)
      , maybeByteStringListPair "filter_subjects" (consumerConfigFilterSubjects config)
      , maybeJsonPair "ack_wait" (diffTimeNanosToJSON <$> consumerConfigAckWait config)
      , maybePair "max_deliver" (consumerConfigMaxDeliver config)
      , maybePair "max_waiting" (consumerConfigMaxWaiting config)
      , maybePair "max_ack_pending" (consumerConfigMaxAckPending config)
      , maybeJsonPair "inactive_threshold" (diffTimeNanosToJSON <$> consumerConfigInactiveThreshold config)
      , maybeJsonPair "idle_heartbeat" (diffTimeNanosToJSON <$> consumerConfigIdleHeartbeat config)
      , maybePair "headers_only" (consumerConfigHeadersOnly config)
      , maybePair "num_replicas" (consumerConfigReplicas config)
      , maybePair "mem_storage" (consumerConfigMemoryStorage config)
      ] ++ fmap Just (deliverPolicyPairs (consumerConfigDeliverPolicy config))

instance FromJSON ConsumerConfig where
  parseJSON = withObject "ConsumerConfig" $ \obj ->
    ConsumerConfig
      <$> parseOptionalByteStringField obj "durable_name"
      <*> parseOptionalByteStringField obj "name"
      <*> parseOptionalByteStringField obj "description"
      <*> parseOptionalByteStringField obj "deliver_subject"
      <*> parseOptionalByteStringField obj "deliver_group"
      <*> parseDeliverPolicyField obj "deliver_policy"
      <*> obj .: "ack_policy"
      <*> obj .: "replay_policy"
      <*> parseOptionalByteStringField obj "filter_subject"
      <*> parseOptionalByteStringListField obj "filter_subjects"
      <*> parseOptionalDurationField obj "ack_wait"
      <*> obj .:? "max_deliver"
      <*> obj .:? "max_waiting"
      <*> obj .:? "max_ack_pending"
      <*> parseOptionalDurationField obj "inactive_threshold"
      <*> parseOptionalDurationField obj "idle_heartbeat"
      <*> obj .:? "headers_only"
      <*> obj .:? "num_replicas"
      <*> obj .:? "mem_storage"

instance FromJSON ConsumerInfo where
  parseJSON = withObject "ConsumerInfo" $ \obj ->
    ConsumerInfo
      <$> parseByteStringField obj "stream_name"
      <*> parseByteStringField obj "name"
      <*> obj .: "created"
      <*> obj .: "config"
      <*> obj .:? "delivered" .!= emptyConsumerSequenceInfo
      <*> obj .:? "ack_floor" .!= emptyConsumerSequenceInfo
      <*> obj .:? "num_ack_pending" .!= 0
      <*> obj .:? "num_redelivered" .!= 0
      <*> obj .:? "num_waiting" .!= 0
      <*> obj .:? "num_pending" .!= 0

instance ToJSON ConsumerInfo where
  toJSON info =
    object
      [ "stream_name" .= byteStringToJSON (consumerInfoStreamName info)
      , "name" .= byteStringToJSON (consumerInfoName info)
      , "created" .= consumerInfoCreated info
      , "config" .= consumerInfoConfig info
      , "delivered" .= consumerInfoDelivered info
      , "ack_floor" .= consumerInfoAckFloor info
      , "num_ack_pending" .= consumerInfoNumAckPending info
      , "num_redelivered" .= consumerInfoNumRedelivered info
      , "num_waiting" .= consumerInfoNumWaiting info
      , "num_pending" .= consumerInfoNumPending info
      ]

instance FromJSON ConsumerSequenceInfo where
  parseJSON = withObject "ConsumerSequenceInfo" $ \obj ->
    ConsumerSequenceInfo
      <$> obj .:? "consumer_seq" .!= 0
      <*> obj .:? "stream_seq" .!= 0
      <*> obj .:? "last_active"

instance ToJSON ConsumerSequenceInfo where
  toJSON info =
    object . catMaybes $
      [ Just ("consumer_seq" .= consumerSequenceConsumer info)
      , Just ("stream_seq" .= consumerSequenceStream info)
      , maybePair "last_active" (consumerSequenceLast info)
      ]

instance FromJSON DeleteConsumerResponse where
  parseJSON = withObject "DeleteConsumerResponse" $ \obj ->
    DeleteConsumerResponse
      <$> obj .:? "success" .!= False

instance ToJSON ConsumerPauseRequest where
  toJSON request =
    object . catMaybes $
      [ maybePair "pause_until" (consumerPauseUntil request)
      ]

instance FromJSON ConsumerPauseResponse where
  parseJSON = withObject "ConsumerPauseResponse" $ \obj ->
    ConsumerPauseResponse
      <$> obj .: "paused"
      <*> obj .:? "pause_until"
      <*> parseOptionalDurationField obj "pause_remaining"

instance ToJSON ConsumerResetRequest where
  toJSON request =
    object . catMaybes $
      [ maybePair "seq" (consumerResetRequestSequence request)
      ]

instance FromJSON ConsumerResetResponse where
  parseJSON value =
    ConsumerResetResponse
      <$> parseJSON value
      <*> parseResetSequence value
    where
      parseResetSequence =
        withObject "ConsumerResetResponse" $ \obj ->
          obj .: "reset_seq"

instance ToJSON ConsumerListRequest where
  toJSON request =
    object . catMaybes $
      [ maybePair "offset" (consumerListRequestOffset request)
      ]

instance FromJSON ConsumerListResponse where
  parseJSON = withObject "ConsumerListResponse" $ \obj ->
    ConsumerListResponse
      <$> obj .: "total"
      <*> obj .: "offset"
      <*> obj .: "limit"
      <*> obj .:? "consumers" .!= []

instance FromJSON ConsumerNamesResponse where
  parseJSON = withObject "ConsumerNamesResponse" $ \obj ->
    ConsumerNamesResponse
      <$> obj .: "total"
      <*> obj .: "offset"
      <*> obj .: "limit"
      <*> parseOptionalByteStringListField obj "consumers" .!= []

byteStringPair :: Key -> BS.ByteString -> Pair
byteStringPair key value =
  key .= byteStringToJSON value

maybeByteStringPair :: Key -> Maybe BS.ByteString -> Maybe Pair
maybeByteStringPair key =
  fmap (byteStringPair key)

maybeByteStringListPair :: Key -> Maybe [BS.ByteString] -> Maybe Pair
maybeByteStringListPair key =
  fmap ((key .=) . map byteStringToJSON)

maybePair :: ToJSON value => Key -> Maybe value -> Maybe Pair
maybePair key =
  fmap (key .=)

maybeJsonPair :: Key -> Maybe Value -> Maybe Pair
maybeJsonPair key =
  fmap (key .=)

consumerFilterPairs :: ConsumerFilter -> [Maybe Pair]
consumerFilterPairs consumerFilter =
  case consumerFilter of
    ConsumerFilterSubject subject ->
      [maybeByteStringPair "filter_subject" (Just subject)]
    ConsumerFilterSubjects subjects ->
      [maybeByteStringListPair "filter_subjects" (Just subjects)]

deliverPolicyRequestPairs :: DeliverPolicy -> [Maybe Pair]
deliverPolicyRequestPairs policy =
  fmap Just (deliverPolicyPairs policy)

deliverPolicyPairs :: DeliverPolicy -> [Pair]
deliverPolicyPairs policy =
  "deliver_policy" .= policy : extraPairs policy
  where
    extraPairs selectedPolicy =
      case selectedPolicy of
        DeliverByStartSequence sequenceNumber ->
          ["opt_start_seq" .= sequenceNumber]
        DeliverByStartTime startTime ->
          ["opt_start_time" .= startTime]
        _ ->
          []

parseDeliverPolicyField :: Object -> Key -> Parser DeliverPolicy
parseDeliverPolicyField obj key = do
  policyName <- obj .: key :: Parser T.Text
  case policyName of
    "all" ->
      pure DeliverAll
    "last" ->
      pure DeliverLast
    "new" ->
      pure DeliverNew
    "by_start_sequence" ->
      fmap DeliverByStartSequence (obj .: "opt_start_seq")
    "by_start_time" ->
      fmap DeliverByStartTime (obj .: "opt_start_time")
    "last_per_subject" ->
      pure DeliverLastPerSubject
    value ->
      fail ("unknown deliver policy: " ++ T.unpack value)

parseByteStringField :: Object -> Key -> Parser BS.ByteString
parseByteStringField obj key =
  obj .: key >>= parseByteString

parseOptionalByteStringField :: Object -> Key -> Parser (Maybe BS.ByteString)
parseOptionalByteStringField obj key = do
  value <- obj .:? key
  traverse parseByteString value

parseOptionalByteStringListField :: Object -> Key -> Parser (Maybe [BS.ByteString])
parseOptionalByteStringListField obj key = do
  value <- obj .:? key
  traverse (traverse parseByteString) value

parseOptionalDurationField :: Object -> Key -> Parser (Maybe NominalDiffTime)
parseOptionalDurationField obj key =
  fmap nanosToDiffTime <$> obj .:? key

nanosToDiffTime :: Integer -> NominalDiffTime
nanosToDiffTime nanoseconds =
  fromRational (toRational nanoseconds / 1000000000)

emptyConsumerSequenceInfo :: ConsumerSequenceInfo
emptyConsumerSequenceInfo =
  ConsumerSequenceInfo
    { consumerSequenceConsumer = 0
    , consumerSequenceStream = 0
    , consumerSequenceLast = Nothing
    }