Skip to content

Commit b0ab1b8

Browse files
committed
wip
1 parent 98a23b6 commit b0ab1b8

File tree

8 files changed

+244
-28
lines changed

8 files changed

+244
-28
lines changed

Sources/AWSLambdaRuntime/Lambda+Codable.swift

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,47 @@ import class Foundation.JSONEncoder
1919
import NIOCore
2020
import NIOFoundationCompat
2121

22-
// MARK: - Codable support
22+
// MARK: - LambdaHandler Codable support
23+
24+
#if compiler(>=5.5) && canImport(_Concurrency)
25+
/// Implementation of a`ByteBuffer` to `Event` decoding.
26+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
27+
extension LambdaHandler where Event: Decodable {
28+
@inlinable
29+
public func decode(buffer: ByteBuffer) throws -> Event {
30+
try self.decoder.decode(Event.self, from: buffer)
31+
}
32+
}
33+
34+
/// Implementation of `Output` to `ByteBuffer` encoding.
35+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
36+
extension LambdaHandler where Output: Encodable {
37+
@inlinable
38+
public func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer? {
39+
try self.encoder.encode(value, using: allocator)
40+
}
41+
}
42+
43+
/// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`.
44+
/// Advanced users that want to inject their own codec can do it by overriding these functions.
45+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
46+
extension LambdaHandler where Event: Decodable {
47+
public var decoder: LambdaCodableDecoder {
48+
Lambda.defaultJSONDecoder
49+
}
50+
}
51+
52+
/// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`.
53+
/// Advanced users that want to inject their own codec can do it by overriding these functions.
54+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
55+
extension LambdaHandler where Output: Encodable {
56+
public var encoder: LambdaCodableEncoder {
57+
Lambda.defaultJSONEncoder
58+
}
59+
}
60+
#endif
61+
62+
// MARK: - EventLoopLambdaHandler Codable support
2363

