aws-lambda-haskell-runtime 1.0.10 → 1.1.0
raw patch · 21 files changed
+784/−505 lines, 21 filesdep +http-typesdep +pathdep +path-iodep −case-insensitivedep −conduitdep −directory
Dependencies added: http-types, path, path-io, safe-exceptions-checked
Dependencies removed: case-insensitive, conduit, directory, filepath, microlens-platform, mtl, wreq
Files
- README.md +3/−3
- app/Main.hs +16/−8
- aws-lambda-haskell-runtime.cabal +29/−19
- src/Aws/Lambda.hs +6/−0
- src/Aws/Lambda/Configuration.hs +18/−175
- src/Aws/Lambda/Meta/Common.hs +36/−0
- src/Aws/Lambda/Meta/Discover.hs +51/−0
- src/Aws/Lambda/Meta/Dispatch.hs +61/−0
- src/Aws/Lambda/Meta/Main.hs +27/−0
- src/Aws/Lambda/Meta/Run.hs +22/−0
- src/Aws/Lambda/Runtime.hs +42/−265
- src/Aws/Lambda/Runtime/API/Endpoints.hs +61/−0
- src/Aws/Lambda/Runtime/API/Version.hs +7/−0
- src/Aws/Lambda/Runtime/ApiInfo.hs +77/−0
- src/Aws/Lambda/Runtime/Context.hs +46/−0
- src/Aws/Lambda/Runtime/Environment.hs +65/−0
- src/Aws/Lambda/Runtime/Error.hs +39/−0
- src/Aws/Lambda/Runtime/IPC.hs +116/−0
- src/Aws/Lambda/Runtime/Publish.hs +55/−0
- src/Aws/Lambda/Runtime/Result.hs +7/−0
- src/Aws/Lambda/ThHelpers.hs +0/−35
README.md view
@@ -9,7 +9,7 @@ ## Sample lambda function ```-stack new my-haskell-lambda https://github.com/theam/aws-lambda-haskell-runtime/raw/master/stack-template.hsfiles --resolver=lts-12.13 --omit-packages+stack new my-haskell-lambda https://github.com/theam/aws-lambda-haskell-runtime/raw/master/stack-template.hsfiles --resolver=lts-13.0 --omit-packages cd my-haskell-lambda stack docker pull ```@@ -21,7 +21,7 @@ - . extra-deps:-- aws-lambda-haskell-runtime-1.0.9+- aws-lambda-haskell-runtime-1.0.10 ``` to your `stack.yaml`@@ -40,7 +40,7 @@ The ARN of the runtime layer is: ```-arn:aws:lambda:<YOUR REGION>:785355572843:layer:aws-haskell-runtime:2+arn:aws:lambda:<YOUR REGION>:785355572843:layer:aws-haskell-runtime:5 ```` ## Full user guide
app/Main.hs view
@@ -1,13 +1,21 @@-module Main where+-- | Main entry point for the layer+module Main+ ( main+ ) where -import Control.Monad-import Control.Monad.Except import Aws.Lambda.Runtime+import Control.Monad+import qualified Network.HTTP.Client as Http +httpManagerSettings :: Http.ManagerSettings+httpManagerSettings =+ -- We set the timeout to none, as AWS Lambda freezes the containers.+ Http.defaultManagerSettings+ { Http.managerResponseTimeout = Http.responseTimeoutNone+ }+ main :: IO ()-main = forever $ do- res <- runExceptT lambdaRunner- case res of- Right _ -> return ()- Left err -> putStrLn $ show err+main = do+ manager <- Http.newManager httpManagerSettings+ forever (runLambda manager)
aws-lambda-haskell-runtime.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 7cc036543eaa96e63b9689f813bbc9413840dd185700a6f40ef90f3a40341b52+-- hash: a4a6b9dc1371218a01d228e551520a6e1296955192cb06ca15f14ebe0923badd name: aws-lambda-haskell-runtime-version: 1.0.10+version: 1.1.0 synopsis: Haskell runtime for AWS Lambda description: Please see the README on GitHub at <https://github.com/theam/aws-lambda-haskell-runtime#readme> category: AWS@@ -29,32 +29,43 @@ library exposed-modules:+ Aws.Lambda Aws.Lambda.Runtime- Aws.Lambda.Configuration other-modules:- Aws.Lambda.ThHelpers+ Aws.Lambda.Configuration+ Aws.Lambda.Meta.Common+ Aws.Lambda.Meta.Discover+ Aws.Lambda.Meta.Dispatch+ Aws.Lambda.Meta.Main+ Aws.Lambda.Meta.Run+ Aws.Lambda.Runtime.API.Endpoints+ Aws.Lambda.Runtime.API.Version+ Aws.Lambda.Runtime.ApiInfo+ Aws.Lambda.Runtime.Context+ Aws.Lambda.Runtime.Environment+ Aws.Lambda.Runtime.Error+ Aws.Lambda.Runtime.IPC+ Aws.Lambda.Runtime.Publish+ Aws.Lambda.Runtime.Result Paths_aws_lambda_haskell_runtime hs-source-dirs: src- default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications- ghc-options: -Wall -optP-Wno-nonportable-include-path+ default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications FlexibleContexts DeriveAnyClass QuasiQuotes+ ghc-options: -Wall -fno-warn-orphans -optP-Wno-nonportable-include-path -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities -Wredundant-constraints -Wmissing-export-lists -Wpartial-fields -fhide-source-paths -freverse-errors build-depends: aeson , base >=4.7 && <5 , bytestring- , case-insensitive- , conduit- , directory- , filepath , http-client- , microlens-platform- , mtl+ , http-types , optparse-generic+ , path+ , path-io , process+ , safe-exceptions-checked , template-haskell , text , uuid- , wreq default-language: Haskell2010 executable bootstrap@@ -63,12 +74,12 @@ Paths_aws_lambda_haskell_runtime hs-source-dirs: app- default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications- ghc-options: -Wall -optP-Wno-nonportable-include-path+ default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications FlexibleContexts DeriveAnyClass QuasiQuotes+ ghc-options: -Wall -fno-warn-orphans -optP-Wno-nonportable-include-path -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities -Wredundant-constraints -Wmissing-export-lists -Wpartial-fields -fhide-source-paths -freverse-errors build-depends: aws-lambda-haskell-runtime , base >=4.7 && <5- , mtl+ , http-client default-language: Haskell2010 test-suite aws-lambda-haskell-runtime-test@@ -78,10 +89,9 @@ Paths_aws_lambda_haskell_runtime hs-source-dirs: test- default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications- ghc-options: -Wall -optP-Wno-nonportable-include-path -threaded -rtsopts -with-rtsopts=-N+ default-extensions: TemplateHaskell OverloadedStrings RecordWildCards ScopedTypeVariables DeriveGeneric TypeApplications FlexibleContexts DeriveAnyClass QuasiQuotes+ ghc-options: -Wall -fno-warn-orphans -optP-Wno-nonportable-include-path -Wincomplete-uni-patterns -Wincomplete-record-updates -Wcompat -Widentities -Wredundant-constraints -Wmissing-export-lists -Wpartial-fields -fhide-source-paths -freverse-errors -threaded -rtsopts -with-rtsopts=-N build-depends: base >=4.7 && <5 , hspec- , mtl default-language: Haskell2010
+ src/Aws/Lambda.hs view
@@ -0,0 +1,6 @@+module Aws.Lambda+ ( module Reexported+ ) where++import Aws.Lambda.Configuration as Reexported+import Aws.Lambda.Runtime.Context as Reexported
src/Aws/Lambda/Configuration.hs view
@@ -1,184 +1,27 @@ {-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-} module Aws.Lambda.Configuration- ( LambdaOptions (..)+ ( Main.LambdaOptions(..)+ , Main.generate+ , Main.getRecord , configureLambda- , returnAndFail- , returnAndSucceed- , decodeObj- , Options.getRecord+ , IPC.returnAndFail+ , IPC.returnAndSucceed+ , Dispatch.decodeObj ) where -import Data.Aeson--import qualified Data.Text as Text-import Data.Text (Text)-import GHC.Generics-import Data.Function ((&))-import Language.Haskell.TH-import qualified Options.Generic as Options-import qualified Data.Conduit as Conduit-import qualified System.Directory as Directory-import System.FilePath ((</>))-import System.IO.Error-import System.IO (hFlush, stdout, stderr)-import System.Exit (exitSuccess, exitFailure)-import Control.Monad.Trans-import Control.Monad-import qualified Data.Text.Encoding as Encoding-import qualified Data.ByteString.Lazy as LazyByteString-import Data.Void-import Data.Monoid----import Aws.Lambda.ThHelpers--putTextLn :: Text -> IO ()-putTextLn = putStrLn . Text.unpack--data LambdaOptions = LambdaOptions- { eventObject :: Text- , contextObject :: Text- , functionHandler :: Text- , executionUuid :: Text- } deriving (Generic)-instance Options.ParseRecord LambdaOptions----- This function is the reason why we disable the warning on top of the module-mkMain :: Q [Dec]-mkMain = [d|- $(pName "main") = getRecord "" >>= run- |]--mkRun :: Q Dec-mkRun = do- handlers <- runIO getHandlers- clause' <- recordQ "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"]- body <- dispatcherCaseQ handlers- pure $ FunD (mkName "run") [Clause [clause'] (NormalB body) []]---dispatcherCaseQ :: [Text] -> Q Exp-dispatcherCaseQ fileNames = do- caseExp <- eName "functionHandler"- matches <- traverse handlerCaseQ fileNames- unmatched <- unmatchedCaseQ- pure $ CaseE caseExp (matches <> [unmatched])---handlerCaseQ :: Text -> Q Match-handlerCaseQ lambdaHandler = do- let pattern = LitP (StringL $ Text.unpack lambdaHandler)- body <- [e|do- result <- $(eName qualifiedName) (decodeObj $(eName "eventObject")) (decodeObj $(eName "contextObject"))- either (returnAndFail $(eName "executionUuid")) (returnAndSucceed $(eName "executionUuid")) result- |]- pure $ Match pattern (NormalB body) []- where- qualifiedName =- lambdaHandler- & Text.dropWhile (/= '/')- & Text.drop 1- & Text.replace "/" "."-+import qualified Language.Haskell.TH as Meta -unmatchedCaseQ :: Q Match-unmatchedCaseQ = do- let pattern = WildP- body <- [e|- returnAndFail $(eName "executionUuid") ("Handler " <> $(eName "functionHandler") <> " does not exist on project")- |]- pure $ Match pattern (NormalB body) []+import qualified Aws.Lambda.Meta.Dispatch as Dispatch+import qualified Aws.Lambda.Meta.Main as Main+import qualified Aws.Lambda.Meta.Run as Run+import qualified Aws.Lambda.Runtime.IPC as IPC -configureLambda :: Q [Dec]+{-| Generates a @main@ function to be used with the+AWS Lambda layer.+-}+configureLambda :: Meta.DecsQ configureLambda = do- main <- mkMain- run <- mkRun- return $ main <> [run]---returnAndFail :: ToJSON a => Text -> a -> IO ()-returnAndFail uuid v = do- hFlush stdout- putTextLn uuid- hFlush stdout- putTextLn (Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode v)- hFlush stdout- hFlush stderr- exitFailure--returnAndSucceed :: ToJSON a => Text -> a -> IO ()-returnAndSucceed uuid v = do- hFlush stdout- putTextLn uuid- hFlush stdout- putTextLn (Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode v)- hFlush stdout- exitSuccess--decodeObj :: FromJSON a => Text -> a-decodeObj x =- case (eitherDecode $ LazyByteString.fromStrict $ Encoding.encodeUtf8 x) of- Left e -> error e- Right v -> v---data DirContent = DirList [FilePath] [FilePath]- | DirError IOError-data DirData = DirData FilePath DirContent----- Produces directory data-walk :: FilePath -> Conduit.ConduitM () DirData IO ()-walk path = do- result <- lift $ tryIOError listdir- case result of- Right dl@(DirList subdirs _) -> do- Conduit.yield (DirData path dl)- forM_ subdirs (walk . (path </>))- Right e -> Conduit.yield (DirData path e)- Left e -> Conduit.yield (DirData path (DirError e))- where- listdir = do- entries <- filterHidden <$> Directory.getDirectoryContents path- subdirs <- filterM isDir entries- files <- filterM isFile entries- return $ DirList subdirs files- where- isFile entry = Directory.doesFileExist (path </> entry)- isDir entry = Directory.doesDirectoryExist (path </> entry)- filterHidden paths = filter (\p -> head p /= '.' && p /= "node_modules") paths----- Consume directories-myVisitor :: Conduit.ConduitM DirData Void IO [FilePath]-myVisitor = loop []- where- loop n = do- r <- Conduit.await- case r of- Nothing -> return n- Just result -> loop (process result <> n)- process (DirData _ (DirError _)) = []- process (DirData dir (DirList _ files)) = map (\f -> dir <> "/" <> f) files---getHandlers :: IO [Text]-getHandlers = do- files <- Conduit.runConduit $ walk "." Conduit..| myVisitor- handlerFiles <- files- & fmap Text.pack- & filter (Text.isSuffixOf ".hs")- & filterM containsHandler- & fmap (fmap $ Text.dropEnd 3)- & fmap (fmap $ Text.drop 2)- & fmap (fmap (<> ".handler"))- return handlerFiles---containsHandler :: Text -> IO Bool-containsHandler file = do- fileContents <- readFile $ Text.unpack file- return $ "handler :: " `Text.isInfixOf` Text.pack fileContents+ main <- Main.generate+ run <- Run.generate+ return (main <> [run])
+ src/Aws/Lambda/Meta/Common.hs view
@@ -0,0 +1,36 @@+{-| Helper functions to make code generation easier -}+module Aws.Lambda.Meta.Common+ ( declarationName+ , expressionName+ , getFieldsFrom+ ) where++import Data.Text (Text)+import qualified Data.Text as Text+import Language.Haskell.TH++-- | Helper for defining names in declarations+-- think of @myValue@ in @myValue = 2@+declarationName :: Text -> Q Pat+declarationName = pure . VarP . mkName . Text.unpack++-- | Helper for defining names in expressions+-- think of @myFunction@ in @quux = myFunction 3@+expressionName :: Text -> Q Exp+expressionName = pure . VarE . mkName . Text.unpack+++-- | Helper for extracting fields of a specified record+-- it expects the constructor name as the first parameter,+-- and the list of fields to bring into scope as second+-- think of @Person@, and @personAge@, @personName@ in+-- @myFunction Person { personAge, personName } = ...@+getFieldsFrom :: Text -> [Text] -> Q Pat+getFieldsFrom name fields = do+ extractedFields <- traverse extractField fields+ pure $ RecP (mkName $ Text.unpack name) extractedFields+ where+ -- | Helper for extracting fields of records+ -- think of @personAge@ in @myFunction Person { personAge = personAge } = ...@+ extractField :: Text -> Q FieldPat+ extractField n = pure (mkName $ Text.unpack n, VarP $ mkName $ Text.unpack n)
+ src/Aws/Lambda/Meta/Discover.hs view
@@ -0,0 +1,51 @@+{-| Discovery of AWS Lambda handlers+A handler is basically a function that has a type definition that+starts with "handler :: ".+ -}+module Aws.Lambda.Meta.Discover+ ( handlers+ ) where++import qualified Control.Monad as Monad+import Data.Function ((&))+import qualified Data.Maybe as Maybe+import Data.Text (Text)+import qualified Data.Text as Text++import Path+import qualified Path.IO as PathIO++{-| Returns a list of handler paths that look like++@src/Foo/Bar/Quux.handler@++It is the path to the source file, but changing the+extension for ".handler"+-}+handlers :: IO [Text]+handlers = do+ (_, files) <- PathIO.listDirRecurRel [reldir|.|]+ handlerFiles <- modulesWithHandler files+ pure (handlerNames handlerFiles)++modulesWithHandler :: [Path Rel File] -> IO [Path Rel File]+modulesWithHandler files =+ filter isHaskellModule files+ & Monad.filterM containsHandler+ where+ isHaskellModule file =+ fileExtension file == ".hs"++handlerNames :: [Path Rel File] -> [Text]+handlerNames modules =+ fmap changeExtensionToHandler modules+ & fmap (Text.pack . toFilePath)+ where+ changeExtensionToHandler file =+ setFileExtension ".handler" file+ & Maybe.fromJust -- The path will be always parsable, as we just replace the extension++containsHandler :: Path Rel File -> IO Bool+containsHandler file = do+ fileContents <- readFile $ toFilePath file+ pure $ "handler :: " `Text.isInfixOf` Text.pack fileContents
+ src/Aws/Lambda/Meta/Dispatch.hs view
@@ -0,0 +1,61 @@+{-| Dispatcher generation -}+module Aws.Lambda.Meta.Dispatch+ ( generate+ , decodeObj+ ) where++import Data.Function ((&))+import Data.Text (Text)+import qualified Data.Text as Text++import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as LazyByteString+import qualified Language.Haskell.TH as Meta++import Aws.Lambda.Meta.Common++{-| Helper function that the dispatcher will use to+decode the JSON that comes as an AWS Lambda event into the+appropriate type expected by the handler.+-}+decodeObj :: FromJSON a => String -> a+decodeObj x =+ case (eitherDecode $ LazyByteString.pack x) of+ Left e -> error e+ Right v -> v++{-| Generates the dispatcher out of a list of+handler names in the form @src/Foo/Bar.handler@++This dispatcher has a case for each of the handlers that calls+the appropriate qualified function. In the case of the example above,+the dispatcher will call @Foo.Bar.handler@.+-}+generate :: [Text] -> Meta.ExpQ+generate handlerNames = do+ caseExp <- expressionName "functionHandler"+ matches <- traverse handlerCase handlerNames+ unmatched <- unmatchedCase+ pure $ Meta.CaseE caseExp (matches <> [unmatched])++handlerCase :: Text -> Meta.MatchQ+handlerCase lambdaHandler = do+ let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler)+ body <- [e|do+ result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject"))+ either (returnAndFail $(expressionName "executionUuid")) (returnAndSucceed $(expressionName "executionUuid")) result |]+ pure $ Meta.Match pat (Meta.NormalB body) []+ where+ qualifiedName =+ lambdaHandler+ & Text.dropWhile (/= '/')+ & Text.drop 1+ & Text.replace "/" "."++unmatchedCase :: Meta.MatchQ+unmatchedCase = do+ let pattern = Meta.WildP+ body <- [e|+ returnAndFail $(expressionName "executionUuid") ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")+ |]+ pure $ Meta.Match pattern (Meta.NormalB body) []
+ src/Aws/Lambda/Meta/Main.hs view
@@ -0,0 +1,27 @@+{-| main function generation for interoperation with the layer -}+module Aws.Lambda.Meta.Main+ ( LambdaOptions(..)+ , generate+ , Options.getRecord+ ) where++import GHC.Generics (Generic)++import qualified Language.Haskell.TH as Meta+import qualified Options.Generic as Options++import Aws.Lambda.Meta.Common++-- | Options that the generated main expects+data LambdaOptions = LambdaOptions+ { eventObject :: !String+ , contextObject :: !String+ , functionHandler :: !String+ , executionUuid :: !String+ } deriving (Generic, Options.ParseRecord)++-- | Generate the main function that the layer will call+generate :: Meta.DecsQ+generate = [d|+ $(declarationName "main") = getRecord "" >>= run+ |]
+ src/Aws/Lambda/Meta/Run.hs view
@@ -0,0 +1,22 @@+module Aws.Lambda.Meta.Run+ ( generate+ ) where++import qualified Language.Haskell.TH as Meta++import Aws.Lambda.Meta.Common+import qualified Aws.Lambda.Meta.Discover as Discover+import qualified Aws.Lambda.Meta.Dispatch as Dispatch++{-| Generate the run function++It will create a dispatcher that is a huge @case@ expression that+expects the name of the handler provided by AWS Lambda, and will+execute the appropriate user function+ -}+generate :: Meta.DecQ+generate = do+ handlers <- Meta.runIO Discover.handlers+ clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"]+ body <- Dispatch.generate handlers+ pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []]
src/Aws/Lambda/Runtime.hs view
@@ -1,272 +1,49 @@-module Aws.Lambda.Runtime where--import Control.Exception (Exception, IOException, try)-import Control.Monad.Except (ExceptT, catchError, throwError)-import Data.Aeson-import System.Exit (ExitCode (..))-import qualified Data.Text as Text-import Data.Text (Text)-import GHC.Generics-import qualified Data.ByteString.Lazy as LazyByteString-import qualified Data.ByteString as ByteString-import Control.Monad.Trans-import Text.Read (readMaybe)-import qualified Data.Text.Encoding as Encoding-import Control.Monad-import Data.Function ((&))-import Data.Maybe (listToMaybe)-import Data.Monoid ((<>))+module Aws.Lambda.Runtime+ ( runLambda+ ) where -import qualified Data.CaseInsensitive as CI-import Lens.Micro.Platform hiding ((.=))-import qualified Network.Wreq as Wreq+import Control.Exception.Safe.Checked import qualified Network.HTTP.Client as Http-import qualified System.Environment as Environment-import qualified System.Process as Process-import qualified Data.UUID as UUID-import qualified Data.UUID.V4 as UUID-import System.IO (hFlush, stdout) --type LByteString = LazyByteString.ByteString-type ByteString = ByteString.ByteString--type App a =- ExceptT RuntimeError IO a---data RuntimeError- = EnvironmentVariableNotSet Text- | ApiConnectionError- | ApiHeaderNotSet Text- | ParseError Text Text- | InvocationError Text- deriving (Show)-instance Exception RuntimeError--instance ToJSON RuntimeError where- toJSON (EnvironmentVariableNotSet msg) = object- [ "errorType" .= ("EnvironmentVariableNotSet" :: Text)- , "errorMessage" .= msg- ]-- toJSON ApiConnectionError = object- [ "errorType" .= ("ApiConnectionError" :: Text)- , "errorMessage" .= ("Could not connect to API to retrieve AWS Lambda parameters" :: Text)- ]-- toJSON (ApiHeaderNotSet headerName) = object- [ "errorType" .= ("ApiHeaderNotSet" :: Text)- , "errorMessage" .= headerName- ]-- toJSON (ParseError objectBeingParsed value) = object- [ "errorType" .= ("ParseError" :: Text)- , "errorMessage" .= ("Parse error for " <> objectBeingParsed <> ", could not parse value '" <> value <> "'")- ]-- -- We return the user error as it is- toJSON (InvocationError err) = toJSON err----data Context = Context- { memoryLimitInMb :: !Int- , functionName :: !Text- , functionVersion :: !Text- , invokedFunctionArn :: !Text- , awsRequestId :: !Text- , xrayTraceId :: !Text- , logStreamName :: !Text- , logGroupName :: !Text- , deadline :: !Int- } deriving (Generic)-instance FromJSON Context-instance ToJSON Context---newtype LambdaResult =- LambdaResult Text---awsLambdaVersion :: String-awsLambdaVersion = "2018-06-01"---nextInvocationEndpoint :: Text -> String-nextInvocationEndpoint endpoint =- "http://" <> Text.unpack endpoint <> "/"<> awsLambdaVersion <>"/runtime/invocation/next"---responseEndpoint :: Text -> Text -> String-responseEndpoint lambdaApi requestId =- "http://"<> Text.unpack lambdaApi <> "/" <> awsLambdaVersion <> "/runtime/invocation/"<> Text.unpack requestId <> "/response"---invocationErrorEndpoint :: Text -> Text -> String-invocationErrorEndpoint lambdaApi requestId =- "http://"<> Text.unpack lambdaApi <> "/" <> awsLambdaVersion <> "/runtime/invocation/"<> Text.unpack requestId <> "/error"---runtimeInitErrorEndpoint :: Text -> String-runtimeInitErrorEndpoint lambdaApi =- "http://"<> Text.unpack lambdaApi <> "/" <> awsLambdaVersion <> "/runtime/init/error"---readEnvironmentVariable :: Text -> App Text-readEnvironmentVariable envVar = do- v <- lift (Environment.lookupEnv $ Text.unpack envVar)- case v of- Nothing -> throwError (EnvironmentVariableNotSet envVar)- Just value -> pure (Text.pack value)---readFunctionMemory :: App Int-readFunctionMemory = do- let envVar = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"- let parseMemory txt = readMaybe (Text.unpack txt)- memoryValue <- readEnvironmentVariable envVar- case parseMemory memoryValue of- Just value -> pure value- Nothing -> throwError (ParseError envVar memoryValue)---getApiData :: Text -> App (Wreq.Response LByteString)-getApiData endpoint =- keepRetrying (Wreq.getWith opts $ nextInvocationEndpoint endpoint)- where- opts = Wreq.defaults- & Wreq.manager .~ Left (Http.defaultManagerSettings { Http.managerResponseTimeout = Http.responseTimeoutNone })- keepRetrying :: IO (Wreq.Response LByteString) -> App (Wreq.Response LByteString)- keepRetrying f = do- result <- (liftIO $ try f) :: App (Either IOException (Wreq.Response LByteString))- case result of- Right x -> return x- _ -> keepRetrying f---extractHeader :: Wreq.Response LByteString -> Text -> Text-extractHeader apiData header =- Encoding.decodeUtf8 (apiData ^. Wreq.responseHeader (CI.mk $ Encoding.encodeUtf8 header))---extractIntHeader :: Wreq.Response LByteString -> Text -> App Int-extractIntHeader apiData headerName = do- let header = extractHeader apiData headerName- case readMaybe $ Text.unpack header of- Nothing -> throwError (ParseError "deadline" header)- Just value -> pure value---extractBody :: Wreq.Response LByteString -> Text-extractBody apiData =- Encoding.decodeUtf8 $ LazyByteString.toStrict (apiData ^. Wreq.responseBody)---propagateXRayTrace :: Text -> App ()-propagateXRayTrace xrayTraceId =- liftIO $ Environment.setEnv "_X_AMZN_TRACE_ID" $ Text.unpack xrayTraceId---initializeContext :: Wreq.Response LByteString -> App Context-initializeContext apiData = do- functionName <- readEnvironmentVariable "AWS_LAMBDA_FUNCTION_NAME"- version <- readEnvironmentVariable "AWS_LAMBDA_FUNCTION_VERSION"- logStream <- readEnvironmentVariable "AWS_LAMBDA_LOG_STREAM_NAME"- logGroup <- readEnvironmentVariable "AWS_LAMBDA_LOG_GROUP_NAME"- memoryLimitInMb <- readFunctionMemory- deadline <- extractIntHeader apiData "Lambda-Runtime-Deadline-Ms"- let xrayTraceId = extractHeader apiData "Lambda-Runtime-Trace-Id"- let awsRequestId = extractHeader apiData "Lambda-Runtime-Aws-Request-Id"- let invokedFunctionArn = extractHeader apiData "Lambda-Runtime-Invoked-Function-Arn"- propagateXRayTrace xrayTraceId- pure $ Context- { functionName = functionName- , functionVersion = version- , logStreamName = logStream- , logGroupName = logGroup- , memoryLimitInMb = memoryLimitInMb- , invokedFunctionArn = invokedFunctionArn- , xrayTraceId = xrayTraceId- , awsRequestId = awsRequestId- , deadline = deadline- }---getFunctionResult :: UUID.UUID -> Text -> App (Maybe Text)-getFunctionResult u stdOut = do- let out = Text.lines stdOut-- out- & takeWhile (/= uuid)- & mapM_ ( \t -> do- liftIO $ putStrLn $ Text.unpack t- liftIO $ hFlush stdout)-- out- & dropWhile (/= uuid)- & dropWhile (== uuid)- & listToMaybe- & return- where- uuid = Text.pack $ UUID.toString u---invoke :: Text -> Context -> App LambdaResult-invoke event context = do- handlerName <- readEnvironmentVariable "_HANDLER"- runningDirectory <- readEnvironmentVariable "LAMBDA_TASK_ROOT"- let contextJSON = Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode context- uuid <- liftIO UUID.nextRandom- out <- liftIO $ Process.readProcessWithExitCode (Text.unpack runningDirectory <> "/haskell_lambda")- [ "--eventObject", Text.unpack event- , "--contextObject", Text.unpack contextJSON- , "--functionHandler", Text.unpack handlerName- , "--executionUuid", UUID.toString uuid- ]- ""- case out of- (ExitSuccess, stdOut, _) -> do- res <- getFunctionResult uuid (Text.pack stdOut)- case res of- Nothing -> throwError (ParseError "parsing result" $ Text.pack stdOut)- Just value -> pure (LambdaResult value)- (_, stdOut, stdErr) ->- if stdErr /= ""- then throwError (InvocationError $ Text.pack stdErr)- else do- res <- getFunctionResult uuid (Text.pack stdOut)- case res of- Nothing -> throwError (ParseError "parsing error" $ Text.pack stdOut)- Just value -> throwError (InvocationError value)---publishResult :: Context -> Text -> LambdaResult -> App ()-publishResult Context {..} lambdaApi (LambdaResult result) =- void $ liftIO $ Wreq.post (responseEndpoint lambdaApi awsRequestId) (Encoding.encodeUtf8 result)---invokeAndPublish :: Context -> Text -> Text -> App ()-invokeAndPublish ctx event lambdaApiEndpoint = do- res <- invoke event ctx- publishResult ctx lambdaApiEndpoint res---publishError :: Context -> Text -> RuntimeError -> App ()-publishError Context {..} lambdaApiEndpoint (InvocationError err) =- void (liftIO $ Wreq.post (invocationErrorEndpoint lambdaApiEndpoint awsRequestId) (Encoding.encodeUtf8 err))+import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo+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.IPC as IPC+import qualified Aws.Lambda.Runtime.Publish as Publish -publishError Context {..} lambdaApiEndpoint (ParseError t t2) =- void (liftIO $ Wreq.post (invocationErrorEndpoint lambdaApiEndpoint awsRequestId) (toJSON $ ParseError t t2))+-- | Runs the user @haskell_lambda@ executable and posts back the+-- results+runLambda+ :: Http.Manager+ -> IO ()+runLambda manager = do+ lambdaApi <- Environment.apiEndpoint `catch` variableNotSet+ event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing+ context <- Context.initialize event `catch` errorParsing `catch` variableNotSet+ ((invokeAndRun 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 -publishError Context {..} lambdaApiEndpoint err =- void (liftIO $ Wreq.post (runtimeInitErrorEndpoint lambdaApiEndpoint) (toJSON err))+invokeAndRun+ :: Throws Error.Parsing+ => Throws Error.Invocation+ => Throws Error.EnvironmentVariableNotSet+ => Http.Manager+ -> String+ -> ApiInfo.Event+ -> Context.Context+ -> IO ()+invokeAndRun manager lambdaApi event context = do+ result <- IPC.invoke (ApiInfo.event event) context+ Publish.result result lambdaApi context manager+ `catch` \err -> Publish.invocationError err lambdaApi context manager +variableNotSet :: Error.EnvironmentVariableNotSet -> IO a+variableNotSet (Error.EnvironmentVariableNotSet env) =+ error ("Error initializing, variable not set: " <> env) -lambdaRunner :: App ()-lambdaRunner = do- lambdaApiEndpoint <- readEnvironmentVariable "AWS_LAMBDA_RUNTIME_API"- apiData <- getApiData lambdaApiEndpoint- let event = extractBody apiData- ctx <- initializeContext apiData- invokeAndPublish ctx event lambdaApiEndpoint `catchError` publishError ctx lambdaApiEndpoint+errorParsing :: Error.Parsing -> IO a+errorParsing Error.Parsing{..} =+ error ("Failed parsing " <> errorMessage <> ", got" <> actualValue)
+ src/Aws/Lambda/Runtime/API/Endpoints.hs view
@@ -0,0 +1,61 @@+module Aws.Lambda.Runtime.API.Endpoints+ ( response+ , invocationError+ , runtimeInitError+ , nextInvocation+ , Endpoint(..)+ ) where++import qualified Aws.Lambda.Runtime.API.Version as Version++newtype Endpoint =+ Endpoint String+ deriving (Show)++-- | Endpoint that provides the ID of the next invocation+nextInvocation :: String -> Endpoint+nextInvocation lambdaApi =+ Endpoint $ concat+ [ "http://"+ , lambdaApi+ , "/"+ , Version.value+ , "/runtime/invocation/next"+ ]++-- | Where the response of the Lambda gets published+response :: String -> String -> Endpoint+response lambdaApi requestId =+ Endpoint $ concat+ [ "http://"+ , lambdaApi+ , "/"+ , Version.value+ , "/runtime/invocation/"+ , requestId+ , "/response"+ ]++-- | Invocation (runtime) errors should be published here+invocationError :: String -> String -> Endpoint+invocationError lambdaApi requestId =+ Endpoint $ concat+ [ "http://"+ , lambdaApi+ , "/"+ , Version.value+ , "/runtime/invocation/"+ , requestId+ , "/error"+ ]++-- | Runtime initialization errors should go here+runtimeInitError :: String -> Endpoint+runtimeInitError lambdaApi =+ Endpoint $ concat+ [ "http://"+ , lambdaApi+ , "/"+ , Version.value+ , "/runtime/init/error"+ ]
+ src/Aws/Lambda/Runtime/API/Version.hs view
@@ -0,0 +1,7 @@+module Aws.Lambda.Runtime.API.Version+ ( value+ ) where++-- | Version of the AWS Lambda runtime REST API+value :: String+value = "2018-06-01"
+ src/Aws/Lambda/Runtime/ApiInfo.hs view
@@ -0,0 +1,77 @@+module Aws.Lambda.Runtime.ApiInfo+ ( Event(..)+ , fetchEvent+ ) where++import qualified Control.Monad as Monad+import qualified Text.Read as Read++import Control.Exception (IOException)+import Control.Exception.Safe.Checked+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as ByteString+import qualified Data.ByteString.Lazy.Char8 as Lazy+import qualified Network.HTTP.Client as Http+import qualified Network.HTTP.Types.Header as Http++import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints+import qualified Aws.Lambda.Runtime.Error as Error++-- | Event that is fetched out of the AWS Lambda API+data Event = Event+ { deadlineMs :: !Int+ , traceId :: !String+ , awsRequestId :: !String+ , invokedFunctionArn :: !String+ , event :: !Lazy.ByteString+ }++-- | Performs a GET to the endpoint that provides the next event+fetchEvent :: Throws Error.Parsing => Http.Manager -> String -> IO Event+fetchEvent manager lambdaApi = do+ response <- fetchApiData manager lambdaApi+ let body = Http.responseBody response+ let headers = Http.responseHeaders response+ Monad.foldM reduceEvent (initialEvent body) headers++fetchApiData :: Http.Manager -> String -> IO (Http.Response Lazy.ByteString)+fetchApiData manager lambdaApi = do+ let Endpoints.Endpoint endpoint = Endpoints.nextInvocation lambdaApi+ request <- Http.parseRequest endpoint+ keepRetrying $ Http.httpLbs request manager++reduceEvent :: Throws Error.Parsing => 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 "deadlineMs" $ ByteString.unpack value)++ ("Lambda-Runtime-Trace-Id", value) ->+ pure event { traceId = ByteString.unpack value }++ ("Lambda-Runtime-Aws-Request-Id", value) ->+ pure event { awsRequestId = ByteString.unpack value }++ ("Lambda-Runtime-Invoked-Function-Arn", value) ->+ pure event { invokedFunctionArn = 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
+ src/Aws/Lambda/Runtime/Context.hs view
@@ -0,0 +1,46 @@+module Aws.Lambda.Runtime.Context+ ( Context(..)+ , initialize+ ) where++import Control.Exception.Safe.Checked+import Data.Aeson (FromJSON (..), ToJSON (..))+import GHC.Generics (Generic)++import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo+import qualified Aws.Lambda.Runtime.Environment as Environment+import qualified Aws.Lambda.Runtime.Error as Error++-- | Context that is passed to all the handlers+data Context = Context+ { memoryLimitInMb :: !Int+ , functionName :: !String+ , functionVersion :: !String+ , invokedFunctionArn :: !String+ , awsRequestId :: !String+ , xrayTraceId :: !String+ , logStreamName :: !String+ , logGroupName :: !String+ , deadline :: !Int+ } deriving (Generic, FromJSON, ToJSON)++-- | Initializes the context out of the environment+initialize :: Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => ApiInfo.Event -> IO Context+initialize ApiInfo.Event{..} = do+ functionName <- Environment.functionName+ version <- Environment.functionVersion+ logStream <- Environment.logStreamName+ logGroup <- Environment.logGroupName+ memoryLimitInMb <- Environment.functionMemory+ Environment.setXRayTrace traceId+ pure Context+ { functionName = functionName+ , functionVersion = version+ , logStreamName = logStream+ , logGroupName = logGroup+ , memoryLimitInMb = memoryLimitInMb+ , invokedFunctionArn = invokedFunctionArn+ , xrayTraceId = traceId+ , awsRequestId = awsRequestId+ , deadline = deadlineMs+ }
+ src/Aws/Lambda/Runtime/Environment.hs view
@@ -0,0 +1,65 @@+{-| Provides all the values out of+the environment variables of the system+-}+module Aws.Lambda.Runtime.Environment+ ( functionMemory+ , apiEndpoint+ , handlerName+ , taskRoot+ , functionName+ , functionVersion+ , logStreamName+ , logGroupName+ , setXRayTrace+ ) where++import qualified Aws.Lambda.Runtime.Error as Error+import Control.Exception.Safe.Checked+import qualified System.Environment as Environment+import qualified Text.Read as Read++logGroupName :: Throws Error.EnvironmentVariableNotSet => IO String+logGroupName =+ readEnvironmentVariable "AWS_LAMBDA_LOG_GROUP_NAME"++logStreamName :: Throws Error.EnvironmentVariableNotSet => IO String+logStreamName =+ readEnvironmentVariable "AWS_LAMBDA_LOG_STREAM_NAME"++functionVersion :: Throws Error.EnvironmentVariableNotSet => IO String+functionVersion =+ readEnvironmentVariable "AWS_LAMBDA_FUNCTION_VERSION"++functionName :: Throws Error.EnvironmentVariableNotSet => IO String+functionName =+ readEnvironmentVariable "AWS_LAMBDA_FUNCTION_NAME"++setXRayTrace :: String -> IO ()+setXRayTrace = Environment.setEnv "_X_AMZN_TRACE_ID"++taskRoot :: Throws Error.EnvironmentVariableNotSet => IO String+taskRoot =+ readEnvironmentVariable "LAMBDA_TASK_ROOT"++handlerName :: Throws Error.EnvironmentVariableNotSet => IO String+handlerName =+ readEnvironmentVariable "_HANDLER"++apiEndpoint :: Throws Error.EnvironmentVariableNotSet => IO String+apiEndpoint =+ readEnvironmentVariable "AWS_LAMBDA_RUNTIME_API"++functionMemory :: Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => IO Int+functionMemory = do+ let envVar = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"+ memoryValue <- readEnvironmentVariable envVar+ case Read.readMaybe memoryValue of+ Just value -> pure value+ Nothing -> throw (Error.Parsing envVar memoryValue)++readEnvironmentVariable :: Throws Error.EnvironmentVariableNotSet => String -> IO String+readEnvironmentVariable envVar = do+ v <- Environment.lookupEnv envVar+ case v of+ Just value -> pure value+ Nothing -> throw (Error.EnvironmentVariableNotSet envVar)
+ src/Aws/Lambda/Runtime/Error.hs view
@@ -0,0 +1,39 @@+{-| All the errors that the runtime can throw+-}+module Aws.Lambda.Runtime.Error+ ( EnvironmentVariableNotSet(..)+ , Parsing(..)+ , Invocation(..)+ ) where++import Control.Exception.Safe.Checked+import Data.Aeson (ToJSON (..), object, (.=))++newtype EnvironmentVariableNotSet =+ EnvironmentVariableNotSet String+ deriving (Show, Exception)++instance ToJSON EnvironmentVariableNotSet where+ toJSON (EnvironmentVariableNotSet msg) = object+ [ "errorType" .= ("EnvironmentVariableNotSet" :: String)+ , "errorMessage" .= msg+ ]++data Parsing = Parsing+ { errorMessage :: String+ , actualValue :: String+ } deriving (Show, Exception)++instance ToJSON Parsing where+ toJSON (Parsing objectBeingParsed value) = object+ [ "errorType" .= ("Parsing" :: String)+ , "errorMessage" .= ("Parse error for " <> objectBeingParsed <> ", could not parse value '" <> value <> "'")+ ]++newtype Invocation =+ Invocation String+ deriving (Show, Exception)++instance ToJSON Invocation where+ -- We return the user error as it is+ toJSON (Invocation err) = toJSON err
+ src/Aws/Lambda/Runtime/IPC.hs view
@@ -0,0 +1,116 @@+{-| Inter-Process Communication++Used for when the user project is called from a layer.++This is used to call the @haskell_lambda@ executable, which is+provided by the user, when they want to use the layer.++This IPC protocol is based on printing an UUID that is+created by the layer, and then the result. So everything that+is printed before the UUID, is considered STDOUT printed by+the lambda, while what comes after the UUID is considered.++In the case that the lambda execution fails, the exit code+won't be 0 (exit-success), so it will use the STDERR.+-}+module Aws.Lambda.Runtime.IPC+ ( invoke+ , returnAndFail+ , returnAndSucceed+ ) where+++import Data.Function ((&))+import qualified Data.Maybe as Maybe+import qualified Data.String as String+import qualified System.Exit as Exit+import qualified System.IO as IO+import qualified System.Process as Process++import Control.Exception.Safe.Checked+import Data.Aeson+import qualified Data.ByteString.Lazy.Char8 as ByteString+import qualified Data.UUID as UUID+import qualified Data.UUID.V4 as UUID++import Aws.Lambda.Runtime.Context (Context (..))+import qualified Aws.Lambda.Runtime.Environment as Environment+import qualified Aws.Lambda.Runtime.Error as Error+import Aws.Lambda.Runtime.Result (LambdaResult (..))++-- | Returns the JSON value failing, according to the protocol+returnAndFail :: ToJSON a => String -> a -> IO ()+returnAndFail uuid v = do+ IO.hFlush IO.stdout+ putStrLn uuid+ IO.hFlush IO.stdout+ putStrLn (ByteString.unpack $ encode v)+ IO.hFlush IO.stdout+ IO.hFlush IO.stderr+ Exit.exitFailure++-- | Returns the JSON value succeeding, according to the protocol+returnAndSucceed :: ToJSON a => String -> a -> IO ()+returnAndSucceed uuid v = do+ IO.hFlush IO.stdout+ putStrLn uuid+ IO.hFlush IO.stdout+ putStrLn (ByteString.unpack $ encode v)+ IO.hFlush IO.stdout+ Exit.exitSuccess++-- | Invokes a function defined by the user as the @haskell_lambda@ executable+invoke+ :: Throws Error.Invocation+ => Throws Error.Parsing+ => Throws Error.EnvironmentVariableNotSet+ => ByteString.ByteString+ -> Context+ -> IO LambdaResult+invoke event context = do+ handlerName <- Environment.handlerName+ runningDirectory <- Environment.taskRoot+ let contextJSON = ByteString.unpack $ encode context+ uuid <- UUID.nextRandom+ out <- Process.readProcessWithExitCode (runningDirectory <> "/haskell_lambda")+ [ "--eventObject", ByteString.unpack event+ , "--contextObject", contextJSON+ , "--functionHandler", handlerName+ , "--executionUuid", UUID.toString uuid+ ]+ ""+ case out of+ (Exit.ExitSuccess, stdOut, _) -> do+ res <- getFunctionResult uuid stdOut+ case res of+ Nothing -> throw (Error.Parsing "parsing result" stdOut)+ Just value -> pure (LambdaResult value)+ (_, stdOut, stdErr) ->+ if stdErr /= ""+ then throw (Error.Invocation stdErr)+ else do+ res <- getFunctionResult uuid stdOut+ case res of+ Nothing -> throw (Error.Parsing "parsing error" stdOut)+ Just value -> throw (Error.Invocation value)++getFunctionResult :: UUID.UUID -> String -> IO (Maybe String)+getFunctionResult u stdOut = do+ let out = String.lines stdOut+ let uuid = UUID.toString u+ printAfterUuid uuid out+ returnAfterUuid uuid out+ where+ printAfterUuid uuid out =+ out+ & takeWhile (/= uuid)+ & mapM_ ( \t -> do+ putStrLn t+ IO.hFlush IO.stdout )++ returnAfterUuid uuid out =+ out+ & dropWhile (/= uuid)+ & dropWhile (== uuid)+ & Maybe.listToMaybe+ & pure
+ src/Aws/Lambda/Runtime/Publish.hs view
@@ -0,0 +1,55 @@+{-| Publishing of results/errors back to the+AWS Lambda runtime API -}+module Aws.Lambda.Runtime.Publish+ ( result+ , invocationError+ , parsingError+ , runtimeInitError+ ) where++import Control.Monad (void)+import Data.Aeson+import qualified Data.ByteString.Char8 as ByteString+import qualified Network.HTTP.Client as Http++import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints+import Aws.Lambda.Runtime.Context (Context (..))+import qualified Aws.Lambda.Runtime.Error as Error+import Aws.Lambda.Runtime.Result (LambdaResult (..))++-- | Publishes the result back to AWS Lambda+result :: LambdaResult -> String -> Context -> Http.Manager -> IO ()+result (LambdaResult res) lambdaApi context manager = do+ let Endpoints.Endpoint endpoint = Endpoints.response lambdaApi (awsRequestId context)+ rawRequest <- Http.parseRequest endpoint+ let request = rawRequest+ { Http.method = "POST"+ , Http.requestBody = Http.RequestBodyBS (ByteString.pack res)+ }+ void $ Http.httpNoBody request manager++-- | Publishes an invocation error back to AWS Lambda+invocationError :: Error.Invocation -> String -> Context -> Http.Manager -> IO ()+invocationError err lambdaApi context =+ publish err (Endpoints.invocationError lambdaApi $ awsRequestId context)+ context++-- | Publishes a parsing error back to AWS Lambda+parsingError :: Error.Parsing -> String -> Context -> Http.Manager -> IO ()+parsingError err lambdaApi context =+ publish err (Endpoints.invocationError lambdaApi $ awsRequestId context)+ context++-- | Publishes a runtime initialization error back to AWS Lambda+runtimeInitError :: ToJSON err => err -> String -> Context -> Http.Manager -> IO ()+runtimeInitError err lambdaApi =+ publish err (Endpoints.runtimeInitError lambdaApi)++publish :: ToJSON err => err -> Endpoints.Endpoint -> Context -> Http.Manager -> IO ()+publish err (Endpoints.Endpoint endpoint) Context{..} manager = do+ rawRequest <- Http.parseRequest endpoint+ let request = rawRequest+ { Http.method = "POST"+ , Http.requestBody = Http.RequestBodyLBS (encode err)+ }+ void $ Http.httpNoBody request manager
+ src/Aws/Lambda/Runtime/Result.hs view
@@ -0,0 +1,7 @@+module Aws.Lambda.Runtime.Result+ ( LambdaResult(..)+ ) where++-- | Wrapper type to handle the result of the user+newtype LambdaResult =+ LambdaResult String
− src/Aws/Lambda/ThHelpers.hs
@@ -1,35 +0,0 @@-module Aws.Lambda.ThHelpers- ( pName- , eName- , recordQ- ) where--import Language.Haskell.TH-import qualified Data.Text as Text-import Data.Text (Text)---- | Helper for defining names in declarations--- think of @myValue@ in @myValue = 2@-pName :: Text -> Q Pat-pName = pure . VarP . mkName . Text.unpack---- | Helper for defining names in expressions--- think of @myFunction@ in @quux = myFunction 3@-eName :: Text -> Q Exp-eName = pure . VarE . mkName . Text.unpack----- | Helper for extracting fields of a specified record--- it expects the constructor name as the first parameter,--- and the list of fields to bring into scope as second--- think of @Person@, and @personAge@, @personName@ in--- @myFunction Person { personAge, personName } = ...@-recordQ :: Text -> [Text] -> Q Pat-recordQ name fields = do- extractedFields <- traverse fName fields- pure $ RecP (mkName $ Text.unpack name) extractedFields- where- -- | Helper for extracting fields of records- -- think of @personAge@ in @myFunction Person { personAge = personAge } = ...@- fName :: Text -> Q FieldPat- fName n = pure (mkName $ Text.unpack n, VarP $ mkName $ Text.unpack n)