Skip to content

Commit 19fc295

Browse files
fabianfetttomerd
andauthored
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 <[email protected]>
1 parent edbfa79 commit 19fc295

File tree

7 files changed

+140
-3
lines changed

7 files changed

+140
-3
lines changed

Package.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let package = Package(
1515
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
1616
],
1717
dependencies: [
18-
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.26.0")),
18+
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.28.0")),
1919
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.0.0")),
2020
.package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.1.0")),
2121
],
@@ -29,6 +29,7 @@ let package = Package(
2929
.product(name: "Logging", package: "swift-log"),
3030
.product(name: "Backtrace", package: "swift-backtrace"),
3131
.product(name: "NIOHTTP1", package: "swift-nio"),
32+
.product(name: "_NIOConcurrency", package: "swift-nio"),
3233
]),
3334
.testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
3435
.byName(name: "AWSLambdaRuntimeCore"),

Sources/AWSLambdaRuntime/Lambda+Codable.swift

+2
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ internal struct CodableVoidClosureWrapper<In: Decodable>: LambdaHandler {
7878
}
7979
}
8080

81+
// MARK: - Codable support
82+
8183
/// Implementation of a`ByteBuffer` to `In` decoding
8284
extension EventLoopLambdaHandler where In: Decodable {
8385
@inlinable

Sources/AWSLambdaRuntimeCore/Lambda.swift

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Glibc
1818
import Darwin.C
1919
#endif
2020

21+
import _NIOConcurrency
2122
import Backtrace
2223
import Logging
2324
import NIO

Sources/AWSLambdaRuntimeCore/LambdaHandler.swift

+50
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,56 @@ extension LambdaHandler {
8585
}
8686
}
8787

88+
// MARK: - AsyncLambdaHandler
89+
90+
#if compiler(>=5.5)
91+
/// Strongly typed, processing protocol for a Lambda that takes a user defined `In` and returns a user defined `Out` async.
92+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
93+
public protocol AsyncLambdaHandler: EventLoopLambdaHandler {
94+
/// The Lambda initialization method
95+
/// Use this method to initialize resources that will be used in every request.
96+
///
97+
/// Examples for this can be HTTP or database clients.
98+
/// - parameters:
99+
/// - context: Runtime `InitializationContext`.
100+
init(context: Lambda.InitializationContext) async throws
101+
102+
/// The Lambda handling method
103+
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
104+
///
105+
/// - parameters:
106+
/// - event: Event of type `In` representing the event or request.
107+
/// - context: Runtime `Context`.
108+
///
109+
/// - Returns: A Lambda result ot type `Out`.
110+
func handle(event: In, context: Lambda.Context) async throws -> Out
111+
}
112+
113+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
114+
extension AsyncLambdaHandler {
115+
public func handle(context: Lambda.Context, event: In) -> EventLoopFuture<Out> {
116+
let promise = context.eventLoop.makePromise(of: Out.self)
117+
promise.completeWithAsync {
118+
try await self.handle(event: event, context: context)
119+
}
120+
return promise.futureResult
121+
}
122+
}
123+
124+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
125+
extension AsyncLambdaHandler {
126+
public static func main() {
127+
Lambda.run { context -> EventLoopFuture<ByteBufferLambdaHandler> in
128+
let promise = context.eventLoop.makePromise(of: ByteBufferLambdaHandler.self)
129+
promise.completeWithAsync {
130+
try await Self(context: context)
131+
}
132+
return promise.futureResult
133+
}
134+
}
135+
}
136+
#endif
137+
88138
// MARK: - EventLoopLambdaHandler
89139

90140
/// Strongly typed, `EventLoopFuture` based processing protocol for a Lambda that takes a user defined `In` and returns a user defined `Out` asynchronously.

Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift renamed to Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift

+79-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import NIO
1717
import XCTest
1818

