Skip to content

Commit 4b45451

Browse files
authored
Add new handler protocols + Codable support (#351)
1 parent b2da91d commit 4b45451

File tree

8 files changed

+486
-16
lines changed

8 files changed

+486
-16
lines changed

Sources/AWSLambdaRuntime/Lambda+Codable.swift

+46
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616
import NIOCore
1717
import NIOFoundationCompat
1818

19+
#if canImport(FoundationEssentials)
20+
import FoundationEssentials
21+
#else
1922
import struct Foundation.Data
2023
import class Foundation.JSONDecoder
2124
import class Foundation.JSONEncoder
25+
#endif
2226

2327
// MARK: - SimpleLambdaHandler Codable support
2428

@@ -138,3 +142,45 @@ extension Lambda {
138142
extension JSONDecoder: LambdaCodableDecoder {}
139143

140144
extension JSONEncoder: LambdaCodableEncoder {}
145+
146+
extension JSONDecoder: AWSLambdaRuntimeCore.LambdaEventDecoder {}
147+
148+
@usableFromInline
149+
package struct LambdaJSONOutputEncoder<Output: Encodable>: LambdaOutputEncoder {
150+
@usableFromInline let jsonEncoder: JSONEncoder
151+
152+
@inlinable
153+
package init(_ jsonEncoder: JSONEncoder) {
154+
self.jsonEncoder = jsonEncoder
155+
}
156+
157+
@inlinable
158+
package func encode(_ value: Output, into buffer: inout ByteBuffer) throws {
159+
try self.jsonEncoder.encode(value, into: &buffer)
160+
}
161+
}
162+
163+
extension LambdaCodableAdapter {
164+
/// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output.
165+
/// - Parameters:
166+
/// - encoder: The encoder object that will be used to encode the generic ``Output`` obtained from the `handler`'s `outputWriter` into a ``ByteBuffer``.
167+
/// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`.
168+
/// - handler: The handler object.
169+
package init(
170+
encoder: JSONEncoder,
171+
decoder: JSONDecoder,
172+
handler: Handler
173+
)
174+
where
175+
Output: Encodable,
176+
Output == Handler.Output,
177+
Encoder == LambdaJSONOutputEncoder<Output>,
178+
Decoder == JSONDecoder
179+
{
180+
self.init(
181+
encoder: LambdaJSONOutputEncoder(encoder),
182+
decoder: decoder,
183+
handler: handler
184+
)
185+
}
186+
}

Sources/AWSLambdaRuntimeCore/LambdaRuntimeClientProtocol.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414

1515
import NIOCore
1616

17-
package protocol LambdaResponseStreamWriter {
18-
mutating func write(_ buffer: ByteBuffer) async throws
17+
package protocol LambdaRuntimeClientResponseStreamWriter: LambdaResponseStreamWriter {
18+
func write(_ buffer: ByteBuffer) async throws
1919
func finish() async throws
2020
func writeAndFinish(_ buffer: ByteBuffer) async throws
2121
func reportError(_ error: any Error) async throws
2222
}
2323

2424
package protocol LambdaRuntimeClientProtocol {
25-
associatedtype Writer: LambdaResponseStreamWriter
25+
associatedtype Writer: LambdaRuntimeClientResponseStreamWriter
2626

2727
func nextInvocation() async throws -> (Invocation, Writer)
2828
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
17+
/// The protocol a decoder must conform to so that it can be used with ``LambdaCodableAdapter`` to decode incoming
18+
/// ``ByteBuffer`` events.
19+
package protocol LambdaEventDecoder {
20+
/// Decode the ``ByteBuffer`` representing the received event into the generic ``Event`` type
21+
/// the handler will receive.
22+
/// - Parameters:
23+
/// - type: The type of the object to decode the buffer into.
24+
/// - buffer: The buffer to be decoded.
25+
/// - Returns: An object containing the decoded data.
26+
func decode<Event: Decodable>(_ type: Event.Type, from buffer: ByteBuffer) throws -> Event
27+
}
28+
29+
/// The protocol an encoder must conform to so that it can be used with ``LambdaCodableAdapter`` to encode the generic
30+
/// ``Output`` object into a ``ByteBuffer``.
31+
package protocol LambdaOutputEncoder {
32+
associatedtype Output
33+
34+
/// Encode the generic type `Output` the handler has returned into a ``ByteBuffer``.
35+
/// - Parameters:
36+
/// - value: The object to encode into a ``ByteBuffer``.
37+
/// - buffer: The ``ByteBuffer`` where the encoded value will be written to.
38+
func encode(_ value: Output, into buffer: inout ByteBuffer) throws
39+
}
40+
41+
package struct VoidEncoder: LambdaOutputEncoder {
42+
package typealias Output = Void
43+
44+
@inlinable
45+
package func encode(_ value: Void, into buffer: inout NIOCore.ByteBuffer) throws {}
46+
}
47+
48+
/// Adapts a ``NewLambdaHandler`` conforming handler to conform to ``LambdaWithBackgroundProcessingHandler``.
49+
package struct LambdaHandlerAdapter<
50+
Event: Decodable,
51+
Output,
52+
Handler: NewLambdaHandler
53+
>: LambdaWithBackgroundProcessingHandler where Handler.Event == Event, Handler.Output == Output {
54+
@usableFromInline let handler: Handler
55+
56+
/// Initializes an instance given a concrete handler.
57+
/// - Parameter handler: The ``NewLambdaHandler`` conforming handler that is to be adapted to ``LambdaWithBackgroundProcessingHandler``.
58+
@inlinable
59+
package init(handler: Handler) {
60+
self.handler = handler
61+
}
62+
63+
/// Passes the generic ``Event`` object to the ``NewLambdaHandler/handle(_:context:)`` function, and
64+
/// the resulting output is then written to ``LambdaWithBackgroundProcessingHandler``'s `outputWriter`.
65+
/// - Parameters:
66+
/// - event: The received event.
67+
/// - outputWriter: The writer to write the computed response to.
68+
/// - context: The ``NewLambdaContext`` containing the invocation's metadata.
69+
@inlinable
70+
package func handle(
71+
_ event: Event,
72+
outputWriter: some LambdaResponseWriter<Output>,
73+
context: NewLambdaContext
74+
) async throws {
75+
let output = try await self.handler.handle(event, context: context)
76+
try await outputWriter.write(output)
77+
}
78+
}
79+
80+
/// Adapts a ``LambdaWithBackgroundProcessingHandler`` conforming handler to conform to ``StreamingLambdaHandler``.
81+
package struct LambdaCodableAdapter<
82+
Handler: LambdaWithBackgroundProcessingHandler,
83+
Event: Decodable,
84+
Output,
85+
Decoder: LambdaEventDecoder,
86+
Encoder: LambdaOutputEncoder
87+
>: StreamingLambdaHandler where Handler.Event == Event, Handler.Output == Output, Encoder.Output == Output {
88+
@usableFromInline let handler: Handler
89+
@usableFromInline let encoder: Encoder
90+
@usableFromInline let decoder: Decoder
91+
//
92+
@usableFromInline var byteBuffer: ByteBuffer = .init()
93+
94+
/// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output.
95+
/// - Parameters:
96+
/// - encoder: The encoder object that will be used to encode the generic ``Output`` obtained from the `handler`'s `outputWriter` into a ``ByteBuffer``.
97+
/// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`.
98+
/// - handler: The handler object.
99+
@inlinable
100+
package init(encoder: Encoder, decoder: Decoder, handler: Handler) where Output: Encodable {
101+
self.encoder = encoder
102+
self.decoder = decoder
103+
self.handler = handler
104+
}
105+
106+
/// Initializes an instance given a decoder, and a handler with a `Void` output.
107+
/// - Parameters:
108+
/// - decoder: The decoder object that will be used to decode the received ``ByteBuffer`` event into the generic ``Event`` type served to the `handler`.
109+
/// - handler: The handler object.
110+
@inlinable
111+
package init(decoder: Decoder, handler: Handler) where Output == Void, Encoder == VoidEncoder {
112+
self.encoder = VoidEncoder()
113+
self.decoder = decoder
114+
self.handler = handler
115+
}
116+
117+
/// A ``StreamingLambdaHandler/handle(_:responseWriter:context:)`` wrapper.
118+
/// - Parameters:
119+
/// - event: The received event.
120+
/// - outputWriter: The writer to write the computed response to.
121+
/// - context: The ``NewLambdaContext`` containing the invocation's metadata.
122+
@inlinable
123+
package mutating func handle<Writer: LambdaResponseStreamWriter>(
124+
_ request: ByteBuffer,
125+
responseWriter: Writer,
126+
context: NewLambdaContext
127+
) async throws {
128+
let event = try self.decoder.decode(Event.self, from: request)
129+
130+
let writer = LambdaCodableResponseWriter<Output, Encoder, Writer>(
131+
encoder: self.encoder,
132+
streamWriter: responseWriter
133+
)
134+
try await self.handler.handle(event, outputWriter: writer, context: context)
135+
}
136+
}
137+
138+
/// A ``LambdaResponseStreamWriter`` wrapper that conforms to ``LambdaResponseWriter``.
139+
package struct LambdaCodableResponseWriter<Output, Encoder: LambdaOutputEncoder, Base: LambdaResponseStreamWriter>:
140+
LambdaResponseWriter
141+
where Output == Encoder.Output {
142+
@usableFromInline let underlyingStreamWriter: Base
143+
@usableFromInline let encoder: Encoder
144+
145+
/// Initializes an instance given an encoder and an underlying ``LambdaResponseStreamWriter``.
146+
/// - Parameters:
147+
/// - encoder: The encoder object that will be used to encode the generic ``Output`` into a ``ByteBuffer``, which will then be passed to `streamWriter`.
148+
/// - streamWriter: The underlying ``LambdaResponseStreamWriter`` that will be wrapped.
149+
@inlinable
150+
package init(encoder: Encoder, streamWriter: Base) {
151+
self.encoder = encoder
152+
self.underlyingStreamWriter = streamWriter
153+
}
154+
155+
@inlinable
156+
package func write(_ output: Output) async throws {
157+
var outputBuffer = ByteBuffer()
158+
try self.encoder.encode(output, into: &outputBuffer)
159+
try await self.underlyingStreamWriter.writeAndFinish(outputBuffer)
160+
}
161+
}

Sources/AWSLambdaRuntimeCore/NewLambda.swift

-9
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,6 @@
1414

1515
import Dispatch
1616
import Logging
17-
import NIOCore
18-
19-
package protocol StreamingLambdaHandler {
20-
mutating func handle(
21-
_ event: ByteBuffer,
22-
responseWriter: some LambdaResponseStreamWriter,
23-
context: NewLambdaContext
24-
) async throws
25-
}
2617

2718
extension Lambda {
2819
package static func runLoop<RuntimeClient: LambdaRuntimeClientProtocol, Handler>(

Sources/AWSLambdaRuntimeCore/NewLambdaContext.swift

+1-2
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ package struct NewLambdaContext: CustomDebugStringConvertible, Sendable {
126126
traceID: String,
127127
invokedFunctionARN: String,
128128
timeout: DispatchTimeInterval,
129-
logger: Logger,
130-
eventLoop: EventLoop
129+
logger: Logger
131130
) -> NewLambdaContext {
132131
NewLambdaContext(
133132
requestID: requestID,

0 commit comments

Comments
 (0)