Skip to content

Commit 1eda275

Browse files
author
Nick Tchayka
authored
Breaking change: Merge pull request #72 from dnikolovv/persistent-execution-context
Persistent execution context
2 parents a1312d7 + a53c227 commit 1eda275

31 files changed

+731
-272
lines changed

aws-lambda-haskell-runtime.cabal

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ cabal-version: 1.12
44
--
55
-- see: https://github.com/sol/hpack
66
--
7-
-- hash: eab5b338b49db7854613ecd43b17245f105c8c936c40230ecf9d08c5dfad9d6d
7+
-- hash: c21b3c988844f7c4bfb806aff4e307c5d5dc63a7973eff4a83eb329e7bccb1c9
88

99
name: aws-lambda-haskell-runtime
10-
version: 2.0.6
10+
version: 3.0.0
1111
synopsis: Haskell runtime for AWS Lambda
1212
description: Please see the README on GitHub at <https://github.com/theam/aws-lambda-haskell-runtime#readme>
1313
category: AWS
@@ -46,6 +46,7 @@ library
4646
Aws.Lambda.Runtime.Environment
4747
Aws.Lambda.Runtime.Error
4848
Aws.Lambda.Runtime.Publish
49+
Aws.Lambda.Utilities
4950
Paths_aws_lambda_haskell_runtime
5051
hs-source-dirs:
5152
src

doc/01-getting-started.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ If you are testing the package, or you are starting a new project, we have provi
2222
To use it, enter the following command:
2323

2424
```bash
25-
stack new my-haskell-lambda https://github.com/theam/aws-lambda-haskell-runtime/raw/master/stack-template.hsfiles --resolver=lts-13.25 --omit-packages
25+
stack new my-haskell-lambda https://github.com/theam/aws-lambda-haskell-runtime/raw/master/stack-template.hsfiles --resolver=lts-15.16 --omit-packages
2626
```
2727

2828
This will create a `my-haskell-lambda` directory with the following structure:
@@ -49,7 +49,7 @@ packages:
4949
- .
5050

