## Motivation:
AWS SES can return "`DISABLED`" as a valid status value for spam, virus,
DKIM, and SPF verdicts when certain checks are disabled in the SES
configuration. However, the `SESEvent.Receipt.Verdict.Status` enum was
missing this case, causing JSON decoding to fail with a `DecodingError`
when parsing SES events containing `{"status":"DISABLED"}`.
This issue was reported in #110, where users encountered parsing
failures when processing legitimate SES events from AWS that included
disabled verdict checks.
## Modifications:
Added `.disabled = "DISABLED"` case to the
`SESEvent.Receipt.Verdict.Status` enum in
SES.swift
Converted the existing test to a parameterized test using Swift
Testing's `@Test(arguments:)` syntax
Added a new test case (`eventBodyDisabled`) that includes a SES event
with `spamVerdict.status` set to "`DISABLED`"
Updated the test assertion to verify both `.pass` and `.disabled` status
values are handled correctly
## Result:
SES events with verdict statuses set to "`DISABLED`" will now decode
successfully without throwing errors. The library correctly handles all
valid AWS SES verdict status values: `PASS`, `FAIL`, `GRAY`,
`PROCESSING_FAILED`, and `DISABLED`. The parameterized test ensures both
standard and disabled verdict scenarios are validated automatically.
APIGatewayWebSocketRequest was not decoding url query parameters from
event payloads.
### Motivation:
API Gateway WebSocket endpoints do support capturing and forwarding
query parameters. Probably just omitted because it isn't particularly
common.
### Modifications:
Added the field. Added a test. Verified against a real system that uses
the feature.
### Result:
Query parameters for APIGatewayWebSocketRequest will be available to
clients.
Cleaned up version of
[#61](https://github.com/awslabs/swift-aws-lambda-events/pull/61) that
also has tests.
This PR makes it so that in lists, the decoder will not try to access
the 0th index of an empty array.
This PR will also make it so that `AttributeValue`s of type `.binary`
will properly decode to base64 Strings when String is put as the type in
the the `Decodable`. To avoid including `Data` in the code, I included
code from the original source of the base 64 decoding methods
(https://github.com/fabianfett/swift-base64-kit) to add in the
corresponding encoding methods. However, I saw lots of Foundation is
imported throughout so I reverted it. What are your thoughts on this
matter?
I also added tests that aim to make these failure points more resilient
in the future.
In a previous PR (#91), a new init with a different parameters order was
added, marking the previous one was deprecated. The problem is that the
compiler doesn't know which one to choose when omitting the final
parameters.
This PR fixes it by marking the deprecated init with
`@_disfavoredOverload`.
Add helpers for FunctionURL, APIGatewayV2, and SQS events to ease usage
of `Decodable` and `Encodable` types
### Motivation:
When using Function URL (or API Gateway) events and outputs, and SQS
events, it is very common to decode a `Decodable` body, or encode a
`Codable` type into a function URL response. This PR adds convenient
methods that are easy to use, but also maintain a level of customization
by allowing custom JSON encoders/decoders.
### Modifications:
* Add helper method to encode `Encodable` types into a
`FunctionURLReponse` or `APIGatewayV2Response`
* Add helper method do decode `Decodable` types from a
`FunctionURLRequest` or `APIGatewayV2Request`
* Add helper method to decode multiple `Decodable`s from `SQSEvent`
records, or from a single `SQSEvent.Message`
* Add test cases (generated with the help of LLM. Thanks Anthropic.
Verified tests pass after many tries, and went over the final changes to
make sure they're correct)
### Result:
The changes allow the following usage:
```swift
let decodedBody = try functionURLRequest.decode(MyPayload.self)
```
Instead of:
```swift
var bodyData = functionURLRequest.body?.data(using: .utf8) ?? Data()
// check for base 64 encoding of the body, and if that's the case, decode that into bodyData
if isBase64Encoded, let base64Decoded = ... {
bodyData = base64Decoded
}
let decodedBody = JSONDecoder().decode(MyPayload.self, from: bodyData)
```
The same is similar when decoding SQS event messages.
And when encoding a response:
```swift
func handle(...) -> FunctionURLResponse {
// business logic
let responsePayload = MyResponse(text: "Hello Lambda", count: 613)
return .encoding(responsePayload)
}
```
Instead of:
```swift
func handle(...) -> FunctionURLResponse {
// business logic
let responsePayload = MyResponse(text: "Hello Lambda", count: 613)
do {
let data = try JSONEncoder().encode(responsePayload)
return .init(
status: .ok,
body: String(data: data, encoding: .utf8)
}
} catch {
return .init(
status: .internalServerError,
body: "Internal server error: \(error)"
}
}
}
```
Add an `init()` to `FunctionURLResponse` to match the signature of
`APIGatewayv2Response`
### Motivation:
`APIGAtewayV2Response` and `FunctionURLResponse` share teh same
structure, although we provided two different signatures for the
initializers. This prevent writing common code to handle both responses.
### Modifications:
- add a new `init(0` that has the same signature as the init from
APIGatewayV2response
- make the current init as deprecated, we will remove it in a future
version bump
### Result:
Two init() functions on FunctionURLResponse
Add APIGateway WebSockets Event Type
### Motivation:
What I propose is adding WebSockets support to AWS Lambda Events.
Let me begin by stating outright that I am not sure this is the correct
approach to take to bring WebSockets to AWS Lambda Events. Therefore, if
this pull request is outright rejected, it won't hurt my feelings in the
slightest.
API Gateway supports not only RESTful APIs, but also WebSockets. The way
that it works is that API Gateway manages WebSockets sessions with
clients. Whenever a client sends API Gateway some WebSockets data, API
Gateway bundles it up in as an APIGatewayV2 request (at least, according
to Amazon) and passes it along to a designated target…usually a Lambda
function. This is what a bundled request looks like:
```javascript
{
headers: {
Host: 'lqrlmblaa2.execute-api.us-east-1.amazonaws.com',
Origin: 'wss://lqrlmblaa2.execute-api.us-east-1.amazonaws.com',
'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits; server_max_window_bits=15',
'Sec-WebSocket-Key': 'am5ubWVpbHd3bmNyYXF0ag==',
'Sec-WebSocket-Version': '13',
'X-Amzn-Trace-Id': 'Root=1-64b83950-42de8e247b4c2b43091ef67c',
'X-Forwarded-For': '24.148.42.16',
'X-Forwarded-Port': '443',
'X-Forwarded-Proto': 'https'
},
multiValueHeaders: {
Host: [ 'lqrlmblaa2.execute-api.us-east-1.amazonaws.com' ],
Origin: [ 'wss://lqrlmblaa2.execute-api.us-east-1.amazonaws.com' ],
'Sec-WebSocket-Extensions': [
'permessage-deflate; client_max_window_bits; server_max_window_bits=15'
],
'Sec-WebSocket-Key': [ 'am5ubWVpbHd3bmNyYXF0ag==' ],
'Sec-WebSocket-Version': [ '13' ],
'X-Amzn-Trace-Id': [ 'Root=1-64b83950-42de8e247b4c2b43091ef67c' ],
'X-Forwarded-For': [ '24.148.42.16' ],
'X-Forwarded-Port': [ '443' ],
'X-Forwarded-Proto': [ 'https' ]
},
requestContext: {
routeKey: '$connect',
eventType: 'CONNECT',
extendedRequestId: 'IU3kkGyEoAMFwZQ=',
requestTime: '19/Jul/2023:19:28:16 +0000',
messageDirection: 'IN',
stage: 'dev',
connectedAt: 1689794896145,
requestTimeEpoch: 1689794896162,
identity: { sourceIp: '24.148.42.16' },
requestId: 'IU3kkGyEoAMFwZQ=',
domainName: 'lqrlmblaa2.execute-api.us-east-1.amazonaws.com',
connectionId: 'IU3kkeN4IAMCJwA=',
apiId: 'lqrlmblaa2'
},
isBase64Encoded: false
}
```
The problem, of course, is that the current `APIGatewayV2Request` type
cannot decode that JSON because it is is missing a number of
non-optional data values that `APIGatewayV2Request` expects to exist
(e.g., `version`, `rawPath`, etc.).
There are (at least as far as I can tell) two solutions to make this
work. The first is simply to alter the current `APIGatewayV2Request` so
that a number of its data values become optionals. I resisted suggesting
this because I suspected it could easily break production code (forcing
developers to `if-let` things). I thought a better solution might simply
be to create a new request/response type pair that could accommodate
WebSockets APIs.
### Modifications:
I suggest adding a new event source file to AWS Lambda Events:
`APIGateway+WebSockets.swift` containing two new types:
`APIGatewayWebSocketRequest` and `APIGatewayWebSocketResponse`.
`APIGatewayWebSocketResponse` would simply be a type alias (since
responses require that no changes be made to that type);
`APIGatewayWebSocketRequest` would be capable of decoding the JSON
listed above.
A typical Lambda handler supporting WebSockets would look like this:
```swift
func handle(
_ request: APIGatewayWebSocketRequest,
context: LambdaContext
) async throws -> APIGatewayWebSocketResponse {
let connectionID = request.context.connectionId
let routeKey = request.context.routeKey
// Route based on the type of WebSockets request
// The following are "default" request types
switch routeKey {
case "$connect": break
case "$disconnect": break
case "$default":
if let body = request.body {
// Responses are sent to clients via the
// ApiGatewayManagementApi. "post" is a method
// (not shown) which does that
try await post(
message: "{\"echo\": \"\(body)\"}",
toConnectionWithID: connectionID
)
}
default:
logger.log(level: .info, "Something weird happened");
}
// API Gateway requires that "some" status be returned
// "no matter what"
return APIGatewayWebSocketResponse(statusCode: .ok)
}
```
Note that responses to WebSockets clients (including, potentially,
errors) are made through Amazon's `ApiGatewayManagementApi`. However,
API Gateway itself always expects some kind of response…this can be a
simple as always sending a 200 "OK" back to API Gateway.
### Result:
The Swift for AWS Lambda Runtime would be able to support API Gateway
WebSockets applications.
---------
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
Migrate all unti tests to Swift Testing
### Motivation:
Allows to compile and run test on open source Swift toolchain 6
### Modifications:
Migrate according to
https://developer.apple.com/documentation/testing/migratingfromxctest
### Result:
```
Test run with 101 tests passed after 0.012 seconds.
```
Add a convenience initializer to the APIGatewayResponse v2 that accepts
an Encodable object
### Motivation:
Most Lambda developers will return a JSON object when exposing their
function through the API Gateway. The current initializer only accepts a
`String` body as per the AWS message definition.
This obliges all developers to write two lines of code + error handling
to encode their object and transform the `Data` to a string.
### Modifications:
Add a new initializer that accepts a `body` as `Encodable`
```swift
public init<Input: Encodable> (
statusCode: HTTPResponse.Status,
headers: HTTPHeaders? = nil,
body: Input,
isBase64Encoded: Bool? = nil,
cookies: [String]? = nil
) throws { ... }
```
### Result:
Developers can now pass a Swift struct to the APIGatewayResponse.
```swift
let businessResponse = BusinessResponse(message: "Hello World", code: 200)
return APIGatewayV2Response(statusCode: .ok, body: businessResponse)
```
Linking against `Foundation` on linux adds a hefty binary size penalty.
Mostly due to the ICU data from `FoundationInternationalization`. To
address this we conditionally import `FoundationEssentials` instead of
`Foundation` if possible and replace `Foundation`-only API with a
different implementation.
### Motivation:
Smaller binary sizes are better.
### Modifications:
- Replace `import Foundation` with `import FoundationEssentials`.
- Update date parsing logic to use newer API if available.
- Implement a custom parser for RFC5322 dates
### Result:
Ability to build the package without linking `Foundation`.
---------
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
Adds default devcontainer configuration from SSWG:
https://github.com/swift-server/swift-devcontainer-template
### Motivation:
Developing for linux can be challenging on macOS because some APIs are
not available on Linux. By using VSCode and devcontainer you can
essentially develop "on linux".
### Modifications:
Adds devcontainer config.
### Result:
When working in VSCode, you can now easily open the project inside a
devcontainer.
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
Add custom Decodable conformance.
Closes https://github.com/swift-server/swift-aws-lambda-events/issues/73
Duplicate of
https://github.com/swift-server/swift-aws-lambda-events/pull/76 created
by @twistinside
**Motivation:**
Absent collections would fail to decode, when in reality it is possible
to have empty headers, for example. This allows processing to continue
in the context of the Lambda runtime where users can handle the
collections as dictated by their code.
**Modifications:**
Custom Decodable conformance has been added to both API Gateway request
objects to optionally decode absent collections to empty collections.
Unit testa have been added for the same.
**Result:**
This will make the swift lambda runtime more accepting of input,
allowing processing to proceed in the rare cases where collection fields
would be absent in the JSON received by the runtime. This will unlock
certain functionality in edge cases such as testing lambda code from API
Gateway console with empty headers (the use case that prompted this
change).
This change will require users to update their code to remove optional
handling for the collections involved, potentially also requiring extra
logic to validate that the collections they've received aren't empty.
---------
Authored-by: twistinside <twistinside@users.noreply.github.com>
Make S3 Object eTag optional, because it's not always there.
### Motivation:
I'm seeing real events without an eTag, which naturally is causing
decoding failures.
### Modifications:
Made S3 Object eTag optional.
### Result:
S3 Object eTag is now optional...
* remove support for versions of Swift <= 5.6
* Simplify the Package.swift for older Swift versions
* bump swift version on example project
* add StrictConcurrency to prepare for Swift 6with StrictConcurrency=complete syntax
* use a computed property for dateformatter
* refine CloudwatchDetail for Sendable conformance
* remove support for versions of Swift <= 5.6
* Simplify the Package.swift for older Swift versions
* bump swift version on example project and Dockerfile