-
Notifications
You must be signed in to change notification settings - Fork 113
/
Copy pathLambdaHandlers.swift
254 lines (234 loc) · 12.9 KB
/
LambdaHandlers.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//===----------------------------------------------------------------------===//
//
// 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 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.
public 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 ``LambdaContext`` 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,
context: LambdaContext
) 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(_:)``.
public protocol LambdaResponseStreamWriter {
/// Write a response part into the stream. Bytes written are streamed continually.
/// - Parameter buffer: The buffer to write.
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
}
/// 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.
public protocol LambdaHandler {
/// Generic input type.
/// The body of the request sent to Lambda will be decoded into this type for the handler to consume.
associatedtype Event
/// Generic output type.
/// This is the return type of the ``LambdaHandler/handle(_:context:)`` function.
associatedtype Output
/// Implement the business logic of the Lambda function here.
/// - Parameters:
/// - event: The generic ``LambdaHandler/Event`` object representing the invocation's input data.
/// - context: The ``LambdaContext`` containing the invocation's metadata.
/// - Returns: A generic ``Output`` object representing the computed result.
func handle(_ event: Event, context: LambdaContext) async throws -> Output
}
/// This protocol is exactly like ``LambdaHandler``, 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
/// ``LambdaWithBackgroundProcessingHandler/handle(_:outputWriter:context:)`` function is then
/// free to implement any background work after the result has been sent to the AWS Lambda control plane.
public 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
/// Generic output type.
/// This is the type that the `handle` function will send through the ``LambdaResponseWriter``.
associatedtype Output
/// Implement the business logic of the Lambda function here.
/// - Parameters:
/// - event: The generic ``LambdaWithBackgroundProcessingHandler/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 ``LambdaContext`` containing the invocation's metadata.
func handle(
_ event: Event,
outputWriter: some LambdaResponseWriter<Output>,
context: LambdaContext
) 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.
public protocol LambdaResponseWriter<Output> {
associatedtype Output
/// Sends the generic ``LambdaResponseWriter/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`.
func write(_ output: 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.
public struct StreamingClosureHandler: StreamingLambdaHandler {
let body: @Sendable (ByteBuffer, LambdaResponseStreamWriter, LambdaContext) 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.
public init(
body: @Sendable @escaping (ByteBuffer, LambdaResponseStreamWriter, LambdaContext) async throws -> Void
) {
self.body = body
}
/// Calls the provided `self.body` closure with the `ByteBuffer` invocation event, the ``LambdaResponseStreamWriter``, and the ``LambdaContext``
/// - 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 ``LambdaContext`` containing the invocation's metadata.
public func handle(
_ event: ByteBuffer,
responseWriter: some LambdaResponseStreamWriter,
context: LambdaContext
) async throws {
try await self.body(event, responseWriter, context)
}
}
/// A ``LambdaHandler`` 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.
public struct ClosureHandler<Event: Decodable, Output>: LambdaHandler {
let body: (Event, LambdaContext) async throws -> Output
/// Initialize with a closure handler over generic `Input` and `Output` types.
/// - Parameter body: The handler function written as a closure.
public init(body: sending @escaping (Event, LambdaContext) 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.
public init(body: @escaping (Event, LambdaContext) 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 ``LambdaContext``
/// - Parameters:
/// - event: The generic `Event` object representing the invocation's input data.
/// - context: The ``LambdaContext`` containing the invocation's metadata.
public func handle(_ event: Event, context: LambdaContext) async throws -> Output {
try await self.body(event, context)
}
}
extension LambdaRuntime {
/// Initialize an instance with a ``StreamingLambdaHandler`` in the form of a closure.
/// - Parameter body: The handler in the form of a closure.
public convenience init(
body: @Sendable @escaping (ByteBuffer, LambdaResponseStreamWriter, LambdaContext) async throws -> Void
) where Handler == StreamingClosureHandler {
do {
try self.init(handler: StreamingClosureHandler(body: body))
} catch {
fatalError("Failed to initialize LambdaRuntime: \(error)")
}
}
/// Initialize an instance with a ``LambdaHandler`` defined in the form of a closure **with a non-`Void` return type**, an encoder, and a decoder.
/// - Parameters:
/// - encoder: The encoder object that will be used to encode the generic `Output` into a `ByteBuffer`.
/// - decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type.
/// - body: The handler in the form of a closure.
public convenience init<
Event: Decodable,
Output: Encodable,
Encoder: LambdaOutputEncoder,
Decoder: LambdaEventDecoder
>(
encoder: sending Encoder,
decoder: sending Decoder,
body: sending @escaping (Event, LambdaContext) async throws -> Output
)
where
Handler == LambdaCodableAdapter<
LambdaHandlerAdapter<Event, Output, ClosureHandler<Event, Output>>,
Event,
Output,
Decoder,
Encoder
>
{
let closureHandler = ClosureHandler(body: body)
let streamingAdapter = LambdaHandlerAdapter(handler: closureHandler)
let codableWrapper = LambdaCodableAdapter(
encoder: encoder,
decoder: decoder,
handler: streamingAdapter
)
do {
try self.init(handler: codableWrapper)
} catch {
fatalError("Failed to initialize LambdaRuntime: \(error)")
}
}
/// Initialize an instance with a ``LambdaHandler`` defined in the form of a closure **with a `Void` return type**, an encoder, and a decoder.
/// - Parameters:
/// - decoder: The decoder object that will be used to decode the incoming `ByteBuffer` event into the generic `Event` type.
/// - body: The handler in the form of a closure.
public convenience init<Event: Decodable, Decoder: LambdaEventDecoder>(
decoder: sending Decoder,
body: sending @escaping (Event, LambdaContext) async throws -> Void
)
where
Handler == LambdaCodableAdapter<
LambdaHandlerAdapter<Event, Void, ClosureHandler<Event, Void>>,
Event,
Void,
Decoder,
VoidEncoder
>
{
let handler = LambdaCodableAdapter(
decoder: decoder,
handler: LambdaHandlerAdapter(handler: ClosureHandler(body: body))
)
do {
try self.init(handler: handler)
} catch {
fatalError("Failed to initialize LambdaRuntime: \(error)")
}
}
}