{-# LANGUAGE FlexibleContexts #-} {-| Module : AWS.Lambda.Runtime Description : Runtime methods useful when constructing Haskell handlers for the AWS Lambda Custom Runtime. Copyright : (c) Nike, Inc., 2018 License : BSD3 Maintainer : nathan.fairhurst@nike.com, fernando.freire@nike.com Stability : stable These are runtimes designed for AWS Lambda, which accept a handler and return an application that will retreive and execute events as long as a container continues to exist. Many of these runtimes use "AWS.Lambda.Combinators" under the hood. For those interested in peeking below the abstractions provided here, please refer to that module. -} module AWS.Lambda.Runtime ( pureRuntime, pureRuntimeWithContext, fallibleRuntime, fallibleRuntimeWithContext, ioRuntime, ioRuntimeWithContext, readerTRuntime, mRuntime, mRuntimeWithContext ) where import AWS.Lambda.Combinators (withInfallibleParse, withFallibleParse, withoutContext) import AWS.Lambda.Context (LambdaContext(..)) import qualified AWS.Lambda.Runtime.Value as ValueRuntime import Control.Monad (join) import Control.Monad.Catch (MonadCatch) import Control.Monad.IO.Class (MonadIO) import Control.Monad.Reader (ReaderT) import Data.Aeson (FromJSON, ToJSON) -- | For any monad that supports 'IO' and 'catch'. Useful if you need -- caching behaviours or are comfortable manipulating monad -- transformers, and want full control over your monadic interface. -- -- @ -- {-\# LANGUAGE DeriveGeneric, NamedFieldPuns \#-} -- -- module Main where -- -- import AWS.Lambda.Context (LambdaContext(..)) -- import AWS.Lambda.Runtime (mRuntimeWithContext) -- import Control.Monad.State.Lazy (StateT, evalStateT, get, put) -- import Control.Monad.Trans (liftIO) -- import Data.Aeson (FromJSON) -- import Data.Text (unpack) -- import System.Environment (getEnv) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: LambdaContext -> Named -> StateT Int IO String -- myHandler LambdaContext { functionName } Named { name } = do -- greeting <- liftIO $ getEnv \"GREETING\" -- -- greetingCount <- get -- put $ greetingCount + 1 -- -- return $ greeting ++ name ++ " (" ++ show greetingCount ++ ") from " ++ unpack functionName ++ "!" -- -- main :: IO () -- main = evalStateT (mRuntimeWithContext myHandler) 0 -- @ mRuntimeWithContext :: (MonadCatch m, MonadIO m, FromJSON event, ToJSON result) => (LambdaContext -> event -> m result) -> m () mRuntimeWithContext :: forall (m :: * -> *) event result. (MonadCatch m, MonadIO m, FromJSON event, ToJSON result) => (LambdaContext -> event -> m result) -> m () mRuntimeWithContext = forall (m :: * -> *) result. (MonadCatch m, MonadIO m, ToJSON result) => (LambdaContext -> Value -> m result) -> m () ValueRuntime.mRuntimeWithContext forall b c a. (b -> c) -> (a -> b) -> a -> c . forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap forall a b. FromJSON a => (a -> b) -> Value -> b withInfallibleParse -- | For any monad that supports 'IO' and 'catch'. Useful if you need -- caching behaviours or are comfortable manipulating monad -- transformers, want full control over your monadic interface, but -- don't need to inspect the 'LambdaContext'. -- -- @ -- {-\# LANGUAGE DeriveGeneric, NamedFieldPuns \#-} -- -- module Main where -- -- import AWS.Lambda.Runtime (mRuntime) -- import Control.Monad.State.Lazy (StateT, evalStateT, get, put) -- import Control.Monad.Trans (liftIO) -- import Data.Aeson (FromJSON) -- import System.Environment (getEnv) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: Named -> StateT Int IO String -- myHandler Named { name } = do -- greeting <- liftIO $ getEnv \"GREETING\" -- -- greetingCount <- get -- put $ greetingCount + 1 -- -- return $ greeting ++ name ++ " (" ++ show greetingCount ++ ")!" -- -- main :: IO () -- main = evalStateT (mRuntime myHandler) 0 -- @ mRuntime :: (MonadCatch m, MonadIO m, FromJSON event, ToJSON result) => (event -> m result) -> m () mRuntime :: forall (m :: * -> *) event result. (MonadCatch m, MonadIO m, FromJSON event, ToJSON result) => (event -> m result) -> m () mRuntime = forall (m :: * -> *) result. (MonadCatch m, MonadIO m, ToJSON result) => (Value -> m result) -> m () ValueRuntime.mRuntime forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a b. FromJSON a => (a -> b) -> Value -> b withInfallibleParse -- | For functions that can read the lambda context and use IO within the same monad. -- -- Use this for handlers that need any form of side-effect such as reading -- environment variables or making network requests, and prefer to access the -- AWS Lambda Context in the same monad. -- However, do not use this runtime if you need stateful (caching) behaviors. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Context (LambdaContext(..)) -- import AWS.Lambda.Runtime (readerTRuntime) -- import Control.Monad.Reader (ReaderT, ask) -- import Control.Monad.Trans (liftIO) -- import Data.Aeson (FromJSON) -- import Data.Text (unpack) -- import System.Environment (getEnv) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: Named -> ReaderT LambdaContext IO String -- myHandler Named { name } = do -- LambdaContext { functionName } <- ask -- greeting <- liftIO $ getEnv \"GREETING\" -- return $ greeting ++ name ++ " from " ++ unpack functionName ++ "!" -- -- main :: IO () -- main = readerTRuntime myHandler -- @ readerTRuntime :: (FromJSON event, ToJSON result) => (event -> ReaderT LambdaContext IO result) -> IO () readerTRuntime :: forall event result. (FromJSON event, ToJSON result) => (event -> ReaderT LambdaContext IO result) -> IO () readerTRuntime = forall result. ToJSON result => (Value -> ReaderT LambdaContext IO result) -> IO () ValueRuntime.readerTRuntime forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a b. FromJSON a => (a -> b) -> Value -> b withInfallibleParse -- | For functions with IO that can fail in a pure way (or via throw). -- -- Use this for handlers that need any form of side-effect such as reading -- environment variables or making network requests, and also need the -- AWS Lambda Context as input. -- However, do not use this runtime if you need stateful (caching) behaviors. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Context (LambdaContext(..)) -- import AWS.Lambda.Runtime (ioRuntimeWithContext) -- import Data.Aeson (FromJSON) -- import Data.Text (unpack) -- import System.Environment (getEnv) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: LambdaContext -> Named -> IO (Either String String) -- myHandler (LambdaContext { functionName }) (Named { name }) = do -- greeting <- getEnv \"GREETING\" -- return $ pure $ greeting ++ name ++ " from " ++ unpack functionName ++ "!" -- -- main :: IO () -- main = ioRuntimeWithContext myHandler -- @ ioRuntimeWithContext :: (FromJSON event, ToJSON result) => (LambdaContext -> event -> IO (Either String result)) -> IO () ioRuntimeWithContext :: forall event result. (FromJSON event, ToJSON result) => (LambdaContext -> event -> IO (Either String result)) -> IO () ioRuntimeWithContext = forall result. ToJSON result => (LambdaContext -> Value -> IO (Either String result)) -> IO () ValueRuntime.ioRuntimeWithContext forall b c a. (b -> c) -> (a -> b) -> a -> c . forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap forall a b. FromJSON a => (a -> b) -> Value -> b withInfallibleParse -- | For functions with IO that can fail in a pure way (or via throw). -- -- Use this for handlers that need any form of side-effect such as reading -- environment variables or making network requests. -- However, do not use this runtime if you need stateful (caching) behaviors. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Runtime (ioRuntime) -- import Data.Aeson (FromJSON) -- import System.Environment (getEnv) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: Named -> IO (Either String String) -- myHandler (Named { name }) = do -- greeting <- getEnv \"GREETING\" -- return $ pure $ greeting ++ name -- -- main :: IO () -- main = ioRuntime myHandler -- @ ioRuntime :: (FromJSON event, ToJSON result) => (event -> IO (Either String result)) -> IO () ioRuntime :: forall event result. (FromJSON event, ToJSON result) => (event -> IO (Either String result)) -> IO () ioRuntime = forall result. ToJSON result => (Value -> IO (Either String result)) -> IO () ValueRuntime.ioRuntime forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a b. FromJSON a => (a -> b) -> Value -> b withInfallibleParse -- | For pure functions that can still fail. -- -- Use this for simple handlers that just translate input to output without side-effects, -- but can fail and need the AWS Lambda Context as input. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Context (LambdaContext(..)) -- import AWS.Lambda.Runtime (fallibleRuntimeWithContext) -- import Data.Aeson (FromJSON) -- import Data.Text (unpack) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: LambdaContext -> Named -> Either String String -- myHandler (LambdaContext { functionName }) (Named { name }) = -- if name == \"World\" then -- Right $ "Hello, World from " ++ unpack functionName ++ "!" -- else -- Left "Can only greet the world." -- -- main :: IO () -- main = fallibleRuntimeWithContext myHandler -- @ fallibleRuntimeWithContext :: (FromJSON event, ToJSON result) => (LambdaContext -> event -> Either String result) -> IO () fallibleRuntimeWithContext :: forall event result. (FromJSON event, ToJSON result) => (LambdaContext -> event -> Either String result) -> IO () fallibleRuntimeWithContext LambdaContext -> event -> Either String result fn = forall result. ToJSON result => (LambdaContext -> Value -> Either String result) -> IO () ValueRuntime.fallibleRuntimeWithContext forall a b. (a -> b) -> a -> b $ \LambdaContext lc -> forall (m :: * -> *) a. Monad m => m (m a) -> m a join forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a b. FromJSON a => (a -> b) -> Value -> Either String b withFallibleParse (LambdaContext -> event -> Either String result fn LambdaContext lc) -- | For pure functions that can still fail. -- -- Use this for simple handlers that just translate input to output without side-effects, -- but can fail. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Runtime (fallibleRuntime) -- import Data.Aeson (FromJSON) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: Named -> Either String String -- myHandler (Named { name }) = -- if name == \"World\" then -- Right "Hello, World!" -- else -- Left "Can only greet the world." -- -- main :: IO () -- main = fallibleRuntime myHandler -- @ fallibleRuntime :: (FromJSON event, ToJSON result) => (event -> Either String result) -> IO () fallibleRuntime :: forall event result. (FromJSON event, ToJSON result) => (event -> Either String result) -> IO () fallibleRuntime = forall event result. (FromJSON event, ToJSON result) => (LambdaContext -> event -> Either String result) -> IO () fallibleRuntimeWithContext forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a b. a -> b -> a withoutContext -- | For pure functions that can never fail that also need access to the context. -- -- Use this for simple handlers that just translate input to output without side-effects, -- but that need the AWS Lambda Context as input. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Context (LambdaContext(..)) -- import AWS.Lambda.Runtime (pureRuntimeWithContext) -- import Data.Aeson (FromJSON) -- import Data.Text (unpack) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: LambdaContext -> Named -> String -- myHandler (LambdaContext { functionName }) (Named { name }) = -- "Hello, " ++ name ++ " from " ++ unpack functionName ++ "!" -- -- main :: IO () -- main = pureRuntimeWithContext myHandler -- @ pureRuntimeWithContext :: (FromJSON event, ToJSON result) => (LambdaContext -> event -> result) -> IO () pureRuntimeWithContext :: forall event result. (FromJSON event, ToJSON result) => (LambdaContext -> event -> result) -> IO () pureRuntimeWithContext = forall event result. (FromJSON event, ToJSON result) => (LambdaContext -> event -> Either String result) -> IO () fallibleRuntimeWithContext forall b c a. (b -> c) -> (a -> b) -> a -> c . forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap forall (f :: * -> *) a. Applicative f => a -> f a pure) -- | For pure functions that can never fail. -- -- Use this for simple handlers that just translate input to output without side-effects. -- -- @ -- {-\# LANGUAGE NamedFieldPuns, DeriveGeneric \#-} -- -- module Main where -- -- import AWS.Lambda.Runtime (pureRuntime) -- import Data.Aeson (FromJSON) -- import GHC.Generics (Generic) -- -- data Named = Named { -- name :: String -- } deriving Generic -- instance FromJSON Named -- -- myHandler :: Named -> String -- myHandler Named { name } = "Hello, " ++ name ++ "!" -- -- main :: IO () -- main = pureRuntime myHandler -- @ pureRuntime :: (FromJSON event, ToJSON result) => (event -> result) -> IO () pureRuntime :: forall event result. (FromJSON event, ToJSON result) => (event -> result) -> IO () pureRuntime = forall event result. (FromJSON event, ToJSON result) => (LambdaContext -> event -> result) -> IO () pureRuntimeWithContext forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a b. a -> b -> a withoutContext