From 457e0c389d5704bfedcc35b9fd0c567b880db8f9 Mon Sep 17 00:00:00 2001 From: tom doron Date: Fri, 30 Sep 2022 15:37:00 -0700 Subject: [PATCH 1/6] refactor API towards 1.0 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 coding bridge * reuse output ByteBuffer to reduce allocationsi * add no-op initializer for simple landa use cases * update callsites and tests --- Package@swift-5.4.swift | 55 ----- Package@swift-5.5.swift | 55 ----- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 55 +++-- .../AWSLambdaRuntimeCore/Lambda+String.swift | 41 +++- Sources/AWSLambdaRuntimeCore/Lambda.swift | 21 +- .../AWSLambdaRuntimeCore/LambdaContext.swift | 12 +- .../AWSLambdaRuntimeCore/LambdaHandler.swift | 198 ++++++++++++++---- .../AWSLambdaRuntimeCore/LambdaRunner.swift | 12 +- .../AWSLambdaRuntimeCore/LambdaRuntime.swift | 38 +++- .../LambdaRuntimeClient.swift | 5 - Sources/AWSLambdaRuntimeCore/Sendable.swift | 21 -- Sources/AWSLambdaRuntimeCore/Terminator.swift | 4 +- Sources/AWSLambdaTesting/Lambda+Testing.swift | 18 +- .../LambdaHandlerTest.swift | 78 ++++--- .../LambdaTest.swift | 27 +-- Tests/AWSLambdaRuntimeCoreTests/Utils.swift | 17 +- .../Lambda+CodableTest.swift | 128 +++++------ Tests/AWSLambdaTestingTests/Tests.swift | 59 +++--- docker/Dockerfile | 4 +- docker/docker-compose.al2.54.yaml | 18 -- docker/docker-compose.al2.55.yaml | 18 -- docker/docker-compose.al2.56.yaml | 18 -- docker/docker-compose.al2.57.yaml | 2 +- docker/docker-compose.yaml | 2 +- 24 files changed, 471 insertions(+), 435 deletions(-) delete mode 100644 Package@swift-5.4.swift delete mode 100644 Package@swift-5.5.swift delete mode 100644 Sources/AWSLambdaRuntimeCore/Sendable.swift delete mode 100644 docker/docker-compose.al2.54.yaml delete mode 100644 docker/docker-compose.al2.55.yaml delete mode 100644 docker/docker-compose.al2.56.yaml diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift deleted file mode 100644 index 90ace14b..00000000 --- a/Package@swift-5.4.swift +++ /dev/null @@ -1,55 +0,0 @@ -// swift-tools-version:5.4 - -import PackageDescription - -let package = Package( - name: "swift-aws-lambda-runtime", - products: [ - // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods - .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), - // this has all the main functionality for lambda and it does not link Foundation - .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - // for testing only - .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")), - .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")), - .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")), - ], - targets: [ - .target(name: "AWSLambdaRuntime", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .target(name: "AWSLambdaRuntimeCore", dependencies: [ - .product(name: "Logging", package: "swift-log"), - .product(name: "Backtrace", package: "swift-backtrace"), - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), - .product(name: "NIOPosix", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOTestUtils", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .byName(name: "AWSLambdaRuntime"), - ]), - // testing helper - .target(name: "AWSLambdaTesting", dependencies: [ - .byName(name: "AWSLambdaRuntime"), - .product(name: "NIO", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]), - // for perf testing - .target(name: "MockServer", dependencies: [ - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIO", package: "swift-nio"), - ]), - ] -) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift deleted file mode 100644 index 90ace14b..00000000 --- a/Package@swift-5.5.swift +++ /dev/null @@ -1,55 +0,0 @@ -// swift-tools-version:5.4 - -import PackageDescription - -let package = Package( - name: "swift-aws-lambda-runtime", - products: [ - // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods - .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), - // this has all the main functionality for lambda and it does not link Foundation - .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - // for testing only - .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")), - .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")), - .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")), - ], - targets: [ - .target(name: "AWSLambdaRuntime", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .target(name: "AWSLambdaRuntimeCore", dependencies: [ - .product(name: "Logging", package: "swift-log"), - .product(name: "Backtrace", package: "swift-backtrace"), - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), - .product(name: "NIOPosix", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOTestUtils", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .byName(name: "AWSLambdaRuntime"), - ]), - // testing helper - .target(name: "AWSLambdaTesting", dependencies: [ - .byName(name: "AWSLambdaRuntime"), - .product(name: "NIO", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]), - // for perf testing - .target(name: "MockServer", dependencies: [ - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIO", package: "swift-nio"), - ]), - ] -) diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 219abe52..584598bf 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -19,7 +19,45 @@ import class Foundation.JSONEncoder import NIOCore import NIOFoundationCompat -// MARK: - Codable support +// MARK: - LambdaHandler Codable support + +/// Implementation of a`ByteBuffer` to `Event` decoding. +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Event: Decodable { + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + try self.decoder.decode(Event.self, from: buffer) + } +} + +/// Implementation of `Output` to `ByteBuffer` encoding. +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Output: Encodable { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + try self.encoder.encode(value, into: &buffer) + } +} + +/// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`. +/// Advanced users that want to inject their own codec can do it by overriding these functions. +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Event: Decodable { + public var decoder: LambdaCodableDecoder { + Lambda.defaultJSONDecoder + } +} + +/// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`. +/// Advanced users that want to inject their own codec can do it by overriding these functions. +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Output: Encodable { + public var encoder: LambdaCodableEncoder { + Lambda.defaultJSONEncoder + } +} + +// MARK: - EventLoopLambdaHandler Codable support /// Implementation of a`ByteBuffer` to `Event` decoding. extension EventLoopLambdaHandler where Event: Decodable { @@ -32,8 +70,8 @@ extension EventLoopLambdaHandler where Event: Decodable { /// Implementation of `Output` to `ByteBuffer` encoding. extension EventLoopLambdaHandler where Output: Encodable { @inlinable - public func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer? { - try self.encoder.encode(value, using: allocator) + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + try self.encoder.encode(value, into: &buffer) } } @@ -58,7 +96,7 @@ public protocol LambdaCodableDecoder { } public protocol LambdaCodableEncoder { - func encode(_ value: T, using allocator: ByteBufferAllocator) throws -> ByteBuffer + func encode(_ value: T, into buffer: inout ByteBuffer) throws } extension Lambda { @@ -68,11 +106,4 @@ extension Lambda { extension JSONDecoder: LambdaCodableDecoder {} -extension JSONEncoder: LambdaCodableEncoder { - public func encode(_ value: T, using allocator: ByteBufferAllocator) throws -> ByteBuffer where T: Encodable { - // nio will resize the buffer if necessary - var buffer = allocator.buffer(capacity: 1024) - try self.encode(value, into: &buffer) - return buffer - } -} +extension JSONEncoder: LambdaCodableEncoder {} diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift index 8e3da3e5..49c6dc80 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift @@ -13,25 +13,46 @@ //===----------------------------------------------------------------------===// import NIOCore -extension EventLoopLambdaHandler where Event == String { +// MARK: - LambdaHandler String support + +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Event == String { /// Implementation of a `ByteBuffer` to `String` decoding. @inlinable public func decode(buffer: ByteBuffer) throws -> String { - var buffer = buffer - guard let string = buffer.readString(length: buffer.readableBytes) else { - fatalError("buffer.readString(length: buffer.readableBytes) failed") + guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { + throw CodecError.invalidString } - return string + return value } } -extension EventLoopLambdaHandler where Output == String { +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Output == String { /// Implementation of `String` to `ByteBuffer` encoding. @inlinable - public func encode(allocator: ByteBufferAllocator, value: String) throws -> ByteBuffer? { - // FIXME: reusable buffer - var buffer = allocator.buffer(capacity: value.utf8.count) + public func encode(value: String, into buffer: inout ByteBuffer) throws { + buffer.writeString(value) + } +} + +// MARK: - EventLoopLambdaHandler String support + +extension EventLoopLambdaHandler where Event == String { + /// Implementation of `String` to `ByteBuffer` encoding. + @inlinable + public func decode(buffer: ByteBuffer) throws -> String { + guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { + throw CodecError.invalidString + } + return value + } +} + +extension EventLoopLambdaHandler where Output == String { + /// Implementation of a `ByteBuffer` to `String` decoding. + @inlinable + public func encode(value: String, into buffer: inout ByteBuffer) throws { buffer.writeString(value) - return buffer } } diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 0f976c76..f4dad014 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -33,9 +33,25 @@ public enum Lambda { /// - handlerType: The Handler to create and invoke. /// /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. - internal static func run( + + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + internal static func run( + configuration: LambdaConfiguration = .init(), + handlerType: Handler.Type + ) -> Result { + Self.run(configuration: configuration, handlerType: CodableLambdaHandler.self) + } + + internal static func run( configuration: LambdaConfiguration = .init(), handlerType: Handler.Type + ) -> Result { + Self.run(configuration: configuration, handlerType: CodableEventLoopLambdaHandler.self) + } + + internal static func run( + configuration: LambdaConfiguration = .init(), + handlerType: (some ByteBufferLambdaHandler).Type ) -> Result { let _run = { (configuration: LambdaConfiguration) -> Result in Backtrace.install() @@ -44,7 +60,7 @@ public enum Lambda { var result: Result! MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in - let runtime = LambdaRuntime(eventLoop: eventLoop, logger: logger, configuration: configuration) + let runtime = LambdaRuntime(handlerType, eventLoop: eventLoop, logger: logger, configuration: configuration) #if DEBUG let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in logger.info("intercepted signal: \(signal)") @@ -66,7 +82,6 @@ public enum Lambda { result = lifecycleResult } } - logger.info("shutdown completed") return result } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index 7118a5ac..a957ce2c 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -12,15 +12,9 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=5.6) @preconcurrency import Dispatch -@preconcurrency import Logging -@preconcurrency import NIOCore -#else -import Dispatch import Logging import NIOCore -#endif // MARK: - InitializationContext @@ -28,7 +22,7 @@ import NIOCore /// The Lambda runtime generates and passes the `LambdaInitializationContext` to the Handlers /// ``ByteBufferLambdaHandler/makeHandler(context:)`` or ``LambdaHandler/init(context:)`` /// as an argument. -public struct LambdaInitializationContext: _AWSLambdaSendable { +public struct LambdaInitializationContext: Sendable { /// `Logger` to log with. /// /// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. @@ -71,8 +65,8 @@ public struct LambdaInitializationContext: _AWSLambdaSendable { /// Lambda runtime context. /// The Lambda runtime generates and passes the `LambdaContext` to the Lambda handler as an argument. -public struct LambdaContext: CustomDebugStringConvertible, _AWSLambdaSendable { - final class _Storage: _AWSLambdaSendable { +public struct LambdaContext: CustomDebugStringConvertible, Sendable { + final class _Storage: Sendable { let requestID: String let traceID: String let invokedFunctionARN: String diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 8f4b9f6e..5b1ae8ff 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -17,7 +17,6 @@ import NIOCore // MARK: - LambdaHandler -#if compiler(>=5.5) && canImport(_Concurrency) /// Strongly typed, processing protocol for a Lambda that takes a user defined /// ``EventLoopLambdaHandler/Event`` and returns a user defined /// ``EventLoopLambdaHandler/Output`` asynchronously. @@ -26,13 +25,16 @@ import NIOCore /// level protocols ``EventLoopLambdaHandler`` and /// ``ByteBufferLambdaHandler``. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -public protocol LambdaHandler: 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 ``LambdaInitializationContext``. +public protocol LambdaHandler { + /// The lambda functions input. In most cases this should be `Codable`. If your event originates from an + /// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), + /// which provides a number of commonly used AWS Event implementations. + associatedtype Event + /// The lambda functions output. Can be `Void`. + associatedtype Output + + init() + init(context: LambdaInitializationContext) async throws /// The Lambda handling method. @@ -44,45 +46,129 @@ public protocol LambdaHandler: EventLoopLambdaHandler { /// /// - Returns: A Lambda result ot type `Output`. func handle(_ event: Event, context: LambdaContext) async throws -> Output + + /// Encode a response of type ``Output`` to `ByteBuffer`. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// - parameters: + /// - value: Response of type ``Output``. + /// - buffer: A `ByteBuffer` to encode into, will be overwritten. + /// + /// - Returns: A `ByteBuffer` with the encoded version of the `value`. + func encode(value: Output, into buffer: inout ByteBuffer) throws + + /// Decode a `ByteBuffer` to a request or event of type ``Event``. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// + /// - parameters: + /// - buffer: The `ByteBuffer` to decode. + /// + /// - Returns: A request or event of type ``Event``. + func decode(buffer: ByteBuffer) throws -> Event } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler { - public static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { - let promise = context.eventLoop.makePromise(of: Self.self) + public init() { + self.init() + } + + /// 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 ``LambdaInitializationContext``. + public init(context: LambdaInitializationContext) async throws { + self.init() + } +} + +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +final class CodableLambdaHandler: ByteBufferLambdaHandler { + private let handler: Underlying + private var outputBuffer: ByteBuffer + + @inlinable + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: CodableLambdaHandler.self) promise.completeWithTask { - try await Self(context: context) + let handler = try await Underlying(context: context) + return CodableLambdaHandler(handler: handler, allocator: context.allocator) } return promise.futureResult } - public func handle(_ event: Event, context: LambdaContext) -> EventLoopFuture { - let promise = context.eventLoop.makePromise(of: Output.self) - // using an unchecked sendable wrapper for the handler - // this is safe since lambda runtime is designed to calls the handler serially - let handler = UncheckedSendableHandler(underlying: self) + @inlinable + init(handler: Underlying, allocator: ByteBufferAllocator) { + self.handler = handler + self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) + } + + @inlinable + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: ByteBuffer?.self) promise.completeWithTask { - try await handler.handle(event, context: context) + let input: Underlying.Event + do { + input = try self.handler.decode(buffer: buffer) + } catch { + throw CodecError.requestDecoding(error) + } + + let output = try await self.handler.handle(input, context: context) + + do { + self.outputBuffer.clear() + try self.handler.encode(value: output, into: &self.outputBuffer) + return self.outputBuffer + } catch { + throw CodecError.responseEncoding(error) + } } return promise.futureResult } } +/// Implementation of `ByteBuffer` to `Void` decoding. +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler where Output == Void { + @inlinable + public func encode(value: Void, into buffer: inout ByteBuffer) throws {} +} + +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension LambdaHandler { + /// Initializes and runs the Lambda function. + /// + /// If you precede your ``ByteBufferLambdaHandler`` conformer's declaration with the + /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) + /// attribute, the system calls the conformer's `main()` method to launch the lambda function. + /// + /// The lambda runtime provides a default implementation of the method that manages the launch + /// process. + public static func main() { + _ = Lambda.run(configuration: .init(), handlerType: Self.self) + } +} + /// unchecked sendable wrapper for the handler /// this is safe since lambda runtime is designed to calls the handler serially +@usableFromInline @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -fileprivate struct UncheckedSendableHandler: @unchecked Sendable where Event == Underlying.Event, Output == Underlying.Output { +internal struct UncheckedSendableHandler: @unchecked Sendable where Event == Underlying.Event, Output == Underlying.Output { + @usableFromInline let underlying: Underlying + @inlinable init(underlying: Underlying) { self.underlying = underlying } + @inlinable func handle(_ event: Event, context: LambdaContext) async throws -> Output { try await self.underlying.handle(event, context: context) } } -#endif // MARK: - EventLoopLambdaHandler @@ -100,7 +186,7 @@ fileprivate struct UncheckedSendableHandler EventLoopFuture + /// The Lambda handling method. /// Concrete Lambda handlers implement this method to provide the Lambda functionality. /// @@ -122,11 +216,11 @@ public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler { /// Encode a response of type ``Output`` to `ByteBuffer`. /// Concrete Lambda handlers implement this method to provide coding functionality. /// - parameters: - /// - allocator: A `ByteBufferAllocator` to help allocate the `ByteBuffer`. /// - value: Response of type ``Output``. + /// - buffer: A `ByteBuffer` to encode into, will be overwritten. /// /// - Returns: A `ByteBuffer` with the encoded version of the `value`. - func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer? + func encode(value: Output, into buffer: inout ByteBuffer) throws /// Decode a `ByteBuffer` to a request or event of type ``Event``. /// Concrete Lambda handlers implement this method to provide coding functionality. @@ -138,20 +232,43 @@ public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } -extension EventLoopLambdaHandler { - /// Driver for `ByteBuffer` -> ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding +/// Implementation of `ByteBuffer` to `Void` decoding. +extension EventLoopLambdaHandler where Output == Void { + @inlinable + public func encode(value: Void, into buffer: inout ByteBuffer) throws {} +} + +internal final class CodableEventLoopLambdaHandler: ByteBufferLambdaHandler { + private let handler: Underlying + private var outputBuffer: ByteBuffer + @inlinable - public func handle(_ event: ByteBuffer, context: LambdaContext) -> EventLoopFuture { - let input: Event + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { + Underlying.makeHandler(context: context).map { handler -> CodableEventLoopLambdaHandler in + CodableEventLoopLambdaHandler(handler: handler, allocator: context.allocator) + } + } + + @inlinable + init(handler: Underlying, allocator: ByteBufferAllocator) { + self.handler = handler + self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) + } + + @inlinable + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture { + let input: Underlying.Event do { - input = try self.decode(buffer: event) + input = try self.handler.decode(buffer: buffer) } catch { return context.eventLoop.makeFailedFuture(CodecError.requestDecoding(error)) } - return self.handle(input, context: context).flatMapThrowing { output in + return self.handler.handle(input, context: context).flatMapThrowing { output in do { - return try self.encode(allocator: context.allocator, value: output) + self.outputBuffer.clear() + try self.handler.encode(value: output, into: &self.outputBuffer) + return self.outputBuffer } catch { throw CodecError.responseEncoding(error) } @@ -159,11 +276,17 @@ extension EventLoopLambdaHandler { } } -/// Implementation of `ByteBuffer` to `Void` decoding. -extension EventLoopLambdaHandler where Output == Void { - @inlinable - public func encode(allocator: ByteBufferAllocator, value: Void) throws -> ByteBuffer? { - nil +extension EventLoopLambdaHandler { + /// Initializes and runs the Lambda function. + /// + /// If you precede your ``ByteBufferLambdaHandler`` conformer's declaration with the + /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) + /// attribute, the system calls the conformer's `main()` method to launch the lambda function. + /// + /// The lambda runtime provides a default implementation of the method that manages the launch + /// process. + public static func main() { + _ = Lambda.run(configuration: .init(), handlerType: Self.self) } } @@ -176,7 +299,7 @@ extension EventLoopLambdaHandler where Output == Void { /// ``LambdaHandler`` based APIs. /// Most users are not expected to use this protocol. public protocol ByteBufferLambdaHandler { - /// Create your Lambda handler for the runtime. + /// Create a Lambda handler for the runtime. /// /// Use this to initialize all your resources that you want to cache between invocations. This could be database /// connections and HTTP clients for example. It is encouraged to use the given `EventLoop`'s conformance @@ -193,7 +316,7 @@ public protocol ByteBufferLambdaHandler { /// /// - Returns: An `EventLoopFuture` to report the result of the Lambda back to the runtime engine. /// The `EventLoopFuture` should be completed with either a response encoded as `ByteBuffer` or an `Error`. - func handle(_ event: ByteBuffer, context: LambdaContext) -> EventLoopFuture + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture } extension ByteBufferLambdaHandler { @@ -210,8 +333,11 @@ extension ByteBufferLambdaHandler { } } +// MARK: - Other + @usableFromInline enum CodecError: Error { case requestDecoding(Error) case responseEncoding(Error) + case invalidString } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift index 54e90f95..c8f7c0b5 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift @@ -33,7 +33,7 @@ internal final class LambdaRunner { /// Run the user provided initializer. This *must* only be called once. /// /// - Returns: An `EventLoopFuture` fulfilled with the outcome of the initialization. - func initialize(logger: Logger, terminator: LambdaTerminator, handlerType: Handler.Type) -> EventLoopFuture { + func initialize(handlerType: (some ByteBufferLambdaHandler).Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture { logger.debug("initializing lambda") // 1. create the handler from the factory // 2. report initialization error if one occurred @@ -43,7 +43,9 @@ internal final class LambdaRunner { allocator: self.allocator, terminator: terminator ) - return Handler.makeHandler(context: context) + + return handlerType.makeHandler(context: context) + .map { $0 as any ByteBufferLambdaHandler } // Hopping back to "our" EventLoop is important in case the factory returns a future // that originated from a foreign EventLoop/EventLoopGroup. // This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops @@ -58,7 +60,7 @@ internal final class LambdaRunner { } } - func run(logger: Logger, handler: Handler) -> EventLoopFuture { + func run(logger: Logger, handler: any ByteBufferLambdaHandler) -> EventLoopFuture { logger.debug("lambda invocation sequence starting") // 1. request invocation from lambda runtime engine self.isGettingNextInvocation = true @@ -73,10 +75,10 @@ internal final class LambdaRunner { allocator: self.allocator, invocation: invocation ) - logger.debug("sending invocation to lambda handler \(handler)") + logger.debug("sending invocation to lambda handler") return handler.handle(bytes, context: context) // Hopping back to "our" EventLoop is important in case the handler returns a future that - // originiated from a foreign EventLoop/EventLoopGroup. + // originated from a foreign EventLoop/EventLoopGroup. // This can happen if the handler uses a library (lets say a DB client) that manages its own threads/loops // for whatever reason and returns a future that originated from that foreign EventLoop. .hop(to: self.eventLoop) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index cd2b9588..0ca245e0 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -19,12 +19,14 @@ import NIOCore /// `LambdaRuntime` manages the Lambda process lifecycle. /// /// Use this API, if you build a higher level web framework which shall be able to run inside the Lambda environment. -public final class LambdaRuntime { +public final class LambdaRuntime { private let eventLoop: EventLoop private let shutdownPromise: EventLoopPromise private let logger: Logger private let configuration: LambdaConfiguration + private let handlerType: any ByteBufferLambdaHandler.Type + private var state = State.idle { willSet { self.eventLoop.assertInEventLoop() @@ -32,21 +34,43 @@ public final class LambdaRuntime { } } + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { + self.init(CodableLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``EventLoopLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { + self.init(CodableEventLoopLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + /// Create a new `LambdaRuntime`. /// /// - parameters: /// - handlerType: The ``ByteBufferLambdaHandler`` type the `LambdaRuntime` shall create and manage. /// - eventLoop: An `EventLoop` to run the Lambda on. /// - logger: A `Logger` to log the Lambda events. - public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { - self.init(eventLoop: eventLoop, logger: logger, configuration: .init()) + public convenience init(_ handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger) { + self.init(handlerType, eventLoop: eventLoop, logger: logger, configuration: .init()) } - init(eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { + init(_ handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { self.eventLoop = eventLoop self.shutdownPromise = eventLoop.makePromise(of: Int.self) self.logger = logger self.configuration = configuration + self.handlerType = handlerType } deinit { @@ -85,7 +109,7 @@ public final class LambdaRuntime { let terminator = LambdaTerminator() let runner = LambdaRunner(eventLoop: self.eventLoop, configuration: self.configuration) - let startupFuture = runner.initialize(logger: logger, terminator: terminator, handlerType: Handler.self) + let startupFuture = runner.initialize(handlerType: self.handlerType, logger: logger, terminator: terminator) startupFuture.flatMap { handler -> EventLoopFuture> in // after the startup future has succeeded, we have a handler that we can use // to `run` the lambda. @@ -175,7 +199,7 @@ public final class LambdaRuntime { private enum State { case idle case initializing - case active(LambdaRunner, Handler) + case active(LambdaRunner, any ByteBufferLambdaHandler) case shuttingdown case shutdown @@ -197,6 +221,4 @@ public final class LambdaRuntime { } /// This is safe since lambda runtime synchronizes by dispatching all methods to a single `EventLoop` -#if compiler(>=5.5) && canImport(_Concurrency) extension LambdaRuntime: @unchecked Sendable {} -#endif diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift index 29d04b9d..bcc65736 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift @@ -13,13 +13,8 @@ //===----------------------------------------------------------------------===// import Logging -#if compiler(>=5.6) -@preconcurrency import NIOCore -@preconcurrency import NIOHTTP1 -#else import NIOCore import NIOHTTP1 -#endif /// An HTTP based client for AWS Runtime Engine. This encapsulates the RESTful methods exposed by the Runtime Engine: /// * /runtime/invocation/next diff --git a/Sources/AWSLambdaRuntimeCore/Sendable.swift b/Sources/AWSLambdaRuntimeCore/Sendable.swift deleted file mode 100644 index 936403e4..00000000 --- a/Sources/AWSLambdaRuntimeCore/Sendable.swift +++ /dev/null @@ -1,21 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftAWSLambdaRuntime open source project -// -// Copyright (c) 2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -// Sendable bridging types - -#if compiler(>=5.6) -public typealias _AWSLambdaSendable = Sendable -#else -public typealias _AWSLambdaSendable = Any -#endif diff --git a/Sources/AWSLambdaRuntimeCore/Terminator.swift b/Sources/AWSLambdaRuntimeCore/Terminator.swift index 6a0b65c0..cba8fd99 100644 --- a/Sources/AWSLambdaRuntimeCore/Terminator.swift +++ b/Sources/AWSLambdaRuntimeCore/Terminator.swift @@ -100,7 +100,7 @@ extension LambdaTerminator { extension LambdaTerminator { fileprivate final class Storage { - private let lock: Lock + private let lock: NIOLock private var index: [RegistrationKey] private var map: [RegistrationKey: (name: String, handler: Handler)] @@ -140,7 +140,5 @@ extension LambdaTerminator { // Ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks // We can transition this to an actor once we drop support for older Swift versions -#if compiler(>=5.5) && canImport(_Concurrency) extension LambdaTerminator: @unchecked Sendable {} extension LambdaTerminator.Storage: @unchecked Sendable {} -#endif diff --git a/Sources/AWSLambdaTesting/Lambda+Testing.swift b/Sources/AWSLambdaTesting/Lambda+Testing.swift index 11e2bf89..785751e6 100644 --- a/Sources/AWSLambdaTesting/Lambda+Testing.swift +++ b/Sources/AWSLambdaTesting/Lambda+Testing.swift @@ -33,8 +33,7 @@ // XCTAssertEqual(result, "echo" + input) // } -#if compiler(>=5.5) && canImport(_Concurrency) -import AWSLambdaRuntime +@testable import AWSLambdaRuntime import Dispatch import Logging import NIOCore @@ -63,15 +62,15 @@ extension Lambda { _ handlerType: Handler.Type, with event: Handler.Event, using config: TestConfig = .init() - ) throws -> Handler.Output { + ) async throws -> Handler.Output { let logger = Logger(label: "test") + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { try! eventLoopGroup.syncShutdownGracefully() } let eventLoop = eventLoopGroup.next() - let promise = eventLoop.makePromise(of: Handler.self) let initContext = LambdaInitializationContext.__forTestsOnly( logger: logger, eventLoop: eventLoop @@ -86,14 +85,7 @@ extension Lambda { eventLoop: eventLoop ) - promise.completeWithTask { - try await Handler(context: initContext) - } - let handler = try promise.futureResult.wait() - - return try eventLoop.flatSubmit { - handler.handle(event, context: context) - }.wait() + let handler = try await Handler(context: initContext) + return try await handler.handle(event, context: context) } } -#endif diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift index 98b49ca7..99e63ca0 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift @@ -17,8 +17,6 @@ import NIOCore import XCTest class LambdaHandlerTest: XCTestCase { - #if compiler(>=5.5) && canImport(_Concurrency) - // MARK: - LambdaHandler @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) @@ -28,9 +26,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct TestBootstrapHandler: LambdaHandler { - typealias Event = String - typealias Output = String - var initialized = false init(context: LambdaInitializationContext) async throws { @@ -47,7 +42,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) @@ -80,17 +75,55 @@ class LambdaHandlerTest: XCTestCase { } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testHandlerSuccess() { + func testBootstrapInitSimple() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { - typealias Event = String - typealias Output = String + struct TestBootstrapHandler: LambdaHandler { + var initialized = false + + init() { + XCTAssertFalse(self.initialized) + self.initialized = true + } + + func handle(_ event: String, context: LambdaContext) async throws -> String { + event + } + } + + let maxTimes = Int.random(in: 10 ... 20) + let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) + } + + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + func testBootstrapNoInit() { + let server = MockLambdaServer(behavior: Behavior()) + XCTAssertNoThrow(try server.start().wait()) + defer { XCTAssertNoThrow(try server.stop().wait()) } + + struct TestBootstrapHandler: LambdaHandler { + func handle(_ event: String, context: LambdaContext) async throws -> String { + event + } + } - init(context: LambdaInitializationContext) {} + let maxTimes = Int.random(in: 10 ... 20) + let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) + } + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + func testHandlerSuccess() { + let server = MockLambdaServer(behavior: Behavior()) + XCTAssertNoThrow(try server.start().wait()) + defer { XCTAssertNoThrow(try server.stop().wait()) } + + struct Handler: LambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { event } @@ -99,7 +132,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) @@ -109,11 +142,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: LambdaHandler { - typealias Event = String - typealias Output = Void - - init(context: LambdaInitializationContext) {} - func handle(_ event: String, context: LambdaContext) async throws {} } @@ -121,7 +149,7 @@ class LambdaHandlerTest: XCTestCase { let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) @@ -131,11 +159,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) {} - func handle(_ event: String, context: LambdaContext) async throws -> String { throw TestError("boom") } @@ -144,9 +167,8 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - #endif // MARK: - EventLoopLambdaHandler @@ -171,7 +193,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testVoidEventLoopSuccess() { @@ -195,7 +217,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testEventLoopFailure() { @@ -219,7 +241,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testEventLoopBootstrapFailure() { diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 1cf6aa1a..956832a5 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -13,14 +13,9 @@ //===----------------------------------------------------------------------===// @testable import AWSLambdaRuntimeCore -#if compiler(>=5.6) -@preconcurrency import Logging -@preconcurrency import NIOPosix -#else import Logging -import NIOPosix -#endif import NIOCore +import NIOPosix import XCTest class LambdaTest: XCTestCase { @@ -32,7 +27,7 @@ class LambdaTest: XCTestCase { let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testFailure() { @@ -43,7 +38,7 @@ class LambdaTest: XCTestCase { let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: RuntimeErrorHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testBootstrapFailure() { @@ -141,7 +136,7 @@ class LambdaTest: XCTestCase { let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: 1)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: 1) + assertLambdaRuntimeResult(result, shouldHaveRun: 1) } func testKeepAliveServer() { @@ -152,7 +147,7 @@ class LambdaTest: XCTestCase { let maxTimes = 10 let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testNoKeepAliveServer() { @@ -163,7 +158,7 @@ class LambdaTest: XCTestCase { let maxTimes = 10 let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testServerFailure() { @@ -286,9 +281,15 @@ class LambdaTest: XCTestCase { logger.info("hello") let runner = LambdaRunner(eventLoop: eventLoopGroup.next(), configuration: configuration) - try runner.run(logger: logger, handler: handler1).wait() + try runner.run( + logger: logger, + handler: CodableEventLoopLambdaHandler( + handler: handler1, + allocator: ByteBufferAllocator() + ) + ).wait() - try runner.initialize(logger: logger, terminator: LambdaTerminator(), handlerType: Handler.self).flatMap { handler2 in + try runner.initialize(handlerType: CodableEventLoopLambdaHandler.self, logger: logger, terminator: LambdaTerminator()).flatMap { handler2 in runner.run(logger: logger, handler: handler2) }.wait() } diff --git a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift index 49cd7708..e3ded229 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift @@ -18,7 +18,16 @@ import NIOCore import NIOPosix import XCTest -func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { + try runLambda(behavior: behavior, handlerType: CodableLambdaHandler.self) +} + +func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { + try runLambda(behavior: behavior, handlerType: CodableEventLoopLambdaHandler.self) +} + +func runLambda(behavior: LambdaServerBehavior, handlerType: (some ByteBufferLambdaHandler).Type) throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let logger = Logger(label: "TestLogger") @@ -27,17 +36,17 @@ func runLambda(behavior: LambdaServerBehavior, let runner = LambdaRunner(eventLoop: eventLoopGroup.next(), configuration: configuration) let server = try MockLambdaServer(behavior: behavior).start().wait() defer { XCTAssertNoThrow(try server.stop().wait()) } - try runner.initialize(logger: logger, terminator: terminator, handlerType: handlerType).flatMap { handler in + try runner.initialize(handlerType: handlerType, logger: logger, terminator: terminator).flatMap { handler in runner.run(logger: logger, handler: handler) }.wait() } -func assertLambdaRuntimeResult(_ result: Result, shoudHaveRun: Int = 0, shouldFailWithError: Error? = nil, file: StaticString = #file, line: UInt = #line) { +func assertLambdaRuntimeResult(_ result: Result, shouldHaveRun: Int = 0, shouldFailWithError: Error? = nil, file: StaticString = #file, line: UInt = #line) { switch result { case .success where shouldFailWithError != nil: XCTFail("should fail with \(shouldFailWithError!)", file: file, line: line) case .success(let count) where shouldFailWithError == nil: - XCTAssertEqual(shoudHaveRun, count, "should have run \(shoudHaveRun) times", file: file, line: line) + XCTAssertEqual(shouldHaveRun, count, "should have run \(shouldHaveRun) times", file: file, line: line) case .failure(let error) where shouldFailWithError == nil: XCTFail("should succeed, but failed with \(error)", file: file, line: line) case .failure(let error) where shouldFailWithError != nil: diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift index eceaa2d8..af8836cb 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift @@ -33,10 +33,6 @@ class CodableLambdaTest: XCTestCase { } func testCodableVoidEventLoopFutureHandler() { - let request = Request(requestId: UUID().uuidString) - var inputBuffer: ByteBuffer? - var outputBuffer: ByteBuffer? - struct Handler: EventLoopLambdaHandler { typealias Event = Request typealias Output = Void @@ -53,19 +49,22 @@ class CodableLambdaTest: XCTestCase { } } - let handler = Handler(expected: request) + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) - XCTAssertNil(outputBuffer) - } + let handler = CodableEventLoopLambdaHandler( + handler: Handler(expected: request), + allocator: context.allocator + ) - func testCodableEventLoopFutureHandler() { - let request = Request(requestId: UUID().uuidString) - var inputBuffer: ByteBuffer? + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) var outputBuffer: ByteBuffer? - var response: Response? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(outputBuffer?.readableBytes, 0) + } + func testCodableEventLoopFutureHandler() { struct Handler: EventLoopLambdaHandler { typealias Event = Request typealias Output = Response @@ -82,76 +81,87 @@ class CodableLambdaTest: XCTestCase { } } - let handler = Handler(expected: request) + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + var response: Response? - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) + let handler = CodableEventLoopLambdaHandler( + handler: Handler(expected: request), + allocator: context.allocator + ) + + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) XCTAssertEqual(response?.requestId, request.requestId) } - #if compiler(>=5.5) && canImport(_Concurrency) @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testCodableVoidHandler() { + func testCodableVoidHandler() async throws { struct Handler: LambdaHandler { typealias Event = Request typealias Output = Void var expected: Request? - init(context: LambdaInitializationContext) async throws {} - func handle(_ event: Request, context: LambdaContext) async throws { XCTAssertEqual(event, self.expected) } } - XCTAsyncTest { - let request = Request(requestId: UUID().uuidString) - var inputBuffer: ByteBuffer? - var outputBuffer: ByteBuffer? + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) - var handler = try await Handler(context: self.newInitContext()) - handler.expected = request + var underlying = try await Handler(context: self.newInitContext()) + underlying.expected = request + let handler = CodableLambdaHandler( + handler: underlying, + allocator: context.allocator + ) - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) - XCTAssertNil(outputBuffer) - } + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(outputBuffer?.readableBytes, 0) } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testCodableHandler() { + func testCodableHandler() async throws { struct Handler: LambdaHandler { typealias Event = Request typealias Output = Response var expected: Request? - init(context: LambdaInitializationContext) async throws {} - func handle(_ event: Request, context: LambdaContext) async throws -> Response { XCTAssertEqual(event, self.expected) return Response(requestId: event.requestId) } } - XCTAsyncTest { - let request = Request(requestId: UUID().uuidString) - var response: Response? - var inputBuffer: ByteBuffer? - var outputBuffer: ByteBuffer? + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + var response: Response? + + var underlying = try await Handler(context: self.newInitContext()) + underlying.expected = request + let handler = CodableLambdaHandler( + handler: underlying, + allocator: context.allocator + ) - var handler = try await Handler(context: self.newInitContext()) - handler.expected = request + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) - XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) - XCTAssertEqual(response?.requestId, request.requestId) - } + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) + XCTAssertNoThrow(try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(response?.requestId, request.requestId) } - #endif // convenience method func newContext() -> LambdaContext { @@ -191,29 +201,3 @@ private struct Response: Codable, Equatable { self.requestId = requestId } } - -#if compiler(>=5.5) && canImport(_Concurrency) -// NOTE: workaround until we have async test support on linux -// https://github.com/apple/swift-corelibs-xctest/pull/326 -extension XCTestCase { - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func XCTAsyncTest( - expectationDescription: String = "Async operation", - timeout: TimeInterval = 3, - file: StaticString = #file, - line: Int = #line, - operation: @escaping () async throws -> Void - ) { - let expectation = self.expectation(description: expectationDescription) - Task { - do { try await operation() } - catch { - XCTFail("Error thrown while executing async function @ \(file):\(line): \(error)") - Thread.callStackSymbols.forEach { print($0) } - } - expectation.fulfill() - } - self.wait(for: [expectation], timeout: timeout) - } -} -#endif diff --git a/Tests/AWSLambdaTestingTests/Tests.swift b/Tests/AWSLambdaTestingTests/Tests.swift index f4520aa2..b5312323 100644 --- a/Tests/AWSLambdaTestingTests/Tests.swift +++ b/Tests/AWSLambdaTestingTests/Tests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=5.5) && canImport(_Concurrency) import AWSLambdaRuntime import AWSLambdaTesting import NIOCore @@ -20,7 +19,22 @@ import XCTest @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) class LambdaTestingTests: XCTestCase { - func testCodableClosure() { + func testBasics() async throws { + struct MyLambda: LambdaHandler { + typealias Event = String + typealias Output = String + + func handle(_ event: String, context: LambdaContext) async throws -> String { + event + } + } + + let uuid = UUID().uuidString + let result = try await Lambda.test(MyLambda.self, with: uuid) + XCTAssertEqual(result, uuid) + } + + func testCodableClosure() async throws { struct Request: Codable { let name: String } @@ -33,69 +47,64 @@ class LambdaTestingTests: XCTestCase { typealias Event = Request typealias Output = Response - init(context: LambdaInitializationContext) {} - func handle(_ event: Request, context: LambdaContext) async throws -> Response { Response(message: "echo" + event.name) } } let request = Request(name: UUID().uuidString) - var response: Response? - XCTAssertNoThrow(response = try Lambda.test(MyLambda.self, with: request)) - XCTAssertEqual(response?.message, "echo" + request.name) + let response = try await Lambda.test(MyLambda.self, with: request) + XCTAssertEqual(response.message, "echo" + request.name) } - // DIRTY HACK: To verify the handler was actually invoked, we change a global variable. - static var VoidLambdaHandlerInvokeCount: Int = 0 - func testCodableVoidClosure() { + func testCodableVoidClosure() async throws { struct Request: Codable { let name: String } struct MyLambda: LambdaHandler { + // DIRTY HACK: To verify the handler was actually invoked, we change a global variable. + static var VoidLambdaHandlerInvokeCount: Int = 0 + typealias Event = Request typealias Output = Void - init(context: LambdaInitializationContext) {} - func handle(_ event: Request, context: LambdaContext) async throws { - LambdaTestingTests.VoidLambdaHandlerInvokeCount += 1 + Self.VoidLambdaHandlerInvokeCount += 1 } } - Self.VoidLambdaHandlerInvokeCount = 0 let request = Request(name: UUID().uuidString) - XCTAssertNoThrow(try Lambda.test(MyLambda.self, with: request)) - XCTAssertEqual(Self.VoidLambdaHandlerInvokeCount, 1) + MyLambda.VoidLambdaHandlerInvokeCount = 0 + try await Lambda.test(MyLambda.self, with: request) + XCTAssertEqual(MyLambda.VoidLambdaHandlerInvokeCount, 1) } - func testInvocationFailure() { + func testInvocationFailure() async throws { struct MyError: Error {} struct MyLambda: LambdaHandler { typealias Event = String typealias Output = Void - init(context: LambdaInitializationContext) {} - func handle(_ event: String, context: LambdaContext) async throws { throw MyError() } } - XCTAssertThrowsError(try Lambda.test(MyLambda.self, with: UUID().uuidString)) { error in + do { + try await Lambda.test(MyLambda.self, with: UUID().uuidString) + XCTFail("expected to throw") + } catch { XCTAssert(error is MyError) } } - func testAsyncLongRunning() { + func testAsyncLongRunning() async throws { struct MyLambda: LambdaHandler { typealias Event = String typealias Output = String - init(context: LambdaInitializationContext) {} - func handle(_ event: String, context: LambdaContext) async throws -> String { try await Task.sleep(nanoseconds: 500 * 1000 * 1000) return event @@ -103,7 +112,7 @@ class LambdaTestingTests: XCTestCase { } let uuid = UUID().uuidString - XCTAssertEqual(try Lambda.test(MyLambda.self, with: uuid), uuid) + let result = try await Lambda.test(MyLambda.self, with: uuid) + XCTAssertEqual(result, uuid) } } -#endif diff --git a/docker/Dockerfile b/docker/Dockerfile index 28d65fc1..ec97cef2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG swift_version=5.4 +ARG swift_version=5.7 ARG base_image=swift:$swift_version-amazonlinux2 FROM $base_image # needed to do again after FROM due to docker limitation @@ -14,7 +14,7 @@ RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile # swiftformat (until part of the toolchain) -ARG swiftformat_version=0.47.3 +ARG swiftformat_version=0.50.1 RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format RUN cd $HOME/.tools/swift-format && swift build -c release RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat diff --git a/docker/docker-compose.al2.54.yaml b/docker/docker-compose.al2.54.yaml deleted file mode 100644 index cb300eb7..00000000 --- a/docker/docker-compose.al2.54.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-aws-lambda:al2-5.4 - build: - args: - swift_version: "5.4" - - test: - image: swift-aws-lambda:al2-5.4 - - test-samples: - image: swift-aws-lambda:al2-5.4 - - shell: - image: swift-aws-lambda:al2-5.4 diff --git a/docker/docker-compose.al2.55.yaml b/docker/docker-compose.al2.55.yaml deleted file mode 100644 index 488c629a..00000000 --- a/docker/docker-compose.al2.55.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-aws-lambda:al2-5.5 - build: - args: - swift_version: "5.5" - - test: - image: swift-aws-lambda:al2-5.5 - - test-samples: - image: swift-aws-lambda:al2-5.5 - - shell: - image: swift-aws-lambda:al2-5.5 diff --git a/docker/docker-compose.al2.56.yaml b/docker/docker-compose.al2.56.yaml deleted file mode 100644 index 561ea804..00000000 --- a/docker/docker-compose.al2.56.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-aws-lambda:al2-5.6 - build: - args: - swift_version: "5.6" - - test: - image: swift-aws-lambda:al2-5.6 - - test-samples: - image: swift-aws-lambda:al2-5.6 - - shell: - image: swift-aws-lambda:al2-5.6 diff --git a/docker/docker-compose.al2.57.yaml b/docker/docker-compose.al2.57.yaml index 0e280837..dc5044fa 100644 --- a/docker/docker-compose.al2.57.yaml +++ b/docker/docker-compose.al2.57.yaml @@ -6,7 +6,7 @@ services: image: swift-aws-lambda:al2-5.7 build: args: - base_image: "swiftlang/swift:nightly-main-amazonlinux2" + swift_version: "5.7" test: image: swift-aws-lambda:al2-5.7 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index e5ba9ddb..7e41db16 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -28,7 +28,7 @@ services: test: <<: *common - command: /bin/bash -cl "swift test --enable-test-discovery -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" + command: /bin/bash -cl "swift test -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" test-samples: <<: *common From 9258f8e3d9477e61267235291e0b9dfcb99eda0d Mon Sep 17 00:00:00 2001 From: tom doron Date: Fri, 7 Oct 2022 17:56:58 -0700 Subject: [PATCH 2/6] update examples --- Examples/Benchmark/Package.swift | 2 +- Examples/Deployment/Package.swift | 2 +- .../Deployment/Sources/Benchmark/BenchmarkHandler.swift | 2 +- .../Deployment/Sources/HelloWorld/HelloWorldHandler.swift | 7 ------- Examples/Echo/Lambda.swift | 7 ------- Examples/Echo/Package.swift | 2 +- Examples/ErrorHandling/Lambda.swift | 2 -- Examples/ErrorHandling/Package.swift | 2 +- Examples/Foundation/Package.swift | 2 +- Examples/JSON/Lambda.swift | 4 ---- Examples/JSON/Package.swift | 2 +- Examples/LocalDebugging/MyLambda/Lambda.swift | 4 ---- Examples/LocalDebugging/MyLambda/Package.swift | 2 +- Examples/Testing/Package.swift | 2 +- Examples/Testing/Sources/Lambda.swift | 7 ------- Examples/Testing/Tests/LambdaTests.swift | 4 ++-- 16 files changed, 11 insertions(+), 42 deletions(-) diff --git a/Examples/Benchmark/Package.swift b/Examples/Benchmark/Package.swift index ddadbae8..7cceea76 100644 --- a/Examples/Benchmark/Package.swift +++ b/Examples/Benchmark/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Deployment/Package.swift b/Examples/Deployment/Package.swift index a34069b9..4fb37203 100644 --- a/Examples/Deployment/Package.swift +++ b/Examples/Deployment/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift b/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift index 87754176..ed89629d 100644 --- a/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift +++ b/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import AWSLambdaRuntimeCore -import NIO +import NIOCore // If you would like to benchmark Swift's Lambda Runtime, // use this example which is more performant. diff --git a/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift b/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift index cddae31b..2340e7a5 100644 --- a/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift +++ b/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift @@ -17,13 +17,6 @@ import AWSLambdaRuntime // introductory example, the obligatory "hello, world!" @main struct HelloWorldHandler: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse here. - } - func handle(_ event: String, context: LambdaContext) async throws -> String { "hello, world" } diff --git a/Examples/Echo/Lambda.swift b/Examples/Echo/Lambda.swift index 58b6a32e..285fc701 100644 --- a/Examples/Echo/Lambda.swift +++ b/Examples/Echo/Lambda.swift @@ -18,13 +18,6 @@ import AWSLambdaRuntime @main struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - func handle(_ input: String, context: LambdaContext) async throws -> String { // as an example, respond with the input's reversed String(input.reversed()) diff --git a/Examples/Echo/Package.swift b/Examples/Echo/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/Echo/Package.swift +++ b/Examples/Echo/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/ErrorHandling/Lambda.swift b/Examples/ErrorHandling/Lambda.swift index d11d21c1..901e72f1 100644 --- a/Examples/ErrorHandling/Lambda.swift +++ b/Examples/ErrorHandling/Lambda.swift @@ -21,8 +21,6 @@ struct MyLambda: LambdaHandler { typealias Event = Request typealias Output = Response - init(context: LambdaInitializationContext) async throws {} - func handle(_ request: Request, context: LambdaContext) async throws -> Response { // switch over the error type "requested" by the request, and trigger such error accordingly switch request.error { diff --git a/Examples/ErrorHandling/Package.swift b/Examples/ErrorHandling/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/ErrorHandling/Package.swift +++ b/Examples/ErrorHandling/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Foundation/Package.swift b/Examples/Foundation/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/Foundation/Package.swift +++ b/Examples/Foundation/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/JSON/Lambda.swift b/Examples/JSON/Lambda.swift index 0c2664d8..ad63fc0a 100644 --- a/Examples/JSON/Lambda.swift +++ b/Examples/JSON/Lambda.swift @@ -30,10 +30,6 @@ struct MyLambda: LambdaHandler { typealias Event = Request typealias Output = Response - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - func handle(_ event: Request, context: LambdaContext) async throws -> Response { // as an example, respond with the input event's reversed body Response(body: String(event.body.reversed())) diff --git a/Examples/JSON/Package.swift b/Examples/JSON/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/JSON/Package.swift +++ b/Examples/JSON/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/LocalDebugging/MyLambda/Lambda.swift b/Examples/LocalDebugging/MyLambda/Lambda.swift index 582d20ae..c1508d34 100644 --- a/Examples/LocalDebugging/MyLambda/Lambda.swift +++ b/Examples/LocalDebugging/MyLambda/Lambda.swift @@ -23,10 +23,6 @@ struct MyLambda: LambdaHandler { typealias Event = Request typealias Output = Response - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - func handle(_ request: Request, context: LambdaContext) async throws -> Response { // TODO: something useful Response(message: "Hello, \(request.name)!") diff --git a/Examples/LocalDebugging/MyLambda/Package.swift b/Examples/LocalDebugging/MyLambda/Package.swift index 2b8860de..567d0c49 100644 --- a/Examples/LocalDebugging/MyLambda/Package.swift +++ b/Examples/LocalDebugging/MyLambda/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Testing/Package.swift b/Examples/Testing/Package.swift index 5f7cb131..c80d39d9 100644 --- a/Examples/Testing/Package.swift +++ b/Examples/Testing/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Testing/Sources/Lambda.swift b/Examples/Testing/Sources/Lambda.swift index 51714798..020080a6 100644 --- a/Examples/Testing/Sources/Lambda.swift +++ b/Examples/Testing/Sources/Lambda.swift @@ -18,13 +18,6 @@ import AWSLambdaRuntime @main struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - func handle(_ event: String, context: LambdaContext) async throws -> String { // as an example, respond with the event's reversed body String(event.reversed()) diff --git a/Examples/Testing/Tests/LambdaTests.swift b/Examples/Testing/Tests/LambdaTests.swift index 26d6ea38..f6676c10 100644 --- a/Examples/Testing/Tests/LambdaTests.swift +++ b/Examples/Testing/Tests/LambdaTests.swift @@ -18,9 +18,9 @@ import AWSLambdaTesting import XCTest class LambdaTest: XCTestCase { - func testIt() throws { + func testIt() async throws { let input = UUID().uuidString - let result = try Lambda.test(MyLambda.self, with: input) + let result = try await Lambda.test(MyLambda.self, with: input) XCTAssertEqual(result, String(input.reversed())) } } From 2ed058b11a402614cf8f675de7df7d0dbf92f6e5 Mon Sep 17 00:00:00 2001 From: tomer doron Date: Mon, 10 Oct 2022 14:02:23 -0700 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Yim Lee --- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 12 ++++++------ Sources/AWSLambdaRuntimeCore/LambdaHandler.swift | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 584598bf..f8fe926d 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -21,7 +21,7 @@ import NIOFoundationCompat // MARK: - LambdaHandler Codable support -/// Implementation of a`ByteBuffer` to `Event` decoding. +/// Implementation of `ByteBuffer` to `Event` decoding. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Event: Decodable { @inlinable @@ -30,7 +30,7 @@ extension LambdaHandler where Event: Decodable { } } -/// Implementation of `Output` to `ByteBuffer` encoding. +/// Implementation of `Output` to `ByteBuffer` encoding. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output: Encodable { @inlinable @@ -40,7 +40,7 @@ extension LambdaHandler where Output: Encodable { } /// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`. -/// Advanced users that want to inject their own codec can do it by overriding these functions. +/// Advanced users who want to inject their own codec can do it by overriding these functions. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Event: Decodable { public var decoder: LambdaCodableDecoder { @@ -49,7 +49,7 @@ extension LambdaHandler where Event: Decodable { } /// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`. -/// Advanced users that want to inject their own codec can do it by overriding these functions. +/// Advanced users who want to inject their own codec can do it by overriding these functions. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output: Encodable { public var encoder: LambdaCodableEncoder { @@ -59,7 +59,7 @@ extension LambdaHandler where Output: Encodable { // MARK: - EventLoopLambdaHandler Codable support -/// Implementation of a`ByteBuffer` to `Event` decoding. +/// Implementation of `ByteBuffer` to `Event` decoding. extension EventLoopLambdaHandler where Event: Decodable { @inlinable public func decode(buffer: ByteBuffer) throws -> Event { @@ -67,7 +67,7 @@ extension EventLoopLambdaHandler where Event: Decodable { } } -/// Implementation of `Output` to `ByteBuffer` encoding. +/// Implementation of `Output` to `ByteBuffer` encoding. extension EventLoopLambdaHandler where Output: Encodable { @inlinable public func encode(value: Output, into buffer: inout ByteBuffer) throws { diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 5b1ae8ff..cf3f38e3 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -26,11 +26,11 @@ import NIOCore /// ``ByteBufferLambdaHandler``. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) public protocol LambdaHandler { - /// The lambda functions input. In most cases this should be `Codable`. If your event originates from an + /// The lambda function's input. In most cases this should be `Codable`. If your event originates from an /// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), /// which provides a number of commonly used AWS Event implementations. associatedtype Event - /// The lambda functions output. Can be `Void`. + /// The lambda function's output. Can be `Void`. associatedtype Output init() @@ -129,7 +129,7 @@ final class CodableLambdaHandler: ByteBufferLambdaHan } } -/// Implementation of `ByteBuffer` to `Void` decoding. +/// Implementation of `ByteBuffer` to `Void` decoding. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output == Void { @inlinable @@ -232,7 +232,7 @@ public protocol EventLoopLambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } -/// Implementation of `ByteBuffer` to `Void` decoding. +/// Implementation of `ByteBuffer` to `Void` decoding. extension EventLoopLambdaHandler where Output == Void { @inlinable public func encode(value: Void, into buffer: inout ByteBuffer) throws {} From 17ed17b0023b52536c067a4c7883a14ba4079671 Mon Sep 17 00:00:00 2001 From: tom doron Date: Mon, 10 Oct 2022 17:52:00 -0700 Subject: [PATCH 4/6] separate protocol for simpler lambda (no init) --- Examples/Benchmark/BenchmarkHandler.swift | 3 - .../Sources/Benchmark/BenchmarkHandler.swift | 3 - .../HelloWorld/HelloWorldHandler.swift | 2 +- Examples/Echo/Lambda.swift | 2 +- Examples/ErrorHandling/Lambda.swift | 5 +- Examples/Foundation/Lambda.swift | 3 - Examples/JSON/Lambda.swift | 5 +- Examples/LocalDebugging/MyLambda/Lambda.swift | 5 +- Examples/Testing/Sources/Lambda.swift | 2 +- .../AWSLambdaRuntimeCore/Lambda+String.swift | 8 +- .../AWSLambdaRuntimeCore/LambdaHandler.swift | 58 +++++++------- .../LambdaHandlerTest.swift | 77 ++++++++----------- .../LambdaHandlers.swift | 9 --- .../LambdaRuntimeTest.swift | 3 - .../LambdaTest.swift | 3 - .../Lambda+CodableTest.swift | 16 +--- Tests/AWSLambdaTestingTests/Tests.swift | 25 ++---- scripts/soundness.sh | 1 + 18 files changed, 81 insertions(+), 149 deletions(-) diff --git a/Examples/Benchmark/BenchmarkHandler.swift b/Examples/Benchmark/BenchmarkHandler.swift index ed89629d..d9cfce6e 100644 --- a/Examples/Benchmark/BenchmarkHandler.swift +++ b/Examples/Benchmark/BenchmarkHandler.swift @@ -22,9 +22,6 @@ import NIOCore @main struct BenchmarkHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(BenchmarkHandler()) } diff --git a/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift b/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift index ed89629d..d9cfce6e 100644 --- a/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift +++ b/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift @@ -22,9 +22,6 @@ import NIOCore @main struct BenchmarkHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(BenchmarkHandler()) } diff --git a/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift b/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift index 2340e7a5..9f4a16f3 100644 --- a/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift +++ b/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift @@ -16,7 +16,7 @@ import AWSLambdaRuntime // introductory example, the obligatory "hello, world!" @main -struct HelloWorldHandler: LambdaHandler { +struct HelloWorldHandler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { "hello, world" } diff --git a/Examples/Echo/Lambda.swift b/Examples/Echo/Lambda.swift index 285fc701..00c0a5e5 100644 --- a/Examples/Echo/Lambda.swift +++ b/Examples/Echo/Lambda.swift @@ -17,7 +17,7 @@ import AWSLambdaRuntime // in this example we are receiving and responding with strings @main -struct MyLambda: LambdaHandler { +struct MyLambda: SimpleLambdaHandler { func handle(_ input: String, context: LambdaContext) async throws -> String { // as an example, respond with the input's reversed String(input.reversed()) diff --git a/Examples/ErrorHandling/Lambda.swift b/Examples/ErrorHandling/Lambda.swift index 901e72f1..d8e560aa 100644 --- a/Examples/ErrorHandling/Lambda.swift +++ b/Examples/ErrorHandling/Lambda.swift @@ -17,10 +17,7 @@ import AWSLambdaRuntime // MARK: - Run Lambda @main -struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - +struct MyLambda: SimpleLambdaHandler { func handle(_ request: Request, context: LambdaContext) async throws -> Response { // switch over the error type "requested" by the request, and trigger such error accordingly switch request.error { diff --git a/Examples/Foundation/Lambda.swift b/Examples/Foundation/Lambda.swift index dda564f0..660574a1 100644 --- a/Examples/Foundation/Lambda.swift +++ b/Examples/Foundation/Lambda.swift @@ -25,9 +25,6 @@ import Logging @main struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = [Exchange] - let calculator: ExchangeRatesCalculator init(context: LambdaInitializationContext) async throws { diff --git a/Examples/JSON/Lambda.swift b/Examples/JSON/Lambda.swift index ad63fc0a..cad3b650 100644 --- a/Examples/JSON/Lambda.swift +++ b/Examples/JSON/Lambda.swift @@ -26,10 +26,7 @@ struct Response: Codable { // codables to model your request and response objects @main -struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - +struct MyLambda: SimpleLambdaHandler { func handle(_ event: Request, context: LambdaContext) async throws -> Response { // as an example, respond with the input event's reversed body Response(body: String(event.body.reversed())) diff --git a/Examples/LocalDebugging/MyLambda/Lambda.swift b/Examples/LocalDebugging/MyLambda/Lambda.swift index c1508d34..397ece30 100644 --- a/Examples/LocalDebugging/MyLambda/Lambda.swift +++ b/Examples/LocalDebugging/MyLambda/Lambda.swift @@ -19,10 +19,7 @@ import Shared // a local server simulator which will allow local debugging @main -struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - +struct MyLambda: SimpleLambdaHandler { func handle(_ request: Request, context: LambdaContext) async throws -> Response { // TODO: something useful Response(message: "Hello, \(request.name)!") diff --git a/Examples/Testing/Sources/Lambda.swift b/Examples/Testing/Sources/Lambda.swift index 020080a6..568710e4 100644 --- a/Examples/Testing/Sources/Lambda.swift +++ b/Examples/Testing/Sources/Lambda.swift @@ -17,7 +17,7 @@ import AWSLambdaRuntime // in this example we are receiving and responding with strings @main -struct MyLambda: LambdaHandler { +struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { // as an example, respond with the event's reversed body String(event.reversed()) diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift index 49c6dc80..3eaec5b4 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift @@ -19,7 +19,7 @@ import NIOCore extension LambdaHandler where Event == String { /// Implementation of a `ByteBuffer` to `String` decoding. @inlinable - public func decode(buffer: ByteBuffer) throws -> String { + public func decode(buffer: ByteBuffer) throws -> Event { guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { throw CodecError.invalidString } @@ -31,7 +31,7 @@ extension LambdaHandler where Event == String { extension LambdaHandler where Output == String { /// Implementation of `String` to `ByteBuffer` encoding. @inlinable - public func encode(value: String, into buffer: inout ByteBuffer) throws { + public func encode(value: Output, into buffer: inout ByteBuffer) throws { buffer.writeString(value) } } @@ -41,7 +41,7 @@ extension LambdaHandler where Output == String { extension EventLoopLambdaHandler where Event == String { /// Implementation of `String` to `ByteBuffer` encoding. @inlinable - public func decode(buffer: ByteBuffer) throws -> String { + public func decode(buffer: ByteBuffer) throws -> Event { guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { throw CodecError.invalidString } @@ -52,7 +52,7 @@ extension EventLoopLambdaHandler where Event == String { extension EventLoopLambdaHandler where Output == String { /// Implementation of a `ByteBuffer` to `String` decoding. @inlinable - public func encode(value: String, into buffer: inout ByteBuffer) throws { + public func encode(value: Output, into buffer: inout ByteBuffer) throws { buffer.writeString(value) } } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index cf3f38e3..1ba277d8 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -15,11 +15,31 @@ import Dispatch import NIOCore +// MARK: - SimpleLambdaHandler + +/// Strongly typed, processing protocol for a Lambda that takes a user defined +/// ``LambdaHandler/Event`` and returns a user defined +/// ``LambdaHandler/Output`` asynchronously. +/// +/// - note: Most users should implement the ``LambdaHandler`` protocol instead +/// which defines the Lambda initialization method. +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +public protocol SimpleLambdaHandler: LambdaHandler { + init() +} + +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +extension SimpleLambdaHandler { + public init(context: LambdaInitializationContext) async throws { + self.init() + } +} + // MARK: - LambdaHandler /// Strongly typed, processing protocol for a Lambda that takes a user defined -/// ``EventLoopLambdaHandler/Event`` and returns a user defined -/// ``EventLoopLambdaHandler/Output`` asynchronously. +/// ``LambdaHandler/Event`` and returns a user defined +/// ``LambdaHandler/Output`` asynchronously. /// /// - note: Most users should implement this protocol instead of the lower /// level protocols ``EventLoopLambdaHandler`` and @@ -33,8 +53,12 @@ public protocol LambdaHandler { /// The lambda function's output. Can be `Void`. associatedtype Output - init() - + /// 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 ``LambdaInitializationContext``. init(context: LambdaInitializationContext) async throws /// The Lambda handling method. @@ -66,23 +90,6 @@ public protocol LambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -extension LambdaHandler { - public init() { - self.init() - } - - /// 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 ``LambdaInitializationContext``. - public init(context: LambdaInitializationContext) async throws { - self.init() - } -} - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) final class CodableLambdaHandler: ByteBufferLambdaHandler { private let handler: Underlying @@ -133,7 +140,7 @@ final class CodableLambdaHandler: ByteBufferLambdaHan @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output == Void { @inlinable - public func encode(value: Void, into buffer: inout ByteBuffer) throws {} + public func encode(value: Output, into buffer: inout ByteBuffer) throws {} } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) @@ -173,10 +180,7 @@ internal struct UncheckedSendableHandler ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding. +/// defined ``EventLoopLambdaHandler/Event`` and returns a user defined ``EventLoopLambdaHandler/Output`` asynchronously. /// /// - note: To implement a Lambda, implement either ``LambdaHandler`` or the /// ``EventLoopLambdaHandler`` protocol. The ``LambdaHandler`` will offload @@ -235,7 +239,7 @@ public protocol EventLoopLambdaHandler { /// Implementation of `ByteBuffer` to `Void` decoding. extension EventLoopLambdaHandler where Output == Void { @inlinable - public func encode(value: Void, into buffer: inout ByteBuffer) throws {} + public func encode(value: Output, into buffer: inout ByteBuffer) throws {} } internal final class CodableEventLoopLambdaHandler: ByteBufferLambdaHandler { diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift index 99e63ca0..99756468 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift @@ -17,23 +17,15 @@ import NIOCore import XCTest class LambdaHandlerTest: XCTestCase { - // MARK: - LambdaHandler + // MARK: - SimpleLambdaHandler @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testBootstrapSuccess() { + func testBootstrapSimpleNoInit() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct TestBootstrapHandler: LambdaHandler { - var initialized = false - - init(context: LambdaInitializationContext) async throws { - XCTAssertFalse(self.initialized) - try await Task.sleep(nanoseconds: 100 * 1000 * 1000) // 0.1 seconds - self.initialized = true - } - + struct TestBootstrapHandler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { event } @@ -46,36 +38,34 @@ class LambdaHandlerTest: XCTestCase { } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testBootstrapFailure() { - let server = MockLambdaServer(behavior: FailedBootstrapBehavior()) + func testBootstrapSimpleInit() { + let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct TestBootstrapHandler: LambdaHandler { - typealias Event = String - typealias Output = Void - + struct TestBootstrapHandler: SimpleLambdaHandler { var initialized = false - init(context: LambdaInitializationContext) async throws { + init() { XCTAssertFalse(self.initialized) - try await Task.sleep(nanoseconds: 100 * 1000 * 1000) // 0.1 seconds - throw TestError("kaboom") + self.initialized = true } - func handle(_ event: String, context: LambdaContext) async throws { - XCTFail("How can this be called if init failed") + func handle(_ event: String, context: LambdaContext) async throws -> String { + event } } let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) - assertLambdaRuntimeResult(result, shouldFailWithError: TestError("kaboom")) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } + // MARK: - LambdaHandler + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testBootstrapInitSimple() { + func testBootstrapSuccess() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } @@ -83,8 +73,9 @@ class LambdaHandlerTest: XCTestCase { struct TestBootstrapHandler: LambdaHandler { var initialized = false - init() { + init(context: LambdaInitializationContext) async throws { XCTAssertFalse(self.initialized) + try await Task.sleep(nanoseconds: 100 * 1000 * 1000) // 0.1 seconds self.initialized = true } @@ -100,21 +91,29 @@ class LambdaHandlerTest: XCTestCase { } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testBootstrapNoInit() { - let server = MockLambdaServer(behavior: Behavior()) + func testBootstrapFailure() { + let server = MockLambdaServer(behavior: FailedBootstrapBehavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } struct TestBootstrapHandler: LambdaHandler { - func handle(_ event: String, context: LambdaContext) async throws -> String { - event + var initialized = false + + init(context: LambdaInitializationContext) async throws { + XCTAssertFalse(self.initialized) + try await Task.sleep(nanoseconds: 100 * 1000 * 1000) // 0.1 seconds + throw TestError("kaboom") + } + + func handle(_ event: String, context: LambdaContext) async throws { + XCTFail("How can this be called if init failed") } } let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) - assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldFailWithError: TestError("kaboom")) } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) @@ -123,7 +122,7 @@ class LambdaHandlerTest: XCTestCase { XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { + struct Handler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { event } @@ -141,7 +140,7 @@ class LambdaHandlerTest: XCTestCase { XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { + struct Handler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws {} } @@ -158,7 +157,7 @@ class LambdaHandlerTest: XCTestCase { XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { + struct Handler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { throw TestError("boom") } @@ -178,9 +177,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -202,9 +198,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = Void - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -226,9 +219,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -250,9 +240,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeFailedFuture(TestError("kaboom")) } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift index c2f3fc9e..dd371238 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift @@ -17,9 +17,6 @@ import NIOCore import XCTest struct EchoHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(EchoHandler()) } @@ -32,9 +29,6 @@ struct EchoHandler: EventLoopLambdaHandler { struct StartupError: Error {} struct StartupErrorHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeFailedFuture(StartupError()) } @@ -48,9 +42,6 @@ struct StartupErrorHandler: EventLoopLambdaHandler { struct RuntimeError: Error {} struct RuntimeErrorHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = Void - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(RuntimeErrorHandler()) } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift index 64bc4384..578048cf 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift @@ -71,9 +71,6 @@ class LambdaRuntimeTest: XCTestCase { } struct ShutdownErrorHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = Void - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { // register shutdown operation context.terminator.register(name: "test 1", handler: { eventLoop in diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 956832a5..bed9f01c 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -254,9 +254,6 @@ class LambdaTest: XCTestCase { #if compiler(>=5.6) func testSendable() async throws { struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift index af8836cb..50e98cc7 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift @@ -34,9 +34,6 @@ class CodableLambdaTest: XCTestCase { func testCodableVoidEventLoopFutureHandler() { struct Handler: EventLoopLambdaHandler { - typealias Event = Request - typealias Output = Void - var expected: Request? static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { @@ -66,9 +63,6 @@ class CodableLambdaTest: XCTestCase { func testCodableEventLoopFutureHandler() { struct Handler: EventLoopLambdaHandler { - typealias Event = Request - typealias Output = Response - var expected: Request? static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { @@ -100,10 +94,7 @@ class CodableLambdaTest: XCTestCase { @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testCodableVoidHandler() async throws { - struct Handler: LambdaHandler { - typealias Event = Request - typealias Output = Void - + struct Handler: SimpleLambdaHandler { var expected: Request? func handle(_ event: Request, context: LambdaContext) async throws { @@ -130,10 +121,7 @@ class CodableLambdaTest: XCTestCase { @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testCodableHandler() async throws { - struct Handler: LambdaHandler { - typealias Event = Request - typealias Output = Response - + struct Handler: SimpleLambdaHandler { var expected: Request? func handle(_ event: Request, context: LambdaContext) async throws -> Response { diff --git a/Tests/AWSLambdaTestingTests/Tests.swift b/Tests/AWSLambdaTestingTests/Tests.swift index b5312323..64f4187f 100644 --- a/Tests/AWSLambdaTestingTests/Tests.swift +++ b/Tests/AWSLambdaTestingTests/Tests.swift @@ -20,10 +20,7 @@ import XCTest @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) class LambdaTestingTests: XCTestCase { func testBasics() async throws { - struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - + struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { event } @@ -43,10 +40,7 @@ class LambdaTestingTests: XCTestCase { let message: String } - struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - + struct MyLambda: SimpleLambdaHandler { func handle(_ event: Request, context: LambdaContext) async throws -> Response { Response(message: "echo" + event.name) } @@ -62,13 +56,10 @@ class LambdaTestingTests: XCTestCase { let name: String } - struct MyLambda: LambdaHandler { + struct MyLambda: SimpleLambdaHandler { // DIRTY HACK: To verify the handler was actually invoked, we change a global variable. static var VoidLambdaHandlerInvokeCount: Int = 0 - typealias Event = Request - typealias Output = Void - func handle(_ event: Request, context: LambdaContext) async throws { Self.VoidLambdaHandlerInvokeCount += 1 } @@ -83,10 +74,7 @@ class LambdaTestingTests: XCTestCase { func testInvocationFailure() async throws { struct MyError: Error {} - struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = Void - + struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws { throw MyError() } @@ -101,10 +89,7 @@ class LambdaTestingTests: XCTestCase { } func testAsyncLongRunning() async throws { - struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - + struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { try await Task.sleep(nanoseconds: 500 * 1000 * 1000) return event diff --git a/scripts/soundness.sh b/scripts/soundness.sh index 26b348ad..bd321f62 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -129,6 +129,7 @@ EOF cd "$here/.." find . \ \( \! -path '*/.build/*' -a \ + \( \! -path '*/.git/*' \) -a \ \( "${matching_files[@]}" \) -a \ \( \! \( "${exceptions[@]}" \) \) \) | while read line; do if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then From 91b3a48768f153e12fb2e509d97d836bc053a813 Mon Sep 17 00:00:00 2001 From: tom doron Date: Thu, 13 Oct 2022 11:05:29 -0700 Subject: [PATCH 5/6] cleanup --- Package.swift | 10 +- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 38 +++++- .../AWSLambdaRuntimeCore/Lambda+String.swift | 23 +++- Sources/AWSLambdaRuntimeCore/Lambda.swift | 41 ++++++- .../AWSLambdaRuntimeCore/LambdaHandler.swift | 115 +++++++++++++++--- .../AWSLambdaRuntimeCore/LambdaRunner.swift | 2 +- .../AWSLambdaRuntimeCore/LambdaRuntime.swift | 17 ++- Sources/AWSLambdaTesting/Lambda+Testing.swift | 41 ++++++- .../LambdaHandlerTest.swift | 7 -- .../LambdaTest.swift | 6 +- Tests/AWSLambdaRuntimeCoreTests/Utils.swift | 7 +- .../Lambda+CodableTest.swift | 67 +++++++++- Tests/AWSLambdaTestingTests/Tests.swift | 1 - 13 files changed, 323 insertions(+), 52 deletions(-) diff --git a/Package.swift b/Package.swift index 78b1c36e..a4559656 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,15 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.7 import PackageDescription let package = Package( name: "swift-aws-lambda-runtime", + platforms: [ + .macOS(.v12), + .iOS(.v15), + .tvOS(.v15), + .watchOS(.v8), + ], products: [ // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), @@ -15,7 +21,7 @@ let package = Package( .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")), + .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.43.1")), .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")), .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index f8fe926d..f925754b 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -19,10 +19,43 @@ import class Foundation.JSONEncoder import NIOCore import NIOFoundationCompat +// MARK: - SimpleLambdaHandler Codable support + +/// Implementation of `ByteBuffer` to `Event` decoding. +extension SimpleLambdaHandler where Event: Decodable { + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + try self.decoder.decode(Event.self, from: buffer) + } +} + +/// Implementation of `Output` to `ByteBuffer` encoding. +extension SimpleLambdaHandler where Output: Encodable { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + try self.encoder.encode(value, into: &buffer) + } +} + +/// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`. +/// Advanced users who want to inject their own codec can do it by overriding these functions. +extension SimpleLambdaHandler where Event: Decodable { + public var decoder: LambdaCodableDecoder { + Lambda.defaultJSONDecoder + } +} + +/// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`. +/// Advanced users who want to inject their own codec can do it by overriding these functions. +extension SimpleLambdaHandler where Output: Encodable { + public var encoder: LambdaCodableEncoder { + Lambda.defaultJSONEncoder + } +} + // MARK: - LambdaHandler Codable support /// Implementation of `ByteBuffer` to `Event` decoding. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Event: Decodable { @inlinable public func decode(buffer: ByteBuffer) throws -> Event { @@ -31,7 +64,6 @@ extension LambdaHandler where Event: Decodable { } /// Implementation of `Output` to `ByteBuffer` encoding. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output: Encodable { @inlinable public func encode(value: Output, into buffer: inout ByteBuffer) throws { @@ -41,7 +73,6 @@ extension LambdaHandler where Output: Encodable { /// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`. /// Advanced users who want to inject their own codec can do it by overriding these functions. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Event: Decodable { public var decoder: LambdaCodableDecoder { Lambda.defaultJSONDecoder @@ -50,7 +81,6 @@ extension LambdaHandler where Event: Decodable { /// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`. /// Advanced users who want to inject their own codec can do it by overriding these functions. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output: Encodable { public var encoder: LambdaCodableEncoder { Lambda.defaultJSONEncoder diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift index 3eaec5b4..e7674e28 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift @@ -13,9 +13,29 @@ //===----------------------------------------------------------------------===// import NIOCore +// MARK: - SimpleLambdaHandler String support + +extension SimpleLambdaHandler where Event == String { + /// Implementation of a `ByteBuffer` to `String` decoding. + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { + throw CodecError.invalidString + } + return value + } +} + +extension SimpleLambdaHandler where Output == String { + /// Implementation of `String` to `ByteBuffer` encoding. + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + buffer.writeString(value) + } +} + // MARK: - LambdaHandler String support -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Event == String { /// Implementation of a `ByteBuffer` to `String` decoding. @inlinable @@ -27,7 +47,6 @@ extension LambdaHandler where Event == String { } } -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output == String { /// Implementation of `String` to `ByteBuffer` encoding. @inlinable diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index f4dad014..2592f48c 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -24,17 +24,30 @@ import NIOCore import NIOPosix public enum Lambda { - /// Run a Lambda defined by implementing the ``ByteBufferLambdaHandler`` protocol. - /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the - /// ``ByteBufferLambdaHandler/makeHandler(context:)`` to create a new Handler. + /// Run a Lambda defined by implementing the ``SimpleLambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. /// /// - parameters: /// - configuration: A Lambda runtime configuration object /// - handlerType: The Handler to create and invoke. /// /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. + internal static func run( + configuration: LambdaConfiguration = .init(), + handlerType: Handler.Type + ) -> Result { + Self.run(configuration: configuration, handlerType: CodableSimpleLambdaHandler.self) + } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) + /// Run a Lambda defined by implementing the ``LambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the + /// ``LambdaHandler/makeHandler(context:)`` to create a new Handler. + /// + /// - parameters: + /// - configuration: A Lambda runtime configuration object + /// - handlerType: The Handler to create and invoke. + /// + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. internal static func run( configuration: LambdaConfiguration = .init(), handlerType: Handler.Type @@ -42,6 +55,15 @@ public enum Lambda { Self.run(configuration: configuration, handlerType: CodableLambdaHandler.self) } + /// Run a Lambda defined by implementing the ``EventLoopLambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the + /// ``EventLoopLambdaHandler/makeHandler(context:)`` to create a new Handler. + /// + /// - parameters: + /// - configuration: A Lambda runtime configuration object + /// - handlerType: The Handler to create and invoke. + /// + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. internal static func run( configuration: LambdaConfiguration = .init(), handlerType: Handler.Type @@ -49,6 +71,15 @@ public enum Lambda { Self.run(configuration: configuration, handlerType: CodableEventLoopLambdaHandler.self) } + /// Run a Lambda defined by implementing the ``ByteBufferLambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the + /// ``ByteBufferLambdaHandler/makeHandler(context:)`` to create a new Handler. + /// + /// - parameters: + /// - configuration: A Lambda runtime configuration object + /// - handlerType: The Handler to create and invoke. + /// + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. internal static func run( configuration: LambdaConfiguration = .init(), handlerType: (some ByteBufferLambdaHandler).Type @@ -60,7 +91,7 @@ public enum Lambda { var result: Result! MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in - let runtime = LambdaRuntime(handlerType, eventLoop: eventLoop, logger: logger, configuration: configuration) + let runtime = LambdaRuntime(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: configuration) #if DEBUG let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in logger.info("intercepted signal: \(signal)") diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 1ba277d8..62a51892 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -18,20 +18,112 @@ import NIOCore // MARK: - SimpleLambdaHandler /// Strongly typed, processing protocol for a Lambda that takes a user defined -/// ``LambdaHandler/Event`` and returns a user defined -/// ``LambdaHandler/Output`` asynchronously. +/// ``SimpleLambdaHandler/Event`` and returns a user defined +/// ``SimpleLambdaHandler/Output`` asynchronously. /// /// - note: Most users should implement the ``LambdaHandler`` protocol instead /// which defines the Lambda initialization method. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -public protocol SimpleLambdaHandler: LambdaHandler { +public protocol SimpleLambdaHandler { + /// The lambda function's input. In most cases this should be `Codable`. If your event originates from an + /// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), + /// which provides a number of commonly used AWS Event implementations. + associatedtype Event + /// The lambda function's output. Can be `Void`. + associatedtype Output + init() + + /// The Lambda handling method. + /// Concrete Lambda handlers implement this method to provide the Lambda functionality. + /// + /// - parameters: + /// - event: Event of type `Event` representing the event or request. + /// - context: Runtime ``LambdaContext``. + /// + /// - Returns: A Lambda result ot type `Output`. + func handle(_ event: Event, context: LambdaContext) async throws -> Output + + /// Encode a response of type ``Output`` to `ByteBuffer`. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// - parameters: + /// - value: Response of type ``Output``. + /// - buffer: A `ByteBuffer` to encode into, will be overwritten. + /// + /// - Returns: A `ByteBuffer` with the encoded version of the `value`. + func encode(value: Output, into buffer: inout ByteBuffer) throws + + /// Decode a `ByteBuffer` to a request or event of type ``Event``. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// + /// - parameters: + /// - buffer: The `ByteBuffer` to decode. + /// + /// - Returns: A request or event of type ``Event``. + func decode(buffer: ByteBuffer) throws -> Event +} + +final class CodableSimpleLambdaHandler: ByteBufferLambdaHandler { + private let handler: Underlying + private var outputBuffer: ByteBuffer + + @inlinable + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: CodableSimpleLambdaHandler.self) + promise.completeWithTask { + let handler = Underlying() + return CodableSimpleLambdaHandler(handler: handler, allocator: context.allocator) + } + return promise.futureResult + } + + @inlinable + init(handler: Underlying, allocator: ByteBufferAllocator) { + self.handler = handler + self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) + } + + @inlinable + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: ByteBuffer?.self) + promise.completeWithTask { + let input: Underlying.Event + do { + input = try self.handler.decode(buffer: buffer) + } catch { + throw CodecError.requestDecoding(error) + } + + let output = try await self.handler.handle(input, context: context) + + do { + self.outputBuffer.clear() + try self.handler.encode(value: output, into: &self.outputBuffer) + return self.outputBuffer + } catch { + throw CodecError.responseEncoding(error) + } + } + return promise.futureResult + } +} + +/// Implementation of `ByteBuffer` to `Void` decoding. +extension SimpleLambdaHandler where Output == Void { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws {} } -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension SimpleLambdaHandler { - public init(context: LambdaInitializationContext) async throws { - self.init() + /// Initializes and runs the Lambda function. + /// + /// If you precede your ``SimpleLambdaHandler`` conformer's declaration with the + /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) + /// attribute, the system calls the conformer's `main()` method to launch the lambda function. + /// + /// The lambda runtime provides a default implementation of the method that manages the launch + /// process. + public static func main() { + _ = Lambda.run(configuration: .init(), handlerType: Self.self) } } @@ -44,7 +136,6 @@ extension SimpleLambdaHandler { /// - note: Most users should implement this protocol instead of the lower /// level protocols ``EventLoopLambdaHandler`` and /// ``ByteBufferLambdaHandler``. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) public protocol LambdaHandler { /// The lambda function's input. In most cases this should be `Codable`. If your event originates from an /// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), @@ -90,7 +181,6 @@ public protocol LambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) final class CodableLambdaHandler: ByteBufferLambdaHandler { private let handler: Underlying private var outputBuffer: ByteBuffer @@ -137,17 +227,15 @@ final class CodableLambdaHandler: ByteBufferLambdaHan } /// Implementation of `ByteBuffer` to `Void` decoding. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler where Output == Void { @inlinable public func encode(value: Output, into buffer: inout ByteBuffer) throws {} } -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension LambdaHandler { /// Initializes and runs the Lambda function. /// - /// If you precede your ``ByteBufferLambdaHandler`` conformer's declaration with the + /// If you precede your ``LambdaHandler`` conformer's declaration with the /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) /// attribute, the system calls the conformer's `main()` method to launch the lambda function. /// @@ -161,7 +249,6 @@ extension LambdaHandler { /// unchecked sendable wrapper for the handler /// this is safe since lambda runtime is designed to calls the handler serially @usableFromInline -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) internal struct UncheckedSendableHandler: @unchecked Sendable where Event == Underlying.Event, Output == Underlying.Output { @usableFromInline let underlying: Underlying @@ -283,7 +370,7 @@ internal final class CodableEventLoopLambdaHandler EventLoopFuture { + func run(handler: any ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture { logger.debug("lambda invocation sequence starting") // 1. request invocation from lambda runtime engine self.isGettingNextInvocation = true diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index 0ca245e0..689559d5 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -34,13 +34,22 @@ public final class LambdaRuntime { } } + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``SimpleLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { + self.init(CodableSimpleLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + /// Create a new `LambdaRuntime`. /// /// - parameters: /// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage. /// - eventLoop: An `EventLoop` to run the Lambda on. /// - logger: A `Logger` to log the Lambda events. - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { self.init(CodableLambdaHandler.self, eventLoop: eventLoop, logger: logger) } @@ -62,10 +71,10 @@ public final class LambdaRuntime { /// - eventLoop: An `EventLoop` to run the Lambda on. /// - logger: A `Logger` to log the Lambda events. public convenience init(_ handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger) { - self.init(handlerType, eventLoop: eventLoop, logger: logger, configuration: .init()) + self.init(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: .init()) } - init(_ handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { + init(handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { self.eventLoop = eventLoop self.shutdownPromise = eventLoop.makePromise(of: Int.self) self.logger = logger @@ -165,7 +174,7 @@ public final class LambdaRuntime { } var logger = self.logger logger[metadataKey: "lifecycleIteration"] = "\(count)" - runner.run(logger: logger, handler: handler).whenComplete { result in + runner.run(handler: handler, logger: logger).whenComplete { result in switch result { case .success: logger.log(level: .debug, "lambda invocation sequence completed successfully") diff --git a/Sources/AWSLambdaTesting/Lambda+Testing.swift b/Sources/AWSLambdaTesting/Lambda+Testing.swift index 785751e6..9f77e8ac 100644 --- a/Sources/AWSLambdaTesting/Lambda+Testing.swift +++ b/Sources/AWSLambdaTesting/Lambda+Testing.swift @@ -34,12 +34,12 @@ // } @testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import Dispatch import Logging import NIOCore import NIOPosix -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension Lambda { public struct TestConfig { public var requestID: String @@ -58,11 +58,47 @@ extension Lambda { } } + public static func test( + _ handlerType: Handler.Type, + with event: Handler.Event, + using config: TestConfig = .init() + ) async throws -> Handler.Output { + let context = Self.makeContext(config: config) + let handler = Handler() + return try await handler.handle(event, context: context.1) + } + public static func test( _ handlerType: Handler.Type, with event: Handler.Event, using config: TestConfig = .init() ) async throws -> Handler.Output { + let context = Self.makeContext(config: config) + let handler = try await Handler(context: context.0) + return try await handler.handle(event, context: context.1) + } + + public static func test( + _ handlerType: Handler.Type, + with event: Handler.Event, + using config: TestConfig = .init() + ) async throws -> Handler.Output { + let context = Self.makeContext(config: config) + let handler = try await Handler.makeHandler(context: context.0).get() + return try await handler.handle(event, context: context.1).get() + } + + public static func test( + _ handlerType: Handler.Type, + with buffer: ByteBuffer, + using config: TestConfig = .init() + ) async throws -> ByteBuffer? { + let context = Self.makeContext(config: config) + let handler = try await Handler.makeHandler(context: context.0).get() + return try await handler.handle(buffer, context: context.1).get() + } + + private static func makeContext(config: TestConfig) -> (LambdaInitializationContext, LambdaContext) { let logger = Logger(label: "test") let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -85,7 +121,6 @@ extension Lambda { eventLoop: eventLoop ) - let handler = try await Handler(context: initContext) - return try await handler.handle(event, context: context) + return (initContext, context) } } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift index 99756468..ac4b2a65 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift @@ -19,7 +19,6 @@ import XCTest class LambdaHandlerTest: XCTestCase { // MARK: - SimpleLambdaHandler - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testBootstrapSimpleNoInit() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) @@ -37,7 +36,6 @@ class LambdaHandlerTest: XCTestCase { assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testBootstrapSimpleInit() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) @@ -64,7 +62,6 @@ class LambdaHandlerTest: XCTestCase { // MARK: - LambdaHandler - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testBootstrapSuccess() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) @@ -90,7 +87,6 @@ class LambdaHandlerTest: XCTestCase { assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testBootstrapFailure() { let server = MockLambdaServer(behavior: FailedBootstrapBehavior()) XCTAssertNoThrow(try server.start().wait()) @@ -116,7 +112,6 @@ class LambdaHandlerTest: XCTestCase { assertLambdaRuntimeResult(result, shouldFailWithError: TestError("kaboom")) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testHandlerSuccess() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) @@ -134,7 +129,6 @@ class LambdaHandlerTest: XCTestCase { assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testVoidHandlerSuccess() { let server = MockLambdaServer(behavior: Behavior(result: .success(nil))) XCTAssertNoThrow(try server.start().wait()) @@ -151,7 +145,6 @@ class LambdaHandlerTest: XCTestCase { assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testHandlerFailure() { let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index bed9f01c..fefa6c4f 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -279,15 +279,15 @@ class LambdaTest: XCTestCase { let runner = LambdaRunner(eventLoop: eventLoopGroup.next(), configuration: configuration) try runner.run( - logger: logger, handler: CodableEventLoopLambdaHandler( handler: handler1, allocator: ByteBufferAllocator() - ) + ), + logger: logger ).wait() try runner.initialize(handlerType: CodableEventLoopLambdaHandler.self, logger: logger, terminator: LambdaTerminator()).flatMap { handler2 in - runner.run(logger: logger, handler: handler2) + runner.run(handler: handler2, logger: logger) }.wait() } diff --git a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift index e3ded229..aecd3186 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift @@ -18,7 +18,10 @@ import NIOCore import NIOPosix import XCTest -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) +func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { + try runLambda(behavior: behavior, handlerType: CodableSimpleLambdaHandler.self) +} + func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { try runLambda(behavior: behavior, handlerType: CodableLambdaHandler.self) } @@ -37,7 +40,7 @@ func runLambda(behavior: LambdaServerBehavior, handlerType: (some ByteBufferLamb let server = try MockLambdaServer(behavior: behavior).start().wait() defer { XCTAssertNoThrow(try server.stop().wait()) } try runner.initialize(handlerType: handlerType, logger: logger, terminator: terminator).flatMap { handler in - runner.run(logger: logger, handler: handler) + runner.run(handler: handler, logger: logger) }.wait() } diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift index 50e98cc7..43e50423 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift @@ -92,9 +92,10 @@ class CodableLambdaTest: XCTestCase { XCTAssertEqual(response?.requestId, request.requestId) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testCodableVoidHandler() async throws { - struct Handler: SimpleLambdaHandler { + struct Handler: LambdaHandler { + init(context: AWSLambdaRuntimeCore.LambdaInitializationContext) async throws {} + var expected: Request? func handle(_ event: Request, context: LambdaContext) async throws { @@ -119,9 +120,10 @@ class CodableLambdaTest: XCTestCase { XCTAssertEqual(outputBuffer?.readableBytes, 0) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testCodableHandler() async throws { - struct Handler: SimpleLambdaHandler { + struct Handler: LambdaHandler { + init(context: AWSLambdaRuntimeCore.LambdaInitializationContext) async throws {} + var expected: Request? func handle(_ event: Request, context: LambdaContext) async throws -> Response { @@ -151,6 +153,63 @@ class CodableLambdaTest: XCTestCase { XCTAssertEqual(response?.requestId, request.requestId) } + func testCodableVoidSimpleHandler() async throws { + struct Handler: SimpleLambdaHandler { + var expected: Request? + + func handle(_ event: Request, context: LambdaContext) async throws { + XCTAssertEqual(event, self.expected) + } + } + + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + + var underlying = Handler() + underlying.expected = request + let handler = CodableSimpleLambdaHandler( + handler: underlying, + allocator: context.allocator + ) + + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(outputBuffer?.readableBytes, 0) + } + + func testCodableSimpleHandler() async throws { + struct Handler: SimpleLambdaHandler { + var expected: Request? + + func handle(_ event: Request, context: LambdaContext) async throws -> Response { + XCTAssertEqual(event, self.expected) + return Response(requestId: event.requestId) + } + } + + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + var response: Response? + + var underlying = Handler() + underlying.expected = request + let handler = CodableSimpleLambdaHandler( + handler: underlying, + allocator: context.allocator + ) + + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) + XCTAssertNoThrow(try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(response?.requestId, request.requestId) + } + // convenience method func newContext() -> LambdaContext { LambdaContext( diff --git a/Tests/AWSLambdaTestingTests/Tests.swift b/Tests/AWSLambdaTestingTests/Tests.swift index 64f4187f..dd152ac2 100644 --- a/Tests/AWSLambdaTestingTests/Tests.swift +++ b/Tests/AWSLambdaTestingTests/Tests.swift @@ -17,7 +17,6 @@ import AWSLambdaTesting import NIOCore import XCTest -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) class LambdaTestingTests: XCTestCase { func testBasics() async throws { struct MyLambda: SimpleLambdaHandler { From 7745274144ca380f00aea92c5649d1349187b294 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Wed, 19 Oct 2022 20:10:50 +0200 Subject: [PATCH 6/6] Preserve Types. (#2) * Preserve Types. * Go inline! --- .../AWSLambdaRuntimeCore/LambdaHandler.swift | 23 ++++-- .../AWSLambdaRuntimeCore/LambdaRunner.swift | 5 +- .../AWSLambdaRuntimeCore/LambdaRuntime.swift | 76 ++++++++++--------- .../LambdaRuntimeTest.swift | 6 +- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 62a51892..fc3611ba 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -62,9 +62,12 @@ public protocol SimpleLambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } +@usableFromInline final class CodableSimpleLambdaHandler: ByteBufferLambdaHandler { - private let handler: Underlying - private var outputBuffer: ByteBuffer + @usableFromInline + let handler: Underlying + @usableFromInline + private(set) var outputBuffer: ByteBuffer @inlinable static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { @@ -181,9 +184,12 @@ public protocol LambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } +@usableFromInline final class CodableLambdaHandler: ByteBufferLambdaHandler { - private let handler: Underlying - private var outputBuffer: ByteBuffer + @usableFromInline + let handler: Underlying + @usableFromInline + private(set) var outputBuffer: ByteBuffer @inlinable static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { @@ -329,9 +335,12 @@ extension EventLoopLambdaHandler where Output == Void { public func encode(value: Output, into buffer: inout ByteBuffer) throws {} } -internal final class CodableEventLoopLambdaHandler: ByteBufferLambdaHandler { - private let handler: Underlying - private var outputBuffer: ByteBuffer +@usableFromInline +final class CodableEventLoopLambdaHandler: ByteBufferLambdaHandler { + @usableFromInline + let handler: Underlying + @usableFromInline + private(set) var outputBuffer: ByteBuffer @inlinable static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift index 31136d8b..3d4f55b3 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift @@ -33,7 +33,7 @@ internal final class LambdaRunner { /// Run the user provided initializer. This *must* only be called once. /// /// - Returns: An `EventLoopFuture` fulfilled with the outcome of the initialization. - func initialize(handlerType: (some ByteBufferLambdaHandler).Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture { + func initialize(handlerType: Handler.Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture { logger.debug("initializing lambda") // 1. create the handler from the factory // 2. report initialization error if one occurred @@ -45,7 +45,6 @@ internal final class LambdaRunner { ) return handlerType.makeHandler(context: context) - .map { $0 as any ByteBufferLambdaHandler } // Hopping back to "our" EventLoop is important in case the factory returns a future // that originated from a foreign EventLoop/EventLoopGroup. // This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops @@ -60,7 +59,7 @@ internal final class LambdaRunner { } } - func run(handler: any ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture { + func run(handler: some ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture { logger.debug("lambda invocation sequence starting") // 1. request invocation from lambda runtime engine self.isGettingNextInvocation = true diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index 689559d5..96b77489 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -19,14 +19,12 @@ import NIOCore /// `LambdaRuntime` manages the Lambda process lifecycle. /// /// Use this API, if you build a higher level web framework which shall be able to run inside the Lambda environment. -public final class LambdaRuntime { +public final class LambdaRuntime { private let eventLoop: EventLoop private let shutdownPromise: EventLoopPromise private let logger: Logger private let configuration: LambdaConfiguration - private let handlerType: any ByteBufferLambdaHandler.Type - private var state = State.idle { willSet { self.eventLoop.assertInEventLoop() @@ -34,52 +32,21 @@ public final class LambdaRuntime { } } - /// Create a new `LambdaRuntime`. - /// - /// - parameters: - /// - handlerType: The ``SimpleLambdaHandler`` type the `LambdaRuntime` shall create and manage. - /// - eventLoop: An `EventLoop` to run the Lambda on. - /// - logger: A `Logger` to log the Lambda events. - public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { - self.init(CodableSimpleLambdaHandler.self, eventLoop: eventLoop, logger: logger) - } - - /// Create a new `LambdaRuntime`. - /// - /// - parameters: - /// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage. - /// - eventLoop: An `EventLoop` to run the Lambda on. - /// - logger: A `Logger` to log the Lambda events. - public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { - self.init(CodableLambdaHandler.self, eventLoop: eventLoop, logger: logger) - } - - /// Create a new `LambdaRuntime`. - /// - /// - parameters: - /// - handlerType: The ``EventLoopLambdaHandler`` type the `LambdaRuntime` shall create and manage. - /// - eventLoop: An `EventLoop` to run the Lambda on. - /// - logger: A `Logger` to log the Lambda events. - public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { - self.init(CodableEventLoopLambdaHandler.self, eventLoop: eventLoop, logger: logger) - } - /// Create a new `LambdaRuntime`. /// /// - parameters: /// - handlerType: The ``ByteBufferLambdaHandler`` type the `LambdaRuntime` shall create and manage. /// - eventLoop: An `EventLoop` to run the Lambda on. /// - logger: A `Logger` to log the Lambda events. - public convenience init(_ handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger) { + public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { self.init(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: .init()) } - init(handlerType: (some ByteBufferLambdaHandler).Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { + init(handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { self.eventLoop = eventLoop self.shutdownPromise = eventLoop.makePromise(of: Int.self) self.logger = logger self.configuration = configuration - self.handlerType = handlerType } deinit { @@ -118,7 +85,7 @@ public final class LambdaRuntime { let terminator = LambdaTerminator() let runner = LambdaRunner(eventLoop: self.eventLoop, configuration: self.configuration) - let startupFuture = runner.initialize(handlerType: self.handlerType, logger: logger, terminator: terminator) + let startupFuture = runner.initialize(handlerType: Handler.self, logger: logger, terminator: terminator) startupFuture.flatMap { handler -> EventLoopFuture> in // after the startup future has succeeded, we have a handler that we can use // to `run` the lambda. @@ -229,5 +196,40 @@ public final class LambdaRuntime { } } +public enum LambdaRuntimeFactory { + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``SimpleLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @inlinable + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(CodableSimpleLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @inlinable + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(CodableLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``EventLoopLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @inlinable + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(CodableEventLoopLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } +} + /// This is safe since lambda runtime synchronizes by dispatching all methods to a single `EventLoop` extension LambdaRuntime: @unchecked Sendable {} diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift index 578048cf..39764ccc 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift @@ -29,7 +29,7 @@ class LambdaRuntimeTest: XCTestCase { let eventLoop = eventLoopGroup.next() let logger = Logger(label: "TestLogger") - let runtime = LambdaRuntime(StartupErrorHandler.self, eventLoop: eventLoop, logger: logger) + let runtime = LambdaRuntimeFactory.makeRuntime(StartupErrorHandler.self, eventLoop: eventLoop, logger: logger) // eventLoop.submit in this case returns an EventLoopFuture> // which is why we need `wait().wait()` @@ -51,7 +51,7 @@ class LambdaRuntimeTest: XCTestCase { let eventLoop = eventLoopGroup.next() let logger = Logger(label: "TestLogger") - let runtime = LambdaRuntime(EchoHandler.self, eventLoop: eventLoop, logger: logger) + let runtime = LambdaRuntimeFactory.makeRuntime(EchoHandler.self, eventLoop: eventLoop, logger: logger) XCTAssertNoThrow(_ = try eventLoop.flatSubmit { runtime.start() }.wait()) XCTAssertThrowsError(_ = try runtime.shutdownFuture.wait()) { @@ -98,7 +98,7 @@ class LambdaRuntimeTest: XCTestCase { let eventLoop = eventLoopGroup.next() let logger = Logger(label: "TestLogger") - let runtime = LambdaRuntime(ShutdownErrorHandler.self, eventLoop: eventLoop, logger: logger) + let runtime = LambdaRuntimeFactory.makeRuntime(ShutdownErrorHandler.self, eventLoop: eventLoop, logger: logger) XCTAssertNoThrow(try eventLoop.flatSubmit { runtime.start() }.wait()) XCTAssertThrowsError(try runtime.shutdownFuture.wait()) { error in