Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new handler protocols + Codable support #351

Merged
merged 8 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions Sources/AWSLambdaRuntime/Lambda+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -138,3 +142,45 @@ extension Lambda {
extension JSONDecoder: LambdaCodableDecoder {}

extension JSONEncoder: LambdaCodableEncoder {}

extension JSONDecoder: AWSLambdaRuntimeCore.LambdaEventDecoder {}

@usableFromInline
package struct LambdaJSONOutputEncoder<Output: Encodable>: 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<Output>,
Decoder == JSONDecoder
{
self.init(
encoder: LambdaJSONOutputEncoder(encoder),
decoder: decoder,
handler: handler
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@

import NIOCore

package protocol LambdaResponseStreamWriter {
mutating func write(_ buffer: ByteBuffer) async throws
package protocol LambdaRuntimeClientResponseStreamWriter: LambdaResponseStreamWriter {
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)
}
Expand Down
161 changes: 161 additions & 0 deletions Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//===----------------------------------------------------------------------===//
//
// 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

/// 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<Event: Decodable>(_ 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 {
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
}

package struct VoidEncoder: LambdaOutputEncoder {
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``.
package struct LambdaHandlerAdapter<
Event: Decodable,
Output,
Handler: NewLambdaHandler
>: LambdaWithBackgroundProcessingHandler where Handler.Event == Event, Handler.Output == Output {
@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
}

/// 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.
@inlinable
package func handle(
_ event: Event,
outputWriter: some LambdaResponseWriter<Output>,
context: NewLambdaContext
) async throws {
let output = try await self.handler.handle(event, context: context)
try await outputWriter.write(output)
}
}

/// Adapts a ``LambdaWithBackgroundProcessingHandler`` conforming handler to conform to ``StreamingLambdaHandler``.
package struct LambdaCodableAdapter<
Handler: LambdaWithBackgroundProcessingHandler,
Event: Decodable,
Output,
Decoder: LambdaEventDecoder,
Encoder: LambdaOutputEncoder
>: StreamingLambdaHandler where Handler.Event == Event, Handler.Output == Output, Encoder.Output == Output {
@usableFromInline let handler: Handler
@usableFromInline let encoder: Encoder
@usableFromInline let decoder: Decoder
//
@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
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.
@inlinable
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.
@inlinable
package mutating func handle<Writer: LambdaResponseStreamWriter>(
_ request: ByteBuffer,
responseWriter: Writer,
context: NewLambdaContext
) async throws {
let event = try self.decoder.decode(Event.self, from: request)

let writer = LambdaCodableResponseWriter<Output, Encoder, Writer>(
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<Output, Encoder: LambdaOutputEncoder, Base: LambdaResponseStreamWriter>:
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: Encoder, streamWriter: Base) {
self.encoder = encoder
self.underlyingStreamWriter = streamWriter
}

@inlinable
package func write(_ output: Output) async throws {
var outputBuffer = ByteBuffer()
try self.encoder.encode(output, into: &outputBuffer)
try await self.underlyingStreamWriter.writeAndFinish(outputBuffer)
}
}
9 changes: 0 additions & 9 deletions Sources/AWSLambdaRuntimeCore/NewLambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<RuntimeClient: LambdaRuntimeClientProtocol, Handler>(
Expand Down
3 changes: 1 addition & 2 deletions Sources/AWSLambdaRuntimeCore/NewLambdaContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading
Loading