packages feed

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 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)