diff --git a/Package.swift b/Package.swift index 4f6ebc22..02641a5a 100644 --- a/Package.swift +++ b/Package.swift @@ -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"), diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 620c52e2..e80022a8 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -78,6 +78,8 @@ internal struct CodableVoidClosureWrapper: LambdaHandler { } } +// MARK: - Codable support + /// Implementation of a`ByteBuffer` to `In` decoding extension EventLoopLambdaHandler where In: Decodable { @inlinable diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 5dc27648..264a5138 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -18,6 +18,7 @@ import Glibc import Darwin.C #endif +import _NIOConcurrency import Backtrace import Logging import NIO diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 008620d8..542b1bc4 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -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 { + 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 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. diff --git a/Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift similarity index 77% rename from Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift rename to Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift index 8e880296..53a56910 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift @@ -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()) diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift index 9aa3f72a..814977fa 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift @@ -53,7 +53,7 @@ class CodableLambdaTest: XCTestCase { var response: Response? let closureWrapper = CodableClosureWrapper { (_, req: Request, completion: (Result) -> Void) in - XCTAssertEqual(request, request) + XCTAssertEqual(request, req) completion(.success(Response(requestId: req.requestId))) } diff --git a/docker/docker-compose.al2.main.yaml b/docker/docker-compose.al2.main.yaml index c63a13e2..741c8a43 100644 --- a/docker/docker-compose.al2.main.yaml +++ b/docker/docker-compose.al2.main.yaml @@ -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