content: module Aws.Lambda.Runtime.ApiInfo
    ( Event (..),
      fetchEvent,
    )
  where
  
  import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints
  import qualified Aws.Lambda.Runtime.Error as Error
  import Control.Exception.Safe
  import qualified Control.Monad as Monad
  import Data.ByteString.Char8 (ByteString)
  import qualified Data.ByteString.Char8 as ByteString
  import qualified Data.ByteString.Lazy.Char8 as Lazy
  import Data.Text (Text, pack)
  import qualified Data.Text as Text
  import qualified Network.HTTP.Client as Http
  import qualified Network.HTTP.Types.Header as Http
  import qualified Text.Read as Read
  
  -- | Event that is fetched out of the AWS Lambda API
  data Event = Event
    { deadlineMs :: !Int,
      traceId :: !Text,
      awsRequestId :: !Text,
      invokedFunctionArn :: !Text,
      event :: !Lazy.ByteString
    }
    deriving (Show)
  
  -- | Performs a GET to the endpoint that provides the next event
  fetchEvent :: Http.Manager -> Text -> IO Event
  fetchEvent manager lambdaApi = do
    response <- fetchApiData manager lambdaApi
    let body = Http.responseBody response
        headers = Http.responseHeaders response
    Monad.foldM reduceEvent (initialEvent body) headers
  
  fetchApiData :: Http.Manager -> Text -> IO (Http.Response Lazy.ByteString)
  fetchApiData manager lambdaApi = do
    let Endpoints.Endpoint endpoint = Endpoints.nextInvocation lambdaApi
    request <- Http.parseRequest . Text.unpack $ endpoint
    keepRetrying $ Http.httpLbs request manager
  
  reduceEvent :: Event -> (Http.HeaderName, ByteString) -> IO Event
  reduceEvent event header =
    case header of
      ("Lambda-Runtime-Deadline-Ms", value) ->
        case Read.readMaybe $ ByteString.unpack value of
          Just ms -> pure event {deadlineMs = ms}
          Nothing -> throw (Error.Parsing "Could not parse deadlineMs." (pack $ ByteString.unpack value) "deadlineMs")
      ("Lambda-Runtime-Trace-Id", value) ->
        pure event {traceId = pack $ ByteString.unpack value}
      ("Lambda-Runtime-Aws-Request-Id", value) ->
        pure event {awsRequestId = pack $ ByteString.unpack value}
      ("Lambda-Runtime-Invoked-Function-Arn", value) ->
        pure event {invokedFunctionArn = pack $ ByteString.unpack value}
      _ ->
        pure event
  
  initialEvent :: Lazy.ByteString -> Event
  initialEvent body =
    Event
      { deadlineMs = 0,
        traceId = "",
        awsRequestId = "",
        invokedFunctionArn = "",
        event = body
      }
  
  keepRetrying :: IO (Http.Response Lazy.ByteString) -> IO (Http.Response Lazy.ByteString)
  keepRetrying action = do
    result <- try action :: IO (Either IOException (Http.Response Lazy.ByteString))
    case result of
      Right success -> pure success
      _ -> keepRetrying action
  