packages feed

serverless-haskell 0.4.3 → 0.5.0

raw patch · 5 files changed

+124/−55 lines, 5 filesdep +http-typesdep +iproute

Dependencies added: http-types, iproute

Files

README.md view
@@ -40,13 +40,17 @@    provider:     name: aws-    runtime: nodejs6.10+    runtime: nodejs8.10    functions:     myfunc:       handler: mypackage.myfunc       # Here, mypackage is the Haskell package name and myfunc is the executable-      # name as defined in the Cabal file+      # name as defined in the Cabal file. The handler field may be prefixed+      # with a path of the form `dir1/.../dirn`, relative to `serverless.yml`,+      # which points to the location where the Haskell package `mypackage` is+      # defined. This prefix is not needed when the Stack project is defined at+      # the same level as `serverless.yml`.    plugins:     - serverless-haskell@@ -96,13 +100,18 @@  ### Integration tests -Integration tests are not run automatically due to the need for an AWS account.-To run them manually:+Integration test verifies that the project can build and deploy a complete+function to AWS, and it runs with expected functionality. +Integration test is only automatically run up to deployment due to the need for+an AWS account. To run manually:+ * Ensure you have the required dependencies: `curl`, [jq], [NPM], `pwgen` and   [Stack]. * Get an AWS account and add the access credentials into your shell environment. * Run `./integration-test/run.sh`. The exit code indicates success.+* To verify just the packaging, without deployment, run+  `./integration-test/run.sh --dry-run`.  ## Releasing 
serverless-haskell.cabal view
@@ -1,11 +1,11 @@--- This file has been generated from package.yaml by hpack version 0.20.0.+-- This file has been generated from package.yaml by hpack version 0.28.2. -- -- see: https://github.com/sol/hpack ----- hash: 298ad7fe6395331a112788dc36344fd07fa6c61411aa488fd4f0b128edec9710+-- hash: 6aead2a5ac837e6007c05071a0b459bd55ce9a015ff3fc03b6d176d288dd453a  name:           serverless-haskell-version:        0.4.3+version:        0.5.0 synopsis:       Deploying Haskell code onto AWS Lambda using Serverless description:    Utilities to help process the events from AWS Lambda when deployed with the serverless-haskell plugin. category:       AWS, Cloud, Network@@ -16,7 +16,6 @@ license-file:   LICENSE build-type:     Simple cabal-version:  >= 1.10- extra-source-files:     README.md @@ -25,6 +24,21 @@   location: https://github.com/seek-oss/serverless-haskell  library+  exposed-modules:+      AWSLambda+      AWSLambda.Events+      AWSLambda.Events.APIGateway+      AWSLambda.Events.KinesisEvent+      AWSLambda.Events.Records+      AWSLambda.Events.S3Event+      AWSLambda.Events.SNSEvent+      AWSLambda.Handler+      AWSLambda.Orphans+      Data.Aeson.Alternative+      Data.Aeson.Embedded+      Data.Aeson.TextValue+  other-modules:+      Paths_serverless_haskell   hs-source-dirs:       src   ghc-options: -Wall@@ -38,31 +52,27 @@     , base >=4.7 && <5     , bytestring     , case-insensitive+    , http-types+    , iproute     , lens     , text     , time     , unix     , unordered-containers-  exposed-modules:-      AWSLambda-      AWSLambda.Events-      AWSLambda.Events.APIGateway-      AWSLambda.Events.KinesisEvent-      AWSLambda.Events.Records-      AWSLambda.Events.S3Event-      AWSLambda.Events.SNSEvent-      AWSLambda.Handler-      AWSLambda.Orphans-      Data.Aeson.Alternative-      Data.Aeson.Embedded-      Data.Aeson.TextValue-  other-modules:-      Paths_serverless_haskell   default-language: Haskell2010  test-suite tests   type: exitcode-stdio-1.0   main-is: Spec.hs+  other-modules:+      AWSLambda.Events.APIGatewaySpec+      AWSLambda.Events.KinesisEventSpec+      AWSLambda.Events.S3EventSpec+      AWSLambda.Events.SNSEventSpec+      Data.Aeson.AlternativeSpec+      Data.Aeson.EmbeddedSpec+      Data.Aeson.TestUtil+      Paths_serverless_haskell   hs-source-dirs:       test   ghc-options: -Wall@@ -78,6 +88,8 @@     , case-insensitive     , hspec     , hspec-discover+    , http-types+    , iproute     , lens     , raw-strings-qq     , serverless-haskell@@ -85,13 +97,4 @@     , time     , unix     , unordered-containers-  other-modules:-      AWSLambda.Events.APIGatewaySpec-      AWSLambda.Events.KinesisEventSpec-      AWSLambda.Events.S3EventSpec-      AWSLambda.Events.SNSEventSpec-      Data.Aeson.AlternativeSpec-      Data.Aeson.EmbeddedSpec-      Data.Aeson.TestUtil-      Paths_serverless_haskell   default-language: Haskell2010
src/AWSLambda.hs view
@@ -21,7 +21,7 @@   >   > provider:   >   name: aws-  >   runtime: nodejs6.10+  >   runtime: nodejs8.10   >   > functions:   >   myfunc:
src/AWSLambda/Events/APIGateway.hs view
@@ -11,20 +11,27 @@ -} module AWSLambda.Events.APIGateway where -import           Control.Lens+import           Control.Lens            hiding ((.=)) import           Data.Aeson import           Data.Aeson.Casing       (aesonDrop, camelCase) import           Data.Aeson.TH           (deriveFromJSON) -- import           Data.CaseInsensitive (CI (..)) import           Data.Aeson.Embedded import           Data.Aeson.TextValue+import           Data.Aeson.Types        (Parser) import           Data.ByteString         (ByteString)+import qualified Data.CaseInsensitive    as CI import           Data.HashMap.Strict     (HashMap) import qualified Data.HashMap.Strict     as HashMap+import           Data.IP import           Data.Text               (Text)+import qualified Data.Text               as Text+import           Data.Text.Encoding      (decodeUtf8, encodeUtf8) import           GHC.Generics            (Generic) import           Network.AWS.Data.Base64 import           Network.AWS.Data.Text+import qualified Network.HTTP.Types      as HTTP+import           Text.Read  import           AWSLambda.Handler       (lambdaMain) @@ -45,7 +52,7 @@   , _riCognitoIdentityId             :: !(Maybe Text)   , _riCaller                        :: !(Maybe Text)   , _riApiKey                        :: !(Maybe Text)-  , _riSourceIp                      :: !(Maybe Text)+  , _riSourceIp                      :: !(Maybe IP)   , _riCognitoAuthenticationType     :: !(Maybe Text)   , _riCognitoAuthenticationProvider :: !(Maybe Text)   , _riUserArn                       :: !(Maybe Text)@@ -53,7 +60,25 @@   , _riUser                          :: !(Maybe Text)   } deriving (Eq, Show) -$(deriveFromJSON (aesonDrop 3 camelCase) ''RequestIdentity)+readParse :: Read a => String -> Text -> Parser a+readParse msg str =+  case readMaybe (Text.unpack str) of+    Just result -> pure result+    Nothing     -> fail $ "Failed to parse an " ++ msg++instance FromJSON RequestIdentity where+  parseJSON =+    withObject "RequestIdentity" $ \o ->+      RequestIdentity <$> o .:? "cognitoIdentityPoolId" <*> o .:? "accountId" <*>+      o .:? "cognitoIdentityId" <*>+      o .:? "caller" <*>+      o .:? "apiKey" <*>+      (o .:? "sourceIp" >>= traverse (readParse "IP address")) <*>+      o .:? "cognitoAuthenticationType" <*>+      o .:? "cognitoAuthenticationProvider" <*>+      o .:? "userArn" <*>+      o .:? "userAgent" <*>+      o .:? "user" $(makeLenses ''RequestIdentity)  data ProxyRequestContext = ProxyRequestContext@@ -66,16 +91,17 @@   , _prcResourcePath :: !Text   , _prcHttpMethod   :: !Text   , _prcApiId        :: !Text+  , _prcProtocol     :: !Text   } deriving (Eq, Show) $(deriveFromJSON (aesonDrop 4 camelCase) ''ProxyRequestContext) $(makeLenses ''ProxyRequestContext)  data APIGatewayProxyRequest body = APIGatewayProxyRequest   { _agprqResource              :: !Text-  , _agprqPath                  :: !Text-  , _agprqHttpMethod            :: !Method-  , _agprqHeaders               :: !(HashMap HeaderName HeaderValue)-  , _agprqQueryStringParameters :: !(HashMap QueryParamName QueryParamValue)+  , _agprqPath                  :: !ByteString+  , _agprqHttpMethod            :: !HTTP.Method+  , _agprqHeaders               :: !HTTP.RequestHeaders+  , _agprqQueryStringParameters :: !HTTP.Query   , _agprqPathParameters        :: !(HashMap PathParamName PathParamValue)   , _agprqStageVariables        :: !(HashMap StageVarName StageVarValue)   , _agprqRequestContext        :: !ProxyRequestContext@@ -86,15 +112,25 @@   parseJSON = withObject "APIGatewayProxyRequest" $ \o ->     APIGatewayProxyRequest     <$> o .: "resource"-    <*> o .: "path"-    <*> o .: "httpMethod"-    <*> o .:? "headers" .!= HashMap.empty-    <*> o .:? "queryStringParameters" .!= HashMap.empty+    <*> (encodeUtf8 <$> o .: "path")+    <*> (encodeUtf8 <$> o .: "httpMethod")+    <*> (fmap fromAWSHeaders <$> o .:? "headers") .!= mempty+    <*> (fmap fromAWSQuery <$> o .:? "queryStringParameters") .!= mempty     <*> o .:? "pathParameters" .!= HashMap.empty     <*> o .:? "stageVariables" .!= HashMap.empty     <*> o .: "requestContext"     <*> o .:? "body"-+    where+      -- Explicit type signatures so that we don't accidentally tell Aeson+      -- to try to parse the wrong sort of structure+      fromAWSHeaders :: HashMap HeaderName HeaderValue -> HTTP.RequestHeaders+      fromAWSHeaders = fmap toHeader . HashMap.toList+        where+          toHeader = bimap (CI.mk . encodeUtf8) encodeUtf8+      fromAWSQuery :: HashMap QueryParamName QueryParamValue -> HTTP.Query+      fromAWSQuery = fmap toQueryItem . HashMap.toList+        where+          toQueryItem = bimap encodeUtf8 (\x -> if Text.null x then Nothing else Just . encodeUtf8 $ x)  $(makeLenses ''APIGatewayProxyRequest) @@ -112,20 +148,39 @@  data APIGatewayProxyResponse body = APIGatewayProxyResponse   { _agprsStatusCode :: !Int-  , _agprsHeaders    :: !(HashMap HeaderName HeaderValue)+  , _agprsHeaders    :: !HTTP.ResponseHeaders   , _agprsBody       :: !(Maybe (TextValue body))   } deriving (Eq, Show, Generic)  instance ToText body => ToJSON (APIGatewayProxyResponse body) where-  toJSON = genericToJSON $ aesonDrop 6 camelCase+  toJSON APIGatewayProxyResponse {..} =+    object+      [ "statusCode" .= _agprsStatusCode+      , "headers" .= toAWSHeaders _agprsHeaders+      , "body" .= _agprsBody+      ]+    where+      toAWSHeaders :: HTTP.ResponseHeaders -> HashMap HeaderName HeaderValue+      toAWSHeaders = HashMap.fromList . fmap (bimap (decodeUtf8 . CI.original) decodeUtf8)  instance FromText body => FromJSON (APIGatewayProxyResponse body) where-  parseJSON = genericParseJSON $ aesonDrop 6 camelCase+  parseJSON =+    withObject "APIGatewayProxyResponse" $ \o ->+      APIGatewayProxyResponse <$> o .: "statusCode" <*>+      (fromAWSHeaders <$> o .: "headers") <*>+      o .:? "body"+      -- Explicit type signatures so that we don't accidentally tell Aeson+      -- to try to parse the wrong sort of structure+    where+      fromAWSHeaders :: HashMap HeaderName HeaderValue -> HTTP.RequestHeaders+      fromAWSHeaders = fmap toHeader . HashMap.toList+        where+          toHeader = bimap (CI.mk . encodeUtf8) encodeUtf8  $(makeLenses ''APIGatewayProxyResponse)  response :: Int -> APIGatewayProxyResponse body-response statusCode = APIGatewayProxyResponse statusCode HashMap.empty Nothing+response statusCode = APIGatewayProxyResponse statusCode mempty Nothing  responseOK :: APIGatewayProxyResponse body responseOK = response 200
test/AWSLambda/Events/APIGatewaySpec.hs view
@@ -9,6 +9,7 @@ import           Data.Aeson import           Data.ByteString.Lazy        (ByteString) import qualified Data.HashMap.Strict         as HashMap+import           Data.IP import           Data.Text                   (Text)  import           Text.RawString.QQ@@ -19,10 +20,10 @@ spec =  do   describe "APIGatewayProxyRequest" $     it "parses sample GET request" $-      decode sampleGetRequestJSON `shouldBe` Just sampleGetRequest+      eitherDecode sampleGetRequestJSON `shouldBe` Right sampleGetRequest   describe "APIGatewayProxyResponse" $     it "parses sample text event" $-      decode sampleOKResponseJSON `shouldBe` Just sampleOKResponse+      eitherDecode sampleOKResponseJSON `shouldBe` Right sampleOKResponse  sampleGetRequestJSON :: ByteString sampleGetRequestJSON = [r|@@ -55,6 +56,7 @@     "resourceId": "us4z18",     "stage": "test",     "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9",+    "protocol": "HTTP/1.1",     "identity": {       "cognitoIdentityPoolId": "",       "accountId": "",@@ -90,7 +92,6 @@   , _agprqPath = "/test/hello"   , _agprqHttpMethod = "GET"   , _agprqHeaders =-    HashMap.fromList       [ ("X-Forwarded-Proto", "https")       , ("CloudFront-Is-Desktop-Viewer", "true")       , ( "Accept"@@ -113,7 +114,7 @@         , "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)")       , ("X-Forwarded-For", "192.168.100.1, 192.168.1.1")       ]-  , _agprqQueryStringParameters = HashMap.fromList [("name", "me")]+  , _agprqQueryStringParameters = [("name", Just "me")]   , _agprqPathParameters = HashMap.fromList [("proxy", "hello")]   , _agprqStageVariables = HashMap.fromList [("stageVarName", "stageVarValue")]   , _agprqRequestContext =@@ -130,7 +131,7 @@       , _riCognitoIdentityId = Just ""       , _riCaller = Just ""       , _riApiKey = Just ""-      , _riSourceIp = Just "192.168.100.1"+      , _riSourceIp = Just $ IPv4 $ toIPv4 [192, 168, 100, 1]       , _riCognitoAuthenticationType = Just ""       , _riCognitoAuthenticationProvider = Just ""       , _riUserArn = Just ""@@ -141,6 +142,7 @@     , _prcResourcePath = "/{proxy+}"     , _prcHttpMethod = "GET"     , _prcApiId = "wt6mne2s9k"+    , _prcProtocol = "HTTP/1.1"     }   , _agprqBody = Nothing   }@@ -176,7 +178,7 @@ sampleOKResponse =   responseOK   & responseBody ?~ "Hello World"-  & agprsHeaders .~ HashMap.fromList+  & agprsHeaders .~     [ ("X-Forwarded-Proto", "https")     , ("CloudFront-Is-Desktop-Viewer", "true")     , ( "Accept"