2464
/// Implementation of a`ByteBuffer` to `Event` decoding.
2565
extension EventLoopLambdaHandler where Event: Decodable {

Sources/AWSLambdaRuntimeCore/Lambda.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,32 @@ public enum Lambda {
3333
/// - handlerType: The Handler to create and invoke.
3434
///
3535
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
36+
3637
internal static func run<Handler: ByteBufferLambdaHandler>(
3738
configuration: LambdaConfiguration = .init(),
3839
handlerType: Handler.Type
40+
) -> Result<Int, Error> {
41+
Self.run(configuration: configuration, initializationHandler: Handler.makeHandler(context:))
42+
}
43+
44+
internal static func run<Handler: EventLoopLambdaHandler>(
45+
configuration: LambdaConfiguration = .init(),
46+
handlerType: Handler.Type
47+
) -> Result<Int, Error> {
48+
Self.run(configuration: configuration, initializationHandler: Handler.makeHandler(context:))
49+
}
50+
51+
@available(macOS 12, *)
52+
internal static func run<Handler: LambdaHandler>(
53+
configuration: LambdaConfiguration = .init(),
54+
handlerType: Handler.Type
55+
) -> Result<Int, Error> {
56+
Self.run(configuration: configuration, initializationHandler: Handler.makeHandler(context:))
57+
58+
}
59+
private static func run(
60+
configuration: LambdaConfiguration = .init(),
61+
initializationHandler: @escaping (LambdaInitializationContext) -> EventLoopFuture<(ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>>
3962
) -> Result<Int, Error> {
4063
let _run = { (configuration: LambdaConfiguration) -> Result<Int, Error> in
4164
Backtrace.install()
@@ -44,7 +67,7 @@ public enum Lambda {
4467

4568
var result: Result<Int, Error>!
4669
MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in
47-
let runtime = LambdaRuntime<Handler>(eventLoop: eventLoop, logger: logger, configuration: configuration)
70+
let runtime = LambdaRuntime(eventLoop: eventLoop, logger: logger, configuration: configuration, initializationHandler: initializationHandler)
4871
#if DEBUG
4972
let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in
5073
logger.info("intercepted signal: \(signal)")

Sources/AWSLambdaRuntimeCore/LambdaHandler.swift

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ import NIOCore
2626
/// level protocols ``EventLoopLambdaHandler`` and
2727
/// ``ByteBufferLambdaHandler``.
2828
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
29-
public protocol LambdaHandler: EventLoopLambdaHandler {
29+
public protocol LambdaHandler {
30+
/// The lambda functions input. In most cases this should be `Codable`. If your event originates from an
31+
/// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events),
32+
/// which provides a number of commonly used AWS Event implementations.
33+
associatedtype Event
34+
/// The lambda functions output. Can be `Void`.
35+
associatedtype Output
36+
3037
/// The Lambda initialization method.
3138
/// Use this method to initialize resources that will be used in every request.
3239
///
@@ -44,18 +51,38 @@ public protocol LambdaHandler: EventLoopLambdaHandler {
4451
///
4552
/// - Returns: A Lambda result ot type `Output`.
4653
func handle(_ event: Event, context: LambdaContext) async throws -> Output
54+
55+
/// Encode a response of type ``Output`` to `ByteBuffer`.
56+
/// Concrete Lambda handlers implement this method to provide coding functionality.
57+
/// - parameters:
58+
/// - allocator: A `ByteBufferAllocator` to help allocate the `ByteBuffer`.
59+
/// - value: Response of type ``Output``.
60+
///
61+
/// - Returns: A `ByteBuffer` with the encoded version of the `value`.
62+
func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer?
63+
64+
/// Decode a `ByteBuffer` to a request or event of type ``Event``.
65+
/// Concrete Lambda handlers implement this method to provide coding functionality.
66+
///
67+
/// - parameters:
68+
/// - buffer: The `ByteBuffer` to decode.
69+
///
70+
/// - Returns: A request or event of type ``Event``.
71+
func decode(buffer: ByteBuffer) throws -> Event
4772
}
4873

4974
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
5075
extension LambdaHandler {
51-
public static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<Self> {
76+
// FIXME: why public?
77+
public static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<(ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>> {
5278
let promise = context.eventLoop.makePromise(of: Self.self)
5379
promise.completeWithTask {
5480
try await Self(context: context)
5581
}
56-
return promise.futureResult
82+
return promise.futureResult.map{ $0.handle(buffer:context:) }
5783
}
5884

85+
// FIXME: why public?
5986
public func handle(_ event: Event, context: LambdaContext) -> EventLoopFuture<Output> {
6087
let promise = context.eventLoop.makePromise(of: Output.self)
6188
// using an unchecked sendable wrapper for the handler
@@ -66,6 +93,68 @@ extension LambdaHandler {
6693
}
6794
return promise.futureResult
6895
}
96+
97+
/// Driver for `ByteBuffer` -> ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding
98+
@inlinable
99+
internal func handle(buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture<ByteBuffer?> {
100+
let input: Event
101+
do {
102+
input = try self.decode(buffer: buffer)
103+
} catch {
104+
return context.eventLoop.makeFailedFuture(CodecError.requestDecoding(error))
105+
}
106+
107+
return self.handle(input, context: context).flatMapThrowing { output in
108+
do {
109+
return try self.encode(allocator: context.allocator, value: output)
110+
} catch {
111+
throw CodecError.responseEncoding(error)
112+
}
113+
}
114+
}
115+
}
116+
117+
/// Implementation of `ByteBuffer` to `Void` decoding.
118+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
119+
extension LambdaHandler where Output == Void {
120+
@inlinable
121+
public func encode(allocator: ByteBufferAllocator, value: Void) throws -> ByteBuffer? {
122+
nil
123+
}
124+
}
125+
126+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
127+
extension LambdaHandler where Event == String {
128+
@inlinable
129+
public func decode(buffer: ByteBuffer) throws -> String {
130+
guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else {
131+
throw CodecError.invalidString
132+
}
133+
return value
134+
}
135+
}
136+
137+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
138+
extension LambdaHandler where Output == String {
139+
@inlinable
140+
public func encode(allocator: ByteBufferAllocator, value: String) throws -> ByteBuffer? {
141+
allocator.buffer(string: value)
142+
}
143+
}
144+
145+
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
146+
extension LambdaHandler {
147+
/// Initializes and runs the Lambda function.
148+
///
149+
/// If you precede your ``ByteBufferLambdaHandler`` conformer's declaration with the
150+
/// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626)
151+
/// attribute, the system calls the conformer's `main()` method to launch the lambda function.
152+
///
153+
/// The lambda runtime provides a default implementation of the method that manages the launch
154+
/// process.
155+
public static func main() {
156+
_ = Lambda.run(configuration: .init(), handlerType: Self.self)
157+
}
69158
}
70159

71160
/// unchecked sendable wrapper for the handler
@@ -100,14 +189,22 @@ fileprivate struct UncheckedSendableHandler<Underlying: LambdaHandler, Event, Ou
100189
/// as the core runtime engine, making the processing faster but requires more care from the
101190
/// implementation to never block the `EventLoop`. Implement this protocol only in performance
102191
/// critical situations and implement ``LambdaHandler`` in all other circumstances.
103-
public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler {
192+
public protocol EventLoopLambdaHandler {
104193
/// The lambda functions input. In most cases this should be `Codable`. If your event originates from an
105194
/// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events),
106195
/// which provides a number of commonly used AWS Event implementations.
107196
associatedtype Event
108197
/// The lambda functions output. Can be `Void`.
109198
associatedtype Output
110199

200+
/// Create a Lambda handler for the runtime.
201+
///
202+
/// Use this to initialize all your resources that you want to cache between invocations. This could be database
203+
/// connections and HTTP clients for example. It is encouraged to use the given `EventLoop`'s conformance
204+
/// to `EventLoopGroup` when initializing NIO dependencies. This will improve overall performance, as it
205+
/// minimizes thread hopping.
206+
static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<Self>
207+
111208
/// The Lambda handling method.
112209
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
113210
///
@@ -139,12 +236,17 @@ public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler {
139236
}
140237

141238
extension EventLoopLambdaHandler {
239+
@inlinable
240+
internal static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<(ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>> {
241+
Self.makeHandler(context: context).map{ $0.handle(buffer:context:) }
242+
}
243+
142244
/// Driver for `ByteBuffer` -> ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding
143245
@inlinable
144-
public func handle(_ event: ByteBuffer, context: LambdaContext) -> EventLoopFuture<ByteBuffer?> {
246+
internal func handle(buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture<ByteBuffer?> {
145247
let input: Event
146248
do {
147-
input = try self.decode(buffer: event)
249+
input = try self.decode(buffer: buffer)
148250
} catch {
149251
return context.eventLoop.makeFailedFuture(CodecError.requestDecoding(error))
150252
}
@@ -167,6 +269,20 @@ extension EventLoopLambdaHandler where Output == Void {
167269
}
168270
}
169271

272+
extension EventLoopLambdaHandler {
273+
/// Initializes and runs the Lambda function.
274+
///
275+
/// If you precede your ``ByteBufferLambdaHandler`` conformer's declaration with the
276+
/// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626)
277+
/// attribute, the system calls the conformer's `main()` method to launch the lambda function.
278+
///
279+
/// The lambda runtime provides a default implementation of the method that manages the launch
280+
/// process.
281+
public static func main() {
282+
_ = Lambda.run(configuration: .init(), handlerType: Self.self)
283+
}
284+
}
285+
170286
// MARK: - ByteBufferLambdaHandler
171287

172288
/// An `EventLoopFuture` based processing protocol for a Lambda that takes a `ByteBuffer` and returns
@@ -176,7 +292,7 @@ extension EventLoopLambdaHandler where Output == Void {
176292
/// ``LambdaHandler`` based APIs.
177293
/// Most users are not expected to use this protocol.
178294
public protocol ByteBufferLambdaHandler {
179-
/// Create your Lambda handler for the runtime.
295+
/// Create a Lambda handler for the runtime.
180296
///
181297
/// Use this to initialize all your resources that you want to cache between invocations. This could be database
182298
/// connections and HTTP clients for example. It is encouraged to use the given `EventLoop`'s conformance
@@ -193,7 +309,14 @@ public protocol ByteBufferLambdaHandler {
193309
///
194310
/// - Returns: An `EventLoopFuture` to report the result of the Lambda back to the runtime engine.
195311
/// The `EventLoopFuture` should be completed with either a response encoded as `ByteBuffer` or an `Error`.
196-
func handle(_ event: ByteBuffer, context: LambdaContext) -> EventLoopFuture<ByteBuffer?>
312+
func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture<ByteBuffer?>
313+
}
314+
315+
extension ByteBufferLambdaHandler {
316+
@inlinable
317+
internal static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture<(ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>> {
318+
Self.makeHandler(context: context).map{ $0.handle(_:context:)}
319+
}
197320
}
198321

199322
extension ByteBufferLambdaHandler {
@@ -210,8 +333,12 @@ extension ByteBufferLambdaHandler {
210333
}
211334
}
212335

336+
// MARK: - Other
337+
213338
@usableFromInline
214339
enum CodecError: Error {
215340
case requestDecoding(Error)
216341
case responseEncoding(Error)
342+
case invalidString
217343
}
344+

Sources/AWSLambdaRuntimeCore/LambdaRunner.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal final class LambdaRunner {
3333
/// Run the user provided initializer. This *must* only be called once.
3434
///
3535
/// - Returns: An `EventLoopFuture<LambdaHandler>` fulfilled with the outcome of the initialization.
36-
func initialize<Handler: ByteBufferLambdaHandler>(logger: Logger, terminator: LambdaTerminator, handlerType: Handler.Type) -> EventLoopFuture<Handler> {
36+
func initialize(logger: Logger, terminator: LambdaTerminator, initializationHandler: (LambdaInitializationContext) -> EventLoopFuture<(ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>>) -> EventLoopFuture<(ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>> {
3737
logger.debug("initializing lambda")
3838
// 1. create the handler from the factory
3939
// 2. report initialization error if one occurred
@@ -43,7 +43,7 @@ internal final class LambdaRunner {
4343
allocator: self.allocator,
4444
terminator: terminator
4545
)
46-
return Handler.makeHandler(context: context)
46+
return initializationHandler(context)
4747
// Hopping back to "our" EventLoop is important in case the factory returns a future
4848
// that originated from a foreign EventLoop/EventLoopGroup.
4949
// This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops
@@ -58,7 +58,7 @@ internal final class LambdaRunner {
5858
}
5959
}
6060

61-
func run<Handler: ByteBufferLambdaHandler>(logger: Logger, handler: Handler) -> EventLoopFuture<Void> {
61+
func run(logger: Logger, handler: @escaping (ByteBuffer, LambdaContext) -> EventLoopFuture<ByteBuffer?>) -> EventLoopFuture<Void> {
6262
logger.debug("lambda invocation sequence starting")
6363
// 1. request invocation from lambda runtime engine
6464
self.isGettingNextInvocation = true
@@ -73,8 +73,8 @@ internal final class LambdaRunner {
7373
allocator: self.allocator,
7474
invocation: invocation
7575
)
76-
logger.debug("sending invocation to lambda handler \(handler)")
77-
return handler.handle(bytes, context: context)
76+
logger.debug("sending invocation to lambda handler")
77+
return handler(bytes, context)
7878
// Hopping back to "our" EventLoop is important in case the handler returns a future that
7979
// originiated from a foreign EventLoop/EventLoopGroup.
8080
// This can happen if the handler uses a library (lets say a DB client) that manages its own threads/loops

0 commit comments

Comments
 (0)