packages feed

natskell-1.0.0.1: jetstream/JetStream/Protocol/Request.hs

{-# LANGUAGE OverloadedStrings #-}

module JetStream.Protocol.Request
  ( requestMsg
  , requestJSON
  , requestJSONWithHeaders
  , requestUnit
  , decodeJetStreamResponse
  ) where

import qualified Client.API                 as Nats
import           Control.Monad              (void)
import           Data.Aeson
import qualified Data.Aeson                 as Aeson
import           Data.Aeson.Types           (Parser, parseEither)
import qualified Data.ByteString            as BS
import qualified Data.ByteString.Lazy       as LBS
import           JetStream.Error
import           JetStream.Options
    ( JetStreamContext (..)
    , requestTimeoutMicros
    )
import           JetStream.Protocol.Headers (statusError)
import           JetStream.Types
    ( Headers
    , JetStreamRequestOption
    , Payload
    , Subject
    )

requestMsg :: JetStreamContext -> Subject -> Payload -> Headers -> [JetStreamRequestOption] -> IO (Either JetStreamError Nats.MsgView)
requestMsg ctx subject payload headers options = do
  result <- Nats.request (contextClient ctx) subject payload requestOptions
  pure $ case result of
    Left Nats.NatsRequestTimedOut -> Left JetStreamTimeout
    Left err                      -> Left (JetStreamNatsError err)
    Right msg                     -> Right msg
  where
    timeoutSeconds =
      fromRational (toRational (requestTimeoutMicros ctx options) / 1000000)
    requestOptions =
      Nats.withRequestTimeout timeoutSeconds
        : [Nats.withRequestHeaders headers | not (null headers)]

requestJSON :: FromJSON a => JetStreamContext -> Subject -> Maybe Value -> [JetStreamRequestOption] -> IO (Either JetStreamError a)
requestJSON ctx subject body =
  requestJSONWithHeaders ctx subject body []

requestJSONWithHeaders :: FromJSON a => JetStreamContext -> Subject -> Maybe Value -> Headers -> [JetStreamRequestOption] -> IO (Either JetStreamError a)
requestJSONWithHeaders ctx subject body headers options = do
  msgResult <- requestMsg ctx subject (maybe BS.empty strictEncode body) headers options
  pure $ do
    msg <- msgResult
    case statusError msg of
      Just err ->
        Left err
      Nothing ->
        decodeJetStreamResponse (Nats.payload msg)

requestUnit :: JetStreamContext -> Subject -> Maybe Value -> [JetStreamRequestOption] -> IO (Either JetStreamError ())
requestUnit ctx subject body options = do
  response <- requestJSON ctx subject body options
  pure (void (response :: Either JetStreamError SuccessResponse))

decodeJetStreamResponse :: FromJSON a => BS.ByteString -> Either JetStreamError a
decodeJetStreamResponse payload =
  case Aeson.eitherDecode (LBS.fromStrict payload) :: Either String Value of
    Left err ->
      Left (JetStreamDecodeError err)
    Right value ->
      case parseEither parseApiResult value of
        Left err ->
          Left (JetStreamDecodeError err)
        Right result ->
          result

parseApiResult :: FromJSON a => Value -> Parser (Either JetStreamError a)
parseApiResult value =
  case value of
    Object obj -> do
      apiError <- obj .:? "error"
      case apiError of
        Just err ->
          pure (Left (JetStreamApiFailure err))
        Nothing ->
          Right <$> parseJSON value
    _ ->
      Right <$> parseJSON value

strictEncode :: Value -> BS.ByteString
strictEncode =
  LBS.toStrict . Aeson.encode

newtype SuccessResponse = SuccessResponse Bool
  deriving (Eq, Show)

instance FromJSON SuccessResponse where
  parseJSON = withObject "SuccessResponse" $ \obj ->
    SuccessResponse <$> obj .: "success"