serverless-haskell 0.0.6 → 0.1.0
raw patch · 5 files changed
+183/−14 lines, 5 files
Files
- serverless-haskell.cabal +3/−2
- src/AWSLambda/Events.hs +29/−4
- src/AWSLambda/Events/Embedded.hs +24/−0
- src/AWSLambda/Events/SNSEvent.hs +11/−5
- test/AWSLambda/Events/SNSEventSpec.hs +116/−3
serverless-haskell.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 8744fabb134bae654aeb0568028f9f99e96f2f7feeb7526d6c85b95685022f8e+-- hash: 9f8f42c39c47eb860db76ff109b447b37326ae914e1e88eaccbcb7f6734e00e8 name: serverless-haskell-version: 0.0.6+version: 0.1.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@@ -44,6 +44,7 @@ exposed-modules: AWSLambda AWSLambda.Events+ AWSLambda.Events.Embedded AWSLambda.Events.KinesisEvent AWSLambda.Events.Records AWSLambda.Events.S3Event
src/AWSLambda/Events.hs view
@@ -10,41 +10,59 @@ import AWSLambda.Events.S3Event import AWSLambda.Events.SNSEvent +-- | Not yet implemented data DynamoDBEvent +-- | Not yet implemented data SESEvent +-- | Not yet implemented data CognitoEvent +-- | Not yet implemented data CloudFormationEvent +-- | Not yet implemented data CloudWatchLogsEvent +-- | Not yet implemented data CloudWatchEventsEvent +-- | Not yet implemented data CodeCommitEvent +-- | Not yet implemented data ConfigEvent +-- | Not yet implemented data AlexaEvent +-- | Not yet implemented data LexEvent +-- | Not yet implemented data APIGatewayEvent +-- | Not yet implemented data IoTButtonEvent +-- | Not yet implemented data CloudFrontEvent +-- | Not yet implemented data FirehoseEvent +-- | Not yet implemented data InvokeEvent -data LambdaEvent+-- | Sum type for all possible Lambda events.+-- Parameterised on the type of SNS Events to be handled.+-- See @SNSEvent@ for details.+data LambdaEvent snsMessage = S3 !S3Event | DynamoDB !DynamoDBEvent | KinesisStream !KinesisEvent- | SNS !SNSEvent+ | SNS !(SNSEvent snsMessage) | SES !SESEvent | Cognito !CognitoEvent | CloudFormation !CloudFormationEvent@@ -61,7 +79,14 @@ | Invoke !InvokeEvent | Custom !Value -instance FromJSON LambdaEvent where- parseJSON v = S3 <$> parseJSON v <|> pure (Custom v)+-- | Attempt to parse the various event types.+-- Any valid JSON that can't be parsed as a specific+-- event type will result in a 'Custom' value.+instance FromJSON snsMessage =>+ FromJSON (LambdaEvent snsMessage) where+ parseJSON v =+ try S3 v <|> try KinesisStream v <|> try SNS v <|> pure (Custom v)+ where+ try f = fmap f . parseJSON $(makePrisms ''LambdaEvent)
+ src/AWSLambda/Events/Embedded.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}++{-|+Module: AWSLambda.Events.Embedded+Description: Type for a JSON value embedded within a JSON string value+-}+module AWSLambda.Events.Embedded where++import Control.Lens.TH+import Data.Aeson+import Data.Text.Encoding (encodeUtf8)++-- | Type for a JSON value embedded within a JSON string value+newtype Embedded a = Embedded { _unEmbed :: a } deriving (Eq, Show)++instance FromJSON a =>+ FromJSON (Embedded a) where+ parseJSON =+ withText "Embedded JSON" $+ fmap Embedded . either fail pure . eitherDecodeStrict . encodeUtf8++$(makeLenses ''Embedded)
src/AWSLambda/Events/SNSEvent.hs view
@@ -27,8 +27,8 @@ $(deriveFromJSON (aesonDrop 3 pascalCase) ''MessageAttribute) $(makeLenses ''MessageAttribute) -data SNSMessage = SNSMessage- { _smMessage :: !Text+data SNSMessage message = SNSMessage+ { _smMessage :: !message , _smMessageAttributes :: !(HashMap Text MessageAttribute) , _smMessageId :: !Text , _smSignature :: !Text@@ -44,14 +44,20 @@ $(deriveFromJSON (aesonDrop 3 pascalCase) ''SNSMessage) $(makeLenses ''SNSMessage) -data SNSRecord = SNSRecord+data SNSRecord message = SNSRecord { _srEventVersion :: !Text , _srEventSubscriptionArn :: !Text , _srEventSource :: !Text- , _srSns :: !SNSMessage+ , _srSns :: !(SNSMessage message) } deriving (Eq, Show) $(deriveFromJSON (aesonDrop 3 pascalCase) ''SNSRecord) $(makeLenses ''SNSRecord) -type SNSEvent = RecordsEvent SNSRecord+-- | SNSEvent.+-- The 'message' type is parameterised. To treat it as a text value+-- use @SNSEvent Text@.+-- To extract an embedded event object use the 'Embedded' type.+-- E.g. @SNSEvent (Embedded S3Event)@ will treat the message+-- as an embedded S3Event.+type SNSEvent message = RecordsEvent (SNSRecord message)
test/AWSLambda/Events/SNSEventSpec.hs view
@@ -3,14 +3,19 @@ module AWSLambda.Events.SNSEventSpec where +import AWSLambda.Events.Embedded import AWSLambda.Events.Records+import AWSLambda.Events.S3Event import AWSLambda.Events.SNSEvent import Data.Aeson import Data.ByteString.Lazy (ByteString) import qualified Data.HashMap.Strict as HashMap+import Data.Text (Text) import Data.Time.Calendar import Data.Time.Clock+import Network.AWS.S3 as S3+import Network.AWS.Types as AWS import Text.RawString.QQ @@ -18,9 +23,11 @@ spec :: Spec spec =- describe "SNSEvent" $- it "parses sample event" $+ describe "SNSEvent" $ do+ it "parses sample text event" $ decode sampleSNSJSON `shouldBe` Just sampleSNSEvent+ it "parses sample embedded S3 event" $+ decode sampleSNSS3JSON `shouldBe` Just sampleSNSS3Event sampleSNSJSON :: ByteString sampleSNSJSON = [r|@@ -57,7 +64,7 @@ } |] -sampleSNSEvent :: SNSEvent+sampleSNSEvent :: SNSEvent Text sampleSNSEvent = RecordsEvent [ SNSRecord@@ -92,3 +99,109 @@ } } ]++sampleSNSS3JSON :: ByteString+sampleSNSS3JSON = [r|+{"Records":+ [+ {"EventSource":"aws:sns",+ "EventVersion":"1.0",+ "EventSubscriptionArn":"arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent:23e2d254-e8bb-4db5-92ce-d917e5aad090",+ "Sns":{+ "Type":"Notification",+ "MessageId":"89f6fe8b-a751-5dcd-8e0c-afbd75420455",+ "TopicArn":"arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent",+ "Subject":"Amazon S3 Notification",+ "Message": "{\"Records\":[{\"eventVersion\":\"2.0\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"ap-southeast-2\",\"eventTime\":\"2017-03-06T02:56:19.713Z\",\"eventName\":\"ObjectCreated:Put\",\"userIdentity\":{\"principalId\":\"AWS:DFLKSDFLKJ987SDFLJJDJ:some-principal-id\"},\"requestParameters\":{\"sourceIPAddress\":\"192.168.0.1\"},\"responseElements\":{\"x-amz-request-id\":\"324098EDFLK0894F\",\"x-amz-id-2\":\"xsdSDF/pgAl401Fz3UIATJ5/didfljDSFDSFsdfkjsdfl8JdsfLSDF89ldsf7SDF898jsdfljiA=\"},\"s3\":{\"s3SchemaVersion\":\"1.0\",\"configurationId\":\"SomeS3Event:Created\",\"bucket\":{\"name\":\"some-bucket\",\"ownerIdentity\":{\"principalId\":\"A3O1SDFLKJIJXU\"},\"arn\":\"arn:aws:s3:::some-bucket\"},\"object\":{\"key\":\"path/to/some/object\",\"size\":53598442,\"eTag\":\"6b1f72b9e81e4d6fcd3e0c808e8477f8\",\"sequencer\":\"0058BCCFD25C798E7B\"}}}]}",+ "Timestamp":"2017-03-06T02:56:19.834Z",+ "SignatureVersion":"1",+ "Signature":"aybEgnTjKzSbC2puHxho7SUnYOje4SjBoCyt0Q13bMWyp7M64+EU6jzi7P01+gSIuBFyYPsHreSmyqGMRSxbFuzn7rG5JcVGN0901U3CRXdk42eh03je8evRvs/Oa7TJlhpCTEDDOScalCWbIH0RthYONQpPR01nEgaNKj3e8YVJqyRQV+4RbU3YWJOj+Spyi4u1hOC9PLUv4BH7U80nbhbOe9EwgX0zpeNU1WBRbEpqPoACm+7/uB0w79qFBKjB/Q7OWc1kASUZV9q8bz03yceoQeVvza0QGhPsnSXi49sn1mLWQOFS4KvgbJIC/Qk7H036ShrDioP6pP+UEg6kow==",+ "SigningCertUrl":"https://sns.ap-southeast-2.amazonaws.com/SimpleNotificationService-b95095beb82e8f6a046b3aafc7f4149a.pem",+ "UnsubscribeUrl":"https://sns.ap-southeast-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent:23e2d254-e8bb-4db5-92ce-d917e5aad090",+ "MessageAttributes":{}+ }+ }+ ]+}+|]++sampleSNSS3Event :: SNSEvent (Embedded S3Event)+sampleSNSS3Event =+ RecordsEvent+ { _reRecords =+ [ SNSRecord+ { _srEventVersion = "1.0"+ , _srEventSubscriptionArn =+ "arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent:23e2d254-e8bb-4db5-92ce-d917e5aad090"+ , _srEventSource = "aws:sns"+ , _srSns =+ SNSMessage+ { _smMessage =+ Embedded+ { _unEmbed =+ RecordsEvent+ { _reRecords =+ [ S3EventNotification+ { _senAwsRegion = Sydney+ , _senEventName = S3ObjectCreatedPut+ , _senEventSource = "aws:s3"+ , _senEventTime = UTCTime (fromGregorian 2017 3 6) (picosecondsToDiffTime 10579713000000000)+ , _senEventVersion = "2.0"+ , _senRequestParameters =+ RequestParametersEntity+ { _rpeSourceIPAddress = "192.168.0.1"+ }+ , _senResponseElements =+ ResponseElementsEntity+ { _reeXAmzId2 =+ "xsdSDF/pgAl401Fz3UIATJ5/didfljDSFDSFsdfkjsdfl8JdsfLSDF89ldsf7SDF898jsdfljiA="+ , _reeXAmzRequestId = "324098EDFLK0894F"+ }+ , _senS3 =+ S3Entity+ { _seBucket =+ S3BucketEntity+ { _sbeArn = "arn:aws:s3:::some-bucket"+ , _sbeName = BucketName "some-bucket"+ , _sbeOwnerIdentity =+ UserIdentityEntity+ { _uiePrincipalId = "A3O1SDFLKJIJXU"+ }+ }+ , _seConfigurationId = "SomeS3Event:Created"+ , _seObject =+ S3ObjectEntity+ { _soeETag = Just (ETag "6b1f72b9e81e4d6fcd3e0c808e8477f8")+ , _soeKey = ObjectKey "path/to/some/object"+ , _soeSize = Just 53598442+ , _soeSequencer = "0058BCCFD25C798E7B"+ , _soeVersionId = Nothing+ }+ , _seS3SchemaVersion = "1.0"+ }+ , _senUserIdentity =+ UserIdentityEntity+ { _uiePrincipalId =+ "AWS:DFLKSDFLKJ987SDFLJJDJ:some-principal-id"+ }+ }+ ]+ }+ }+ , _smMessageAttributes = HashMap.fromList []+ , _smMessageId = "89f6fe8b-a751-5dcd-8e0c-afbd75420455"+ , _smSignature =+ "aybEgnTjKzSbC2puHxho7SUnYOje4SjBoCyt0Q13bMWyp7M64+EU6jzi7P01+gSIuBFyYPsHreSmyqGMRSxbFuzn7rG5JcVGN0901U3CRXdk42eh03je8evRvs/Oa7TJlhpCTEDDOScalCWbIH0RthYONQpPR01nEgaNKj3e8YVJqyRQV+4RbU3YWJOj+Spyi4u1hOC9PLUv4BH7U80nbhbOe9EwgX0zpeNU1WBRbEpqPoACm+7/uB0w79qFBKjB/Q7OWc1kASUZV9q8bz03yceoQeVvza0QGhPsnSXi49sn1mLWQOFS4KvgbJIC/Qk7H036ShrDioP6pP+UEg6kow=="+ , _smSignatureVersion = "1"+ , _smSigningCertUrl =+ "https://sns.ap-southeast-2.amazonaws.com/SimpleNotificationService-b95095beb82e8f6a046b3aafc7f4149a.pem"+ , _smSubject = "Amazon S3 Notification"+ , _smTimestamp = UTCTime (fromGregorian 2017 3 6) (picosecondsToDiffTime 10579834000000000)+ , _smTopicArn = "arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent"+ , _smType = "Notification"+ , _smUnsubscribeUrl =+ "https://sns.ap-southeast-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent:23e2d254-e8bb-4db5-92ce-d917e5aad090"+ }+ }+ ]+ }