{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Aws.Lambda.Runtime
( runLambda,
Runtime.LambdaResult (..),
Runtime.ApiGatewayDispatcherOptions (..),
Error.Parsing (..),
)
where
import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo
import qualified Aws.Lambda.Runtime.Common as Runtime
import qualified Aws.Lambda.Runtime.Context as Context
import qualified Aws.Lambda.Runtime.Environment as Environment
import qualified Aws.Lambda.Runtime.Error as Error
import qualified Aws.Lambda.Runtime.Publish as Publish
import Aws.Lambda.Runtime.StandaloneLambda.Types (StandaloneLambdaResponseBody (..))
import qualified Control.Exception as Unchecked
import Control.Exception.Safe
import Control.Monad (forever)
import Data.Aeson (encode)
import Data.IORef (newIORef)
import Data.Text (Text, unpack)
import qualified Network.HTTP.Client as Http
import System.IO (hFlush, stderr, stdout)
-- | Runs the user @haskell_lambda@ executable and posts back the
-- results. This is called from the layer's @main@ function.
runLambda :: forall context handlerType. IO context -> Runtime.RunCallback handlerType context -> IO ()
runLambda initializeCustomContext callback = do
manager <- Http.newManager httpManagerSettings
customContext <- initializeCustomContext
customContextRef <- newIORef customContext
context <- Context.initialize @context customContextRef `catch` errorParsing `catch` variableNotSet
forever $ do
lambdaApi <- Environment.apiEndpoint `catch` variableNotSet
event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing
-- Purposefully shadowing to prevent using the initial "empty" context
context <- Context.setEventData context event
( ( ( invokeAndRun callback manager lambdaApi event context
`catch` \err -> Publish.parsingError err lambdaApi context manager
)
`catch` \err -> Publish.invocationError err lambdaApi context manager
)
`catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager
)
`Unchecked.catch` \err -> Publish.invocationError err lambdaApi context manager
httpManagerSettings :: Http.ManagerSettings
httpManagerSettings =
-- We set the timeout to none, as AWS Lambda freezes the containers.
Http.defaultManagerSettings
{ Http.managerResponseTimeout = Http.responseTimeoutNone
}
invokeAndRun ::
Runtime.RunCallback handlerType context ->
Http.Manager ->
Text ->
ApiInfo.Event ->
Context.Context context ->
IO ()
invokeAndRun callback manager lambdaApi event context = do
result <- invokeWithCallback callback event context
Publish.result result lambdaApi context manager
`catch` \err -> Publish.invocationError err lambdaApi context manager
invokeWithCallback ::
Runtime.RunCallback handlerType context ->
ApiInfo.Event ->
Context.Context context ->
IO (Runtime.LambdaResult handlerType)
invokeWithCallback callback event context = do
handlerName <- Runtime.HandlerName <$> Environment.handlerName
let lambdaOptions =
Runtime.LambdaOptions
{ eventObject = ApiInfo.event event,
functionHandler = handlerName,
executionUuid = "", -- DirectCall doesnt use UUID
contextObject = context
}
result <- callback lambdaOptions
-- Flush output to insure output goes into CloudWatch logs
flushOutput
case result of
Left lambdaError -> case lambdaError of
Runtime.StandaloneLambdaError (StandaloneLambdaResponseBodyPlain err) ->
throw $ Error.Invocation $ encode err
Runtime.StandaloneLambdaError (StandaloneLambdaResponseBodyJson err) ->
throw $ Error.Invocation err
Runtime.APIGatewayLambdaError err ->
throw $ Error.Invocation $ encode err
Runtime.ALBLambdaError err ->
throw $ Error.Invocation $ encode err
Right value ->
pure value
variableNotSet :: Error.EnvironmentVariableNotSet -> IO a
variableNotSet (Error.EnvironmentVariableNotSet env) =
error ("Error initializing, variable not set: " <> unpack env)
errorParsing :: Error.Parsing -> IO a
errorParsing Error.Parsing {..} =
error ("Failed parsing " <> unpack errorMessage <> ", got" <> unpack actualValue)
-- | Flush standard output ('stdout') and standard error output ('stderr') handlers
flushOutput :: IO ()
flushOutput = do
hFlush stdout
hFlush stderr