packages feed

serverless-haskell 0.4.1 → 0.4.2

raw patch · 9 files changed

+536/−69 lines, 9 filesdep +aeson-extradep +case-insensitive

Dependencies added: aeson-extra, case-insensitive

Files

README.md view
@@ -36,6 +36,8 @@ * Create `serverless.yml` with the following contents:    ```yaml+  service: myservice+   provider:     name: aws     runtime: nodejs6.10
serverless-haskell.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 4c9e15e7f2028afe4a93b3dc54949d8501d4634b200ded5e15c1b7f2df5e358b+-- hash: 39c6aa6699d4c9e7504dab750ed591876715bfaa355c00c418ad2b04a388de41  name:           serverless-haskell-version:        0.4.1+version:        0.4.2 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@@ -31,11 +31,13 @@   build-depends:       aeson     , aeson-casing+    , aeson-extra     , amazonka-core     , amazonka-kinesis     , amazonka-s3     , base >=4.7 && <5     , bytestring+    , case-insensitive     , lens     , text     , time@@ -44,6 +46,7 @@   exposed-modules:       AWSLambda       AWSLambda.Events+      AWSLambda.Events.APIGateway       AWSLambda.Events.KinesisEvent       AWSLambda.Events.Records       AWSLambda.Events.S3Event@@ -52,6 +55,7 @@       AWSLambda.Orphans       Data.Aeson.Alternative       Data.Aeson.Embedded+      Data.Aeson.TextValue   other-modules:       Paths_serverless_haskell   default-language: Haskell2010@@ -61,14 +65,17 @@   main-is: Spec.hs   hs-source-dirs:       test+  ghc-options: -Wall   build-depends:       aeson     , aeson-casing+    , aeson-extra     , amazonka-core     , amazonka-kinesis     , amazonka-s3     , base >=4.7 && <5     , bytestring+    , case-insensitive     , hspec     , hspec-discover     , lens@@ -79,6 +86,7 @@     , unix     , unordered-containers   other-modules:+      AWSLambda.Events.APIGatewaySpec       AWSLambda.Events.KinesisEventSpec       AWSLambda.Events.S3EventSpec       AWSLambda.Events.SNSEventSpec
src/AWSLambda/Events.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DeriveGeneric   #-} {-# LANGUAGE TemplateHaskell #-}  module AWSLambda.Events where@@ -5,6 +6,7 @@ import           Control.Applicative           ((<|>)) import           Control.Lens.TH import           Data.Aeson                    (FromJSON (..), Value)+import           Network.AWS.Data.Text         (FromText)  import           AWSLambda.Events.KinesisEvent import           AWSLambda.Events.S3Event@@ -82,7 +84,7 @@ -- | 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 =>+instance FromText snsMessage =>          FromJSON (LambdaEvent snsMessage) where   parseJSON v =     try S3 v <|> try KinesisStream v <|> try SNS v <|> pure (Custom v)
+ src/AWSLambda/Events/APIGateway.hs view
@@ -0,0 +1,182 @@+{-# LANGUAGE DeriveGeneric     #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE TemplateHaskell   #-}++{-|+Module: AWSLambda.Events.APIGateway+Description: Types for APIGateway Lambda requests and responses++Based on https://github.com/aws/aws-lambda-dotnet/tree/master/Libraries/src/Amazon.Lambda.APIGatewayEvents+-}+module AWSLambda.Events.APIGateway where++import           Control.Lens+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.ByteString         (ByteString)+import           Data.HashMap.Strict     (HashMap)+import qualified Data.HashMap.Strict     as HashMap+import           Data.Text               (Text)+import           GHC.Generics            (Generic)+import           Network.AWS.Data.Base64+import           Network.AWS.Data.Text++import           AWSLambda.Handler       (lambdaMain)++type Method = Text+-- type HeaderName = CI Text+type HeaderName = Text --- XXX should be CI Text+type HeaderValue = Text+type QueryParamName = Text+type QueryParamValue = Text+type PathParamName = Text+type PathParamValue = Text+type StageVarName = Text+type StageVarValue = Text++data RequestIdentity = RequestIdentity+  { _riCognitoIdentityPoolId         :: !(Maybe Text)+  , _riAccountId                     :: !(Maybe Text)+  , _riCognitoIdentityId             :: !(Maybe Text)+  , _riCaller                        :: !(Maybe Text)+  , _riApiKey                        :: !(Maybe Text)+  , _riSourceIp                      :: !(Maybe Text)+  , _riCognitoAuthenticationType     :: !(Maybe Text)+  , _riCognitoAuthenticationProvider :: !(Maybe Text)+  , _riUserArn                       :: !(Maybe Text)+  , _riUserAgent                     :: !(Maybe Text)+  , _riUser                          :: !(Maybe Text)+  } deriving (Eq, Show)++$(deriveFromJSON (aesonDrop 3 camelCase) ''RequestIdentity)+$(makeLenses ''RequestIdentity)++data ProxyRequestContext = ProxyRequestContext+  { _prcPath         :: !(Maybe Text)+  , _prcAccountId    :: !Text+  , _prcResourceId   :: !Text+  , _prcStage        :: !Text+  , _prcRequestId    :: !Text+  , _prcIdentity     :: !RequestIdentity+  , _prcResourcePath :: !Text+  , _prcHttpMethod   :: !Text+  , _prcApiId        :: !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)+  , _agprqPathParameters        :: !(HashMap PathParamName PathParamValue)+  , _agprqStageVariables        :: !(HashMap StageVarName StageVarValue)+  , _agprqRequestContext        :: !ProxyRequestContext+  , _agprqBody                  :: !(Maybe (TextValue body))+  } deriving (Eq, Show, Generic)++instance FromText body => FromJSON (APIGatewayProxyRequest body) where+  parseJSON = withObject "APIGatewayProxyRequest" $ \o ->+    APIGatewayProxyRequest+    <$> o .: "resource"+    <*> o .: "path"+    <*> o .: "httpMethod"+    <*> o .:? "headers" .!= HashMap.empty+    <*> o .:? "queryStringParameters" .!= HashMap.empty+    <*> o .:? "pathParameters" .!= HashMap.empty+    <*> o .:? "stageVariables" .!= HashMap.empty+    <*> o .: "requestContext"+    <*> o .:? "body"+++$(makeLenses ''APIGatewayProxyRequest)++-- | Get the request body, if there is one+requestBody :: Getter (APIGatewayProxyRequest body) (Maybe body)+requestBody = agprqBody . mapping unTextValue++-- | Get the embedded request body, if there is one+requestBodyEmbedded :: Getter (APIGatewayProxyRequest (Embedded v)) (Maybe v)+requestBodyEmbedded = requestBody . mapping unEmbed++-- | Get the binary (decoded Base64) request body, if there is one+requestBodyBinary :: Getter (APIGatewayProxyRequest Base64) (Maybe ByteString)+requestBodyBinary = requestBody . mapping _Base64++data APIGatewayProxyResponse body = APIGatewayProxyResponse+  { _agprsStatusCode :: !Int+  , _agprsHeaders    :: !(HashMap HeaderName HeaderValue)+  , _agprsBody       :: !(Maybe (TextValue body))+  } deriving (Eq, Show, Generic)++instance ToText body => ToJSON (APIGatewayProxyResponse body) where+  toJSON = genericToJSON $ aesonDrop 6 camelCase++instance FromText body => FromJSON (APIGatewayProxyResponse body) where+  parseJSON = genericParseJSON $ aesonDrop 6 camelCase++$(makeLenses ''APIGatewayProxyResponse)++response :: Int -> APIGatewayProxyResponse body+response statusCode = APIGatewayProxyResponse statusCode HashMap.empty Nothing++responseOK :: APIGatewayProxyResponse body+responseOK = response 200++responseNotFound :: APIGatewayProxyResponse body+responseNotFound = response 404++responseBadRequest :: APIGatewayProxyResponse body+responseBadRequest = response 400++responseBody :: Setter' (APIGatewayProxyResponse body) (Maybe body)+responseBody = agprsBody . at () . mapping unTextValue++responseBodyEmbedded :: Setter' (APIGatewayProxyResponse (Embedded body)) (Maybe body)+responseBodyEmbedded = responseBody . mapping unEmbed++responseBodyBinary :: Setter' (APIGatewayProxyResponse Base64) (Maybe ByteString)+responseBodyBinary = responseBody . mapping _Base64++-- | Process incoming events from @serverless-haskell@ using a provided+-- function.+--+-- This is a specialisation of lambdaMain for API Gateway.+--+-- The handler receives the input event given to the AWS Lambda function, and+-- its return value is returned from the function.+--+-- This is intended to be used as @main@, for example:+--+-- > import AWSLambda.Events.APIGateway+-- > import Control.Lens+-- > import Data.Aeson+-- > import Data.Aeson.Embedded+-- >+-- > main = apiGatewayMain handler+-- >+-- > handler :: APIGatewayProxyRequest (Embedded Value) -> IO (APIGatewayProxyResponse (Embedded [Int]))+-- > handler request = do+-- >   putStrLn "This should go to logs"+-- >   print $ request ^. requestBody+-- >   pure $ responseOK & responseBodyEmbedded ?~ [1, 2, 3]+--+-- The type parameters @reqBody@ and @resBody@ represent the types of request and response body, respectively.+-- The @FromText@ and @ToText@ contraints are required because these values come from string fields+-- in the request and response JSON objects.+-- To get direct access to the body string, use @Text@ as the parameter type.+-- To treat the body as a stringified embedded JSON value, use @Embedded a@, where @a@ has the+-- appropriate @FromJSON@ or @ToJSON@ instances.+-- To treat the body as base 64 encoded binary use @Base64@.+apiGatewayMain+  :: (FromText reqBody, ToText resBody)+  => (APIGatewayProxyRequest reqBody -> IO (APIGatewayProxyResponse resBody)) -- ^ Function to process the event+  -> IO ()+apiGatewayMain = lambdaMain
src/AWSLambda/Events/SNSEvent.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DeriveGeneric     #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-} {-# LANGUAGE TemplateHaskell   #-}@@ -10,17 +11,24 @@ -} module AWSLambda.Events.SNSEvent where -import           Control.Lens.TH+import           Control.Lens+import           Data.Aeson               (FromJSON (..), genericParseJSON) import           Data.Aeson.Casing        (aesonDrop, pascalCase)+import           Data.Aeson.Embedded+import           Data.Aeson.TextValue import           Data.Aeson.TH            (deriveFromJSON)+import           Data.ByteString          (ByteString) import           Data.HashMap.Strict      (HashMap) import           Data.Text                (Text) import           Data.Time.Clock          (UTCTime)+import           GHC.Generics             (Generic)+import           Network.AWS.Data.Base64+import           Network.AWS.Data.Text    (FromText)  import           AWSLambda.Events.Records  data MessageAttribute = MessageAttribute-  { _maType :: !Text+  { _maType  :: !Text   , _maValue :: !Text   } deriving (Eq, Show) @@ -28,7 +36,7 @@ $(makeLenses ''MessageAttribute)  data SNSMessage message = SNSMessage-  { _smMessage           :: !message+  { _smMessage           :: !(TextValue message )   , _smMessageAttributes :: !(HashMap Text MessageAttribute)   , _smMessageId         :: !Text   , _smSignature         :: !Text@@ -39,9 +47,11 @@   , _smTopicArn          :: !Text   , _smType              :: !Text   , _smUnsubscribeUrl    :: !Text-  } deriving (Eq, Show)+  } deriving (Eq, Show, Generic) -$(deriveFromJSON (aesonDrop 3 pascalCase) ''SNSMessage)+instance FromText message => FromJSON (SNSMessage message) where+  parseJSON = genericParseJSON $ aesonDrop 3 pascalCase+ $(makeLenses ''SNSMessage)  data SNSRecord message = SNSRecord@@ -49,9 +59,11 @@   , _srEventSubscriptionArn :: !Text   , _srEventSource          :: !Text   , _srSns                  :: !(SNSMessage message)-  } deriving (Eq, Show)+  } deriving (Eq, Show, Generic) -$(deriveFromJSON (aesonDrop 3 pascalCase) ''SNSRecord)+instance FromText message => FromJSON (SNSRecord message) where+  parseJSON = genericParseJSON $ aesonDrop 3 pascalCase+ $(makeLenses ''SNSRecord)  -- | SNSEvent.@@ -60,4 +72,17 @@ -- To extract an embedded event object use the 'Embedded' type. -- E.g. @SNSEvent (Embedded S3Event)@ will treat the message -- as an embedded S3Event.+-- To extract embedded Base64 encoded binary use+-- @SNSEvent Base64@ type SNSEvent message = RecordsEvent (SNSRecord message)++-- | A Traversal to get messages from an SNSEvent+messages :: Traversal (SNSEvent message) (SNSEvent message') message message'+messages = reRecords . traverse . srSns . smMessage . unTextValue++-- | A Traversal to get embedded JSON values from an SNSEvent+embedded :: Traversal (SNSEvent (Embedded v)) (SNSEvent (Embedded v')) v v'+embedded = messages . unEmbed++binary :: Traversal' (SNSEvent Base64) ByteString+binary = messages . _Base64
src/Data/Aeson/Embedded.hs view
@@ -1,6 +1,7 @@-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards   #-}-{-# LANGUAGE TemplateHaskell   #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE TemplateHaskell            #-}  {-| Module: Data.Aeson.Embedded@@ -10,22 +11,28 @@  import           Control.Lens.TH import           Data.Aeson-import           Data.Text.Lazy            (Text)-import           Data.Text.Lazy.Encoding   (decodeUtf8, encodeUtf8)+import           Data.Aeson.Extra      (encodeStrict)+import           Data.Text.Encoding    (decodeUtf8, encodeUtf8)+import           Network.AWS.Data.Text (FromText (..), ToText (..), fromText,+                                        takeText)  -- | Type for a JSON value embedded within a JSON string value newtype Embedded a = Embedded { _unEmbed :: a } deriving (Eq, Show)  instance FromJSON a =>+         FromText (Embedded a) where+  parser =+    fmap Embedded . either fail pure . eitherDecodeStrict . encodeUtf8 =<< takeText++instance FromJSON a =>          FromJSON (Embedded a) where-  parseJSON v =-    fmap Embedded . either fail pure . eitherDecode . encodeUtf8 =<< parseJSON v+  parseJSON v = either fail pure . fromText =<< parseJSON v -instance ToJSON a => ToJSON (Embedded a) where-  toJSON = toJSON . embed-  toEncoding = toEncoding . embed+instance ToJSON a => ToText (Embedded a) where+  toText = decodeUtf8 . encodeStrict . _unEmbed -embed :: ToJSON a => Embedded a -> Text-embed = decodeUtf8 . encode . _unEmbed+instance ToJSON a => ToJSON (Embedded a) where+  toJSON = toJSON . toText+  toEncoding = toEncoding . toText  $(makeLenses ''Embedded)
+ src/Data/Aeson/TextValue.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE RecordWildCards            #-}+{-# LANGUAGE TemplateHaskell            #-}++{-|+Module: Data.Aeson.TextValue+Description: Type for things that can be embedded in a JSON string++Provides @FromJSON@ and @ToJSON@ instances for anything that+has @FromText@ and @ToText@ instances, e.g. @TextValue Text@,+@(FromJSON a, ToJSON a) => TextValue (Embedded a)@,+@TextValue Base64@+-}+module Data.Aeson.TextValue where++import           Control.Lens.TH+import           Data.Aeson+import           Data.String+import           Network.AWS.Data.Text (FromText (..), ToText (..), fromText)+++newtype TextValue a = TextValue { _unTextValue :: a } deriving (Eq, Show, IsString)++instance FromText a => FromJSON (TextValue a) where+  parseJSON = withText "TextValue" $ fmap TextValue . either fail pure . fromText++instance ToText a => ToJSON (TextValue a) where+  toJSON = toJSON . toText . _unTextValue+  toEncoding = toEncoding . toText . _unTextValue++$(makeLenses ''TextValue)
+ test/AWSLambda/Events/APIGatewaySpec.hs view
@@ -0,0 +1,199 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}++module AWSLambda.Events.APIGatewaySpec where++import           AWSLambda.Events.APIGateway++import           Control.Lens+import           Data.Aeson+import           Data.ByteString.Lazy        (ByteString)+import qualified Data.HashMap.Strict         as HashMap+import           Data.Text                   (Text)++import           Text.RawString.QQ++import           Test.Hspec++spec :: Spec+spec =  do+  describe "APIGatewayProxyRequest" $+    it "parses sample GET request" $+      decode sampleGetRequestJSON `shouldBe` Just sampleGetRequest+  describe "APIGatewayProxyResponse" $+    it "parses sample text event" $+      decode sampleOKResponseJSON `shouldBe` Just sampleOKResponse++sampleGetRequestJSON :: ByteString+sampleGetRequestJSON = [r|+{+  "path": "/test/hello",+  "headers": {+    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",+    "Accept-Encoding": "gzip, deflate, lzma, sdch, br",+    "Accept-Language": "en-US,en;q=0.8",+    "CloudFront-Forwarded-Proto": "https",+    "CloudFront-Is-Desktop-Viewer": "true",+    "CloudFront-Is-Mobile-Viewer": "false",+    "CloudFront-Is-SmartTV-Viewer": "false",+    "CloudFront-Is-Tablet-Viewer": "false",+    "CloudFront-Viewer-Country": "US",+    "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com",+    "Upgrade-Insecure-Requests": "1",+    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",+    "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)",+    "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==",+    "X-Forwarded-For": "192.168.100.1, 192.168.1.1",+    "X-Forwarded-Port": "443",+    "X-Forwarded-Proto": "https"+  },+  "pathParameters": {+    "proxy": "hello"+  },+  "requestContext": {+    "accountId": "123456789012",+    "resourceId": "us4z18",+    "stage": "test",+    "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9",+    "identity": {+      "cognitoIdentityPoolId": "",+      "accountId": "",+      "cognitoIdentityId": "",+      "caller": "",+      "apiKey": "",+      "sourceIp": "192.168.100.1",+      "cognitoAuthenticationType": "",+      "cognitoAuthenticationProvider": "",+      "userArn": "",+      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",+      "user": ""+    },+    "resourcePath": "/{proxy+}",+    "httpMethod": "GET",+    "apiId": "wt6mne2s9k"+  },+  "resource": "/{proxy+}",+  "httpMethod": "GET",+  "queryStringParameters": {+    "name": "me"+  },+  "stageVariables": {+    "stageVarName": "stageVarValue"+  }+}+|]++sampleGetRequest :: APIGatewayProxyRequest Text+sampleGetRequest =+  APIGatewayProxyRequest+  { _agprqResource = "/{proxy+}"+  , _agprqPath = "/test/hello"+  , _agprqHttpMethod = "GET"+  , _agprqHeaders =+    HashMap.fromList+      [ ("X-Forwarded-Proto", "https")+      , ("CloudFront-Is-Desktop-Viewer", "true")+      , ( "Accept"+        , "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")+      , ( "X-Amz-Cf-Id"+        , "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==")+      , ("Accept-Encoding", "gzip, deflate, lzma, sdch, br")+      , ("CloudFront-Forwarded-Proto", "https")+      , ("Accept-Language", "en-US,en;q=0.8")+      , ("CloudFront-Is-Tablet-Viewer", "false")+      , ("Upgrade-Insecure-Requests", "1")+      , ("CloudFront-Viewer-Country", "US")+      , ( "User-Agent"+        , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48")+      , ("CloudFront-Is-Mobile-Viewer", "false")+      , ("Host", "wt6mne2s9k.execute-api.us-west-2.amazonaws.com")+      , ("X-Forwarded-Port", "443")+      , ("CloudFront-Is-SmartTV-Viewer", "false")+      , ( "Via"+        , "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)")+      , ("X-Forwarded-For", "192.168.100.1, 192.168.1.1")+      ]+  , _agprqQueryStringParameters = HashMap.fromList [("name", "me")]+  , _agprqPathParameters = HashMap.fromList [("proxy", "hello")]+  , _agprqStageVariables = HashMap.fromList [("stageVarName", "stageVarValue")]+  , _agprqRequestContext =+    ProxyRequestContext+    { _prcPath = Nothing+    , _prcAccountId = "123456789012"+    , _prcResourceId = "us4z18"+    , _prcStage = "test"+    , _prcRequestId = "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9"+    , _prcIdentity =+      RequestIdentity+      { _riCognitoIdentityPoolId = Just ""+      , _riAccountId = Just ""+      , _riCognitoIdentityId = Just ""+      , _riCaller = Just ""+      , _riApiKey = Just ""+      , _riSourceIp = Just "192.168.100.1"+      , _riCognitoAuthenticationType = Just ""+      , _riCognitoAuthenticationProvider = Just ""+      , _riUserArn = Just ""+      , _riUserAgent = Just+        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48"+      , _riUser = Just ""+      }+    , _prcResourcePath = "/{proxy+}"+    , _prcHttpMethod = "GET"+    , _prcApiId = "wt6mne2s9k"+    }+  , _agprqBody = Nothing+  }++sampleOKResponseJSON :: ByteString+sampleOKResponseJSON = [r|+{+  "statusCode": 200,+  "headers": {+    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",+    "Accept-Encoding": "gzip, deflate, lzma, sdch, br",+    "Accept-Language": "en-US,en;q=0.8",+    "CloudFront-Forwarded-Proto": "https",+    "CloudFront-Is-Desktop-Viewer": "true",+    "CloudFront-Is-Mobile-Viewer": "false",+    "CloudFront-Is-SmartTV-Viewer": "false",+    "CloudFront-Is-Tablet-Viewer": "false",+    "CloudFront-Viewer-Country": "US",+    "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com",+    "Upgrade-Insecure-Requests": "1",+    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48",+    "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)",+    "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==",+    "X-Forwarded-For": "192.168.100.1, 192.168.1.1",+    "X-Forwarded-Port": "443",+    "X-Forwarded-Proto": "https"+  },+  "body": "Hello World"+}+|]++sampleOKResponse :: APIGatewayProxyResponse Text+sampleOKResponse =+  responseOK+  & responseBody ?~ "Hello World"+  & agprsHeaders .~ HashMap.fromList+    [ ("X-Forwarded-Proto", "https")+    , ("CloudFront-Is-Desktop-Viewer", "true")+    , ( "Accept"+      , "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")+    , ("X-Amz-Cf-Id", "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==")+    , ("Accept-Encoding", "gzip, deflate, lzma, sdch, br")+    , ("CloudFront-Forwarded-Proto", "https")+    , ("Accept-Language", "en-US,en;q=0.8")+    , ("CloudFront-Is-Tablet-Viewer", "false")+    , ("Upgrade-Insecure-Requests", "1")+    , ("CloudFront-Viewer-Country", "US")+    , ( "User-Agent"+      , "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48")+    , ("CloudFront-Is-Mobile-Viewer", "false")+    , ("Host", "wt6mne2s9k.execute-api.us-west-2.amazonaws.com")+    , ("X-Forwarded-Port", "443")+    , ("CloudFront-Is-SmartTV-Viewer", "false")+    , ("Via", "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)")+    , ("X-Forwarded-For", "192.168.100.1, 192.168.1.1")+    ]
test/AWSLambda/Events/SNSEventSpec.hs view
@@ -9,13 +9,13 @@  import           Data.Aeson import           Data.Aeson.Embedded+import           Data.Aeson.TextValue 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 @@ -137,55 +137,62 @@       , _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"+          TextValue+          { _unTextValue =+            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"                     }-                  , _seConfigurationId = "SomeS3Event:Created"-                  , _seObject =-                    S3ObjectEntity-                    { _soeETag = Just (ETag "6b1f72b9e81e4d6fcd3e0c808e8477f8")-                    , _soeKey = ObjectKey "path/to/some/object"-                    , _soeSize = Just 53598442-                    , _soeSequencer = "0058BCCFD25C798E7B"-                    , _soeVersionId = Nothing+                  , _senUserIdentity =+                    UserIdentityEntity+                    { _uiePrincipalId =+                      "AWS:DFLKSDFLKJ987SDFLJJDJ:some-principal-id"                     }-                  , _seS3SchemaVersion = "1.0"                   }-                , _senUserIdentity =-                  UserIdentityEntity-                  { _uiePrincipalId =-                    "AWS:DFLKSDFLKJ987SDFLJJDJ:some-principal-id"-                  }-                }-              ]+                ]+              }             }           }         , _smMessageAttributes = HashMap.fromList []@@ -196,7 +203,10 @@         , _smSigningCertUrl =           "https://sns.ap-southeast-2.amazonaws.com/SimpleNotificationService-b95095beb82e8f6a046b3aafc7f4149a.pem"         , _smSubject = "Amazon S3 Notification"-        , _smTimestamp = UTCTime (fromGregorian 2017 3 6) (picosecondsToDiffTime 10579834000000000)+        , _smTimestamp =+          UTCTime+            (fromGregorian 2017 3 6)+            (picosecondsToDiffTime 10579834000000000)         , _smTopicArn = "arn:aws:sns:ap-southeast-2:012345678901:SomeSNSEvent"         , _smType = "Notification"         , _smUnsubscribeUrl =