Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
47 changes: 27 additions & 20 deletions Sources/AWSLambdaRuntime/Lambda+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,52 @@ import NIOFoundationCompat

/// Extension to the `Lambda` companion to enable execution of Lambdas that take and return `Codable` payloads.
extension Lambda {
/// Run a Lambda defined by implementing the `CodableLambdaClosure` function.
/// An asynchronous Lambda Closure that takes a `In: Decodable` and returns a `Result<Out: Encodable, Error>` via a completion handler.
public typealias CodableClosure<In: Decodable, Out: Encodable> = (Lambda.Context, In, @escaping (Result<Out, Error>) -> Void) -> Void

/// Run a Lambda defined by implementing the `CodableClosure` function.
///
/// - parameters:
/// - closure: `CodableClosure` based Lambda.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run<In: Decodable, Out: Encodable>(_ closure: @escaping CodableLambdaClosure<In, Out>) {
public static func run<In: Decodable, Out: Encodable>(_ closure: @escaping CodableClosure<In, Out>) {
self.run(closure: closure)
}

/// Run a Lambda defined by implementing the `CodableVoidLambdaClosure` function.
/// An asynchronous Lambda Closure that takes a `In: Decodable` and returns a `Result<Void, Error>` via a completion handler.
public typealias CodableVoidClosure<In: Decodable> = (Lambda.Context, In, @escaping (Result<Void, Error>) -> Void) -> Void

/// Run a Lambda defined by implementing the `CodableVoidClosure` function.
///
/// - parameters:
/// - closure: `CodableVoidClosure` based Lambda.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run<In: Decodable>(_ closure: @escaping CodableVoidLambdaClosure<In>) {
public static func run<In: Decodable>(_ closure: @escaping CodableVoidClosure<In>) {
self.run(closure: closure)
}

// for testing
@discardableResult
internal static func run<In: Decodable, Out: Encodable>(configuration: Configuration = .init(), closure: @escaping CodableLambdaClosure<In, Out>) -> Result<Int, Error> {
self.run(configuration: configuration, handler: CodableLambdaClosureWrapper(closure))
internal static func run<In: Decodable, Out: Encodable>(configuration: Configuration = .init(), closure: @escaping CodableClosure<In, Out>) -> Result<Int, Error> {
self.run(configuration: configuration, handler: CodableClosureWrapper(closure))
}

// for testing
@discardableResult
internal static func run<In: Decodable>(configuration: Configuration = .init(), closure: @escaping CodableVoidLambdaClosure<In>) -> Result<Int, Error> {
self.run(configuration: configuration, handler: CodableVoidLambdaClosureWrapper(closure))
internal static func run<In: Decodable>(configuration: Configuration = .init(), closure: @escaping CodableVoidClosure<In>) -> Result<Int, Error> {
self.run(configuration: configuration, handler: CodableVoidClosureWrapper(closure))
}
}

/// A processing closure for a Lambda that takes a `In` and returns a `Result<Out, Error>` via a `CompletionHandler` asynchronously.
public typealias CodableLambdaClosure<In: Decodable, Out: Encodable> = (Lambda.Context, In, @escaping (Result<Out, Error>) -> Void) -> Void

/// A processing closure for a Lambda that takes a `In` and returns a `Result<Void, Error>` via a `CompletionHandler` asynchronously.
public typealias CodableVoidLambdaClosure<In: Decodable> = (Lambda.Context, In, @escaping (Result<Void, Error>) -> Void) -> Void

internal struct CodableLambdaClosureWrapper<In: Decodable, Out: Encodable>: LambdaHandler {
internal struct CodableClosureWrapper<In: Decodable, Out: Encodable>: LambdaHandler {
typealias In = In
typealias Out = Out

private let closure: CodableLambdaClosure<In, Out>
private let closure: Lambda.CodableClosure<In, Out>

init(_ closure: @escaping CodableLambdaClosure<In, Out>) {
init(_ closure: @escaping Lambda.CodableClosure<In, Out>) {
self.closure = closure
}

Expand All @@ -67,13 +73,13 @@ internal struct CodableLambdaClosureWrapper<In: Decodable, Out: Encodable>: Lamb
}
}

internal struct CodableVoidLambdaClosureWrapper<In: Decodable>: LambdaHandler {
internal struct CodableVoidClosureWrapper<In: Decodable>: LambdaHandler {
typealias In = In
typealias Out = Void

private let closure: CodableVoidLambdaClosure<In>
private let closure: Lambda.CodableVoidClosure<In>

init(_ closure: @escaping CodableVoidLambdaClosure<In>) {
init(_ closure: @escaping Lambda.CodableVoidClosure<In>) {
self.closure = closure
}

Expand Down Expand Up @@ -126,6 +132,7 @@ private extension Lambda {
}

extension JSONDecoder: LambdaCodableDecoder {}

extension JSONEncoder: LambdaCodableEncoder {
public func encode<T>(_ value: T, using allocator: ByteBufferAllocator) throws -> ByteBuffer where T: Encodable {
// nio will resize the buffer if necessary
Expand Down
50 changes: 28 additions & 22 deletions Sources/AWSLambdaRuntime/Lambda+String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,52 @@ import NIO

/// Extension to the `Lambda` companion to enable execution of Lambdas that take and return `String` payloads.
extension Lambda {
/// Run a Lambda defined by implementing the `StringLambdaClosure` function.
/// An asynchronous Lambda Closure that takes a `String` and returns a `Result<String, Error>` via a completion handler.
public typealias StringClosure = (Lambda.Context, String, @escaping (Result<String, Error>) -> Void) -> Void

/// Run a Lambda defined by implementing the `StringClosure` function.
///
/// - parameters:
/// - closure: `StringClosure` based Lambda.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run(_ closure: @escaping StringLambdaClosure) {
public static func run(_ closure: @escaping StringClosure) {
self.run(closure: closure)
}

/// Run a Lambda defined by implementing the `StringVoidLambdaClosure` function.
/// An asynchronous Lambda Closure that takes a `String` and returns a `Result<Void, Error>` via a completion handler.
public typealias StringVoidClosure = (Lambda.Context, String, @escaping (Result<Void, Error>) -> Void) -> Void

/// Run a Lambda defined by implementing the `StringVoidClosure` function.
///
/// - parameters:
/// - closure: `StringVoidClosure` based Lambda.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run(_ closure: @escaping StringVoidLambdaClosure) {
public static func run(_ closure: @escaping StringVoidClosure) {
self.run(closure: closure)
}

// for testing
@discardableResult
internal static func run(configuration: Configuration = .init(), closure: @escaping StringLambdaClosure) -> Result<Int, Error> {
self.run(configuration: configuration, handler: StringLambdaClosureWrapper(closure))
internal static func run(configuration: Configuration = .init(), closure: @escaping StringClosure) -> Result<Int, Error> {
self.run(configuration: configuration, handler: StringClosureWrapper(closure))
}

// for testing
@discardableResult
internal static func run(configuration: Configuration = .init(), closure: @escaping StringVoidLambdaClosure) -> Result<Int, Error> {
self.run(configuration: configuration, handler: StringVoidLambdaClosureWrapper(closure))
internal static func run(configuration: Configuration = .init(), closure: @escaping StringVoidClosure) -> Result<Int, Error> {
self.run(configuration: configuration, handler: StringVoidClosureWrapper(closure))
}
}

/// A processing closure for a Lambda that takes a `String` and returns a `Result<String, Error>` via a `CompletionHandler` asynchronously.
public typealias StringLambdaClosure = (Lambda.Context, String, @escaping (Result<String, Error>) -> Void) -> Void

/// A processing closure for a Lambda that takes a `String` and returns a `Result<Void, Error>` via a `CompletionHandler` asynchronously.
public typealias StringVoidLambdaClosure = (Lambda.Context, String, @escaping (Result<Void, Error>) -> Void) -> Void

internal struct StringLambdaClosureWrapper: LambdaHandler {
internal struct StringClosureWrapper: LambdaHandler {
typealias In = String
typealias Out = String

private let closure: StringLambdaClosure
private let closure: Lambda.StringClosure

init(_ closure: @escaping StringLambdaClosure) {
init(_ closure: @escaping Lambda.StringClosure) {
self.closure = closure
}

Expand All @@ -63,13 +69,13 @@ internal struct StringLambdaClosureWrapper: LambdaHandler {
}
}

internal struct StringVoidLambdaClosureWrapper: LambdaHandler {
internal struct StringVoidClosureWrapper: LambdaHandler {
typealias In = String
typealias Out = Void

private let closure: StringVoidLambdaClosure
private let closure: Lambda.StringVoidClosure

init(_ closure: @escaping StringVoidLambdaClosure) {
init(_ closure: @escaping Lambda.StringVoidClosure) {
self.closure = closure
}

Expand All @@ -78,8 +84,8 @@ internal struct StringVoidLambdaClosureWrapper: LambdaHandler {
}
}

/// Implementation of a`ByteBuffer` to `String` encoding
public extension EventLoopLambdaHandler where In == String {
/// Implementation of a `ByteBuffer` to `String` decoding
func decode(buffer: ByteBuffer) throws -> String {
var buffer = buffer
guard let string = buffer.readString(length: buffer.readableBytes) else {
Expand All @@ -89,8 +95,8 @@ public extension EventLoopLambdaHandler where In == String {
}
}

/// Implementation of `String` to `ByteBuffer` decoding
public extension EventLoopLambdaHandler where Out == String {
/// Implementation of `String` to `ByteBuffer` encoding
func encode(allocator: ByteBufferAllocator, value: String) throws -> ByteBuffer? {
// FIXME: reusable buffer
var buffer = allocator.buffer(capacity: value.utf8.count)
Expand Down
36 changes: 26 additions & 10 deletions Sources/AWSLambdaRuntime/Lambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,55 @@ import Logging
import NIO

public enum Lambda {
public typealias Handler = ByteBufferLambdaHandler

/// `ByteBufferLambdaHandler` factory.
///
/// A function that takes a `EventLoop` and returns an `EventLoopFuture` of a `ByteBufferLambdaHandler`
public typealias HandlerFactory = (EventLoop) -> EventLoopFuture<Handler>

/// Run a Lambda defined by implementing the `LambdaHandler` protocol.
///
/// - parameters:
/// - handler: `ByteBufferLambdaHandler` based Lambda.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run(_ handler: ByteBufferLambdaHandler) {
public static func run(_ handler: Handler) {
self.run(handler: handler)
}

/// Run a Lambda defined by implementing the `LambdaHandler` protocol provided via a `LambdaHandlerFactory`.
/// Use this to initialize all your resources that you want to cache between invocations. This could be database connections and HTTP clients for example.
/// It is encouraged to use the given `EventLoop`'s conformance to `EventLoopGroup` when initializing NIO dependencies. This will improve overall performance.
///
/// - parameters:
/// - factory: A `ByteBufferLambdaHandler` factory.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run(_ factory: @escaping LambdaHandlerFactory) {
public static func run(_ factory: @escaping HandlerFactory) {
self.run(factory: factory)
}

/// Run a Lambda defined by implementing the `LambdaHandler` protocol provided via a factory, typically a constructor.
///
/// - parameters:
/// - factory: A `ByteBufferLambdaHandler` factory.
///
/// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine.
public static func run(_ factory: @escaping (EventLoop) throws -> ByteBufferLambdaHandler) {
public static func run(_ factory: @escaping (EventLoop) throws -> Handler) {
self.run(factory: factory)
}

// for testing and internal use
@discardableResult
internal static func run(configuration: Configuration = .init(), handler: ByteBufferLambdaHandler) -> Result<Int, Error> {
internal static func run(configuration: Configuration = .init(), handler: Handler) -> Result<Int, Error> {
self.run(configuration: configuration, factory: { $0.makeSucceededFuture(handler) })
}

// for testing and internal use
@discardableResult
internal static func run(configuration: Configuration = .init(), factory: @escaping (EventLoop) throws -> ByteBufferLambdaHandler) -> Result<Int, Error> {
self.run(configuration: configuration, factory: { eventloop -> EventLoopFuture<ByteBufferLambdaHandler> in
internal static func run(configuration: Configuration = .init(), factory: @escaping (EventLoop) throws -> Handler) -> Result<Int, Error> {
self.run(configuration: configuration, factory: { eventloop -> EventLoopFuture<Handler> in
do {
let handler = try factory(eventloop)
return eventloop.makeSucceededFuture(handler)
Expand All @@ -65,7 +83,7 @@ public enum Lambda {

// for testing and internal use
@discardableResult
internal static func run(configuration: Configuration = .init(), factory: @escaping LambdaHandlerFactory) -> Result<Int, Error> {
internal static func run(configuration: Configuration = .init(), factory: @escaping HandlerFactory) -> Result<Int, Error> {
do {
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) // only need one thread, will improve performance
defer { try! eventLoopGroup.syncShutdownGracefully() }
Expand All @@ -76,7 +94,7 @@ public enum Lambda {
}
}

internal static func runAsync(eventLoopGroup: EventLoopGroup, configuration: Configuration, factory: @escaping LambdaHandlerFactory) -> EventLoopFuture<Int> {
internal static func runAsync(eventLoopGroup: EventLoopGroup, configuration: Configuration, factory: @escaping HandlerFactory) -> EventLoopFuture<Int> {
Backtrace.install()
var logger = Logger(label: "Lambda")
logger.logLevel = configuration.general.logLevel
Expand All @@ -92,5 +110,3 @@ public enum Lambda {
}
}
}

public typealias LambdaHandlerFactory = (EventLoop) -> EventLoopFuture<ByteBufferLambdaHandler>
15 changes: 14 additions & 1 deletion Sources/AWSLambdaRuntime/LambdaContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import Logging
import NIO

extension Lambda {
/// Lambda runtime context.
/// The Lambda runtime generates and passes the `Context` to the Lambda handler as an argument.
public final class Context {
/// The request ID, which identifies the request that triggered the function invocation.
public let requestId: String
Expand All @@ -36,9 +38,20 @@ extension Lambda {
/// For invocations from the AWS Mobile SDK, data about the client application and device.
public let clientContext: String?

/// a logger to log
/// `Logger` to log with
///
/// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable.
public let logger: Logger

/// The `EventLoop` the Lambda is executed on. Use this to schedule work with.
/// This is useful when implementing the `EventLoopLambdaHandler` protocol.
///
/// - note: The `EventLoop` is shared with the Lambda runtime engine and should be handled with extra care.
/// Most importantly the `EventLoop` must never be blocked.
public let eventLoop: EventLoop

/// `ByteBufferAllocator` to allocate `ByteBuffer`
/// This is useful when implementing `EventLoopLambdaHandler`
public let allocator: ByteBufferAllocator

internal init(requestId: String,
Expand Down
45 changes: 43 additions & 2 deletions Sources/AWSLambdaRuntime/LambdaHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@ import NIO
/// The `EventLoopLambdaHandler` will execute the Lambda on the same `EventLoop` as the core runtime engine, making the processing faster but requires
/// more care from the implementation to never block the `EventLoop`.
public protocol LambdaHandler: EventLoopLambdaHandler {
/// Defines to which `DispatchQueue` the Lambda execution is offloaded to.
var offloadQueue: DispatchQueue { get }

/// The Lambda handling method
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
///
/// - parameters:
/// - context: Runtime `Context`.
/// - payload: Payload of type `In` representing the event or request.
/// - callback: Completion handler to report the result of the Lambda back to the runtime engine.
/// The completion handler expects a `Result` with either a response of type `Out` or an `Error`
func handle(context: Lambda.Context, payload: In, callback: @escaping (Result<Out, Error>) -> Void)
}

Expand Down Expand Up @@ -66,14 +75,38 @@ public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler {
associatedtype In
associatedtype Out

/// The Lambda handling method
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
///
/// - parameters:
/// - context: Runtime `Context`.
/// - payload: Payload of type `In` representing the event or request.
///
/// - Returns: An `EventLoopFuture` to report the result of the Lambda back to the runtime engine.
/// The `EventLoopFuture` should be completed with either a response of type `Out` or an `Error`
func handle(context: Lambda.Context, payload: In) -> EventLoopFuture<Out>

/// Encode a response of type `Out` to `ByteBuffer`
/// Concrete Lambda handlers implement this method to provide coding functionality.
/// - parameters:
/// - allocator: A `ByteBufferAllocator` to help allocate the `ByteBuffer`.
/// - value: Response of type `Out`.
///
/// - Returns: A `ByteBuffer` with the encoded version of the `value`.
func encode(allocator: ByteBufferAllocator, value: Out) throws -> ByteBuffer?

/// Decode a`ByteBuffer` to a request or event of type `In`
/// Concrete Lambda handlers implement this method to provide coding functionality.
///
/// - parameters:
/// - buffer: The `ByteBuffer` to decode.
///
/// - Returns: A request or event of type `In`.
func decode(buffer: ByteBuffer) throws -> In
}

/// Driver for `ByteBuffer` -> `In` decoding and `Out` -> `ByteBuffer` encoding
public extension EventLoopLambdaHandler {
/// Driver for `ByteBuffer` -> `In` decoding and `Out` -> `ByteBuffer` encoding
func handle(context: Lambda.Context, payload: ByteBuffer) -> EventLoopFuture<ByteBuffer?> {
switch self.decodeIn(buffer: payload) {
case .failure(let error):
Expand Down Expand Up @@ -121,7 +154,15 @@ public extension EventLoopLambdaHandler where Out == Void {
/// - note: This is a low level protocol designed to power the higher level `EventLoopLambdaHandler` and `LambdaHandler` based APIs.
/// Most users are not expected to use this protocol.
public protocol ByteBufferLambdaHandler {
/// Handles the Lambda request.
/// The Lambda handling method
/// Concrete Lambda handlers implement this method to provide the Lambda functionality.
///
/// - parameters:
/// - context: Runtime `Context`.
/// - payload: The event or request payload encoded as `ByteBuffer`.
///
/// - Returns: An `EventLoopFuture` to report the result of the Lambda back to the runtime engine.
/// The `EventLoopFuture` should be completed with either a response encoded as `ByteBuffer` or an `Error`
func handle(context: Lambda.Context, payload: ByteBuffer) -> EventLoopFuture<ByteBuffer?>
}

Expand Down
Loading