Skip to content

Add new handler protocols + Codable support #351

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

Merged
merged 8 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
114 changes: 114 additions & 0 deletions Sources/AWSLambdaRuntimeCore/NewLambda+JSON.swift
Original file line number Diff line number Diff line change
@@ -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<Event: Decodable>(_ type: Event.Type, from buffer: ByteBuffer) throws -> Event
}

package protocol LambdaOutputEncoder {
func encode<Output: Encodable>(_ value: Output, into buffer: inout ByteBuffer) throws
}

extension JSONEncoder: LambdaOutputEncoder {}

extension JSONDecoder: LambdaEventDecoder {}

package struct VoidEncoder: LambdaOutputEncoder {
package func encode<Output>(_ 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<Output>,
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<Output>(encoder: self.encoder, streamWriter: responseWriter)
try await self.handler.handle(event, outputWriter: writer, context: context)
}
}

package struct ResponseWriter<Output>: 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)
}
}
}
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
93 changes: 93 additions & 0 deletions Sources/AWSLambdaRuntimeCore/NewLambdaHandlers.swift
Original file line number Diff line number Diff line change
@@ -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<Output>,
context: NewLambdaContext
) async throws
}

package protocol LambdaResponseWriter<Output>: ~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<Event: Decodable, Output>: 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)
}
}
Loading