Skip to content

Commit

Permalink
Add new handler protocols + Codable support (#351)
Browse files Browse the repository at this point in the history
  • Loading branch information
aryan-25 authored Sep 4, 2024
1 parent b2da91d commit 4b45451
Show file tree
Hide file tree
Showing 8 changed files with 486 additions and 16 deletions.
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

0 comments on commit 4b45451

Please sign in to comment.