19-
class StringLambdaTest: XCTestCase {
19+
class LambdaHandlerTest: XCTestCase {
20+
// MARK: - Callback
21+
2022
func testCallbackSuccess() {
2123
let server = MockLambdaServer(behavior: Behavior())
2224
XCTAssertNoThrow(try server.start().wait())
@@ -77,6 +79,80 @@ class StringLambdaTest: XCTestCase {
7779
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
7880
}
7981

82+
#if compiler(>=5.5)
83+
84+
// MARK: - AsyncLambdaHandler
85+
86+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
87+
func testAsyncHandlerSuccess() {
88+
let server = MockLambdaServer(behavior: Behavior())
89+
XCTAssertNoThrow(try server.start().wait())
90+
defer { XCTAssertNoThrow(try server.stop().wait()) }
91+
92+
struct Handler: AsyncLambdaHandler {
93+
typealias In = String
94+
typealias Out = String
95+
96+
init(context: Lambda.InitializationContext) {}
97+
98+
func handle(event: String, context: Lambda.Context) async throws -> String {
99+
event
100+
}
101+
}
102+
103+
let maxTimes = Int.random(in: 1 ... 10)
104+
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes))
105+
let result = Lambda.run(configuration: configuration, factory: Handler.init)
106+
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
107+
}
108+
109+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
110+
func testVoidAsyncHandlerSuccess() {
111+
let server = MockLambdaServer(behavior: Behavior(result: .success(nil)))
112+
XCTAssertNoThrow(try server.start().wait())
113+
defer { XCTAssertNoThrow(try server.stop().wait()) }
114+
115+
struct Handler: AsyncLambdaHandler {
116+
typealias In = String
117+
typealias Out = Void
118+
119+
init(context: Lambda.InitializationContext) {}
120+
121+
func handle(event: String, context: Lambda.Context) async throws {}
122+
}
123+
124+
let maxTimes = Int.random(in: 1 ... 10)
125+
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes))
126+
let result = Lambda.run(configuration: configuration, factory: Handler.init)
127+
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
128+
}
129+
130+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
131+
func testAsyncHandlerFailure() {
132+
let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom"))))
133+
XCTAssertNoThrow(try server.start().wait())
134+
defer { XCTAssertNoThrow(try server.stop().wait()) }
135+
136+
struct Handler: AsyncLambdaHandler {
137+
typealias In = String
138+
typealias Out = String
139+
140+
init(context: Lambda.InitializationContext) {}
141+
142+
func handle(event: String, context: Lambda.Context) async throws -> String {
143+
throw TestError("boom")
144+
}
145+
}
146+
147+
let maxTimes = Int.random(in: 1 ... 10)
148+
let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes))
149+
let result = Lambda.run(configuration: configuration, factory: Handler.init)
150+
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
151+
}
152+
#endif
153+
154+
// MARK: - EventLoop
155+
80156
func testEventLoopSuccess() {
81157
let server = MockLambdaServer(behavior: Behavior())
82158
XCTAssertNoThrow(try server.start().wait())
@@ -137,6 +213,8 @@ class StringLambdaTest: XCTestCase {
137213
assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes)
138214
}
139215

216+
// MARK: - Closure
217+
140218
func testClosureSuccess() {
141219
let server = MockLambdaServer(behavior: Behavior())
142220
XCTAssertNoThrow(try server.start().wait())

Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class CodableLambdaTest: XCTestCase {
5353
var response: Response?
5454

5555
let closureWrapper = CodableClosureWrapper { (_, req: Request, completion: (Result<Response, Error>) -> Void) in
56-
XCTAssertEqual(request, request)
56+
XCTAssertEqual(request, req)
5757
completion(.success(Response(requestId: req.requestId)))
5858
}
5959

docker/docker-compose.al2.main.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,14 @@ services:
1010

1111
test:
1212
image: swift-aws-lambda:al2-main
13+
command: /bin/bash -cl "swift test --enable-test-discovery -Xswiftc -warnings-as-errors $${SANITIZER_ARG-} -Xswiftc -Xfrontend -Xswiftc -enable-experimental-concurrency"
1314

1415
test-samples:
1516
image: swift-aws-lambda:al2-main
17+
command: >-
18+
/bin/bash -clx "
19+
swift build -Xswiftc -Xfrontend -Xswiftc -enable-experimental-concurrency --package-path Examples/LambdaFunctions &&
20+
swift build -Xswiftc -Xfrontend -Xswiftc -enable-experimental-concurrency --package-path Examples/LocalDebugging/MyLambda"
1621
1722
shell:
1823
image: swift-aws-lambda:al2-main

0 commit comments

Comments
 (0)