Add user-facing API for Streaming Lambda functions that receives JSON
events
### Motivation:
Streaming Lambda functions developed by developers had no choice but to
implement a handler that receives incoming data as a `ByteBuffer`. While
this is useful for low-level development, I assume most developers will
want to receive a JSON event to trigger their streaming Lambda function.
Going efficiently from a `ByteBuffer` to a Swift struct requires some
code implemented in the `JSON+ByteBuffer.swift` file of the librray. We
propose to further help developers by providing them with a new
`handler()` function that directly receives their `Decodable` type.
### Modifications:
This PR adds a public facing API (+ unit test + updated README) allowing
developers to write a handler method accepting any `Decodable` struct as
input.
```swift
import AWSLambdaRuntime
import NIOCore
// Define your input event structure
struct StreamingRequest: Decodable {
let count: Int
let message: String
let delayMs: Int?
}
// Use the new streaming handler with JSON decoding
let runtime = LambdaRuntime { (event: StreamingRequest, responseWriter, context: LambdaContext) in
context.logger.info("Received request to send \(event.count) messages")
// Stream the messages
for i in 1...event.count {
let response = "Message \(i)/\(event.count): \(event.message)\n"
try await responseWriter.write(ByteBuffer(string: response))
// Optional delay between messages
if let delay = event.delayMs, delay > 0 {
try await Task.sleep(for: .milliseconds(delay))
}
}
// Finish the stream
try await responseWriter.finish()
// Optional: Execute background work after response is sent
context.logger.info("Background work: processing completed")
}
try await runtime.run()
```
This interface provides:
- **Type-safe JSON input**: Automatic decoding of JSON events into Swift
structs
- **Streaming responses**: Full control over when and how to stream data
back to clients
- **Background work support**: Ability to execute code after the
response stream is finished
- **Familiar API**: Uses the same closure-based pattern as regular
Lambda handlers
Because streaming Lambda functions can be invoked either directly
through the API or through Lambda Function URL, this PR adds the
decoding logic to support both types, shielding developers from working
with Function URL requests and base64 encoding.
We understand these choice will have an impact on the raw performance
for event handling. Those advanced users that want to get the maximum
might use the existing `handler(_ event: ByteBuffer, writer:
LambaStreamingWriter)` function to implement their own custom decoding
logic.
This PR provides a balance between ease of use for 80% of the users vs
ultimate performance, without closing the door for the 20% who need it.
### Result:
Lambda function developers can now use arbitrary `Decodable` Swift
struct or Lambda events to trigger their streaming functions. 🎉
---------
Co-authored-by: Tim Condon <0xTim@users.noreply.github.com>
This is a proposal to fix issue #507
**changes**
- `LambdaRuntime.init()` uses a `Mutex<Bool>` to make sure only one
instance is created
- `LambdaRuntime.init()` can now throw an error in case an instance
already exists (I did not use `fatalError()` to make it easier to test)
- All `convenience init()` methods catch possible errors instead of
re-throwing it to a void breaking the user-facing API
- Renamed existing `LambdaRuntimeError` to `LambdaRuntimeClientError`
- Introduced a new type `LambdaRuntimeError` to represent the double
initialization error
---------
Co-authored-by: Fabian Fett <fabianfett@apple.com>
Co-authored-by: Adam Fowler <adamfowler71@gmail.com>
Add `S3EventNotifier` example
### Motivation:
There is currently no example regarding an AWS event triggered lambda,
such as a lambda that gets invoked on an S3 object upload. I've had to
look for a while to figure out that the `AWSLambdaEvents` exists and
that it can be used for that (had to find out via a Java example! 😜) so
I think this could be useful and a somewhat common use case
### Modifications:
Added an example of a lambda that gets triggered when an object gets
uploaded to an S3 bucket.
I have not described how to set up the actual S3 event in AWS because I
figured if you needed such a lambda you'd know, but I can describe that
too in the README if needed
---------
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
The local testing instruction are missing from the hello world example.
The format of the payload is not intuitive or trivial to guess. We need
to make it explicit
When including resources in the package and packaging on Ubuntu,
`FileManager` throws a FilePermission error. The docker daemon runs as
root and files to be copied are owned by `root` while the archiver runs
as the current user (`ubuntu` on EC2 Ubuntu). The `FileManager` manages
to copy the files but throws an error after the copy. We suspect the
`FileManager` to perform some kind of operation after the copy and it
fails because of the `root` permission of the files.
See
https://github.com/swift-server/swift-aws-lambda-runtime/issues/449#issuecomment-2595978246
for a description of the problem.
This PR contains code to reproduce the problem, a very simple
workaround, and an integration test.
The workaround consists of
- trapping all errors
- verify if the error is the permission error (Code = 513)
- verify if the files have been copied or not
- if the two above conditions are met, ignore the error, otherwise
re-throw it
I would rather prefer a solution that solves the root cause rather than
just ignoring the error.
We're still investigating the root cause (see [this
thread](https://forums.swift.org/t/filemanager-copyitem-on-linux-fails-after-copying-the-files/77282)
on the Swift Forum and this issue on Swift Foundation
https://github.com/swiftlang/swift-foundation/issues/1125
Add an example of an APIGateway Lambda Authorizer
### Motivation:
Lambda Authorizers allow developers to write custom logic to validate
HTTP REST requests.
This example shows how to write a Lambda Authorizer in Swift and how to
configure an API Gateway to use a Lambda Authorizer.
This example use a SAM template to deploy the resources.
### Modifications:
- Add `Examples/APIGateway+LambdaAuthorizer` directory with the source
code, a `template.yaml`, and a `README` file.
- Add a reference to the new example in `Examples/README`
- Modify CI to build the new example
### Result:
There is a new example showing how to develop a Lambda authorizer in
Swift and how to attach it to an APIGateway with SAM
As discussed with @0xTim
This PR
- adds a minimal example of deployment using SAM in the README
- adds a `Deployment.md` Swift Docc file to cover deployment with the
AWS console, CLI, SAM, and CDK. It mentions and contains a call to
contributions to further examples for third-party tools such as the
Serverless Framework, Terraform, or Pulumi.
This new example project show four testing strategies for Swift Lambda
function
- Unit testing the business logic (not specific to Swift Lambda)
- Integration testing the handler method
- Local invocation with the Swift Lambda Runtime
- Local invocation with SAM
**[IMPORTANT]**
To allow testing the handler, I had to change visibility of a method in
the Runtime project. This method is clearly marked for testing only, so
it should not be a problem. Happy to read feedback and discuss however.
All the `Package.swift` files from the examples use `path: "."` instead
of `path: "Sources"` which triggers error messages when users add a
`Tests` directory.
We want that the runtime only depends on `FoundationEssentials` where
available (ie. on linux) to ensure small binary size.
### Motivation:
Smaller binary size is good for lambda deployment and cold-start times.
The runtime should only depend on `FoundationEssentials`.
### Modifications:
- replace `import Foundation` with `import FoundationEssentials` if
`FoundationEssentials` is available.
- I also applied the same treatment to tests to ensure that catch error
where tests run on linux and we use API that is only available in
`Foundation` which easily happens when you develop on macOS (where
always full `Foundation` is available).
### Result:
This should allow builds without linking full `Foundation`.
* add aws sdk example
* add soto example
* use amazonlinux docker image instead of ubuntu
* dynamically add runtime dependency based on env var
* remove import of Foundation.ProcessInfo
* workaround https://github.com/actions/checkout/issues/1487
* Add Support For Copying All Resources Into Final Executable
* Run formatter
---------
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
* Add support for resources when packaging using the SwiftPM plugin.
* Copy the resources directory to the working directory instead of recreating the directroy structure.
* Add resource packaging example.
* Use the bundle's url function to locate the file url.
* Fix year for soundness check.
---------
Co-authored-by: Sébastien Stormacq <sebastien.stormacq@gmail.com>
motivation: examples can be confusing since they use relative path to the library for CI purposes
changes:
* update examples to use the library URL, expect when env variable is set for CI purposes
* rename docker compose job to test-examples since it is more accurate
motivation: define stable API in preperation 1.0 release
changes:
* require swift 5.7, remove redundant backwards compatibility code
* make LambdaHandler, EventLoopLambdaHandler, and ByteBufferLambdaHandler disjointed protocols to reduce API surface area
* create coding wrappers for LambdaHandler and EventLoopLambdaHandler to provide bridge to ByteBufferLambdaHandler
* reuse output ByteBuffer to reduce allocations
* add new SimpleLambdaHandler with no-op initializer for simple lambda use cases
* update callsites and tests
* update examples
Co-authored-by: Yim Lee <yim_lee@apple.com>
Co-authored-by: Fabian Fett <fabianfett@apple.com>
Adopt Swift Concurrency adoption guidelines for Swift Server Libraries (swift-server/guides#70).
- Use #if compiler(>=5.5) && canImport(_Concurrency) to judge if Concurrency is available
- Some clean up
Since we already required Swift 5.5 for Concurrency (Xcode 13), it's natural to update the demo app to reflect latest SwiftUI changes, which makes the code neater and more expressive.
* take advantage of @main where possible
* move the top level Sample code to the examples subdirectory
* extract a few examples form the "LambdaFunctions" directory (which is really a deployment demo) and move them to the top level Examples directory
* rename "LambdaFunctions" examples as "Deployments" to make their intent clearer
* add a sample that demonstrates how to test a lambda now that SwiftPM can test executables directly
* update the test-sample docker setup to build & test th new samples
* fix a few typos and In/Out typealias left overs
* remove LinuxMain since its no longer required
motivation: with async/await, no need in closure based APIs
changes:
* Drop closure APIs
* Rename AsyncLambdaHandler -> LambdaHandler
* Removed unnecassary public acls from tests
- Lambda examples use async/await
- SwiftUI example uses async/await
- Handlers are marked with `@main`
- Executables are defined as `.executableTarget`
- Examples require Swift 5.5
motivation:
the runtime library has a stable API while the events are still moving target. In order to provide a 1.0 stable version we separate them out
changes:
* remove Events module
* update readme
* update Samples
* remove gateway example
Co-authored-by: Fabian Fett <fabianfett@apple.com>