diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index f925754b..7de3389e 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -19,10 +19,10 @@ import class Foundation.JSONEncoder import NIOCore import NIOFoundationCompat -// MARK: - SimpleLambdaHandler Codable support +// MARK: - NonFactoryLambdaHandler Codable support /// Implementation of `ByteBuffer` to `Event` decoding. -extension SimpleLambdaHandler where Event: Decodable { +extension NonFactoryLambdaHandler where Event: Decodable { @inlinable public func decode(buffer: ByteBuffer) throws -> Event { try self.decoder.decode(Event.self, from: buffer) @@ -30,7 +30,7 @@ extension SimpleLambdaHandler where Event: Decodable { } /// Implementation of `Output` to `ByteBuffer` encoding. -extension SimpleLambdaHandler where Output: Encodable { +extension NonFactoryLambdaHandler where Output: Encodable { @inlinable public func encode(value: Output, into buffer: inout ByteBuffer) throws { try self.encoder.encode(value, into: &buffer) @@ -39,7 +39,7 @@ extension SimpleLambdaHandler 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. -extension SimpleLambdaHandler where Event: Decodable { +extension NonFactoryLambdaHandler where Event: Decodable { public var decoder: LambdaCodableDecoder { Lambda.defaultJSONDecoder } @@ -47,41 +47,7 @@ extension SimpleLambdaHandler 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. -extension SimpleLambdaHandler where Output: Encodable { - public var encoder: LambdaCodableEncoder { - Lambda.defaultJSONEncoder - } -} - -// MARK: - LambdaHandler Codable support - -/// Implementation of `ByteBuffer` to `Event` decoding. -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. -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 who want to inject their own codec can do it by overriding these functions. -extension LambdaHandler 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 LambdaHandler where Output: Encodable { +extension NonFactoryLambdaHandler where Output: Encodable { public var encoder: LambdaCodableEncoder { Lambda.defaultJSONEncoder } diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift index e7674e28..115af342 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift @@ -13,9 +13,9 @@ //===----------------------------------------------------------------------===// import NIOCore -// MARK: - SimpleLambdaHandler String support +// MARK: - NonFactoryLambdaHandler String support -extension SimpleLambdaHandler where Event == String { +extension NonFactoryLambdaHandler where Event == String { /// Implementation of a `ByteBuffer` to `String` decoding. @inlinable public func decode(buffer: ByteBuffer) throws -> Event { @@ -26,28 +26,7 @@ extension SimpleLambdaHandler where Event == String { } } -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 - -extension LambdaHandler 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 LambdaHandler where Output == String { +extension NonFactoryLambdaHandler where Output == String { /// Implementation of `String` to `ByteBuffer` encoding. @inlinable public func encode(value: Output, into buffer: inout ByteBuffer) throws { diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 2592f48c..75e386a3 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -36,7 +36,7 @@ public enum Lambda { configuration: LambdaConfiguration = .init(), handlerType: Handler.Type ) -> Result { - Self.run(configuration: configuration, handlerType: CodableSimpleLambdaHandler.self) + Self.run(configuration: configuration, handlerProvider: Handler.makeCodableHandler) } /// Run a Lambda defined by implementing the ``LambdaHandler`` protocol. @@ -52,7 +52,7 @@ public enum Lambda { configuration: LambdaConfiguration = .init(), handlerType: Handler.Type ) -> Result { - Self.run(configuration: configuration, handlerType: CodableLambdaHandler.self) + Self.run(configuration: configuration, handlerProvider: Handler.makeCodableHandler) } /// Run a Lambda defined by implementing the ``EventLoopLambdaHandler`` protocol. @@ -68,7 +68,7 @@ public enum Lambda { configuration: LambdaConfiguration = .init(), handlerType: Handler.Type ) -> Result { - Self.run(configuration: configuration, handlerType: CodableEventLoopLambdaHandler.self) + Self.run(configuration: configuration, handlerProvider: CodableEventLoopLambdaHandler.makeHandler) } /// Run a Lambda defined by implementing the ``ByteBufferLambdaHandler`` protocol. @@ -77,12 +77,12 @@ public enum Lambda { /// /// - parameters: /// - configuration: A Lambda runtime configuration object - /// - handlerType: The Handler to create and invoke. + /// - handlerProvider: Provides the Handler to 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 + handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture ) -> Result { let _run = { (configuration: LambdaConfiguration) -> Result in Backtrace.install() @@ -91,7 +91,8 @@ public enum Lambda { var result: Result! MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in - let runtime = LambdaRuntime(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: configuration) + let runtime = LambdaRuntime(handlerProvider: handlerProvider, 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 fc3611ba..5abb1111 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -23,103 +23,71 @@ import NIOCore /// /// - note: Most users should implement the ``LambdaHandler`` protocol instead /// which defines the Lambda initialization method. -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 - +public protocol SimpleLambdaHandler: NonFactoryLambdaHandler { 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 } -@usableFromInline -final class CodableSimpleLambdaHandler: ByteBufferLambdaHandler { - @usableFromInline - let handler: Underlying - @usableFromInline - private(set) var outputBuffer: ByteBuffer - +extension SimpleLambdaHandler { @inlinable - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { - let promise = context.eventLoop.makePromise(of: CodableSimpleLambdaHandler.self) + static func makeCodableHandler(context: LambdaInitializationContext) -> EventLoopFuture> { + let promise = context.eventLoop.makePromise(of: NonFactoryCodableLambdaHandler.self) promise.completeWithTask { - let handler = Underlying() - return CodableSimpleLambdaHandler(handler: handler, allocator: context.allocator) + let handler = Self() + return NonFactoryCodableLambdaHandler(handler: handler, allocator: context.allocator) } return promise.futureResult } +} - @inlinable - init(handler: Underlying, allocator: ByteBufferAllocator) { - self.handler = handler - self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) +extension SimpleLambdaHandler { + /// 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) } +} - @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) - } +// MARK: - LambdaHandler - let output = try await self.handler.handle(input, context: context) +/// 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 this protocol instead of the lower +/// level protocols ``EventLoopLambdaHandler`` and +/// ``ByteBufferLambdaHandler``. +public protocol LambdaHandler: NonFactoryLambdaHandler { + /// 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 +} - do { - self.outputBuffer.clear() - try self.handler.encode(value: output, into: &self.outputBuffer) - return self.outputBuffer - } catch { - throw CodecError.responseEncoding(error) - } +extension LambdaHandler { + @inlinable + static func makeCodableHandler(context: LambdaInitializationContext) -> EventLoopFuture> { + let promise = context.eventLoop.makePromise(of: NonFactoryCodableLambdaHandler.self) + promise.completeWithTask { + let handler = try await Self(context: context) + return NonFactoryCodableLambdaHandler(handler: handler, allocator: context.allocator) } 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 {} -} - -extension SimpleLambdaHandler { +extension LambdaHandler { /// Initializes and runs the Lambda function. /// - /// If you precede your ``SimpleLambdaHandler`` 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. /// @@ -130,16 +98,34 @@ extension SimpleLambdaHandler { } } -// MARK: - LambdaHandler +/// unchecked sendable wrapper for the handler +/// this is safe since lambda runtime is designed to calls the handler serially +@usableFromInline +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) + } +} + +// MARK: - NonFactoryLambdaHandler /// 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 this protocol instead of the lower -/// level protocols ``EventLoopLambdaHandler`` and -/// ``ByteBufferLambdaHandler``. -public protocol LambdaHandler { +/// Unlike the `LambdaHandler` and `SimpleLambdaHandler` protocols, +/// this does not have any initialization requirement, allowing it to be used - along with +/// the `NonFactoryByteBufferLambdaHandler` protocol with custom initialization. +public protocol NonFactoryLambdaHandler { /// 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. @@ -147,14 +133,6 @@ public protocol LambdaHandler { /// The lambda function's output. Can be `Void`. associatedtype Output - /// 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. /// Concrete Lambda handlers implement this method to provide the Lambda functionality. /// @@ -184,23 +162,21 @@ public protocol LambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } +public extension NonFactoryLambdaHandler { + /// Creates a `NonFactoryByteBufferLambdaHandler` from the current instance that will handle + /// Codable serialization and deserialization. + func withWrappingCodableHandler(allocator: ByteBufferAllocator) -> some NonFactoryByteBufferLambdaHandler { + return NonFactoryCodableLambdaHandler(handler: self, allocator: allocator) + } +} + @usableFromInline -final class CodableLambdaHandler: ByteBufferLambdaHandler { +final class NonFactoryCodableLambdaHandler: NonFactoryByteBufferLambdaHandler { @usableFromInline let handler: Underlying @usableFromInline private(set) var outputBuffer: ByteBuffer - @inlinable - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { - let promise = context.eventLoop.makePromise(of: CodableLambdaHandler.self) - promise.completeWithTask { - let handler = try await Underlying(context: context) - return CodableLambdaHandler(handler: handler, allocator: context.allocator) - } - return promise.futureResult - } - @inlinable init(handler: Underlying, allocator: ByteBufferAllocator) { self.handler = handler @@ -233,42 +209,11 @@ final class CodableLambdaHandler: ByteBufferLambdaHan } /// Implementation of `ByteBuffer` to `Void` decoding. -extension LambdaHandler where Output == Void { +extension NonFactoryLambdaHandler where Output == Void { @inlinable public func encode(value: Output, into buffer: inout ByteBuffer) throws {} } -extension LambdaHandler { - /// Initializes and runs the Lambda function. - /// - /// 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. - /// - /// 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 -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) - } -} // MARK: - EventLoopLambdaHandler @@ -390,22 +335,16 @@ extension EventLoopLambdaHandler { } } -// MARK: - ByteBufferLambdaHandler +// MARK: - CoreByteBufferLambdaHandler /// An `EventLoopFuture` based processing protocol for a Lambda that takes a `ByteBuffer` and returns -/// an optional `ByteBuffer` asynchronously. +/// an optional `ByteBuffer` asynchronously. Unlike the higher level `ByteBufferLambdaHandler` protocol, +/// this doesn't provide a factory method for creating an instance of itself. /// -/// - note: This is a low level protocol designed to power the higher level ``EventLoopLambdaHandler`` and -/// ``LambdaHandler`` based APIs. -/// Most users are not expected to use this protocol. -public protocol ByteBufferLambdaHandler { - /// 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 - /// to `EventLoopGroup` when initializing NIO dependencies. This will improve overall performance, as it - /// minimizes thread hopping. - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture +/// - note: This is a low level protocol designed designed for use cases where the flexibility of the +/// `LambdaRuntime` type is required directly. Applications can provide the runtime with a +/// provider function that returns an instance of a type conforming to this protocol. +public protocol NonFactoryByteBufferLambdaHandler { /// The Lambda handling method. /// Concrete Lambda handlers implement this method to provide the Lambda functionality. @@ -419,6 +358,26 @@ public protocol ByteBufferLambdaHandler { func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture } +// MARK: - ByteBufferLambdaHandler + +/// An extension of the `CoreByteBufferLambdaHandler` protocol that provides the ability for the conforming type to +/// specify how it is instantiated. This allows conforming types to use the +/// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) +/// attribute directly. +/// +/// - note: This is a low level protocol designed to power the higher level ``EventLoopLambdaHandler`` and +/// ``LambdaHandler`` based APIs. +/// Most users are not expected to use this protocol. +public protocol ByteBufferLambdaHandler: NonFactoryByteBufferLambdaHandler { + /// 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 + /// to `EventLoopGroup` when initializing NIO dependencies. This will improve overall performance, as it + /// minimizes thread hopping. + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture +} + extension ByteBufferLambdaHandler { /// Initializes and runs the Lambda function. /// @@ -429,7 +388,7 @@ extension ByteBufferLambdaHandler { /// 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) + _ = Lambda.run(configuration: .init(), handlerProvider: Self.makeHandler) } } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift index 0094ed7e..a6d8de7e 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift @@ -33,7 +33,9 @@ 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: Handler.Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture { + func initialize(handlerProvider: (LambdaInitializationContext) -> EventLoopFuture, + logger: Logger, + terminator: LambdaTerminator) -> EventLoopFuture { logger.debug("initializing lambda") // 1. create the handler from the factory // 2. report initialization error if one occurred @@ -44,8 +46,8 @@ internal final class LambdaRunner { terminator: terminator ) - return handlerType.makeHandler(context: context) - // Hopping back to "our" EventLoop is important in case the factory returns a future + return handlerProvider(context) + // Hopping back to "our" EventLoop is important in case the provider 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 // for whatever reason and returns a future that originated from that foreign EventLoop. @@ -59,7 +61,7 @@ internal final class LambdaRunner { } } - func run(handler: some ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture { + func run(handler: some NonFactoryByteBufferLambdaHandler, 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 96b77489..83b30a48 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -19,10 +19,11 @@ 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 handlerProvider: (LambdaInitializationContext) -> EventLoopFuture private let configuration: LambdaConfiguration private var state = State.idle { @@ -35,17 +36,20 @@ public final class LambdaRuntime { /// Create a new `LambdaRuntime`. /// /// - parameters: - /// - handlerType: The ``ByteBufferLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - handlerProvider: Provides the ``CoreByteBufferLambdaHandler`` 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(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: .init()) + public convenience init(handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture, + eventLoop: EventLoop, logger: Logger) { + self.init(handlerProvider: handlerProvider, eventLoop: eventLoop, logger: logger, configuration: .init()) } - init(handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { + init(handlerProvider: @escaping (LambdaInitializationContext) -> EventLoopFuture, eventLoop: EventLoop, + logger: Logger, configuration: LambdaConfiguration) { self.eventLoop = eventLoop self.shutdownPromise = eventLoop.makePromise(of: Int.self) self.logger = logger + self.handlerProvider = handlerProvider self.configuration = configuration } @@ -85,7 +89,7 @@ public final class LambdaRuntime { let terminator = LambdaTerminator() let runner = LambdaRunner(eventLoop: self.eventLoop, configuration: self.configuration) - let startupFuture = runner.initialize(handlerType: Handler.self, logger: logger, terminator: terminator) + let startupFuture = runner.initialize(handlerProvider: self.handlerProvider, 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 +179,7 @@ public final class LambdaRuntime { private enum State { case idle case initializing - case active(LambdaRunner, any ByteBufferLambdaHandler) + case active(LambdaRunner, any NonFactoryByteBufferLambdaHandler) case shuttingdown case shutdown @@ -204,8 +208,9 @@ public enum LambdaRuntimeFactory { /// - 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) + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(handlerProvider: H.makeCodableHandler, + eventLoop: eventLoop, logger: logger) } /// Create a new `LambdaRuntime`. @@ -215,8 +220,9 @@ public enum LambdaRuntimeFactory { /// - 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) + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(handlerProvider: H.makeCodableHandler, + eventLoop: eventLoop, logger: logger) } /// Create a new `LambdaRuntime`. @@ -227,7 +233,8 @@ public enum LambdaRuntimeFactory { /// - 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) + LambdaRuntime>(handlerProvider: CodableEventLoopLambdaHandler.makeHandler, + eventLoop: eventLoop, logger: logger) } } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index ffb50953..551ee1d3 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -286,7 +286,7 @@ class LambdaTest: XCTestCase { logger: logger ).get() - try await runner.initialize(handlerType: CodableEventLoopLambdaHandler.self, logger: logger, terminator: LambdaTerminator()).flatMap { handler2 in + try await runner.initialize(handlerProvider: CodableEventLoopLambdaHandler.makeHandler, logger: logger, terminator: LambdaTerminator()).flatMap { handler2 in runner.run(handler: handler2, logger: logger) }.get() } diff --git a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift index aecd3186..6a445550 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift @@ -19,18 +19,19 @@ import NIOPosix import XCTest func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { - try runLambda(behavior: behavior, handlerType: CodableSimpleLambdaHandler.self) + try runLambda(behavior: behavior, handlerProvider: Handler.makeCodableHandler) } func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { - try runLambda(behavior: behavior, handlerType: CodableLambdaHandler.self) + try runLambda(behavior: behavior, handlerProvider: Handler.makeCodableHandler) } func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { - try runLambda(behavior: behavior, handlerType: CodableEventLoopLambdaHandler.self) + try runLambda(behavior: behavior, handlerProvider: CodableEventLoopLambdaHandler.makeHandler) } -func runLambda(behavior: LambdaServerBehavior, handlerType: (some ByteBufferLambdaHandler).Type) throws { +func runLambda(behavior: LambdaServerBehavior, + handlerProvider: (LambdaInitializationContext) -> EventLoopFuture) throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let logger = Logger(label: "TestLogger") @@ -39,7 +40,7 @@ func runLambda(behavior: LambdaServerBehavior, handlerType: (some ByteBufferLamb 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(handlerType: handlerType, logger: logger, terminator: terminator).flatMap { handler in + try runner.initialize(handlerProvider: handlerProvider, logger: logger, terminator: terminator).flatMap { handler in runner.run(handler: handler, logger: logger) }.wait() } diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift index 43e50423..b3e46487 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift @@ -108,7 +108,7 @@ class CodableLambdaTest: XCTestCase { var underlying = try await Handler(context: self.newInitContext()) underlying.expected = request - let handler = CodableLambdaHandler( + let handler = NonFactoryCodableLambdaHandler( handler: underlying, allocator: context.allocator ) @@ -138,64 +138,7 @@ class CodableLambdaTest: XCTestCase { var underlying = try await Handler(context: self.newInitContext()) underlying.expected = request - let handler = CodableLambdaHandler( - 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) - } - - 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( + let handler = NonFactoryCodableLambdaHandler( handler: underlying, allocator: context.allocator )