aws-lambda-haskell-runtime 2.0.4 → 2.0.6
raw patch · 13 files changed
+330/−60 lines, 13 filesdep +case-insensitivedep +unordered-containersPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: case-insensitive, unordered-containers
API changes (from Hackage documentation)
- Aws.Lambda.Runtime: LambdaResult :: String -> LambdaResult
- Aws.Lambda.Runtime: newtype LambdaResult
+ Aws.Lambda: ApiGatewayRequest :: !Text -> !Text -> !Text -> !HashMap Text Text -> !Maybe [(Text, Maybe Text)] -> !Maybe (HashMap Text Text) -> !Maybe (HashMap Text Text) -> !Bool -> !ApiGatewayRequestContext -> !Maybe body -> ApiGatewayRequest body
+ Aws.Lambda: ApiGatewayRequestContext :: !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !Text -> !ApiGatewayRequestContextIdentity -> ApiGatewayRequestContext
+ Aws.Lambda: ApiGatewayRequestContextIdentity :: !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Text -> !Maybe Value -> !Maybe Text -> !Maybe Text -> !Maybe Text -> ApiGatewayRequestContextIdentity
+ Aws.Lambda: ApiGatewayResponse :: !Int -> !ResponseHeaders -> !body -> !Bool -> ApiGatewayResponse body
+ Aws.Lambda: [$sel:apiGatewayRequestBody:ApiGatewayRequest] :: ApiGatewayRequest body -> !Maybe body
+ Aws.Lambda: [$sel:apiGatewayRequestContextAccountId:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextApiId:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextDomainName:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextDomainPrefix:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextExtendedRequestId:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextHttpMethod:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentity:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !ApiGatewayRequestContextIdentity
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityAccesskey:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityAccountId:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityCaller:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityCognitoAuthenticationProvider:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Value
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityCognitoAuthenticationType:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityCognitoIdentityId:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityCognitoIdentityPoolId:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityPrincipalOrgId:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentitySourceIp:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityUser:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityUserAgent:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextIdentityUserArn:ApiGatewayRequestContextIdentity] :: ApiGatewayRequestContextIdentity -> !Maybe Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextPath:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextProtocol:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextRequestId:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextRequestTime:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextResourceId:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextResourcePath:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestContextStage:ApiGatewayRequestContext] :: ApiGatewayRequestContext -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestHeaders:ApiGatewayRequest] :: ApiGatewayRequest body -> !HashMap Text Text
+ Aws.Lambda: [$sel:apiGatewayRequestHttpMethod:ApiGatewayRequest] :: ApiGatewayRequest body -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestIsBase64Encoded:ApiGatewayRequest] :: ApiGatewayRequest body -> !Bool
+ Aws.Lambda: [$sel:apiGatewayRequestPath:ApiGatewayRequest] :: ApiGatewayRequest body -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestPathParameters:ApiGatewayRequest] :: ApiGatewayRequest body -> !Maybe (HashMap Text Text)
+ Aws.Lambda: [$sel:apiGatewayRequestQueryStringParameters:ApiGatewayRequest] :: ApiGatewayRequest body -> !Maybe [(Text, Maybe Text)]
+ Aws.Lambda: [$sel:apiGatewayRequestRequestContext:ApiGatewayRequest] :: ApiGatewayRequest body -> !ApiGatewayRequestContext
+ Aws.Lambda: [$sel:apiGatewayRequestResource:ApiGatewayRequest] :: ApiGatewayRequest body -> !Text
+ Aws.Lambda: [$sel:apiGatewayRequestStageVariables:ApiGatewayRequest] :: ApiGatewayRequest body -> !Maybe (HashMap Text Text)
+ Aws.Lambda: [$sel:apiGatewayResponseBody:ApiGatewayResponse] :: ApiGatewayResponse body -> !body
+ Aws.Lambda: [$sel:apiGatewayResponseHeaders:ApiGatewayResponse] :: ApiGatewayResponse body -> !ResponseHeaders
+ Aws.Lambda: [$sel:apiGatewayResponseIsBase64Encoded:ApiGatewayResponse] :: ApiGatewayResponse body -> !Bool
+ Aws.Lambda: [$sel:apiGatewayResponseStatusCode:ApiGatewayResponse] :: ApiGatewayResponse body -> !Int
+ Aws.Lambda: data ApiGatewayRequest body
+ Aws.Lambda: data ApiGatewayRequestContext
+ Aws.Lambda: data ApiGatewayRequestContextIdentity
+ Aws.Lambda: data ApiGatewayResponse body
+ Aws.Lambda: mkApiGatewayResponse :: Int -> payload -> ApiGatewayResponse payload
+ Aws.Lambda.Runtime: ApiGatewayDispatcherOptions :: Bool -> ApiGatewayDispatcherOptions
+ Aws.Lambda.Runtime: ApiGatewayResult :: ApiGatewayResponse Value -> LambdaResult
+ Aws.Lambda.Runtime: DispatcherOptions :: ApiGatewayDispatcherOptions -> DispatcherOptions
+ Aws.Lambda.Runtime: Parsing :: String -> String -> String -> Parsing
+ Aws.Lambda.Runtime: StandaloneLambda :: DispatcherStrategy
+ Aws.Lambda.Runtime: StandaloneLambdaResult :: String -> LambdaResult
+ Aws.Lambda.Runtime: UseWithAPIGateway :: DispatcherStrategy
+ Aws.Lambda.Runtime: [actualValue] :: Parsing -> String
+ Aws.Lambda.Runtime: [apiGatewayDispatcherOptions] :: DispatcherOptions -> ApiGatewayDispatcherOptions
+ Aws.Lambda.Runtime: [errorMessage] :: Parsing -> String
+ Aws.Lambda.Runtime: [propagateImpureExceptions] :: ApiGatewayDispatcherOptions -> Bool
+ Aws.Lambda.Runtime: [valueName] :: Parsing -> String
+ Aws.Lambda.Runtime: data DispatcherStrategy
+ Aws.Lambda.Runtime: data LambdaResult
+ Aws.Lambda.Runtime: data Parsing
+ Aws.Lambda.Runtime: defaultDispatcherOptions :: DispatcherOptions
+ Aws.Lambda.Runtime: newtype ApiGatewayDispatcherOptions
+ Aws.Lambda.Runtime: newtype DispatcherOptions
- Aws.Lambda: decodeObj :: FromJSON a => String -> a
+ Aws.Lambda: decodeObj :: forall a. (FromJSON a, Typeable a) => String -> Either Parsing a
- Aws.Lambda: generateLambdaDispatcher :: DecsQ
+ Aws.Lambda: generateLambdaDispatcher :: DispatcherStrategy -> DispatcherOptions -> DecsQ
Files
- aws-lambda-haskell-runtime.cabal +7/−4
- src/Aws/Lambda.hs +1/−0
- src/Aws/Lambda/Configuration.hs +3/−3
- src/Aws/Lambda/Meta/Dispatch.hs +71/−21
- src/Aws/Lambda/Meta/Main.hs +5/−1
- src/Aws/Lambda/Meta/Run.hs +4/−3
- src/Aws/Lambda/Runtime.hs +20/−7
- src/Aws/Lambda/Runtime/ApiGatewayInfo.hs +153/−0
- src/Aws/Lambda/Runtime/ApiInfo.hs +3/−3
- src/Aws/Lambda/Runtime/Common.hs +42/−5
- src/Aws/Lambda/Runtime/Environment.hs +1/−1
- src/Aws/Lambda/Runtime/Error.hs +6/−5
- src/Aws/Lambda/Runtime/Publish.hs +14/−7
aws-lambda-haskell-runtime.cabal view
@@ -1,13 +1,13 @@-cabal-version: 1.12 +cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: bb9ab608e3081e6e7eb875e81698026b23343c4e3c64e5ab89e4df76aeb280f3+-- hash: eab5b338b49db7854613ecd43b17245f105c8c936c40230ecf9d08c5dfad9d6d name: aws-lambda-haskell-runtime-version: 2.0.4+version: 2.0.6 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@@ -39,6 +39,7 @@ Aws.Lambda.Meta.Run Aws.Lambda.Runtime.API.Endpoints Aws.Lambda.Runtime.API.Version+ Aws.Lambda.Runtime.ApiGatewayInfo Aws.Lambda.Runtime.ApiInfo Aws.Lambda.Runtime.Common Aws.Lambda.Runtime.Context@@ -54,6 +55,7 @@ aeson , base >=4.7 && <5 , bytestring+ , case-insensitive , http-client , http-types , path >0.7@@ -61,6 +63,7 @@ , safe-exceptions-checked , template-haskell , text+ , unordered-containers default-language: Haskell2010 test-suite aws-lambda-haskell-runtime-test
src/Aws/Lambda.hs view
@@ -5,3 +5,4 @@ import Aws.Lambda.Configuration as Reexported import Aws.Lambda.Runtime as Reexported import Aws.Lambda.Runtime.Context as Reexported+import Aws.Lambda.Runtime.ApiGatewayInfo as Reexported
src/Aws/Lambda/Configuration.hs view
@@ -15,8 +15,8 @@ {-| Generates a @main@ function that acts as a dispatcher -}-generateLambdaDispatcher :: Meta.DecsQ-generateLambdaDispatcher = do+generateLambdaDispatcher :: Main.DispatcherStrategy -> Main.DispatcherOptions -> Meta.DecsQ+generateLambdaDispatcher strategy options = do main <- Main.generate- run <- Run.generate+ run <- Run.generate options strategy return (main <> [run])
src/Aws/Lambda/Meta/Dispatch.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE BangPatterns #-}+ {-| Dispatcher generation -} module Aws.Lambda.Meta.Dispatch ( generate@@ -17,16 +20,22 @@ import Aws.Lambda.Meta.Common import qualified Aws.Lambda.Runtime.Common as Runtime+import qualified Aws.Lambda.Runtime.Error as Error+import qualified Aws.Lambda.Runtime.ApiGatewayInfo as ApiGatewayInfo+import qualified Control.Exception as Unchecked+import qualified Aws.Lambda.Meta.Main as Main+import Data.Typeable (Typeable, typeRep, Proxy (..)) {-| 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 :: forall a. (FromJSON a, Typeable a) => String -> Either Error.Parsing a decodeObj x =+ let objName = show (typeRep (Proxy :: Proxy a)) in case (eitherDecode $ LazyByteString.pack x) of- Left e -> error e- Right v -> v+ Left e -> Left $ Error.Parsing e x objName+ Right v -> return v {-| Helper function that the dispatcher will use to decode the JSON that comes as an AWS Lambda event into the@@ -43,31 +52,72 @@ 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+generate :: Main.DispatcherOptions -> Main.DispatcherStrategy -> [Text] -> Meta.ExpQ+generate options strategy handlerNames = do caseExp <- expressionName "functionHandler"- matches <- traverse handlerCase handlerNames- unmatched <- unmatchedCase- pure $ Meta.CaseE caseExp (matches <> [unmatched])+ case strategy of+ Main.StandaloneLambda -> do+ matches <- traverse standaloneLambdaHandlerCase handlerNames+ unmatched <- standaloneLambdaUnmatchedCase+ pure $ Meta.CaseE caseExp (matches <> [unmatched])+ Main.UseWithAPIGateway -> do+ matches <- traverse (apiGatewayHandlerCase options) handlerNames+ unmatched <- apiGatewayUnmatchedCase+ pure $ Meta.CaseE caseExp (matches <> [unmatched]) -handlerCase :: Text -> Meta.MatchQ-handlerCase lambdaHandler = do+standaloneLambdaHandlerCase :: Text -> Meta.MatchQ+standaloneLambdaHandlerCase lambdaHandler = do let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler) body <- [e|do- result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject"))- either (pure . Left . encodeObj) (pure . Right . $(constructorName "LambdaResult") . encodeObj) result |]+ case decodeObj $(expressionName "eventObject") of+ Right eventObject -> case decodeObj $(expressionName "contextObject") of+ Right context -> (do+ result <- $(expressionName (qualifiedHandlerName lambdaHandler)) eventObject context+ either (pure . Left . Runtime.StandaloneLambdaError . encodeObj) (pure . Right . Runtime.StandaloneLambdaResult . encodeObj) result)+ `Unchecked.catch` \(handlerError :: Unchecked.SomeException) -> pure . Left . Runtime.StandaloneLambdaError . encodeObj . show $ handlerError+ Left err -> pure . Left . Runtime.StandaloneLambdaError . encodeObj $ err+ Left err -> pure . Left . Runtime.StandaloneLambdaError . encodeObj $ err|] pure $ Meta.Match pat (Meta.NormalB body) []- where- qualifiedName =- lambdaHandler- & Text.splitOn "/"- & filter (Char.isUpper . Text.head)- & Text.intercalate "." -unmatchedCase :: Meta.MatchQ-unmatchedCase = do+standaloneLambdaUnmatchedCase :: Meta.MatchQ+standaloneLambdaUnmatchedCase = do let pattern = Meta.WildP body <- [e|- pure $ Left ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")+ pure . Left . Runtime.StandaloneLambdaError . encodeObj $ ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project") |] pure $ Meta.Match pattern (Meta.NormalB body) []++apiGatewayHandlerCase :: Main.DispatcherOptions -> Text -> Meta.MatchQ+apiGatewayHandlerCase options lambdaHandler = do+ let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler)+ body <- [e|do+ let returnErr statusCode = pure . Left . Runtime.ApiGatewayLambdaError . ApiGatewayInfo.mkApiGatewayResponse statusCode+ case decodeObj $(expressionName "eventObject") of+ Right eventObject -> case decodeObj $(expressionName "contextObject") of+ Right context -> do+ resultE <- Unchecked.try $ $(expressionName (qualifiedHandlerName lambdaHandler)) eventObject context+ case resultE of+ Right result ->+ either (pure . Left . Runtime.ApiGatewayLambdaError . fmap toJSON) (pure . Right . Runtime.ApiGatewayResult . fmap toJSON) result+ Left (handlerError :: Unchecked.SomeException) ->+ if (Runtime.propagateImpureExceptions . Runtime.apiGatewayDispatcherOptions $ options)+ then returnErr 500 . toJSON . show $ handlerError+ else returnErr 500 . toJSON $ "Something went wrong."+ Left err -> returnErr 500 . toJSON $ err+ Left err -> returnErr 400 . toJSON $ err|]+ pure $ Meta.Match pat (Meta.NormalB body) []++apiGatewayUnmatchedCase :: Meta.MatchQ+apiGatewayUnmatchedCase = do+ let pattern = Meta.WildP+ body <- [e|+ pure . Left . Runtime.ApiGatewayLambdaError . ApiGatewayInfo.mkApiGatewayResponse 500 . toJSON $ ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project")+ |]+ pure $ Meta.Match pattern (Meta.NormalB body) []++qualifiedHandlerName :: Text -> Text+qualifiedHandlerName lambdaHandler =+ lambdaHandler+ & Text.splitOn "/"+ & filter (Char.isUpper . Text.head)+ & Text.intercalate "."
src/Aws/Lambda/Meta/Main.hs view
@@ -1,6 +1,10 @@ {-| main function generation for interoperation with the layer -} module Aws.Lambda.Meta.Main ( Runtime.LambdaOptions(..)+ , Runtime.DispatcherStrategy(..)+ , Runtime.DispatcherOptions(..)+ , Runtime.ApiGatewayDispatcherOptions(..)+ , Runtime.defaultDispatcherOptions , generate ) where @@ -16,6 +20,6 @@ |] where directCallBody =- [e|do+ [e| runLambda run |]
src/Aws/Lambda/Meta/Run.hs view
@@ -7,10 +7,11 @@ import Aws.Lambda.Meta.Common import qualified Aws.Lambda.Meta.Discover as Discover import qualified Aws.Lambda.Meta.Dispatch as Dispatch+import qualified Aws.Lambda.Meta.Main as Main -generate :: Meta.DecQ-generate = do+generate :: Main.DispatcherOptions -> Main.DispatcherStrategy -> Meta.DecQ+generate options strategy = do handlers <- Meta.runIO Discover.handlers clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"]- body <- Dispatch.generate handlers+ body <- Dispatch.generate options strategy handlers pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []]
src/Aws/Lambda/Runtime.hs view
@@ -1,9 +1,15 @@ module Aws.Lambda.Runtime ( runLambda , Runtime.LambdaResult(..)+ , Runtime.DispatcherStrategy(..)+ , Runtime.DispatcherOptions(..)+ , Runtime.ApiGatewayDispatcherOptions(..)+ , Runtime.defaultDispatcherOptions+ , Error.Parsing(..) ) where import Control.Exception.Safe.Checked+import qualified Control.Exception.Safe.Checked as Checked import Control.Monad (forever) import System.IO (hFlush, stdout, stderr) import qualified Network.HTTP.Client as Http@@ -17,6 +23,7 @@ import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error import qualified Aws.Lambda.Runtime.Publish as Publish+import qualified Control.Exception as Unchecked -- | Runs the user @haskell_lambda@ executable and posts back the -- results. This is called from the layer's @main@ function.@@ -27,11 +34,13 @@ lambdaApi <- Environment.apiEndpoint `catch` variableNotSet event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing context <- Context.initialize event `catch` errorParsing `catch` variableNotSet- ((invokeAndRun callback manager lambdaApi event context- `catch` \err -> Publish.parsingError err lambdaApi context manager)- `catch` \err -> Publish.invocationError err lambdaApi context manager)- `catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager + (((invokeAndRun callback manager lambdaApi event context+ `Checked.catch` \err -> Publish.parsingError err lambdaApi context manager)+ `Checked.catch` \err -> Publish.invocationError err lambdaApi context manager)+ `Checked.catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager)+ `Unchecked.catch` \err -> Publish.invocationError err lambdaApi context manager+ httpManagerSettings :: Http.ManagerSettings httpManagerSettings = -- We set the timeout to none, as AWS Lambda freezes the containers.@@ -49,7 +58,8 @@ -> Context.Context -> IO () invokeAndRun callback manager lambdaApi event context = do- result <- invokeWithCallback callback event context+ result <- invokeWithCallback callback event context+ Publish.result result lambdaApi context manager `catch` \err -> Publish.invocationError err lambdaApi context manager @@ -72,8 +82,11 @@ -- Flush output to insure output goes into CloudWatch logs flushOutput case result of- Left err ->- throw $ Error.Invocation err+ Left lambdaError -> case lambdaError of+ Runtime.StandaloneLambdaError err ->+ throw $ Error.Invocation $ toJSON err+ Runtime.ApiGatewayLambdaError err ->+ throw $ Error.Invocation $ toJSON err Right value -> pure value
+ src/Aws/Lambda/Runtime/ApiGatewayInfo.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE DuplicateRecordFields #-}++module Aws.Lambda.Runtime.ApiGatewayInfo+ ( ApiGatewayRequest(..)+ , ApiGatewayRequestContext(..)+ , ApiGatewayRequestContextIdentity(..)+ , ApiGatewayResponse(..)+ , mkApiGatewayResponse ) where++import Data.Aeson+import Data.HashMap.Strict (HashMap)+import Data.Text (Text)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import GHC.Generics (Generic)+import Network.HTTP.Types+import qualified Data.CaseInsensitive as CI+import qualified Data.Aeson.Types as T+import qualified Data.ByteString.Lazy.Char8 as LazyByteString+import Data.Aeson.Types (Parser)++data ApiGatewayRequest body = ApiGatewayRequest+ { apiGatewayRequestResource :: !Text+ , apiGatewayRequestPath :: !Text+ , apiGatewayRequestHttpMethod :: !Text+ , apiGatewayRequestHeaders :: !(HashMap Text Text)+ , apiGatewayRequestQueryStringParameters :: !(Maybe [(Text, Maybe Text)])+ , apiGatewayRequestPathParameters :: !(Maybe (HashMap Text Text))+ , apiGatewayRequestStageVariables :: !(Maybe (HashMap Text Text))+ , apiGatewayRequestIsBase64Encoded :: !Bool+ , apiGatewayRequestRequestContext :: !ApiGatewayRequestContext+ , apiGatewayRequestBody :: !(Maybe body)+ } deriving (Show)++instance FromJSON body => FromJSON (ApiGatewayRequest body) where+ parseJSON (Object v) = ApiGatewayRequest <$>+ v .: "resource" <*>+ v .: "path" <*>+ v .: "httpMethod" <*>+ v .: "headers" <*>+ v .: "queryStringParameters" <*>+ v .: "pathParameters" <*>+ v .: "stageVariables" <*>+ v .: "isBase64Encoded" <*>+ v .: "requestContext" <*>+ v `parseObjectFromStringField` "body"+ parseJSON _ = fail "Expected ApiGatewayRequest to be an object."++data ApiGatewayRequestContext = ApiGatewayRequestContext+ { apiGatewayRequestContextResourceId :: !Text+ , apiGatewayRequestContextResourcePath :: !Text+ , apiGatewayRequestContextHttpMethod :: !Text+ , apiGatewayRequestContextExtendedRequestId :: !Text+ , apiGatewayRequestContextRequestTime :: !Text+ , apiGatewayRequestContextPath :: !Text+ , apiGatewayRequestContextAccountId :: !Text+ , apiGatewayRequestContextProtocol :: !Text+ , apiGatewayRequestContextStage :: !Text+ , apiGatewayRequestContextDomainPrefix :: !Text+ , apiGatewayRequestContextRequestId :: !Text+ , apiGatewayRequestContextDomainName :: !Text+ , apiGatewayRequestContextApiId :: !Text+ , apiGatewayRequestContextIdentity :: !ApiGatewayRequestContextIdentity+ } deriving (Show)++instance FromJSON ApiGatewayRequestContext where+ parseJSON (Object v) = ApiGatewayRequestContext <$>+ v .: "resourceId" <*>+ v .: "path" <*>+ v .: "httpMethod" <*>+ v .: "extendedRequestId" <*>+ v .: "requestTime" <*>+ v .: "path" <*>+ v .: "accountId" <*>+ v .: "protocol" <*>+ v .: "stage" <*>+ v .: "domainPrefix" <*>+ v .: "requestId" <*>+ v .: "domainName" <*>+ v .: "apiId" <*>+ v .: "identity"+ parseJSON _ = fail "Expected ApiGatewayRequestContext to be an object."++data ApiGatewayRequestContextIdentity = ApiGatewayRequestContextIdentity+ { apiGatewayRequestContextIdentityCognitoIdentityPoolId :: !(Maybe Text)+ , apiGatewayRequestContextIdentityAccountId :: !(Maybe Text)+ , apiGatewayRequestContextIdentityCognitoIdentityId :: !(Maybe Text)+ , apiGatewayRequestContextIdentityCaller :: !(Maybe Text)+ , apiGatewayRequestContextIdentitySourceIp :: !(Maybe Text)+ , apiGatewayRequestContextIdentityPrincipalOrgId :: !(Maybe Text)+ , apiGatewayRequestContextIdentityAccesskey :: !(Maybe Text)+ , apiGatewayRequestContextIdentityCognitoAuthenticationType :: !(Maybe Text)+ , apiGatewayRequestContextIdentityCognitoAuthenticationProvider :: !(Maybe Value)+ , apiGatewayRequestContextIdentityUserArn :: !(Maybe Text)+ , apiGatewayRequestContextIdentityUserAgent :: !(Maybe Text)+ , apiGatewayRequestContextIdentityUser :: !(Maybe Text)+ } deriving (Show)++instance FromJSON ApiGatewayRequestContextIdentity where+ parseJSON (Object v) = ApiGatewayRequestContextIdentity <$>+ v .: "cognitoIdentityPoolId" <*>+ v .: "accountId" <*>+ v .: "cognitoIdentityId" <*>+ v .: "caller" <*>+ v .: "sourceIp" <*>+ v .: "principalOrgId" <*>+ v .: "accessKey" <*>+ v .: "cognitoAuthenticationType" <*>+ v .: "cognitoAuthenticationProvider" <*>+ v .: "userArn" <*>+ v .: "userAgent" <*>+ v .: "user"+ parseJSON _ = fail "Expected ApiGatewayRequestContextIdentity to be an object."++-- We need this because API Gateway is going to send us the payload as a JSON string+parseObjectFromStringField :: FromJSON a => Object -> Text -> Parser (Maybe a)+parseObjectFromStringField obj fieldName = do+ fieldContents <- obj .: fieldName+ case fieldContents of+ String stringContents ->+ case eitherDecodeStrict (T.encodeUtf8 stringContents) of+ Right success -> pure success+ Left err -> fail err+ Null -> pure Nothing+ other -> T.typeMismatch "String or Null" other++data ApiGatewayResponse body = ApiGatewayResponse+ { apiGatewayResponseStatusCode :: !Int+ , apiGatewayResponseHeaders :: !ResponseHeaders+ , apiGatewayResponseBody :: !body+ , apiGatewayResponseIsBase64Encoded :: !Bool+ } deriving (Generic, Show)++instance Functor ApiGatewayResponse where+ fmap f v = v { apiGatewayResponseBody = f (apiGatewayResponseBody v) }++instance ToJSON body => ToJSON (ApiGatewayResponse body) where+ toJSON ApiGatewayResponse {..} = object+ [ "statusCode" .= apiGatewayResponseStatusCode+ , "body" .= (String . T.pack . LazyByteString.unpack . encode @body $ apiGatewayResponseBody)+ , "headers" .= object (map headerToPair apiGatewayResponseHeaders)+ , "isBase64Encoded" .= apiGatewayResponseIsBase64Encoded+ ]++mkApiGatewayResponse :: Int -> payload -> ApiGatewayResponse payload+mkApiGatewayResponse code payload =+ ApiGatewayResponse code [] payload False++headerToPair :: Header -> T.Pair+headerToPair (cibyte, bstr) = k .= v+ where+ k = (T.decodeUtf8 . CI.original) cibyte+ v = T.decodeUtf8 bstr
src/Aws/Lambda/Runtime/ApiInfo.hs view
@@ -24,14 +24,14 @@ , awsRequestId :: !String , invokedFunctionArn :: !String , event :: !Lazy.ByteString- }+ } deriving (Show) -- | 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+ headers = Http.responseHeaders response Monad.foldM reduceEvent (initialEvent body) headers fetchApiData :: Http.Manager -> String -> IO (Http.Response Lazy.ByteString)@@ -46,7 +46,7 @@ ("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)+ Nothing -> throw (Error.Parsing "Could not parse deadlineMs." (ByteString.unpack value) "deadlineMs") ("Lambda-Runtime-Trace-Id", value) -> pure event { traceId = ByteString.unpack value }
src/Aws/Lambda/Runtime/Common.hs view
@@ -1,15 +1,56 @@+{-# LANGUAGE DeriveLift #-}+ module Aws.Lambda.Runtime.Common ( RunCallback , LambdaResult(..)+ , LambdaError(..) , LambdaOptions(..)+ , DispatcherOptions(..)+ , ApiGatewayDispatcherOptions(..)+ , DispatcherStrategy(..)+ , defaultDispatcherOptions ) where +import Aws.Lambda.Runtime.ApiGatewayInfo+import Data.Aeson (Value) import GHC.Generics (Generic)+import Language.Haskell.TH.Syntax (Lift) +-- | API Gateway specific dispatcher options+newtype ApiGatewayDispatcherOptions = ApiGatewayDispatcherOptions+ { propagateImpureExceptions :: Bool+ -- ^ Should impure exceptions be propagated through the API Gateway interface+ } deriving (Lift)++-- | Options that the dispatcher generator expects+newtype DispatcherOptions = DispatcherOptions+ { apiGatewayDispatcherOptions :: ApiGatewayDispatcherOptions+ } deriving (Lift)++defaultDispatcherOptions :: DispatcherOptions+defaultDispatcherOptions =+ DispatcherOptions (ApiGatewayDispatcherOptions True)++-- | A strategy on how to generate the dispatcher functions+data DispatcherStrategy =+ UseWithAPIGateway |+ StandaloneLambda+ deriving (Lift)+ -- | Callback that we pass to the dispatcher function type RunCallback =- LambdaOptions -> IO (Either String LambdaResult)+ LambdaOptions -> IO (Either LambdaError LambdaResult) +-- | Wrapper type for lambda execution results+data LambdaError =+ StandaloneLambdaError String |+ ApiGatewayLambdaError (ApiGatewayResponse Value)++-- | Wrapper type to handle the result of the user+data LambdaResult =+ StandaloneLambdaResult String |+ ApiGatewayResult (ApiGatewayResponse Value)+ -- | Options that the generated main expects data LambdaOptions = LambdaOptions { eventObject :: !String@@ -17,7 +58,3 @@ , functionHandler :: !String , executionUuid :: !String } deriving (Generic)---- | Wrapper type to handle the result of the user-newtype LambdaResult =- LambdaResult String
src/Aws/Lambda/Runtime/Environment.hs view
@@ -55,7 +55,7 @@ memoryValue <- readEnvironmentVariable envVar case Read.readMaybe memoryValue of Just value -> pure value- Nothing -> throw (Error.Parsing envVar memoryValue)+ Nothing -> throw (Error.Parsing envVar memoryValue envVar) readEnvironmentVariable :: Throws Error.EnvironmentVariableNotSet => String -> IO String readEnvironmentVariable envVar = do
src/Aws/Lambda/Runtime/Error.hs view
@@ -7,7 +7,7 @@ ) where import Control.Exception.Safe.Checked-import Data.Aeson (ToJSON (..), object, (.=))+import Data.Aeson (ToJSON (..), object, (.=), Value) newtype EnvironmentVariableNotSet = EnvironmentVariableNotSet String@@ -22,18 +22,19 @@ data Parsing = Parsing { errorMessage :: String , actualValue :: String+ , valueName :: String } deriving (Show, Exception) instance ToJSON Parsing where- toJSON (Parsing objectBeingParsed value) = object+ toJSON (Parsing errorMessage _ valueName) = object [ "errorType" .= ("Parsing" :: String)- , "errorMessage" .= ("Parse error for " <> objectBeingParsed <> ", could not parse value '" <> value <> "'")+ , "errorMessage" .= ("Could not parse '" <> valueName <> "': " <> errorMessage) ] newtype Invocation =- Invocation String+ Invocation Value deriving (Show, Exception) instance ToJSON Invocation where -- We return the user error as it is- toJSON (Invocation err) = toJSON err+ toJSON (Invocation err) = err
src/Aws/Lambda/Runtime/Publish.hs view
@@ -19,20 +19,24 @@ -- | Publishes the result back to AWS Lambda result :: LambdaResult -> String -> Context -> Http.Manager -> IO ()-result (LambdaResult res) lambdaApi context manager = do+result lambdaResult lambdaApi context manager = do let Endpoints.Endpoint endpoint = Endpoints.response lambdaApi (awsRequestId context) rawRequest <- Http.parseRequest endpoint- let request = rawRequest++ let requestBody = case lambdaResult of+ (StandaloneLambdaResult res) -> Http.RequestBodyBS (ByteString.pack res)+ (ApiGatewayResult res) -> Http.RequestBodyLBS (encode res)+ request = rawRequest { Http.method = "POST"- , Http.requestBody = Http.RequestBodyBS (ByteString.pack res)+ , Http.requestBody = requestBody }+ 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+ publish err (Endpoints.invocationError lambdaApi $ awsRequestId context) context -- | Publishes a parsing error back to AWS Lambda parsingError :: Error.Parsing -> String -> Context -> Http.Manager -> IO ()@@ -48,8 +52,11 @@ 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++ let requestBody = Http.RequestBodyLBS (encode err)+ request = rawRequest { Http.method = "POST"- , Http.requestBody = Http.RequestBodyLBS (encode err)+ , Http.requestBody = requestBody }+ void $ Http.httpNoBody request manager