5151
extra-deps:
52-
- aws-lambda-haskell-runtime-2.0.1
52+
- aws-lambda-haskell-runtime-3.0.0
5353
```
5454
5555
## Adding the dependency to an existing project
@@ -60,15 +60,15 @@ to the `stack.yaml` file:
6060

6161
```yaml
6262
extra-deps:
63-
- aws-lambda-haskell-runtime-2.0.1
63+
- aws-lambda-haskell-runtime-3.0.0
6464
```
6565

6666
and, to the `package.yaml` file:
6767

6868
```yaml
6969
dependencies:
7070
- ... # other dependencies of your project
71-
- aws-lambda-haskell-runtime >= 2.0
71+
- aws-lambda-haskell-runtime >= 3.0.0
7272
```
7373

7474
## Keep reading!

doc/02-adding-a-handler.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ modules:
2121
```haskell
2222
{-# LANGUAGE DeriveGeneric #-}
2323
{-# LANGUAGE DeriveAnyClass #-}
24+
2425
module Lib where
2526
```
2627

@@ -46,7 +47,7 @@ Now, let's write the handler. It **must** be a function that is called `handler`
4647
The arguments to this function will always go like this:
4748

4849
* The first argument to this handler will always be the input type we expect (note that it has to implement `FromJSON`).
49-
* The second argument is the `Aws.Lambda` `Context` type, which has some information regarding our Lambda execution.
50+
* The second argument is the `Aws.Lambda` `Context` type, which has some information regarding our Lambda execution. The `Context` type also takes a `context` parameter, which we can use if we want to have some state that is shared between Lambda calls. For this example, we don't want to have such state, so we'll just use `()`.
5051

5152
The output will always be an `IO (Either errorType resultType)` where
5253

@@ -59,7 +60,7 @@ For example, here we will check if the age of a `Person` is positive, and will r
5960
will return a `String` error:
6061

6162
```haskell top
62-
handler :: Person -> Context -> IO (Either String Person)
63+
handler :: Person -> Context () -> IO (Either String Person)
6364
handler person context =
6465
if age person > 0 then
6566
pure (Right person)

doc/03-configuring-the-dispatcher.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,34 @@ The dispatcher is a special main function that checks which handler it has to ru
88
and runs it
99

1010
To make the package generate a dispatcher for you, you have to configure it in
11-
your `app/Main.hs` file.
11+
your `app/Main.hs` file to use the `generateLambdaDispatcher` function from `Aws.Lambda`.
1212

13-
First, activate `TemplateHaskell` for enabling the metaprogramming features of Haskell.
13+
The `generateLambdaDispatcher` function has two options: `StandaloneLambda` and `UseWithAPIGateway`.
14+
15+
Use `UseWithAPIGateway` when you'll be exposing your lambda through API Gateway. For more information check the [Use with API Gateway](./04-usage-with-api-gateway.md) page.
16+
17+
Use `StandaloneLambda` when you want to use your lambda as usual. This is what we're going to use for our person age validator function.
18+
19+
To continue the person age validator lambda, activate `TemplateHaskell` for enabling the metaprogramming features of Haskell.
1420
Then, import the `Aws.Lambda` module:
1521

1622
```haskell
17-
{-# LANGUAGE TemplateHaskell #-}
23+
{-# LANGUAGE TemplateHaskell #-}
24+
{-# LANGUAGE ScopedTypeVariables #-}
25+
1826
module Main where
1927
import Aws.Lambda
2028
```
2129

22-
After this, add a line with the following statement:
30+
After this, add the following lines:
2331

2432
```haskell
25-
generateLambdaDispatcher
33+
-- Use this action if you want to have context that is shared between lambda calls.
34+
-- It is called once per every cold start. We do not wish to have a shared context for our lambda, so we simply use Unit.
35+
initializeContext :: IO ()
36+
initializeContext = return ()
37+
38+
generateLambdaDispatcher StandaloneLambda defaultDispatcherOptions
2639
```
2740

2841
This statement will generate something that looks a little bit like this:

doc/04-usage-with-api-gateway.md

Lines changed: 25 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -4,158 +4,42 @@ title: Usage with API Gateway
44

55
# Usage with API Gateway
66

7-
In order to write a handler for the API Gateway, we will receive a special JSON with
8-
a lot of fields with information from the API Gateway, and we will have to return
9-
a special JSON as the response.
7+
A common use-case is to expose your lambdas through API Gateway. Luckily, `aws-lambda-haskell-runtime` has native support for that.
108

11-
## The request
12-
13-
The full request type looks more or less like this:
14-
15-
```haskell
16-
data APIGatewayRequest = APIGatewayRequest
17-
{ resource :: String
18-
, path :: ByteString
19-
, httpMethod :: Method
20-
, headers :: RequestHeaders
21-
, queryStringParameters :: [(ByteString, Maybe ByteString)]
22-
, pathParameters :: HashMap String String
23-
, stageVariables :: HashMap String String
24-
, body :: String
25-
} deriving (Generic, FromJSON)
26-
```
27-
28-
Feel free to copy-paste this data type into your project. Note that you don't have to use all of
29-
it's fields, if you only need, say, the `body` and the `httpMethod` fields, you can use this one
30-
(`resource` is mandatory):
9+
If you're using API Gateway, simply pass the `UseWithAPIGateway` option to `generateLambdaDispatcher` in your `app/Main.hs` file.
3110

3211
```haskell
33-
data APIGatewayRequest = APIGatewayRequest
34-
{ resource :: String
35-
, httpMethod :: Method
36-
, body :: String
37-
} deriving (Generic, FromJSON)
12+
generateLambdaDispatcher UseWithAPIGateway defaultDispatcherOptions
3813
```
3914

40-
## The response
41-
42-
The response type looks more or less like this:
15+
Passing `UseWithAPIGateway` will make the compiler error if you do not have your handlers in the following form:
4316

4417
```haskell
45-
data APIGatewayResponse = APIGatewayResponse
46-
{ statusCode :: Int
47-
, headers :: [(HeaderName, ByteString)]
48-
, body :: String
49-
} deriving (Generic, ToJSON)
18+
handler :: ApiGatewayRequest request -> Context context -> IO (Either (ApiGatewayResponse error) (ApiGatewayResponse success))
19+
handler = ...
5020
```
5121

52-
Again, you can use this type in your project, and use only the fields you need.
53-
Note that `HeaderName` comes from [`Network.HTTP.Simple`](https://hackage.haskell.org/package/http-conduit-2.3.7.1/docs/Network-HTTP-Simple.html#t:Header).
54-
55-
Notice some fields need implementation of ToJSON. See the example with headers below.
56-
57-
## An example
58-
59-
```haskell top
60-
{-# LANGUAGE RecordWildCards #-}
61-
{-# LANGUAGE DuplicateRecordFields #-}
62-
{-# LANGUAGE DeriveAnyClass #-}
63-
{-# LANGUAGE DeriveGeneric #-}
64-
65-
module Lib where
66-
67-
import GHC.Generics
68-
import Aws.Lambda
69-
import Data.Aeson
70-
import qualified Data.ByteString.Lazy.Char8 as ByteString
22+
You can use the `ApiGatewayRequest` wrapper to access additional request information that API Gateway provides, such as path, query string parameters, authentication info and much more. You can find more information about that under the `Input format of a Lambda function for proxy integration` section [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html).
7123

72-
-- Input
73-
data Event = Event
74-
{ resource :: String
75-
, body :: String
76-
} deriving (Generic, FromJSON)
24+
You can use the `ApiGatewayResponse` wrapper in order to return a custom status code or attach custom headers to the response.
7725

78-
-- Output
79-
data Response = Response
80-
{ statusCode:: Int
81-
, body :: String
82-
} deriving (Generic, ToJSON)
26+
Also note the `defaultDispatcherOptions`. They look like this:
8327

84-
-- Type that we decode from the 'body' of 'Event'
85-
data Person = Person
86-
{ name :: String
87-
, age :: Int
88-
} deriving (Generic, FromJSON, ToJSON)
89-
90-
greet :: Person -> String
91-
greet person =
92-
"Hello, " ++ name person ++ "!"
93-
94-
handler :: Event -> Context -> IO (Either String Response)
95-
handler Event{..} context = do
96-
case decode (ByteString.pack body) of
97-
Just person ->
98-
pure $ Right Response
99-
{ statusCode = 200
100-
, body = greet person
101-
}
102-
Nothing ->
103-
pure $ Right Response
104-
{ statusCode = 200
105-
, body = "bad person"
106-
}
107-
```
108-
## An example with headers
109-
```src/Hello.hs```
11028
```haskell
111-
{-# LANGUAGE OverloadedStrings #-}
112-
{-# LANGUAGE FlexibleInstances #-}
113-
{-# LANGUAGE RecordWildCards #-}
114-
{-# LANGUAGE DuplicateRecordFields #-}
115-
{-# LANGUAGE DeriveAnyClass #-}
116-
{-# LANGUAGE DeriveGeneric #-}
117-
118-
module Hello where
119-
120-
import GHC.Generics
121-
import Aws.Lambda
122-
import Data.Aeson
123-
import Network.HTTP.Types.Header
124-
import Data.Text.Encoding
125-
import qualified Data.CaseInsensitive as CI
126-
import qualified Data.Aeson.Types as T
127-
128-
-- Input
129-
data Event = Event
130-
{ resource :: String
131-
, body :: String
132-
} deriving (Generic, FromJSON)
133-
134-
-- Output
135-
data ApiGateWayResponse = ApiGateWayResponse
136-
{ statusCode:: Int
137-
, headers :: ResponseHeaders
138-
, body :: String
139-
} deriving (Generic)
140-
141-
-- function to encode Header
142-
headerToPair :: Header -> T.Pair
143-
headerToPair (cibyte, bstr) = k .= v
144-
where
145-
k = (decodeUtf8 . CI.original) cibyte
146-
v = decodeUtf8 bstr
147-
148-
instance ToJSON ApiGateWayResponse where
149-
toJSON ApiGateWayResponse {..} = object
150-
[ "statusCode" .= statusCode
151-
, "body" .= body
152-
, "headers" .= object (map headerToPair headers)
153-
]
29+
-- | Options that the dispatcher generator expects
30+
newtype DispatcherOptions = DispatcherOptions
31+
{ apiGatewayDispatcherOptions :: ApiGatewayDispatcherOptions
32+
} deriving (Lift)
33+
34+
-- | API Gateway specific dispatcher options
35+
newtype ApiGatewayDispatcherOptions = ApiGatewayDispatcherOptions
36+
{ propagateImpureExceptions :: Bool
37+
-- ^ Should impure exceptions be propagated through the API Gateway interface
38+
} deriving (Lift)
39+
40+
defaultDispatcherOptions :: DispatcherOptions
41+
defaultDispatcherOptions =
42+
DispatcherOptions (ApiGatewayDispatcherOptions True)
43+
```
15444

155-
handler :: Event -> Context -> IO (Either String ApiGateWayResponse)
156-
handler Event {..} _ = pure $ Right ApiGateWayResponse
157-
{ statusCode = 200
158-
, headers = [("Access-Control-Allow-Origin", "*")]
159-
, body = "hello, cors"
160-
}
161-
```
45+
For production environments, you'd likely want to set `propagateImpureExceptions` to `False` in order to prevent your exception messages from being seen.

doc/index.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module WordCount where
2020

2121
import Aws.Lambda
2222

23-
handler :: String -> Context -> IO (Either String Int)
23+
handler :: String -> Context () -> IO (Either String Int)
2424
handler someText _ = do
2525
let wordsCount = length (words someText)
2626
if wordsCount > 0 then
@@ -34,7 +34,11 @@ Then, in the `Main` module:
3434
```haskell
3535
import Aws.Lambda
3636
import qualified WordCount
37-
generateLambdaDispatcher
37+
38+
initializeContext :: IO ()
39+
initializeContext = return ()
40+
41+
generateLambdaDispatcher StandaloneLambda defaultDispatcherOptions
3842
```
3943

4044
# Performance

examples/wai-app/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.stack-work/
2+
*~
3+
build
4+
*.iml

examples/wai-app/ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Changelog for lambda-testing
2+
3+
## Unreleased changes

examples/wai-app/LICENSE

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Copyright Dobromir Nikolov (c) 2020
2+
3+
All rights reserved.
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
11+
* Redistributions in binary form must reproduce the above
12+
copyright notice, this list of conditions and the following
13+
disclaimer in the documentation and/or other materials provided
14+
with the distribution.
15+
16+
* Neither the name of Dobromir Nikolov nor the names of other
17+
contributors may be used to endorse or promote products derived
18+
from this software without specific prior written permission.
19+
20+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0 commit comments

Comments
 (0)