From 804bed70d6b6db36aa7b6efd9d59120ff0e0436f Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Mon, 2 Sep 2024 14:44:17 +0100 Subject: [PATCH 1/8] Add NewLambdaHandler + LambdaWithBackgroundProcessingHandler protocols. Add closure handlers for StreamingLambdaHandler + NewLambdaHandler. Add Codable adapters. --- .../LambdaRuntimeClientProtocol.swift | 7 -- .../AWSLambdaRuntimeCore/NewLambda+JSON.swift | 114 ++++++++++++++++++ Sources/AWSLambdaRuntimeCore/NewLambda.swift | 9 -- .../NewLambdaHandlers.swift | 93 ++++++++++++++ 4 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift create mode 100644 Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift index f85b1b4f..14efaa7a 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift @@ -14,13 +14,6 @@ import NIOCore -package protocol LambdaResponseStreamWriter { - mutating func write(_ buffer: ByteBuffer) async throws - func finish() async throws - func writeAndFinish(_ buffer: ByteBuffer) async throws - func reportError(_ error: any Error) async throws -} - package protocol LambdaRuntimeClientProtocol { associatedtype Writer: LambdaResponseStreamWriter diff --git a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift new file mode 100644 index 00000000..6eaff33f --- /dev/null +++ b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift @@ -0,0 +1,114 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import NIOCore +import NIOFoundationCompat + +import class Foundation.JSONDecoder +import class Foundation.JSONEncoder + +package protocol LambdaEventDecoder { + func decode(_ type: Event.Type, from buffer: ByteBuffer) throws -> Event +} + +package protocol LambdaOutputEncoder { + func encode(_ value: Output, into buffer: inout ByteBuffer) throws +} + +extension JSONEncoder: LambdaOutputEncoder {} + +extension JSONDecoder: LambdaEventDecoder {} + +package struct VoidEncoder: LambdaOutputEncoder { + package func encode(_ value: Output, into buffer: inout NIOCore.ByteBuffer) throws where Output: Encodable { + fatalError("LambdaOutputEncoder must never be called on a void output") + } +} + +package struct LambdaHandlerAdapter< + Event: Decodable, + Output, + Handler: NewLambdaHandler +>: LambdaWithBackgroundProcessingHandler where Handler.Event == Event, Handler.Output == Output { + let handler: Handler + + package init(handler: Handler) { + self.handler = handler + } + + package func handle( + _ event: Event, + outputWriter: consuming some LambdaResponseWriter, + context: NewLambdaContext + ) async throws { + let response = try await self.handler.handle(event, context: context) + try await outputWriter.write(response: response) + } +} + +package struct LambdaCodableAdapter< + Handler: LambdaWithBackgroundProcessingHandler, + Event: Decodable, + Output, + Decoder: LambdaEventDecoder, + Encoder: LambdaOutputEncoder +>: StreamingLambdaHandler where Handler.Event == Event, Handler.Output == Output { + let handler: Handler + let encoder: Encoder + let decoder: Decoder + private var byteBuffer: ByteBuffer = .init() + + package init(encoder: Encoder, decoder: Decoder, handler: Handler) where Output: Encodable { + self.encoder = encoder + self.decoder = decoder + self.handler = handler + } + + package init(decoder: Decoder, handler: Handler) where Output == Void, Encoder == VoidEncoder { + self.encoder = VoidEncoder() + self.decoder = decoder + self.handler = handler + } + + package mutating func handle( + _ request: ByteBuffer, + responseWriter: some LambdaResponseStreamWriter, + context: NewLambdaContext + ) async throws { + let event = try self.decoder.decode(Event.self, from: request) + + let writer = ResponseWriter(encoder: self.encoder, streamWriter: responseWriter) + try await self.handler.handle(event, outputWriter: writer, context: context) + } +} + +package struct ResponseWriter: LambdaResponseWriter { + let underlyingStreamWriter: LambdaResponseStreamWriter + let encoder: LambdaOutputEncoder + var byteBuffer = ByteBuffer() + + package init(encoder: LambdaOutputEncoder, streamWriter: LambdaResponseStreamWriter) { + self.encoder = encoder + self.underlyingStreamWriter = streamWriter + } + + package mutating func write(response: Output) async throws { + if Output.self == Void.self { + try await self.underlyingStreamWriter.finish() + } else if let response = response as? Encodable { + try self.encoder.encode(response, into: &self.byteBuffer) + try await self.underlyingStreamWriter.writeAndFinish(self.byteBuffer) + } + } +} diff --git a/Sources/AWSLambdaRuntimeCore/NewLambda.swift b/Sources/AWSLambdaRuntimeCore/NewLambda.swift index 5adb8f57..28eb7df9 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambda.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambda.swift @@ -14,15 +14,6 @@ import Dispatch import Logging -import NIOCore - -package protocol StreamingLambdaHandler { - mutating func handle( - _ event: ByteBuffer, - responseWriter: some LambdaResponseStreamWriter, - context: NewLambdaContext - ) async throws -} extension Lambda { package static func runLoop( diff --git a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift new file mode 100644 index 00000000..59619c66 --- /dev/null +++ b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift @@ -0,0 +1,93 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import NIOCore + +package protocol StreamingLambdaHandler { + mutating func handle( + _ event: ByteBuffer, + responseWriter: some LambdaResponseStreamWriter, + context: NewLambdaContext + ) async throws +} + +package protocol LambdaResponseStreamWriter { + mutating func write(_ buffer: ByteBuffer) async throws + func finish() async throws + func writeAndFinish(_ buffer: ByteBuffer) async throws + func reportError(_ error: any Error) async throws +} + +package protocol NewLambdaHandler { + associatedtype Event: Decodable + associatedtype Output + + func handle(_ event: Event, context: NewLambdaContext) async throws -> Output +} + +package protocol LambdaWithBackgroundProcessingHandler { + associatedtype Event: Decodable + associatedtype Output + + /// The business logic of the Lambda function. Receives a generic input type and returns a generic output type. + /// Agnostic to JSON encoding/decoding + func handle( + _ event: Event, + outputWriter: some LambdaResponseWriter, + context: NewLambdaContext + ) async throws +} + +package protocol LambdaResponseWriter: ~Copyable { + associatedtype Output + /// Sends the generic Output object (representing the computed result of the handler) + /// to the AWS Lambda response endpoint. + /// This function simply serves as a mechanism to return the computed result from a handler function + /// without an explicit `return`. + mutating func write(response: Output) async throws +} + +package struct StreamingClosureHandler: StreamingLambdaHandler { + let body: @Sendable (ByteBuffer, LambdaResponseStreamWriter, NewLambdaContext) async throws -> Void + + package init( + body: @Sendable @escaping (ByteBuffer, LambdaResponseStreamWriter, NewLambdaContext) async throws -> Void + ) { + self.body = body + } + + package func handle( + _ request: ByteBuffer, + responseWriter: some LambdaResponseStreamWriter, + context: NewLambdaContext + ) async throws { + try await self.body(request, responseWriter, context) + } +} + +package struct ClosureHandler: NewLambdaHandler { + let body: (Event, NewLambdaContext) async throws -> Output + + package init(body: @escaping (Event, NewLambdaContext) async throws -> Output) where Output: Encodable { + self.body = body + } + + package init(body: @escaping (Event, NewLambdaContext) async throws -> Void) where Output == Void { + self.body = body + } + + package func handle(_ event: Event, context: NewLambdaContext) async throws -> Output { + try await self.body(event, context) + } +} From b4a6a4feaadcf6715bd28019f09cec174516d3e8 Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Tue, 3 Sep 2024 14:58:23 +0100 Subject: [PATCH 2/8] Add documentation, add new internal `LambdaRuntimeClientResponseStreamWriter`, move Foundation import from AWSLambdaRuntimeCore --- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 7 ++ .../LambdaRuntimeClientProtocol.swift | 9 +- .../AWSLambdaRuntimeCore/NewLambda+JSON.swift | 53 +++++++++-- .../NewLambdaHandlers.swift | 88 ++++++++++++++++++- .../LambdaMockClient.swift | 2 +- 5 files changed, 145 insertions(+), 14 deletions(-) diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 3f80ee17..da7f358b 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -16,9 +16,13 @@ import NIOCore import NIOFoundationCompat +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import struct Foundation.Data import class Foundation.JSONDecoder import class Foundation.JSONEncoder +#endif // MARK: - SimpleLambdaHandler Codable support @@ -138,3 +142,6 @@ extension Lambda { extension JSONDecoder: LambdaCodableDecoder {} extension JSONEncoder: LambdaCodableEncoder {} + +extension JSONDecoder: AWSLambdaRuntimeCore.LambdaEventDecoder {} +extension JSONEncoder: AWSLambdaRuntimeCore.LambdaOutputEncoder {} diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift index 14efaa7a..86f5cf1a 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift @@ -14,8 +14,15 @@ import NIOCore +package protocol LambdaRuntimeClientResponseStreamWriter: LambdaResponseStreamWriter { + mutating func write(_ buffer: ByteBuffer) async throws + func finish() async throws + func writeAndFinish(_ buffer: ByteBuffer) async throws + func reportError(_ error: any Error) async throws +} + package protocol LambdaRuntimeClientProtocol { - associatedtype Writer: LambdaResponseStreamWriter + associatedtype Writer: LambdaRuntimeClientResponseStreamWriter func nextInvocation() async throws -> (Invocation, Writer) } diff --git a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift index 6eaff33f..660ff8d9 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift @@ -13,29 +13,36 @@ //===----------------------------------------------------------------------===// import NIOCore -import NIOFoundationCompat - -import class Foundation.JSONDecoder -import class Foundation.JSONEncoder +/// The protocol a decoder must conform to so that it can be used with ``LambdaCodableAdapter`` to decode incoming +/// ``ByteBuffer`` events. package protocol LambdaEventDecoder { + /// Decode the ``ByteBuffer`` representing the received event into the generic ``Event`` type + /// the handler will receive. + /// - Parameters: + /// - type: The type of the object to decode the buffer into. + /// - buffer: The buffer to be decoded. + /// - Returns: An object containing the decoded data. func decode(_ type: Event.Type, from buffer: ByteBuffer) throws -> Event } +/// The protocol an encoder must conform to so that it can be used with ``LambdaCodableAdapter`` to encode the generic +/// ``Output`` object into a ``ByteBuffer``. package protocol LambdaOutputEncoder { + /// Encode the generic type `Output` the handler has returned into a ``ByteBuffer``. + /// - Parameters: + /// - value: The object to encode into a ``ByteBuffer``. + /// - buffer: The ``ByteBuffer`` where the encoded value will be written to. func encode(_ value: Output, into buffer: inout ByteBuffer) throws } -extension JSONEncoder: LambdaOutputEncoder {} - -extension JSONDecoder: LambdaEventDecoder {} - package struct VoidEncoder: LambdaOutputEncoder { package func encode(_ value: Output, into buffer: inout NIOCore.ByteBuffer) throws where Output: Encodable { fatalError("LambdaOutputEncoder must never be called on a void output") } } +/// Adapts a ``NewLambdaHandler`` conforming handler to conform to ``LambdaWithBackgroundProcessingHandler``. package struct LambdaHandlerAdapter< Event: Decodable, Output, @@ -43,10 +50,18 @@ package struct LambdaHandlerAdapter< >: LambdaWithBackgroundProcessingHandler where Handler.Event == Event, Handler.Output == Output { let handler: Handler + /// Initializes an instance given a concrete handler. + /// - Parameter handler: The ``NewLambdaHandler`` conforming handler that is to be adapted to ``LambdaWithBackgroundProcessingHandler``. package init(handler: Handler) { self.handler = handler } + /// Passes the generic ``Event`` object to the ``NewLambdaHandler/handle(_:context:)`` function, and + /// the resulting output is then written to ``LambdaWithBackgroundProcessingHandler``'s `outputWriter`. + /// - Parameters: + /// - event: The received event. + /// - outputWriter: The writer to write the computed response to. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. package func handle( _ event: Event, outputWriter: consuming some LambdaResponseWriter, @@ -57,6 +72,7 @@ package struct LambdaHandlerAdapter< } } +/// Adapts a ``LambdaWithBackgroundProcessingHandler`` conforming handler to conform to ``StreamingLambdaHandler``. package struct LambdaCodableAdapter< Handler: LambdaWithBackgroundProcessingHandler, Event: Decodable, @@ -69,18 +85,32 @@ package struct LambdaCodableAdapter< let decoder: Decoder private var byteBuffer: ByteBuffer = .init() + /// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output. + /// - Parameters: + /// - encoder: The encoder object that will be used to encode the generic ``Output`` obtained from the `handler`'s `outputWriter` into a ``ByteBuffer``. + /// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`. + /// - handler: The handler object. package init(encoder: Encoder, decoder: Decoder, handler: Handler) where Output: Encodable { self.encoder = encoder self.decoder = decoder self.handler = handler } + /// Initializes an instance given a decoder, and a handler with a `Void` output. + /// - Parameters: + /// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`. + /// - handler: The handler object. package init(decoder: Decoder, handler: Handler) where Output == Void, Encoder == VoidEncoder { self.encoder = VoidEncoder() self.decoder = decoder self.handler = handler } + /// A ``StreamingLambdaHandler/handle(_:responseWriter:context:)`` wrapper. + /// - Parameters: + /// - event: The received event. + /// - outputWriter: The writer to write the computed response to. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. package mutating func handle( _ request: ByteBuffer, responseWriter: some LambdaResponseStreamWriter, @@ -93,16 +123,23 @@ package struct LambdaCodableAdapter< } } +/// A ``LambdaResponseStreamWriter`` wrapper that conforms to ``LambdaResponseWriter``. package struct ResponseWriter: LambdaResponseWriter { let underlyingStreamWriter: LambdaResponseStreamWriter let encoder: LambdaOutputEncoder var byteBuffer = ByteBuffer() + /// Initializes an instance given an encoder and an underlying ``LambdaResponseStreamWriter``. + /// - Parameters: + /// - encoder: The encoder object that will be used to encode the generic ``Output`` into a ``ByteBuffer``, which will then be passed to `streamWriter`. + /// - streamWriter: The underlying ``LambdaResponseStreamWriter`` that will be wrapped. package init(encoder: LambdaOutputEncoder, streamWriter: LambdaResponseStreamWriter) { self.encoder = encoder self.underlyingStreamWriter = streamWriter } + /// Passes the `response` argument to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. + /// - Parameter response: The generic ``Output`` object that will be passed to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. package mutating func write(response: Output) async throws { if Output.self == Void.self { try await self.underlyingStreamWriter.finish() diff --git a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift index 59619c66..ddaeb237 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift @@ -14,7 +14,28 @@ import NIOCore +/// The base handler protocol that receives a ``ByteBuffer`` representing the incoming event and returns the response as a ``ByteBuffer`` too. +/// This handler protocol supports response streaming. Bytes can be streamed outwards through the ``LambdaResponseStreamWriter`` +/// passed as an argument in the ``handle(_:responseWriter:context:)`` function. +/// Background work can also be executed after returning the response. After closing the response stream by calling +/// ``LambdaResponseStreamWriter/finish()`` or ``LambdaResponseStreamWriter/writeAndFinish(_:)``, +/// the ``handle(_:responseWriter:context:)`` function is free to execute any background work. package protocol StreamingLambdaHandler { + /// The handler function -- implement the business logic of the Lambda function here. + /// - Parameters: + /// - event: The invocation's input data. + /// - responseWriter: A ``LambdaResponseStreamWriter`` to write the invocation's response to. + /// If no response or error is written to `responseWriter` an error will be reported to the invoker. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. + /// - Throws: + /// How the thrown error will be handled by the runtime: + /// - An invocation error will be reported if the error is thrown before the first call to + /// ``LambdaResponseStreamWriter/write(_:)``. + /// - If the error is thrown after call(s) to ``LambdaResponseStreamWriter/write(_:)`` but before + /// a call to ``LambdaResponseStreamWriter/finish()``, the response stream will be closed and trailing + /// headers will be sent. + /// - If ``LambdaResponseStreamWriter/finish()`` has already been called before the error is thrown, the + /// error will be logged. mutating func handle( _ event: ByteBuffer, responseWriter: some LambdaResponseStreamWriter, @@ -22,26 +43,62 @@ package protocol StreamingLambdaHandler { ) async throws } +/// A writer object to write the Lambda response stream into. The HTTP response is started lazily. +/// before the first call to ``write(_:)`` or ``writeAndFinish(_:)``. package protocol LambdaResponseStreamWriter { + /// Write a response part into the stream. Bytes written are streamed continually. + /// - Parameter buffer: The buffer to write. mutating func write(_ buffer: ByteBuffer) async throws + + /// End the response stream and the underlying HTTP response. func finish() async throws + + /// Write a response part into the stream and then end the stream as well as the underlying HTTP response. + /// - Parameter buffer: The buffer to write. func writeAndFinish(_ buffer: ByteBuffer) async throws - func reportError(_ error: any Error) async throws } +/// This handler protocol is intended to serve the most common use-cases. +/// This protocol is completely agnostic to any encoding/decoding -- decoding the received event invocation into an ``Event`` object and encoding the returned ``Output`` object is handled by the library. +/// The``handle(_:context:)`` function simply receives the generic ``Event`` object as input and returns the generic ``Output`` object. +/// +/// - note: This handler protocol does not support response streaming because the output has to be encoded prior to it being sent, e.g. it is not possible to encode a partial/incomplete JSON string. +/// This protocol also does not support the execution of background work after the response has been returned -- the ``LambdaWithBackgroundProcessingHandler`` protocol caters for such use-cases. package protocol NewLambdaHandler { + /// Generic input type. + /// The body of the request sent to Lambda will be decoded into this type for the handler to consume. associatedtype Event: Decodable + /// Generic output type. + /// This is the return type of the ``handle(_:context:)`` function. associatedtype Output + /// Implement the business logic of the Lambda function here. + /// - Parameters: + /// - event: The generic ``Event`` object representing the invocation's input data. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. + /// - Returns: A generic ``Output`` object representing the computed result. func handle(_ event: Event, context: NewLambdaContext) async throws -> Output } +/// This protocol is exactly like ``NewLambdaHandler``, with the only difference being the added support for executing background +/// work after the result has been sent to the AWS Lambda control plane. +/// This is achieved by not having a return type in the `handle` function. The output is instead written into a +/// ``LambdaResponseWriter``that is passed in as an argument, meaning that the ``handle(_:)`` function is then free to implement +/// any background work after the result has been sent to the AWS Lambda control plane. package protocol LambdaWithBackgroundProcessingHandler { + /// Generic input type. + /// The body of the request sent to Lambda will be decoded into this type for the handler to consume. associatedtype Event: Decodable + /// Generic output type. + /// This is the type that the `handle` function will send through the ``LambdaResponseWriter``. associatedtype Output - /// The business logic of the Lambda function. Receives a generic input type and returns a generic output type. - /// Agnostic to JSON encoding/decoding + /// Implement the business logic of the Lambda function here. + /// - Parameters: + /// - event: The generic ``Event`` object representing the invocation's input data. + /// - outputWriter: The writer to send the computed response to. A call to `outputWriter.write(_:)` will return the response to the AWS Lambda response endpoint. + /// Any background work can then be executed before returning. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. func handle( _ event: Event, outputWriter: some LambdaResponseWriter, @@ -49,24 +106,37 @@ package protocol LambdaWithBackgroundProcessingHandler { ) async throws } +/// Used with ``LambdaWithBackgroundProcessingHandler``. +/// A mechanism to "return" an output from ``LambdaWithBackgroundProcessingHandler/handle(_:outputWriter:context:)`` without the function needing to +/// have a return type and exit at that point. This allows for background work to be executed _after_ a response has been sent to the AWS Lambda response endpoint. package protocol LambdaResponseWriter: ~Copyable { associatedtype Output - /// Sends the generic Output object (representing the computed result of the handler) + /// Sends the generic ``Output`` object (representing the computed result of the handler) /// to the AWS Lambda response endpoint. /// This function simply serves as a mechanism to return the computed result from a handler function /// without an explicit `return`. mutating func write(response: Output) async throws } +/// A ``StreamingLambdaHandler`` conforming handler object that can be constructed with a closure. +/// Allows for a handler to be defined in a clean manner, leveraging Swift's trailing closure syntax. package struct StreamingClosureHandler: StreamingLambdaHandler { let body: @Sendable (ByteBuffer, LambdaResponseStreamWriter, NewLambdaContext) async throws -> Void + /// Initialize an instance from a handler function in the form of a closure. + /// - Parameter body: The handler function written as a closure. package init( body: @Sendable @escaping (ByteBuffer, LambdaResponseStreamWriter, NewLambdaContext) async throws -> Void ) { self.body = body } + /// Calls the provided `self.body` closure with the ``ByteBuffer`` invocation event, the ``LambdaResponseStreamWriter``, and the ``NewLambdaContext`` + /// - Parameters: + /// - event: The invocation's input data. + /// - responseWriter: A ``LambdaResponseStreamWriter`` to write the invocation's response to. + /// If no response or error is written to `responseWriter` an error will be reported to the invoker. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. package func handle( _ request: ByteBuffer, responseWriter: some LambdaResponseStreamWriter, @@ -76,17 +146,27 @@ package struct StreamingClosureHandler: StreamingLambdaHandler { } } +/// A ``NewLambdaHandler`` conforming handler object that can be constructed with a closure. +/// Allows for a handler to be defined in a clean manner, leveraging Swift's trailing closure syntax. package struct ClosureHandler: NewLambdaHandler { let body: (Event, NewLambdaContext) async throws -> Output + /// Initialize with a closure handler over generic `Input` and `Output` types. + /// - Parameter body: The handler function written as a closure. package init(body: @escaping (Event, NewLambdaContext) async throws -> Output) where Output: Encodable { self.body = body } + /// Initialize with a closure handler over a generic `Input` type, and a `Void` `Output`. + /// - Parameter body: The handler function written as a closure. package init(body: @escaping (Event, NewLambdaContext) async throws -> Void) where Output == Void { self.body = body } + /// Calls the provided `self.body` closure with the generic ``Event`` object representing the incoming event, and the ``NewLambdaContext`` + /// - Parameters: + /// - event: The generic ``Event`` object representing the invocation's input data. + /// - context: The ``NewLambdaContext`` containing the invocation's metadata. package func handle(_ event: Event, context: NewLambdaContext) async throws -> Output { try await self.body(event, context) } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift index 4023dc76..3f226481 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift @@ -17,7 +17,7 @@ import Foundation import Logging import NIOCore -struct LambdaMockWriter: LambdaResponseStreamWriter { +struct LambdaMockWriter: LambdaRuntimeClientResponseStreamWriter { var underlying: LambdaMockClient init(underlying: LambdaMockClient) { From 6bf879ce9da9228426f02e61d867cc0fb2a67dff Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Tue, 3 Sep 2024 15:55:49 +0100 Subject: [PATCH 3/8] Refactor --- .../AWSLambdaRuntimeCore/NewLambda+JSON.swift | 30 ++++++++++--------- .../NewLambdaHandlers.swift | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift index 660ff8d9..a52de6db 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift @@ -38,7 +38,7 @@ package protocol LambdaOutputEncoder { package struct VoidEncoder: LambdaOutputEncoder { package func encode(_ value: Output, into buffer: inout NIOCore.ByteBuffer) throws where Output: Encodable { - fatalError("LambdaOutputEncoder must never be called on a void output") + } } @@ -67,8 +67,8 @@ package struct LambdaHandlerAdapter< outputWriter: consuming some LambdaResponseWriter, context: NewLambdaContext ) async throws { - let response = try await self.handler.handle(event, context: context) - try await outputWriter.write(response: response) + let output = try await self.handler.handle(event, context: context) + try await outputWriter.write(output) } } @@ -118,34 +118,36 @@ package struct LambdaCodableAdapter< ) async throws { let event = try self.decoder.decode(Event.self, from: request) - let writer = ResponseWriter(encoder: self.encoder, streamWriter: responseWriter) + let writer = LambdaCodableResponseWriter(encoder: self.encoder, streamWriter: responseWriter) try await self.handler.handle(event, outputWriter: writer, context: context) } } /// A ``LambdaResponseStreamWriter`` wrapper that conforms to ``LambdaResponseWriter``. -package struct ResponseWriter: LambdaResponseWriter { - let underlyingStreamWriter: LambdaResponseStreamWriter - let encoder: LambdaOutputEncoder - var byteBuffer = ByteBuffer() +package struct LambdaCodableResponseWriter: LambdaResponseWriter { + @usableFromInline let underlyingStreamWriter: LambdaResponseStreamWriter + @usableFromInline let encoder: LambdaOutputEncoder /// Initializes an instance given an encoder and an underlying ``LambdaResponseStreamWriter``. /// - Parameters: /// - encoder: The encoder object that will be used to encode the generic ``Output`` into a ``ByteBuffer``, which will then be passed to `streamWriter`. /// - streamWriter: The underlying ``LambdaResponseStreamWriter`` that will be wrapped. + @inlinable package init(encoder: LambdaOutputEncoder, streamWriter: LambdaResponseStreamWriter) { self.encoder = encoder self.underlyingStreamWriter = streamWriter } - /// Passes the `response` argument to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. - /// - Parameter response: The generic ``Output`` object that will be passed to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. - package mutating func write(response: Output) async throws { + /// Passes the `output` argument to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. + /// - Parameter output: The generic ``Output`` object that will be passed to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. + @inlinable + package func write(_ output: Output) async throws { if Output.self == Void.self { try await self.underlyingStreamWriter.finish() - } else if let response = response as? Encodable { - try self.encoder.encode(response, into: &self.byteBuffer) - try await self.underlyingStreamWriter.writeAndFinish(self.byteBuffer) + } else if let output = output as? Encodable { + var outputBuffer = ByteBuffer() + try self.encoder.encode(output, into: &outputBuffer) + try await self.underlyingStreamWriter.writeAndFinish(outputBuffer) } } } diff --git a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift index ddaeb237..63436bce 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift @@ -115,7 +115,7 @@ package protocol LambdaResponseWriter: ~Copyable { /// to the AWS Lambda response endpoint. /// This function simply serves as a mechanism to return the computed result from a handler function /// without an explicit `return`. - mutating func write(response: Output) async throws + func write(_ output: Output) async throws } /// A ``StreamingLambdaHandler`` conforming handler object that can be constructed with a closure. From 3bcc17a5281d5269cce1399a6218625b5a42cb98 Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Tue, 3 Sep 2024 16:48:05 +0100 Subject: [PATCH 4/8] Refactor --- .../AWSLambdaRuntimeCore/NewLambda+JSON.swift | 59 +++++++++++-------- .../NewLambdaHandlers.swift | 2 +- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift index a52de6db..3969b69d 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift @@ -29,17 +29,20 @@ package protocol LambdaEventDecoder { /// The protocol an encoder must conform to so that it can be used with ``LambdaCodableAdapter`` to encode the generic /// ``Output`` object into a ``ByteBuffer``. package protocol LambdaOutputEncoder { + associatedtype Output + /// Encode the generic type `Output` the handler has returned into a ``ByteBuffer``. /// - Parameters: /// - value: The object to encode into a ``ByteBuffer``. /// - buffer: The ``ByteBuffer`` where the encoded value will be written to. - func encode(_ value: Output, into buffer: inout ByteBuffer) throws + func encode(_ value: Output, into buffer: inout ByteBuffer) throws } package struct VoidEncoder: LambdaOutputEncoder { - package func encode(_ value: Output, into buffer: inout NIOCore.ByteBuffer) throws where Output: Encodable { + package typealias Output = Void - } + @inlinable + package func encode(_ value: Void, into buffer: inout NIOCore.ByteBuffer) throws {} } /// Adapts a ``NewLambdaHandler`` conforming handler to conform to ``LambdaWithBackgroundProcessingHandler``. @@ -48,10 +51,11 @@ package struct LambdaHandlerAdapter< Output, Handler: NewLambdaHandler >: LambdaWithBackgroundProcessingHandler where Handler.Event == Event, Handler.Output == Output { - let handler: Handler + @usableFromInline let handler: Handler /// Initializes an instance given a concrete handler. /// - Parameter handler: The ``NewLambdaHandler`` conforming handler that is to be adapted to ``LambdaWithBackgroundProcessingHandler``. + @inlinable package init(handler: Handler) { self.handler = handler } @@ -62,9 +66,10 @@ package struct LambdaHandlerAdapter< /// - event: The received event. /// - outputWriter: The writer to write the computed response to. /// - context: The ``NewLambdaContext`` containing the invocation's metadata. + @inlinable package func handle( _ event: Event, - outputWriter: consuming some LambdaResponseWriter, + outputWriter: some LambdaResponseWriter, context: NewLambdaContext ) async throws { let output = try await self.handler.handle(event, context: context) @@ -79,17 +84,18 @@ package struct LambdaCodableAdapter< Output, Decoder: LambdaEventDecoder, Encoder: LambdaOutputEncoder ->: StreamingLambdaHandler where Handler.Event == Event, Handler.Output == Output { - let handler: Handler - let encoder: Encoder - let decoder: Decoder - private var byteBuffer: ByteBuffer = .init() +>: StreamingLambdaHandler where Handler.Event == Event, Handler.Output == Output, Encoder.Output == Output { + @usableFromInline let handler: Handler + @usableFromInline let encoder: Encoder + @usableFromInline let decoder: Decoder + /*private*/ @usableFromInline var byteBuffer: ByteBuffer = .init() /// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output. /// - Parameters: /// - encoder: The encoder object that will be used to encode the generic ``Output`` obtained from the `handler`'s `outputWriter` into a ``ByteBuffer``. /// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`. /// - handler: The handler object. + @inlinable package init(encoder: Encoder, decoder: Decoder, handler: Handler) where Output: Encodable { self.encoder = encoder self.decoder = decoder @@ -100,6 +106,7 @@ package struct LambdaCodableAdapter< /// - Parameters: /// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`. /// - handler: The handler object. + @inlinable package init(decoder: Decoder, handler: Handler) where Output == Void, Encoder == VoidEncoder { self.encoder = VoidEncoder() self.decoder = decoder @@ -111,43 +118,43 @@ package struct LambdaCodableAdapter< /// - event: The received event. /// - outputWriter: The writer to write the computed response to. /// - context: The ``NewLambdaContext`` containing the invocation's metadata. - package mutating func handle( + @inlinable + package mutating func handle( _ request: ByteBuffer, - responseWriter: some LambdaResponseStreamWriter, + responseWriter: Writer, context: NewLambdaContext ) async throws { let event = try self.decoder.decode(Event.self, from: request) - let writer = LambdaCodableResponseWriter(encoder: self.encoder, streamWriter: responseWriter) + let writer = LambdaCodableResponseWriter( + encoder: self.encoder, + streamWriter: responseWriter + ) try await self.handler.handle(event, outputWriter: writer, context: context) } } /// A ``LambdaResponseStreamWriter`` wrapper that conforms to ``LambdaResponseWriter``. -package struct LambdaCodableResponseWriter: LambdaResponseWriter { - @usableFromInline let underlyingStreamWriter: LambdaResponseStreamWriter - @usableFromInline let encoder: LambdaOutputEncoder +package struct LambdaCodableResponseWriter: + LambdaResponseWriter +where Output == Encoder.Output { + @usableFromInline let underlyingStreamWriter: Base + @usableFromInline let encoder: Encoder /// Initializes an instance given an encoder and an underlying ``LambdaResponseStreamWriter``. /// - Parameters: /// - encoder: The encoder object that will be used to encode the generic ``Output`` into a ``ByteBuffer``, which will then be passed to `streamWriter`. /// - streamWriter: The underlying ``LambdaResponseStreamWriter`` that will be wrapped. @inlinable - package init(encoder: LambdaOutputEncoder, streamWriter: LambdaResponseStreamWriter) { + package init(encoder: Encoder, streamWriter: Base) { self.encoder = encoder self.underlyingStreamWriter = streamWriter } - /// Passes the `output` argument to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. - /// - Parameter output: The generic ``Output`` object that will be passed to ``LambdaResponseStreamWriter/writeAndFinish(_:)``. @inlinable package func write(_ output: Output) async throws { - if Output.self == Void.self { - try await self.underlyingStreamWriter.finish() - } else if let output = output as? Encodable { - var outputBuffer = ByteBuffer() - try self.encoder.encode(output, into: &outputBuffer) - try await self.underlyingStreamWriter.writeAndFinish(outputBuffer) - } + var outputBuffer = ByteBuffer() + try self.encoder.encode(output, into: &outputBuffer) + try await self.underlyingStreamWriter.writeAndFinish(outputBuffer) } } diff --git a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift index 63436bce..e7be6b18 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift @@ -109,7 +109,7 @@ package protocol LambdaWithBackgroundProcessingHandler { /// Used with ``LambdaWithBackgroundProcessingHandler``. /// A mechanism to "return" an output from ``LambdaWithBackgroundProcessingHandler/handle(_:outputWriter:context:)`` without the function needing to /// have a return type and exit at that point. This allows for background work to be executed _after_ a response has been sent to the AWS Lambda response endpoint. -package protocol LambdaResponseWriter: ~Copyable { +package protocol LambdaResponseWriter { associatedtype Output /// Sends the generic ``Output`` object (representing the computed result of the handler) /// to the AWS Lambda response endpoint. From 5eebc529923ec53faaf3a4229fbca9ea19b29235 Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Tue, 3 Sep 2024 16:50:25 +0100 Subject: [PATCH 5/8] Remove mutating keyword from write(_:) --- Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift | 2 +- Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift | 2 +- Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift index 86f5cf1a..801bdf82 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift @@ -15,7 +15,7 @@ import NIOCore package protocol LambdaRuntimeClientResponseStreamWriter: LambdaResponseStreamWriter { - mutating func write(_ buffer: ByteBuffer) async throws + func write(_ buffer: ByteBuffer) async throws func finish() async throws func writeAndFinish(_ buffer: ByteBuffer) async throws func reportError(_ error: any Error) async throws diff --git a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift index e7be6b18..0f9e7412 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift @@ -48,7 +48,7 @@ package protocol StreamingLambdaHandler { package protocol LambdaResponseStreamWriter { /// Write a response part into the stream. Bytes written are streamed continually. /// - Parameter buffer: The buffer to write. - mutating func write(_ buffer: ByteBuffer) async throws + func write(_ buffer: ByteBuffer) async throws /// End the response stream and the underlying HTTP response. func finish() async throws diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift index 3f226481..15a30223 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaMockClient.swift @@ -24,7 +24,7 @@ struct LambdaMockWriter: LambdaRuntimeClientResponseStreamWriter { self.underlying = underlying } - mutating func write(_ buffer: ByteBuffer) async throws { + func write(_ buffer: ByteBuffer) async throws { try await self.underlying.write(buffer) } From bf7497a79b1c4cb97402e3877b7746d2d475beb3 Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Tue, 3 Sep 2024 17:02:20 +0100 Subject: [PATCH 6/8] Fix formatting --- Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift index 3969b69d..4475a81f 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift @@ -88,7 +88,8 @@ package struct LambdaCodableAdapter< @usableFromInline let handler: Handler @usableFromInline let encoder: Encoder @usableFromInline let decoder: Decoder - /*private*/ @usableFromInline var byteBuffer: ByteBuffer = .init() + // + @usableFromInline var byteBuffer: ByteBuffer = .init() /// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output. /// - Parameters: From 6f384ae9697588784f082e763f611844c2986162 Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Wed, 4 Sep 2024 10:15:05 +0100 Subject: [PATCH 7/8] Add LambdaJSONOutputEncoder --- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 35 ++++++- .../NewLambdaContext.swift | 3 +- .../NewLambda+CodableTests.swift | 95 +++++++++++++++++++ 3 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index da7f358b..16fb38bc 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -144,4 +144,37 @@ extension JSONDecoder: LambdaCodableDecoder {} extension JSONEncoder: LambdaCodableEncoder {} extension JSONDecoder: AWSLambdaRuntimeCore.LambdaEventDecoder {} -extension JSONEncoder: AWSLambdaRuntimeCore.LambdaOutputEncoder {} + +@usableFromInline +package struct LambdaJSONOutputEncoder: LambdaOutputEncoder { + @usableFromInline let jsonEncoder: JSONEncoder + + @inlinable + package init(_ jsonEncoder: JSONEncoder) { + self.jsonEncoder = jsonEncoder + } + + @inlinable + package func encode(_ value: Output, into buffer: inout ByteBuffer) throws { + try self.jsonEncoder.encode(value, into: &buffer) + } +} + +extension LambdaCodableAdapter { + /// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output. + /// - Parameters: + /// - encoder: The encoder object that will be used to encode the generic ``Output`` obtained from the `handler`'s `outputWriter` into a ``ByteBuffer``. + /// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`. + /// - handler: The handler object. + package init( + encoder: JSONEncoder, + decoder: JSONDecoder, + handler: Handler + ) where Output: Encodable, Output == Handler.Output, Encoder == LambdaJSONOutputEncoder, Decoder == JSONDecoder { + self.init( + encoder: LambdaJSONOutputEncoder(encoder), + decoder: decoder, + handler: handler + ) + } +} diff --git a/Sources/AWSLambdaRuntimeCore/NewLambdaContext.swift b/Sources/AWSLambdaRuntimeCore/NewLambdaContext.swift index 89414728..abd2e61f 100644 --- a/Sources/AWSLambdaRuntimeCore/NewLambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/NewLambdaContext.swift @@ -126,8 +126,7 @@ package struct NewLambdaContext: CustomDebugStringConvertible, Sendable { traceID: String, invokedFunctionARN: String, timeout: DispatchTimeInterval, - logger: Logger, - eventLoop: EventLoop + logger: Logger ) -> NewLambdaContext { NewLambdaContext( requestID: requestID, diff --git a/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift b/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift new file mode 100644 index 00000000..b01308c1 --- /dev/null +++ b/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift @@ -0,0 +1,95 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 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 +// +//===----------------------------------------------------------------------===// + +import Testing +import AWSLambdaRuntime +import NIOCore +import Logging +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif + +@Suite +struct JSONTests { + + let logger = Logger(label: "JSONTests") + + struct Foo: Codable { + var bar: String + } + + @Test + func testEncodingConformance() { + let encoder = LambdaJSONOutputEncoder(JSONEncoder()) + let foo = Foo(bar: "baz") + var byteBuffer = ByteBuffer() + + #expect(throws: Never.self) { + try encoder.encode(foo, into: &byteBuffer) + } + + #expect(byteBuffer == ByteBuffer(string: #"{"bar":"baz"}"#)) + } + + @Test + func testJSONHandlerWithOutput() async { + let jsonEncoder = JSONEncoder() + let jsonDecoder = JSONDecoder() + + let closureHandler = ClosureHandler { (foo: Foo, context) in + foo + } + + var handler = LambdaCodableAdapter(encoder: jsonEncoder, decoder: jsonDecoder, handler: LambdaHandlerAdapter(handler: closureHandler)) + + let event = ByteBuffer(string: #"{"bar":"baz"}"#) + let writer = MockLambdaWriter() + let context = NewLambdaContext.__forTestsOnly( + requestID: UUID().uuidString, + traceID: UUID().uuidString, + invokedFunctionARN: "arn:", + timeout: .milliseconds(6000), + logger: self.logger + ) + + await #expect(throws: Never.self) { + try await handler.handle(event, responseWriter: writer, context: context) + } + + let result = await writer.output + #expect(result == ByteBuffer(string: #"{"bar":"baz"}"#)) + } +} + +final actor MockLambdaWriter: LambdaResponseStreamWriter { + private var _buffer: ByteBuffer? + + var output: ByteBuffer? { + self._buffer + } + + func writeAndFinish(_ buffer: ByteBuffer) async throws { + self._buffer = buffer + } + + func write(_ buffer: ByteBuffer) async throws { + fatalError("Unexpected call") + } + + func finish() async throws { + fatalError("Unexpected call") + } +} From f386e762695c0064ff543fa550dbca13f30a5e03 Mon Sep 17 00:00:00 2001 From: Aryan Shah Date: Wed, 4 Sep 2024 10:15:34 +0100 Subject: [PATCH 8/8] Apply formatter --- Sources/AWSLambdaRuntime/Lambda+Codable.swift | 8 +++++++- .../NewLambda+CodableTests.swift | 11 ++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 16fb38bc..fedd85c9 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -170,7 +170,13 @@ extension LambdaCodableAdapter { encoder: JSONEncoder, decoder: JSONDecoder, handler: Handler - ) where Output: Encodable, Output == Handler.Output, Encoder == LambdaJSONOutputEncoder, Decoder == JSONDecoder { + ) + where + Output: Encodable, + Output == Handler.Output, + Encoder == LambdaJSONOutputEncoder, + Decoder == JSONDecoder + { self.init( encoder: LambdaJSONOutputEncoder(encoder), decoder: decoder, diff --git a/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift b/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift index b01308c1..08e55f36 100644 --- a/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift +++ b/Tests/AWSLambdaRuntimeTests/NewLambda+CodableTests.swift @@ -12,10 +12,11 @@ // //===----------------------------------------------------------------------===// -import Testing import AWSLambdaRuntime -import NIOCore import Logging +import NIOCore +import Testing + #if canImport(FoundationEssentials) import FoundationEssentials #else @@ -53,7 +54,11 @@ struct JSONTests { foo } - var handler = LambdaCodableAdapter(encoder: jsonEncoder, decoder: jsonDecoder, handler: LambdaHandlerAdapter(handler: closureHandler)) + var handler = LambdaCodableAdapter( + encoder: jsonEncoder, + decoder: jsonDecoder, + handler: LambdaHandlerAdapter(handler: closureHandler) + ) let event = ByteBuffer(string: #"{"bar":"baz"}"#) let writer = MockLambdaWriter()