{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} {-# OPTIONS_GHC -fno-warn-unused-binds #-} {-# OPTIONS_GHC -fno-warn-unused-imports #-} module Aws.Lambda.Setup ( Handler (..), HandlerName (..), Handlers, run, addStandaloneLambdaHandler, addAPIGatewayHandler, addALBHandler, runLambdaHaskellRuntime, ) where import Aws.Lambda.Runtime (runLambda) import Aws.Lambda.Runtime.ALB.Types ( ALBRequest, ALBResponse, ToALBResponseBody (..), mkALBResponse, ) import Aws.Lambda.Runtime.APIGateway.Types ( ApiGatewayDispatcherOptions (propagateImpureExceptions), ApiGatewayRequest, ApiGatewayResponse, ToApiGatewayResponseBody (..), mkApiGatewayResponse, ) import Aws.Lambda.Runtime.Common ( HandlerName (..), HandlerType (..), LambdaError (..), LambdaOptions (LambdaOptions), LambdaResult (..), RawEventObject, ) import Aws.Lambda.Runtime.Configuration ( DispatcherOptions (apiGatewayDispatcherOptions), ) import Aws.Lambda.Runtime.Context (Context) import Aws.Lambda.Runtime.StandaloneLambda.Types ( ToStandaloneLambdaResponseBody (..), ) import Aws.Lambda.Utilities (decodeObj) import Control.Exception (SomeException) import Control.Monad.Catch (MonadCatch (catch), throwM) import Control.Monad.State as State ( MonadIO (..), MonadState, StateT (..), modify, ) import Data.Aeson (FromJSON) import qualified Data.HashMap.Strict as HM import qualified Data.Text as Text import Data.Typeable (Typeable) import GHC.IO.Handle.FD (stderr) import GHC.IO.Handle.Text (hPutStr) type Handlers handlerType m context request response error = HM.HashMap HandlerName (Handler handlerType m context request response error) type StandaloneCallback m context request response error = (request -> Context context -> m (Either error response)) type APIGatewayCallback m context request response error = (ApiGatewayRequest request -> Context context -> m (Either (ApiGatewayResponse error) (ApiGatewayResponse response))) type ALBCallback m context request response error = (ALBRequest request -> Context context -> m (Either (ALBResponse error) (ALBResponse response))) data Handler (handlerType :: HandlerType) m context request response error where StandaloneLambdaHandler :: StandaloneCallback m context request response error -> Handler 'StandaloneHandlerType m context request response error APIGatewayHandler :: APIGatewayCallback m context request response error -> Handler 'APIGatewayHandlerType m context request response error ALBHandler :: ALBCallback m context request response error -> Handler 'ALBHandlerType m context request response error newtype HandlersM (handlerType :: HandlerType) m context request response error a = HandlersM {runHandlersM :: StateT (Handlers handlerType m context request response error) IO a} deriving newtype ( Functor, Applicative, Monad, MonadState (Handlers handlerType m context request response error) ) type RuntimeContext (handlerType :: HandlerType) m context request response error = ( MonadIO m, MonadCatch m, ToStandaloneLambdaResponseBody error, ToStandaloneLambdaResponseBody response, ToApiGatewayResponseBody error, ToApiGatewayResponseBody response, ToALBResponseBody error, ToALBResponseBody response, FromJSON (ApiGatewayRequest request), FromJSON (ALBRequest request), FromJSON request, Typeable request ) runLambdaHaskellRuntime :: RuntimeContext handlerType m context request response error => DispatcherOptions -> IO context -> (forall a. m a -> IO a) -> HandlersM handlerType m context request response error () -> IO () runLambdaHaskellRuntime options initializeContext mToIO initHandlers = do handlers <- fmap snd . flip runStateT HM.empty . runHandlersM $ initHandlers runLambda initializeContext (run options mToIO handlers) run :: RuntimeContext handlerType m context request response error => DispatcherOptions -> (forall a. m a -> IO a) -> Handlers handlerType m context request response error -> LambdaOptions context -> IO (Either (LambdaError handlerType) (LambdaResult handlerType)) run dispatcherOptions mToIO handlers (LambdaOptions eventObject functionHandler _executionUuid contextObject) = do let asIOCallbacks = HM.map (mToIO . handlerToCallback dispatcherOptions eventObject contextObject) handlers case HM.lookup functionHandler asIOCallbacks of Just handlerToCall -> handlerToCall Nothing -> throwM $ userError $ "Could not find handler '" <> (Text.unpack . unHandlerName $ functionHandler) <> "'." addStandaloneLambdaHandler :: HandlerName -> StandaloneCallback m context request response error -> HandlersM 'StandaloneHandlerType m context request response error () addStandaloneLambdaHandler handlerName handler = State.modify (HM.insert handlerName (StandaloneLambdaHandler handler)) addAPIGatewayHandler :: HandlerName -> APIGatewayCallback m context request response error -> HandlersM 'APIGatewayHandlerType m context request response error () addAPIGatewayHandler handlerName handler = State.modify (HM.insert handlerName (APIGatewayHandler handler)) addALBHandler :: HandlerName -> ALBCallback m context request response error -> HandlersM 'ALBHandlerType m context request response error () addALBHandler handlerName handler = State.modify (HM.insert handlerName (ALBHandler handler)) handlerToCallback :: forall handlerType m context request response error. RuntimeContext handlerType m context request response error => DispatcherOptions -> RawEventObject -> Context context -> Handler handlerType m context request response error -> m (Either (LambdaError handlerType) (LambdaResult handlerType)) handlerToCallback dispatcherOptions rawEventObject context handlerToCall = call `catch` handleError where call = case handlerToCall of StandaloneLambdaHandler handler -> case decodeObj @request rawEventObject of Right request -> either (Left . StandaloneLambdaError . toStandaloneLambdaResponse) (Right . StandaloneLambdaResult . toStandaloneLambdaResponse) <$> handler request context Left err -> return . Left . StandaloneLambdaError . toStandaloneLambdaResponse $ err APIGatewayHandler handler -> do case decodeObj @(ApiGatewayRequest request) rawEventObject of Right request -> either (Left . APIGatewayLambdaError . fmap toApiGatewayResponseBody) (Right . APIGatewayResult . fmap toApiGatewayResponseBody) <$> handler request context Left err -> apiGatewayErr 400 . toApiGatewayResponseBody . Text.pack . show $ err ALBHandler handler -> case decodeObj @(ALBRequest request) rawEventObject of Right request -> either (Left . ALBLambdaError . fmap toALBResponseBody) (Right . ALBResult . fmap toALBResponseBody) <$> handler request context Left err -> albErr 400 . toALBResponseBody . Text.pack . show $ err handleError (exception :: SomeException) = do liftIO $ hPutStr stderr . show $ exception case handlerToCall of StandaloneLambdaHandler _ -> return . Left . StandaloneLambdaError . toStandaloneLambdaResponse . Text.pack . show $ exception ALBHandler _ -> albErr 500 . toALBResponseBody . Text.pack . show $ exception APIGatewayHandler _ -> if propagateImpureExceptions . apiGatewayDispatcherOptions $ dispatcherOptions then apiGatewayErr 500 . toApiGatewayResponseBody . Text.pack . show $ exception else apiGatewayErr 500 . toApiGatewayResponseBody . Text.pack $ "Something went wrong." apiGatewayErr statusCode = pure . Left . APIGatewayLambdaError . mkApiGatewayResponse statusCode [] albErr statusCode = pure . Left . ALBLambdaError . mkALBResponse statusCode []