packages feed

stackctl-1.4.2.2: src/Stackctl/AWS/Lambda.hs

{-# LANGUAGE MultiWayIf #-}

module Stackctl.AWS.Lambda
  ( LambdaInvokeResult (..)
  , LambdaError (..)
  , logLambdaInvocationResult
  , isLambdaInvocationSuccess
  , awsLambdaInvoke
  ) where

import Stackctl.Prelude hiding (trace)

import Amazonka.Lambda.Invoke
import Data.Aeson
import qualified Data.ByteString.Lazy as BSL
import Stackctl.AWS.Core

data LambdaInvokeResult
  = LambdaInvokeSuccess ByteString
  | LambdaInvokeError LambdaError (Maybe Text)
  | LambdaInvokeFailure Int (Maybe Text)
  deriving stock (Show)

logLambdaInvocationResult :: MonadLogger m => LambdaInvokeResult -> m ()
logLambdaInvocationResult = \case
  LambdaInvokeSuccess bs -> do
    let meta = case decode @Value $ BSL.fromStrict bs of
          Nothing -> ["response" .= decodeUtf8 bs]
          Just response -> ["response" .= response]
    logInfo $ "LambdaInvokeSuccess" :# meta
  LambdaInvokeError LambdaError {..} mFunctionError ->
    logError
      $ (:# [])
      $ mconcat
        [ "LambdaInvokeError"
        , "\n  errorType: " <> errorType
        , "\n  errorMessage: " <> errorMessage
        , "\n  trace: "
        , mconcat $ map ("\n    " <>) trace
        , "\n  FunctionError: " <> fromMaybe "none" mFunctionError
        ]
  LambdaInvokeFailure status mFunctionError ->
    logError
      $ (:# [])
      $ mconcat
        [ "LambdaInvokeFailure"
        , "\n  StatusCode: " <> pack (show status)
        , "\n  FunctionError: " <> fromMaybe "none" mFunctionError
        ]

isLambdaInvocationSuccess :: LambdaInvokeResult -> Bool
isLambdaInvocationSuccess = \case
  LambdaInvokeSuccess {} -> True
  LambdaInvokeError {} -> False
  LambdaInvokeFailure {} -> False

data LambdaError = LambdaError
  { errorType :: Text
  , errorMessage :: Text
  , trace :: [Text]
  }
  deriving stock (Show, Generic)
  deriving anyclass (FromJSON, ToJSON)

awsLambdaInvoke
  :: ( MonadResource m
     , MonadLogger m
     , MonadReader env m
     , HasAwsEnv env
     , ToJSON a
     )
  => Text
  -> a
  -- ^ Payload
  -> m LambdaInvokeResult
awsLambdaInvoke name payload = do
  logDebug $ "Invoking function" :# ["name" .= name]

  -- Match Lambda's own limit (15 minutes) and add some buffer
  resp <-
    awsTimeout 905
      $ awsSend
      $ newInvoke name
      $ BSL.toStrict
      $ encode
        payload

  let
    status = resp ^. invokeResponse_statusCode
    mError = decode . BSL.fromStrict =<< resp ^. invokeResponse_payload
    mFunctionError = resp ^. invokeResponse_functionError
    response = fromMaybe "" $ resp ^. invokeResponse_payload

  logDebug
    $ "Function result"
    :# [ "name" .= name
       , "status" .= status
       , "error" .= mError
       , "functionError" .= mFunctionError
       ]

  pure
    $ if
      | statusIsUnsuccessful status -> LambdaInvokeFailure status mFunctionError
      | Just e <- mError -> LambdaInvokeError e mFunctionError
      | otherwise -> LambdaInvokeSuccess response

statusIsUnsuccessful :: Int -> Bool
statusIsUnsuccessful s = s < 200 || s >= 300