async/await support (#186)

- Add an `AsyncLambdaHandler`. Will be renamed to `LambdaHandler` as soon as we drop the current callback based `LambdaHandler`.
- The default way to use an `AsyncLambdaHandler` is to use `@main` to execute it. Don't use `Lambda.run` for it. We wan't to remove `Lambda.run` for 1.0.

Co-authored-by: tomer doron <tomerd@apple.com>
This commit is contained in:
Fabian Fett
2021-04-27 22:47:11 +02:00
committed by GitHub
parent edbfa795b6
commit 19fc295aa0
7 changed files with 140 additions and 3 deletions
+2 -1
View File
@@ -15,7 +15,7 @@ let package = Package(
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.26.0")),
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.28.0")),
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")),
.package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")),
],
@@ -29,6 +29,7 @@ let package = Package(
.product(name: "Logging", package: "swift-log"),
.product(name: "Backtrace", package: "swift-backtrace"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "_NIOConcurrency", package: "swift-nio"),
]),
.testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
.byName(name: "AWSLambdaRuntimeCore"),
@@ -78,6 +78,8 @@ internal struct CodableVoidClosureWrapper<In: Decodable>: LambdaHandler {
}
}
// MARK: - Codable support
/// Implementation of a`ByteBuffer` to `In` decoding
extension EventLoopLambdaHandler where In: Decodable {
@inlinable
@@ -18,6 +18,7 @@ import Glibc
import Darwin.C
#endif
import _NIOConcurrency
import Backtrace
import Logging
import NIO
@@ -85,6 +85,56 @@ extension LambdaHandler {
}
}
// MARK: - AsyncLambdaHandler
#if compiler(>=5.5)
/// Strongly typed, processing protocol for a Lambda that takes a user defined `In` and returns a user defined `Out` async.
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
public protocol AsyncLambdaHandler: EventLoopLambdaHandler {
/// The Lambda initialization method
/// Use this method to initialize resources that will be used in every request.
///
/// Examples for this can be HTTP or database clients.
/// - parameters:
/// - context: Runtime `InitializationContext`.
init(context: Lambda.InitializationContext) async throws
/// The Lambda handling method
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
///
/// - parameters:
/// - event: Event of type `In` representing the event or request.
/// - context: Runtime `Context`.
///
/// - Returns: A Lambda result ot type `Out`.
func handle(event: In, context: Lambda.Context) async throws -> Out
}
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension AsyncLambdaHandler {
public func handle(context: Lambda.Context, event: In) -> EventLoopFuture<Out> {
let promise = context.eventLoop.makePromise(of: Out.self)
promise.completeWithAsync {
try await self.handle(event: event, context: context)
}
return promise.futureResult
}
}
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension AsyncLambdaHandler {
public static func main() {
Lambda.run { context -> EventLoopFuture<ByteBufferLambdaHandler> in
let promise = context.eventLoop.makePromise(of: ByteBufferLambdaHandler.self)
promise.completeWithAsync {
try await Self(context: context)
}
return promise.futureResult
}
}
}
#endif
// MARK: - EventLoopLambdaHandler
/// Strongly typed, `EventLoopFuture` based processing protocol for a Lambda that takes a user defined `In` and returns a user defined `Out` asynchronously.
@@ -16,7 +16,9 @@
import NIO
import XCTest
class StringLambdaTest: XCTestCase {
class LambdaHandlerTest: XCTestCase {
// MARK: - Callback
func testCallbackSuccess() {
let server = MockLambdaServer(behavior: Behavior())
XCTAssertNoThrow(try server.start().wait())
@@ -77,6 +79,80 @@ class StringLambdaTest: XCTestCase {
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
}
#if compiler(>=5.5)
// MARK: - AsyncLambdaHandler
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
func testAsyncHandlerSuccess() {
let server = MockLambdaServer(behavior: Behavior())
XCTAssertNoThrow(try server.start().wait())
defer { XCTAssertNoThrow(try server.stop().wait()) }
struct Handler: AsyncLambdaHandler {
typealias In = String
typealias Out = String
init(context: Lambda.InitializationContext) {}
func handle(event: String, context: Lambda.Context) async throws -> String {
event
}
}
let maxTimes = Int.random(in: 1 ... 10)
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes))
let result = Lambda.run(configuration: configuration, factory: Handler.init)
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
}
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
func testVoidAsyncHandlerSuccess() {
let server = MockLambdaServer(behavior: Behavior(result: .success(nil)))
XCTAssertNoThrow(try server.start().wait())
defer { XCTAssertNoThrow(try server.stop().wait()) }
struct Handler: AsyncLambdaHandler {
typealias In = String
typealias Out = Void
init(context: Lambda.InitializationContext) {}
func handle(event: String, context: Lambda.Context) async throws {}
}
let maxTimes = Int.random(in: 1 ... 10)
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes))
let result = Lambda.run(configuration: configuration, factory: Handler.init)
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
}
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
func testAsyncHandlerFailure() {
let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom"))))
XCTAssertNoThrow(try server.start().wait())
defer { XCTAssertNoThrow(try server.stop().wait()) }
struct Handler: AsyncLambdaHandler {
typealias In = String
typealias Out = String
init(context: Lambda.InitializationContext) {}
func handle(event: String, context: Lambda.Context) async throws -> String {
throw TestError("boom")
}
}
let maxTimes = Int.random(in: 1 ... 10)
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes))
let result = Lambda.run(configuration: configuration, factory: Handler.init)
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
}
#endif
// MARK: - EventLoop
func testEventLoopSuccess() {
let server = MockLambdaServer(behavior: Behavior())
XCTAssertNoThrow(try server.start().wait())
@@ -137,6 +213,8 @@ class StringLambdaTest: XCTestCase {
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
}
// MARK: - Closure
func testClosureSuccess() {
let server = MockLambdaServer(behavior: Behavior())
XCTAssertNoThrow(try server.start().wait())
@@ -53,7 +53,7 @@ class CodableLambdaTest: XCTestCase {
var response: Response?
let closureWrapper = CodableClosureWrapper { (_, req: Request, completion: (Result<Response, Error>) -> Void) in
XCTAssertEqual(request, request)
XCTAssertEqual(request, req)
completion(.success(Response(requestId: req.requestId)))
}
+5
View File
@@ -10,9 +10,14 @@ services:
test:
image: swift-aws-lambda:al2-main
command: /bin/bash -cl "swift test --enable-test-discovery -Xswiftc -warnings-as-errors $${SANITIZER_ARG-} -Xswiftc -Xfrontend -Xswiftc -enable-experimental-concurrency"
test-samples:
image: swift-aws-lambda:al2-main
command: >-
/bin/bash -clx "
swift build -Xswiftc -Xfrontend -Xswiftc -enable-experimental-concurrency --package-path Examples/LambdaFunctions &&
swift build -Xswiftc -Xfrontend -Xswiftc -enable-experimental-concurrency --package-path Examples/LocalDebugging/MyLambda"
shell:
image: swift-aws-lambda:al2-main