diff --git a/Examples/Benchmark/BenchmarkHandler.swift b/Examples/Benchmark/BenchmarkHandler.swift index ed89629d..d9cfce6e 100644 --- a/Examples/Benchmark/BenchmarkHandler.swift +++ b/Examples/Benchmark/BenchmarkHandler.swift @@ -22,9 +22,6 @@ import NIOCore @main struct BenchmarkHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(BenchmarkHandler()) } diff --git a/Examples/Benchmark/Package.swift b/Examples/Benchmark/Package.swift index ddadbae8..7cceea76 100644 --- a/Examples/Benchmark/Package.swift +++ b/Examples/Benchmark/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Deployment/Package.swift b/Examples/Deployment/Package.swift index a34069b9..4fb37203 100644 --- a/Examples/Deployment/Package.swift +++ b/Examples/Deployment/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift b/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift index 87754176..d9cfce6e 100644 --- a/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift +++ b/Examples/Deployment/Sources/Benchmark/BenchmarkHandler.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import AWSLambdaRuntimeCore -import NIO +import NIOCore // If you would like to benchmark Swift's Lambda Runtime, // use this example which is more performant. @@ -22,9 +22,6 @@ import NIO @main struct BenchmarkHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(BenchmarkHandler()) } diff --git a/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift b/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift index cddae31b..9f4a16f3 100644 --- a/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift +++ b/Examples/Deployment/Sources/HelloWorld/HelloWorldHandler.swift @@ -16,14 +16,7 @@ import AWSLambdaRuntime // introductory example, the obligatory "hello, world!" @main -struct HelloWorldHandler: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse here. - } - +struct HelloWorldHandler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { "hello, world" } diff --git a/Examples/Echo/Lambda.swift b/Examples/Echo/Lambda.swift index 58b6a32e..00c0a5e5 100644 --- a/Examples/Echo/Lambda.swift +++ b/Examples/Echo/Lambda.swift @@ -17,14 +17,7 @@ import AWSLambdaRuntime // in this example we are receiving and responding with strings @main -struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - +struct MyLambda: SimpleLambdaHandler { func handle(_ input: String, context: LambdaContext) async throws -> String { // as an example, respond with the input's reversed String(input.reversed()) diff --git a/Examples/Echo/Package.swift b/Examples/Echo/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/Echo/Package.swift +++ b/Examples/Echo/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/ErrorHandling/Lambda.swift b/Examples/ErrorHandling/Lambda.swift index d11d21c1..d8e560aa 100644 --- a/Examples/ErrorHandling/Lambda.swift +++ b/Examples/ErrorHandling/Lambda.swift @@ -17,12 +17,7 @@ import AWSLambdaRuntime // MARK: - Run Lambda @main -struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - - init(context: LambdaInitializationContext) async throws {} - +struct MyLambda: SimpleLambdaHandler { func handle(_ request: Request, context: LambdaContext) async throws -> Response { // switch over the error type "requested" by the request, and trigger such error accordingly switch request.error { diff --git a/Examples/ErrorHandling/Package.swift b/Examples/ErrorHandling/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/ErrorHandling/Package.swift +++ b/Examples/ErrorHandling/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Foundation/Lambda.swift b/Examples/Foundation/Lambda.swift index dda564f0..660574a1 100644 --- a/Examples/Foundation/Lambda.swift +++ b/Examples/Foundation/Lambda.swift @@ -25,9 +25,6 @@ import Logging @main struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = [Exchange] - let calculator: ExchangeRatesCalculator init(context: LambdaInitializationContext) async throws { diff --git a/Examples/Foundation/Package.swift b/Examples/Foundation/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/Foundation/Package.swift +++ b/Examples/Foundation/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/JSON/Lambda.swift b/Examples/JSON/Lambda.swift index 0c2664d8..cad3b650 100644 --- a/Examples/JSON/Lambda.swift +++ b/Examples/JSON/Lambda.swift @@ -26,14 +26,7 @@ struct Response: Codable { // codables to model your request and response objects @main -struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - +struct MyLambda: SimpleLambdaHandler { func handle(_ event: Request, context: LambdaContext) async throws -> Response { // as an example, respond with the input event's reversed body Response(body: String(event.body.reversed())) diff --git a/Examples/JSON/Package.swift b/Examples/JSON/Package.swift index caae8f03..dc5fa33c 100644 --- a/Examples/JSON/Package.swift +++ b/Examples/JSON/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/LocalDebugging/MyLambda/Lambda.swift b/Examples/LocalDebugging/MyLambda/Lambda.swift index 582d20ae..397ece30 100644 --- a/Examples/LocalDebugging/MyLambda/Lambda.swift +++ b/Examples/LocalDebugging/MyLambda/Lambda.swift @@ -19,14 +19,7 @@ import Shared // a local server simulator which will allow local debugging @main -struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - +struct MyLambda: SimpleLambdaHandler { func handle(_ request: Request, context: LambdaContext) async throws -> Response { // TODO: something useful Response(message: "Hello, \(request.name)!") diff --git a/Examples/LocalDebugging/MyLambda/Package.swift b/Examples/LocalDebugging/MyLambda/Package.swift index 2b8860de..567d0c49 100644 --- a/Examples/LocalDebugging/MyLambda/Package.swift +++ b/Examples/LocalDebugging/MyLambda/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Testing/Package.swift b/Examples/Testing/Package.swift index 5f7cb131..c80d39d9 100644 --- a/Examples/Testing/Package.swift +++ b/Examples/Testing/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.7 import PackageDescription diff --git a/Examples/Testing/Sources/Lambda.swift b/Examples/Testing/Sources/Lambda.swift index 51714798..568710e4 100644 --- a/Examples/Testing/Sources/Lambda.swift +++ b/Examples/Testing/Sources/Lambda.swift @@ -17,14 +17,7 @@ import AWSLambdaRuntime // in this example we are receiving and responding with strings @main -struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) async throws { - // setup your resources that you want to reuse for every invocation here. - } - +struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { // as an example, respond with the event's reversed body String(event.reversed()) diff --git a/Examples/Testing/Tests/LambdaTests.swift b/Examples/Testing/Tests/LambdaTests.swift index 26d6ea38..f6676c10 100644 --- a/Examples/Testing/Tests/LambdaTests.swift +++ b/Examples/Testing/Tests/LambdaTests.swift @@ -18,9 +18,9 @@ import AWSLambdaTesting import XCTest class LambdaTest: XCTestCase { - func testIt() throws { + func testIt() async throws { let input = UUID().uuidString - let result = try Lambda.test(MyLambda.self, with: input) + let result = try await Lambda.test(MyLambda.self, with: input) XCTAssertEqual(result, String(input.reversed())) } } diff --git a/Package.swift b/Package.swift index 78b1c36e..a4559656 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,15 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.7 import PackageDescription let package = Package( name: "swift-aws-lambda-runtime", + platforms: [ + .macOS(.v12), + .iOS(.v15), + .tvOS(.v15), + .watchOS(.v8), + ], products: [ // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), @@ -15,7 +21,7 @@ let package = Package( .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")), + .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.43.1")), .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")), .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")), .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), diff --git a/Package@swift-5.4.swift b/Package@swift-5.4.swift deleted file mode 100644 index 90ace14b..00000000 --- a/Package@swift-5.4.swift +++ /dev/null @@ -1,55 +0,0 @@ -// swift-tools-version:5.4 - -import PackageDescription - -let package = Package( - name: "swift-aws-lambda-runtime", - products: [ - // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods - .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), - // this has all the main functionality for lambda and it does not link Foundation - .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - // for testing only - .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")), - .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")), - .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")), - ], - targets: [ - .target(name: "AWSLambdaRuntime", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .target(name: "AWSLambdaRuntimeCore", dependencies: [ - .product(name: "Logging", package: "swift-log"), - .product(name: "Backtrace", package: "swift-backtrace"), - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), - .product(name: "NIOPosix", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOTestUtils", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .byName(name: "AWSLambdaRuntime"), - ]), - // testing helper - .target(name: "AWSLambdaTesting", dependencies: [ - .byName(name: "AWSLambdaRuntime"), - .product(name: "NIO", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]), - // for perf testing - .target(name: "MockServer", dependencies: [ - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIO", package: "swift-nio"), - ]), - ] -) diff --git a/Package@swift-5.5.swift b/Package@swift-5.5.swift deleted file mode 100644 index 90ace14b..00000000 --- a/Package@swift-5.5.swift +++ /dev/null @@ -1,55 +0,0 @@ -// swift-tools-version:5.4 - -import PackageDescription - -let package = Package( - name: "swift-aws-lambda-runtime", - products: [ - // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods - .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), - // this has all the main functionality for lambda and it does not link Foundation - .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), - // for testing only - .library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]), - ], - dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.33.0")), - .package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")), - .package(url: "https://github.com/swift-server/swift-backtrace.git", .upToNextMajor(from: "1.2.3")), - ], - targets: [ - .target(name: "AWSLambdaRuntime", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .target(name: "AWSLambdaRuntimeCore", dependencies: [ - .product(name: "Logging", package: "swift-log"), - .product(name: "Backtrace", package: "swift-backtrace"), - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIOCore", package: "swift-nio"), - .product(name: "NIOConcurrencyHelpers", package: "swift-nio"), - .product(name: "NIOPosix", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .product(name: "NIOTestUtils", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaRuntimeTests", dependencies: [ - .byName(name: "AWSLambdaRuntimeCore"), - .byName(name: "AWSLambdaRuntime"), - ]), - // testing helper - .target(name: "AWSLambdaTesting", dependencies: [ - .byName(name: "AWSLambdaRuntime"), - .product(name: "NIO", package: "swift-nio"), - ]), - .testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]), - // for perf testing - .target(name: "MockServer", dependencies: [ - .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIO", package: "swift-nio"), - ]), - ] -) diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index 219abe52..f925754b 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -19,9 +19,77 @@ import class Foundation.JSONEncoder import NIOCore import NIOFoundationCompat -// MARK: - Codable support +// MARK: - SimpleLambdaHandler Codable support -/// Implementation of a`ByteBuffer` to `Event` decoding. +/// Implementation of `ByteBuffer` to `Event` decoding. +extension SimpleLambdaHandler where Event: Decodable { + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + try self.decoder.decode(Event.self, from: buffer) + } +} + +/// Implementation of `Output` to `ByteBuffer` encoding. +extension SimpleLambdaHandler where Output: Encodable { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + try self.encoder.encode(value, into: &buffer) + } +} + +/// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`. +/// Advanced users who want to inject their own codec can do it by overriding these functions. +extension SimpleLambdaHandler where Event: Decodable { + public var decoder: LambdaCodableDecoder { + Lambda.defaultJSONDecoder + } +} + +/// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`. +/// Advanced users who want to inject their own codec can do it by overriding these functions. +extension SimpleLambdaHandler where Output: Encodable { + public var encoder: LambdaCodableEncoder { + Lambda.defaultJSONEncoder + } +} + +// MARK: - LambdaHandler Codable support + +/// Implementation of `ByteBuffer` to `Event` decoding. +extension LambdaHandler where Event: Decodable { + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + try self.decoder.decode(Event.self, from: buffer) + } +} + +/// Implementation of `Output` to `ByteBuffer` encoding. +extension LambdaHandler where Output: Encodable { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + try self.encoder.encode(value, into: &buffer) + } +} + +/// Default `ByteBuffer` to `Event` decoder using Foundation's `JSONDecoder`. +/// Advanced users who want to inject their own codec can do it by overriding these functions. +extension LambdaHandler where Event: Decodable { + public var decoder: LambdaCodableDecoder { + Lambda.defaultJSONDecoder + } +} + +/// Default `Output` to `ByteBuffer` encoder using Foundation's `JSONEncoder`. +/// Advanced users who want to inject their own codec can do it by overriding these functions. +extension LambdaHandler where Output: Encodable { + public var encoder: LambdaCodableEncoder { + Lambda.defaultJSONEncoder + } +} + +// MARK: - EventLoopLambdaHandler Codable support + +/// Implementation of `ByteBuffer` to `Event` decoding. extension EventLoopLambdaHandler where Event: Decodable { @inlinable public func decode(buffer: ByteBuffer) throws -> Event { @@ -29,11 +97,11 @@ extension EventLoopLambdaHandler where Event: Decodable { } } -/// Implementation of `Output` to `ByteBuffer` encoding. +/// Implementation of `Output` to `ByteBuffer` encoding. extension EventLoopLambdaHandler where Output: Encodable { @inlinable - public func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer? { - try self.encoder.encode(value, using: allocator) + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + try self.encoder.encode(value, into: &buffer) } } @@ -58,7 +126,7 @@ public protocol LambdaCodableDecoder { } public protocol LambdaCodableEncoder { - func encode(_ value: T, using allocator: ByteBufferAllocator) throws -> ByteBuffer + func encode(_ value: T, into buffer: inout ByteBuffer) throws } extension Lambda { @@ -68,11 +136,4 @@ extension Lambda { extension JSONDecoder: LambdaCodableDecoder {} -extension JSONEncoder: LambdaCodableEncoder { - public func encode(_ value: T, using allocator: ByteBufferAllocator) throws -> ByteBuffer where T: Encodable { - // nio will resize the buffer if necessary - var buffer = allocator.buffer(capacity: 1024) - try self.encode(value, into: &buffer) - return buffer - } -} +extension JSONEncoder: LambdaCodableEncoder {} diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift index 8e3da3e5..e7674e28 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+String.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift @@ -13,25 +13,65 @@ //===----------------------------------------------------------------------===// import NIOCore -extension EventLoopLambdaHandler where Event == String { +// MARK: - SimpleLambdaHandler String support + +extension SimpleLambdaHandler where Event == String { /// Implementation of a `ByteBuffer` to `String` decoding. @inlinable - public func decode(buffer: ByteBuffer) throws -> String { - var buffer = buffer - guard let string = buffer.readString(length: buffer.readableBytes) else { - fatalError("buffer.readString(length: buffer.readableBytes) failed") + public func decode(buffer: ByteBuffer) throws -> Event { + guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { + throw CodecError.invalidString } - return string + return value } } -extension EventLoopLambdaHandler where Output == String { +extension SimpleLambdaHandler where Output == String { + /// Implementation of `String` to `ByteBuffer` encoding. + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + buffer.writeString(value) + } +} + +// MARK: - LambdaHandler String support + +extension LambdaHandler where Event == String { + /// Implementation of a `ByteBuffer` to `String` decoding. + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { + throw CodecError.invalidString + } + return value + } +} + +extension LambdaHandler where Output == String { /// Implementation of `String` to `ByteBuffer` encoding. @inlinable - public func encode(allocator: ByteBufferAllocator, value: String) throws -> ByteBuffer? { - // FIXME: reusable buffer - var buffer = allocator.buffer(capacity: value.utf8.count) + public func encode(value: Output, into buffer: inout ByteBuffer) throws { + buffer.writeString(value) + } +} + +// MARK: - EventLoopLambdaHandler String support + +extension EventLoopLambdaHandler where Event == String { + /// Implementation of `String` to `ByteBuffer` encoding. + @inlinable + public func decode(buffer: ByteBuffer) throws -> Event { + guard let value = buffer.getString(at: buffer.readerIndex, length: buffer.readableBytes) else { + throw CodecError.invalidString + } + return value + } +} + +extension EventLoopLambdaHandler where Output == String { + /// Implementation of a `ByteBuffer` to `String` decoding. + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws { buffer.writeString(value) - return buffer } } diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 0f976c76..2592f48c 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -24,6 +24,53 @@ import NIOCore import NIOPosix public enum Lambda { + /// Run a Lambda defined by implementing the ``SimpleLambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. + /// + /// - parameters: + /// - configuration: A Lambda runtime configuration object + /// - handlerType: The Handler to create and invoke. + /// + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. + internal static func run( + configuration: LambdaConfiguration = .init(), + handlerType: Handler.Type + ) -> Result { + Self.run(configuration: configuration, handlerType: CodableSimpleLambdaHandler.self) + } + + /// Run a Lambda defined by implementing the ``LambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the + /// ``LambdaHandler/makeHandler(context:)`` to create a new Handler. + /// + /// - parameters: + /// - configuration: A Lambda runtime configuration object + /// - handlerType: The Handler to create and invoke. + /// + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. + internal static func run( + configuration: LambdaConfiguration = .init(), + handlerType: Handler.Type + ) -> Result { + Self.run(configuration: configuration, handlerType: CodableLambdaHandler.self) + } + + /// Run a Lambda defined by implementing the ``EventLoopLambdaHandler`` protocol. + /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the + /// ``EventLoopLambdaHandler/makeHandler(context:)`` to create a new Handler. + /// + /// - parameters: + /// - configuration: A Lambda runtime configuration object + /// - handlerType: The Handler to create and invoke. + /// + /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. + internal static func run( + configuration: LambdaConfiguration = .init(), + handlerType: Handler.Type + ) -> Result { + Self.run(configuration: configuration, handlerType: CodableEventLoopLambdaHandler.self) + } + /// Run a Lambda defined by implementing the ``ByteBufferLambdaHandler`` protocol. /// The Runtime will manage the Lambdas application lifecycle automatically. It will invoke the /// ``ByteBufferLambdaHandler/makeHandler(context:)`` to create a new Handler. @@ -33,9 +80,9 @@ public enum Lambda { /// - handlerType: The Handler to create and invoke. /// /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. - internal static func run( + internal static func run( configuration: LambdaConfiguration = .init(), - handlerType: Handler.Type + handlerType: (some ByteBufferLambdaHandler).Type ) -> Result { let _run = { (configuration: LambdaConfiguration) -> Result in Backtrace.install() @@ -44,7 +91,7 @@ public enum Lambda { var result: Result! MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in - let runtime = LambdaRuntime(eventLoop: eventLoop, logger: logger, configuration: configuration) + let runtime = LambdaRuntime(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: configuration) #if DEBUG let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in logger.info("intercepted signal: \(signal)") @@ -66,7 +113,6 @@ public enum Lambda { result = lifecycleResult } } - logger.info("shutdown completed") return result } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift index 7118a5ac..a957ce2c 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaContext.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift @@ -12,15 +12,9 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=5.6) @preconcurrency import Dispatch -@preconcurrency import Logging -@preconcurrency import NIOCore -#else -import Dispatch import Logging import NIOCore -#endif // MARK: - InitializationContext @@ -28,7 +22,7 @@ import NIOCore /// The Lambda runtime generates and passes the `LambdaInitializationContext` to the Handlers /// ``ByteBufferLambdaHandler/makeHandler(context:)`` or ``LambdaHandler/init(context:)`` /// as an argument. -public struct LambdaInitializationContext: _AWSLambdaSendable { +public struct LambdaInitializationContext: Sendable { /// `Logger` to log with. /// /// - note: The `LogLevel` can be configured using the `LOG_LEVEL` environment variable. @@ -71,8 +65,8 @@ public struct LambdaInitializationContext: _AWSLambdaSendable { /// Lambda runtime context. /// The Lambda runtime generates and passes the `LambdaContext` to the Lambda handler as an argument. -public struct LambdaContext: CustomDebugStringConvertible, _AWSLambdaSendable { - final class _Storage: _AWSLambdaSendable { +public struct LambdaContext: CustomDebugStringConvertible, Sendable { + final class _Storage: Sendable { let requestID: String let traceID: String let invokedFunctionARN: String diff --git a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift index 8f4b9f6e..fc3611ba 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift @@ -15,18 +15,138 @@ import Dispatch import NIOCore +// MARK: - SimpleLambdaHandler + +/// Strongly typed, processing protocol for a Lambda that takes a user defined +/// ``SimpleLambdaHandler/Event`` and returns a user defined +/// ``SimpleLambdaHandler/Output`` asynchronously. +/// +/// - note: Most users should implement the ``LambdaHandler`` protocol instead +/// which defines the Lambda initialization method. +public protocol SimpleLambdaHandler { + /// The lambda function's input. In most cases this should be `Codable`. If your event originates from an + /// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), + /// which provides a number of commonly used AWS Event implementations. + associatedtype Event + /// The lambda function's output. Can be `Void`. + associatedtype Output + + init() + + /// The Lambda handling method. + /// Concrete Lambda handlers implement this method to provide the Lambda functionality. + /// + /// - parameters: + /// - event: Event of type `Event` representing the event or request. + /// - context: Runtime ``LambdaContext``. + /// + /// - Returns: A Lambda result ot type `Output`. + func handle(_ event: Event, context: LambdaContext) async throws -> Output + + /// Encode a response of type ``Output`` to `ByteBuffer`. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// - parameters: + /// - value: Response of type ``Output``. + /// - buffer: A `ByteBuffer` to encode into, will be overwritten. + /// + /// - Returns: A `ByteBuffer` with the encoded version of the `value`. + func encode(value: Output, into buffer: inout ByteBuffer) throws + + /// Decode a `ByteBuffer` to a request or event of type ``Event``. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// + /// - parameters: + /// - buffer: The `ByteBuffer` to decode. + /// + /// - Returns: A request or event of type ``Event``. + func decode(buffer: ByteBuffer) throws -> Event +} + +@usableFromInline +final class CodableSimpleLambdaHandler: ByteBufferLambdaHandler { + @usableFromInline + let handler: Underlying + @usableFromInline + private(set) var outputBuffer: ByteBuffer + + @inlinable + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: CodableSimpleLambdaHandler.self) + promise.completeWithTask { + let handler = Underlying() + return CodableSimpleLambdaHandler(handler: handler, allocator: context.allocator) + } + return promise.futureResult + } + + @inlinable + init(handler: Underlying, allocator: ByteBufferAllocator) { + self.handler = handler + self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) + } + + @inlinable + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: ByteBuffer?.self) + promise.completeWithTask { + let input: Underlying.Event + do { + input = try self.handler.decode(buffer: buffer) + } catch { + throw CodecError.requestDecoding(error) + } + + let output = try await self.handler.handle(input, context: context) + + do { + self.outputBuffer.clear() + try self.handler.encode(value: output, into: &self.outputBuffer) + return self.outputBuffer + } catch { + throw CodecError.responseEncoding(error) + } + } + return promise.futureResult + } +} + +/// Implementation of `ByteBuffer` to `Void` decoding. +extension SimpleLambdaHandler where Output == Void { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws {} +} + +extension SimpleLambdaHandler { + /// Initializes and runs the Lambda function. + /// + /// If you precede your ``SimpleLambdaHandler`` conformer's declaration with the + /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) + /// attribute, the system calls the conformer's `main()` method to launch the lambda function. + /// + /// The lambda runtime provides a default implementation of the method that manages the launch + /// process. + public static func main() { + _ = Lambda.run(configuration: .init(), handlerType: Self.self) + } +} + // MARK: - LambdaHandler -#if compiler(>=5.5) && canImport(_Concurrency) /// Strongly typed, processing protocol for a Lambda that takes a user defined -/// ``EventLoopLambdaHandler/Event`` and returns a user defined -/// ``EventLoopLambdaHandler/Output`` asynchronously. +/// ``LambdaHandler/Event`` and returns a user defined +/// ``LambdaHandler/Output`` asynchronously. /// /// - note: Most users should implement this protocol instead of the lower /// level protocols ``EventLoopLambdaHandler`` and /// ``ByteBufferLambdaHandler``. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -public protocol LambdaHandler: EventLoopLambdaHandler { +public protocol LambdaHandler { + /// The lambda function's input. In most cases this should be `Codable`. If your event originates from an + /// AWS service, have a look at [AWSLambdaEvents](https://github.com/swift-server/swift-aws-lambda-events), + /// which provides a number of commonly used AWS Event implementations. + associatedtype Event + /// The lambda function's output. Can be `Void`. + associatedtype Output + /// The Lambda initialization method. /// Use this method to initialize resources that will be used in every request. /// @@ -44,53 +164,116 @@ public protocol LambdaHandler: EventLoopLambdaHandler { /// /// - Returns: A Lambda result ot type `Output`. func handle(_ event: Event, context: LambdaContext) async throws -> Output + + /// Encode a response of type ``Output`` to `ByteBuffer`. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// - parameters: + /// - value: Response of type ``Output``. + /// - buffer: A `ByteBuffer` to encode into, will be overwritten. + /// + /// - Returns: A `ByteBuffer` with the encoded version of the `value`. + func encode(value: Output, into buffer: inout ByteBuffer) throws + + /// Decode a `ByteBuffer` to a request or event of type ``Event``. + /// Concrete Lambda handlers implement this method to provide coding functionality. + /// + /// - parameters: + /// - buffer: The `ByteBuffer` to decode. + /// + /// - Returns: A request or event of type ``Event``. + func decode(buffer: ByteBuffer) throws -> Event } -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -extension LambdaHandler { - public static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { - let promise = context.eventLoop.makePromise(of: Self.self) +@usableFromInline +final class CodableLambdaHandler: ByteBufferLambdaHandler { + @usableFromInline + let handler: Underlying + @usableFromInline + private(set) var outputBuffer: ByteBuffer + + @inlinable + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: CodableLambdaHandler.self) promise.completeWithTask { - try await Self(context: context) + let handler = try await Underlying(context: context) + return CodableLambdaHandler(handler: handler, allocator: context.allocator) } return promise.futureResult } - public func handle(_ event: Event, context: LambdaContext) -> EventLoopFuture { - let promise = context.eventLoop.makePromise(of: Output.self) - // using an unchecked sendable wrapper for the handler - // this is safe since lambda runtime is designed to calls the handler serially - let handler = UncheckedSendableHandler(underlying: self) + @inlinable + init(handler: Underlying, allocator: ByteBufferAllocator) { + self.handler = handler + self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) + } + + @inlinable + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture { + let promise = context.eventLoop.makePromise(of: ByteBuffer?.self) promise.completeWithTask { - try await handler.handle(event, context: context) + let input: Underlying.Event + do { + input = try self.handler.decode(buffer: buffer) + } catch { + throw CodecError.requestDecoding(error) + } + + let output = try await self.handler.handle(input, context: context) + + do { + self.outputBuffer.clear() + try self.handler.encode(value: output, into: &self.outputBuffer) + return self.outputBuffer + } catch { + throw CodecError.responseEncoding(error) + } } return promise.futureResult } } +/// Implementation of `ByteBuffer` to `Void` decoding. +extension LambdaHandler where Output == Void { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws {} +} + +extension LambdaHandler { + /// Initializes and runs the Lambda function. + /// + /// If you precede your ``LambdaHandler`` conformer's declaration with the + /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) + /// attribute, the system calls the conformer's `main()` method to launch the lambda function. + /// + /// The lambda runtime provides a default implementation of the method that manages the launch + /// process. + public static func main() { + _ = Lambda.run(configuration: .init(), handlerType: Self.self) + } +} + /// unchecked sendable wrapper for the handler /// this is safe since lambda runtime is designed to calls the handler serially -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -fileprivate struct UncheckedSendableHandler: @unchecked Sendable where Event == Underlying.Event, Output == Underlying.Output { +@usableFromInline +internal struct UncheckedSendableHandler: @unchecked Sendable where Event == Underlying.Event, Output == Underlying.Output { + @usableFromInline let underlying: Underlying + @inlinable init(underlying: Underlying) { self.underlying = underlying } + @inlinable func handle(_ event: Event, context: LambdaContext) async throws -> Output { try await self.underlying.handle(event, context: context) } } -#endif // MARK: - EventLoopLambdaHandler /// Strongly typed, `EventLoopFuture` based processing protocol for a Lambda that takes a user -/// defined ``Event`` and returns a user defined ``Output`` asynchronously. -/// -/// ``EventLoopLambdaHandler`` extends ``ByteBufferLambdaHandler``, performing -/// `ByteBuffer` -> ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding. +/// defined ``EventLoopLambdaHandler/Event`` and returns a user defined ``EventLoopLambdaHandler/Output`` asynchronously. /// /// - note: To implement a Lambda, implement either ``LambdaHandler`` or the /// ``EventLoopLambdaHandler`` protocol. The ``LambdaHandler`` will offload @@ -100,7 +283,7 @@ fileprivate struct UncheckedSendableHandler EventLoopFuture + /// The Lambda handling method. /// Concrete Lambda handlers implement this method to provide the Lambda functionality. /// @@ -122,11 +313,11 @@ public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler { /// Encode a response of type ``Output`` 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 ``Output``. + /// - buffer: A `ByteBuffer` to encode into, will be overwritten. /// /// - Returns: A `ByteBuffer` with the encoded version of the `value`. - func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer? + func encode(value: Output, into buffer: inout ByteBuffer) throws /// Decode a `ByteBuffer` to a request or event of type ``Event``. /// Concrete Lambda handlers implement this method to provide coding functionality. @@ -138,20 +329,46 @@ public protocol EventLoopLambdaHandler: ByteBufferLambdaHandler { func decode(buffer: ByteBuffer) throws -> Event } -extension EventLoopLambdaHandler { - /// Driver for `ByteBuffer` -> ``Event`` decoding and ``Output`` -> `ByteBuffer` encoding +/// Implementation of `ByteBuffer` to `Void` decoding. +extension EventLoopLambdaHandler where Output == Void { + @inlinable + public func encode(value: Output, into buffer: inout ByteBuffer) throws {} +} + +@usableFromInline +final class CodableEventLoopLambdaHandler: ByteBufferLambdaHandler { + @usableFromInline + let handler: Underlying + @usableFromInline + private(set) var outputBuffer: ByteBuffer + + @inlinable + static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { + Underlying.makeHandler(context: context).map { handler -> CodableEventLoopLambdaHandler in + CodableEventLoopLambdaHandler(handler: handler, allocator: context.allocator) + } + } + @inlinable - public func handle(_ event: ByteBuffer, context: LambdaContext) -> EventLoopFuture { - let input: Event + init(handler: Underlying, allocator: ByteBufferAllocator) { + self.handler = handler + self.outputBuffer = allocator.buffer(capacity: 1024 * 1024) + } + + @inlinable + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture { + let input: Underlying.Event do { - input = try self.decode(buffer: event) + input = try self.handler.decode(buffer: buffer) } catch { return context.eventLoop.makeFailedFuture(CodecError.requestDecoding(error)) } - return self.handle(input, context: context).flatMapThrowing { output in + return self.handler.handle(input, context: context).flatMapThrowing { output in do { - return try self.encode(allocator: context.allocator, value: output) + self.outputBuffer.clear() + try self.handler.encode(value: output, into: &self.outputBuffer) + return self.outputBuffer } catch { throw CodecError.responseEncoding(error) } @@ -159,11 +376,17 @@ extension EventLoopLambdaHandler { } } -/// Implementation of `ByteBuffer` to `Void` decoding. -extension EventLoopLambdaHandler where Output == Void { - @inlinable - public func encode(allocator: ByteBufferAllocator, value: Void) throws -> ByteBuffer? { - nil +extension EventLoopLambdaHandler { + /// Initializes and runs the Lambda function. + /// + /// If you precede your ``EventLoopLambdaHandler`` conformer's declaration with the + /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626) + /// attribute, the system calls the conformer's `main()` method to launch the lambda function. + /// + /// The lambda runtime provides a default implementation of the method that manages the launch + /// process. + public static func main() { + _ = Lambda.run(configuration: .init(), handlerType: Self.self) } } @@ -176,7 +399,7 @@ extension EventLoopLambdaHandler where Output == Void { /// ``LambdaHandler`` based APIs. /// Most users are not expected to use this protocol. public protocol ByteBufferLambdaHandler { - /// Create your Lambda handler for the runtime. + /// Create a Lambda handler for the runtime. /// /// 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 @@ -193,7 +416,7 @@ public protocol ByteBufferLambdaHandler { /// /// - 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(_ event: ByteBuffer, context: LambdaContext) -> EventLoopFuture + func handle(_ buffer: ByteBuffer, context: LambdaContext) -> EventLoopFuture } extension ByteBufferLambdaHandler { @@ -210,8 +433,11 @@ extension ByteBufferLambdaHandler { } } +// MARK: - Other + @usableFromInline enum CodecError: Error { case requestDecoding(Error) case responseEncoding(Error) + case invalidString } diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift index 54e90f95..3d4f55b3 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift @@ -33,7 +33,7 @@ internal final class LambdaRunner { /// Run the user provided initializer. This *must* only be called once. /// /// - Returns: An `EventLoopFuture` fulfilled with the outcome of the initialization. - func initialize(logger: Logger, terminator: LambdaTerminator, handlerType: Handler.Type) -> EventLoopFuture { + func initialize(handlerType: Handler.Type, logger: Logger, terminator: LambdaTerminator) -> EventLoopFuture { logger.debug("initializing lambda") // 1. create the handler from the factory // 2. report initialization error if one occurred @@ -43,7 +43,8 @@ internal final class LambdaRunner { allocator: self.allocator, terminator: terminator ) - return Handler.makeHandler(context: context) + + return handlerType.makeHandler(context: context) // Hopping back to "our" EventLoop is important in case the factory returns a future // that originated from a foreign EventLoop/EventLoopGroup. // This can happen if the factory uses a library (let's say a database client) that manages its own threads/loops @@ -58,7 +59,7 @@ internal final class LambdaRunner { } } - func run(logger: Logger, handler: Handler) -> EventLoopFuture { + func run(handler: some ByteBufferLambdaHandler, logger: Logger) -> EventLoopFuture { logger.debug("lambda invocation sequence starting") // 1. request invocation from lambda runtime engine self.isGettingNextInvocation = true @@ -73,10 +74,10 @@ internal final class LambdaRunner { allocator: self.allocator, invocation: invocation ) - logger.debug("sending invocation to lambda handler \(handler)") + logger.debug("sending invocation to lambda handler") return handler.handle(bytes, context: context) // Hopping back to "our" EventLoop is important in case the handler returns a future that - // originiated from a foreign EventLoop/EventLoopGroup. + // originated from a foreign EventLoop/EventLoopGroup. // This can happen if the handler uses a library (lets say a DB client) that manages its own threads/loops // for whatever reason and returns a future that originated from that foreign EventLoop. .hop(to: self.eventLoop) diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift index cd2b9588..96b77489 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift @@ -39,10 +39,10 @@ public final class LambdaRuntime { /// - eventLoop: An `EventLoop` to run the Lambda on. /// - logger: A `Logger` to log the Lambda events. public convenience init(_ handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger) { - self.init(eventLoop: eventLoop, logger: logger, configuration: .init()) + self.init(handlerType: handlerType, eventLoop: eventLoop, logger: logger, configuration: .init()) } - init(eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { + init(handlerType: Handler.Type, eventLoop: EventLoop, logger: Logger, configuration: LambdaConfiguration) { self.eventLoop = eventLoop self.shutdownPromise = eventLoop.makePromise(of: Int.self) self.logger = logger @@ -85,7 +85,7 @@ public final class LambdaRuntime { let terminator = LambdaTerminator() let runner = LambdaRunner(eventLoop: self.eventLoop, configuration: self.configuration) - let startupFuture = runner.initialize(logger: logger, terminator: terminator, handlerType: Handler.self) + let startupFuture = runner.initialize(handlerType: Handler.self, logger: logger, terminator: terminator) startupFuture.flatMap { handler -> EventLoopFuture> in // after the startup future has succeeded, we have a handler that we can use // to `run` the lambda. @@ -141,7 +141,7 @@ public final class LambdaRuntime { } var logger = self.logger logger[metadataKey: "lifecycleIteration"] = "\(count)" - runner.run(logger: logger, handler: handler).whenComplete { result in + runner.run(handler: handler, logger: logger).whenComplete { result in switch result { case .success: logger.log(level: .debug, "lambda invocation sequence completed successfully") @@ -175,7 +175,7 @@ public final class LambdaRuntime { private enum State { case idle case initializing - case active(LambdaRunner, Handler) + case active(LambdaRunner, any ByteBufferLambdaHandler) case shuttingdown case shutdown @@ -196,7 +196,40 @@ public final class LambdaRuntime { } } +public enum LambdaRuntimeFactory { + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``SimpleLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @inlinable + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(CodableSimpleLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``LambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @inlinable + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(CodableLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } + + /// Create a new `LambdaRuntime`. + /// + /// - parameters: + /// - handlerType: The ``EventLoopLambdaHandler`` type the `LambdaRuntime` shall create and manage. + /// - eventLoop: An `EventLoop` to run the Lambda on. + /// - logger: A `Logger` to log the Lambda events. + @inlinable + public static func makeRuntime(_ handlerType: H.Type, eventLoop: any EventLoop, logger: Logger) -> LambdaRuntime { + LambdaRuntime>(CodableEventLoopLambdaHandler.self, eventLoop: eventLoop, logger: logger) + } +} + /// This is safe since lambda runtime synchronizes by dispatching all methods to a single `EventLoop` -#if compiler(>=5.5) && canImport(_Concurrency) extension LambdaRuntime: @unchecked Sendable {} -#endif diff --git a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift index 29d04b9d..bcc65736 100644 --- a/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift +++ b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift @@ -13,13 +13,8 @@ //===----------------------------------------------------------------------===// import Logging -#if compiler(>=5.6) -@preconcurrency import NIOCore -@preconcurrency import NIOHTTP1 -#else import NIOCore import NIOHTTP1 -#endif /// An HTTP based client for AWS Runtime Engine. This encapsulates the RESTful methods exposed by the Runtime Engine: /// * /runtime/invocation/next diff --git a/Sources/AWSLambdaRuntimeCore/Sendable.swift b/Sources/AWSLambdaRuntimeCore/Sendable.swift deleted file mode 100644 index 936403e4..00000000 --- a/Sources/AWSLambdaRuntimeCore/Sendable.swift +++ /dev/null @@ -1,21 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftAWSLambdaRuntime open source project -// -// Copyright (c) 2022 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 -// -//===----------------------------------------------------------------------===// - -// Sendable bridging types - -#if compiler(>=5.6) -public typealias _AWSLambdaSendable = Sendable -#else -public typealias _AWSLambdaSendable = Any -#endif diff --git a/Sources/AWSLambdaRuntimeCore/Terminator.swift b/Sources/AWSLambdaRuntimeCore/Terminator.swift index 6a0b65c0..cba8fd99 100644 --- a/Sources/AWSLambdaRuntimeCore/Terminator.swift +++ b/Sources/AWSLambdaRuntimeCore/Terminator.swift @@ -100,7 +100,7 @@ extension LambdaTerminator { extension LambdaTerminator { fileprivate final class Storage { - private let lock: Lock + private let lock: NIOLock private var index: [RegistrationKey] private var map: [RegistrationKey: (name: String, handler: Handler)] @@ -140,7 +140,5 @@ extension LambdaTerminator { // Ideally this would not be @unchecked Sendable, but Sendable checks do not understand locks // We can transition this to an actor once we drop support for older Swift versions -#if compiler(>=5.5) && canImport(_Concurrency) extension LambdaTerminator: @unchecked Sendable {} extension LambdaTerminator.Storage: @unchecked Sendable {} -#endif diff --git a/Sources/AWSLambdaTesting/Lambda+Testing.swift b/Sources/AWSLambdaTesting/Lambda+Testing.swift index 11e2bf89..9f77e8ac 100644 --- a/Sources/AWSLambdaTesting/Lambda+Testing.swift +++ b/Sources/AWSLambdaTesting/Lambda+Testing.swift @@ -33,14 +33,13 @@ // XCTAssertEqual(result, "echo" + input) // } -#if compiler(>=5.5) && canImport(_Concurrency) -import AWSLambdaRuntime +@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import Dispatch import Logging import NIOCore import NIOPosix -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension Lambda { public struct TestConfig { public var requestID: String @@ -59,19 +58,55 @@ extension Lambda { } } + public static func test( + _ handlerType: Handler.Type, + with event: Handler.Event, + using config: TestConfig = .init() + ) async throws -> Handler.Output { + let context = Self.makeContext(config: config) + let handler = Handler() + return try await handler.handle(event, context: context.1) + } + public static func test( _ handlerType: Handler.Type, with event: Handler.Event, using config: TestConfig = .init() - ) throws -> Handler.Output { + ) async throws -> Handler.Output { + let context = Self.makeContext(config: config) + let handler = try await Handler(context: context.0) + return try await handler.handle(event, context: context.1) + } + + public static func test( + _ handlerType: Handler.Type, + with event: Handler.Event, + using config: TestConfig = .init() + ) async throws -> Handler.Output { + let context = Self.makeContext(config: config) + let handler = try await Handler.makeHandler(context: context.0).get() + return try await handler.handle(event, context: context.1).get() + } + + public static func test( + _ handlerType: Handler.Type, + with buffer: ByteBuffer, + using config: TestConfig = .init() + ) async throws -> ByteBuffer? { + let context = Self.makeContext(config: config) + let handler = try await Handler.makeHandler(context: context.0).get() + return try await handler.handle(buffer, context: context.1).get() + } + + private static func makeContext(config: TestConfig) -> (LambdaInitializationContext, LambdaContext) { let logger = Logger(label: "test") + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { try! eventLoopGroup.syncShutdownGracefully() } let eventLoop = eventLoopGroup.next() - let promise = eventLoop.makePromise(of: Handler.self) let initContext = LambdaInitializationContext.__forTestsOnly( logger: logger, eventLoop: eventLoop @@ -86,14 +121,6 @@ extension Lambda { eventLoop: eventLoop ) - promise.completeWithTask { - try await Handler(context: initContext) - } - let handler = try promise.futureResult.wait() - - return try eventLoop.flatSubmit { - handler.handle(event, context: context) - }.wait() + return (initContext, context) } } -#endif diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift index 98b49ca7..ac4b2a65 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlerTest.swift @@ -17,20 +17,57 @@ import NIOCore import XCTest class LambdaHandlerTest: XCTestCase { - #if compiler(>=5.5) && canImport(_Concurrency) + // MARK: - SimpleLambdaHandler + + func testBootstrapSimpleNoInit() { + let server = MockLambdaServer(behavior: Behavior()) + XCTAssertNoThrow(try server.start().wait()) + defer { XCTAssertNoThrow(try server.stop().wait()) } + + struct TestBootstrapHandler: SimpleLambdaHandler { + func handle(_ event: String, context: LambdaContext) async throws -> String { + event + } + } + + let maxTimes = Int.random(in: 10 ... 20) + let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) + } + + func testBootstrapSimpleInit() { + let server = MockLambdaServer(behavior: Behavior()) + XCTAssertNoThrow(try server.start().wait()) + defer { XCTAssertNoThrow(try server.stop().wait()) } + + struct TestBootstrapHandler: SimpleLambdaHandler { + var initialized = false + + init() { + XCTAssertFalse(self.initialized) + self.initialized = true + } + + func handle(_ event: String, context: LambdaContext) async throws -> String { + event + } + } + + let maxTimes = Int.random(in: 10 ... 20) + let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) + let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) + } // MARK: - LambdaHandler - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testBootstrapSuccess() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } struct TestBootstrapHandler: LambdaHandler { - typealias Event = String - typealias Output = String - var initialized = false init(context: LambdaInitializationContext) async throws { @@ -47,19 +84,15 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: TestBootstrapHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testBootstrapFailure() { let server = MockLambdaServer(behavior: FailedBootstrapBehavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } struct TestBootstrapHandler: LambdaHandler { - typealias Event = String - typealias Output = Void - var initialized = false init(context: LambdaInitializationContext) async throws { @@ -79,18 +112,12 @@ class LambdaHandlerTest: XCTestCase { assertLambdaRuntimeResult(result, shouldFailWithError: TestError("kaboom")) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testHandlerSuccess() { let server = MockLambdaServer(behavior: Behavior()) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) {} - + struct Handler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { event } @@ -99,21 +126,15 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testVoidHandlerSuccess() { let server = MockLambdaServer(behavior: Behavior(result: .success(nil))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { - typealias Event = String - typealias Output = Void - - init(context: LambdaInitializationContext) {} - + struct Handler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws {} } @@ -121,21 +142,15 @@ class LambdaHandlerTest: XCTestCase { let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func testHandlerFailure() { let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) XCTAssertNoThrow(try server.start().wait()) defer { XCTAssertNoThrow(try server.stop().wait()) } - struct Handler: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) {} - + struct Handler: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { throw TestError("boom") } @@ -144,9 +159,8 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } - #endif // MARK: - EventLoopLambdaHandler @@ -156,9 +170,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -171,7 +182,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testVoidEventLoopSuccess() { @@ -180,9 +191,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = Void - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -195,7 +203,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testEventLoopFailure() { @@ -204,9 +212,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -219,7 +224,7 @@ class LambdaHandlerTest: XCTestCase { let maxTimes = Int.random(in: 1 ... 10) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: Handler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testEventLoopBootstrapFailure() { @@ -228,9 +233,6 @@ class LambdaHandlerTest: XCTestCase { defer { XCTAssertNoThrow(try server.stop().wait()) } struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeFailedFuture(TestError("kaboom")) } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift index c2f3fc9e..dd371238 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaHandlers.swift @@ -17,9 +17,6 @@ import NIOCore import XCTest struct EchoHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(EchoHandler()) } @@ -32,9 +29,6 @@ struct EchoHandler: EventLoopLambdaHandler { struct StartupError: Error {} struct StartupErrorHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeFailedFuture(StartupError()) } @@ -48,9 +42,6 @@ struct StartupErrorHandler: EventLoopLambdaHandler { struct RuntimeError: Error {} struct RuntimeErrorHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = Void - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(RuntimeErrorHandler()) } diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift index 64bc4384..39764ccc 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeTest.swift @@ -29,7 +29,7 @@ class LambdaRuntimeTest: XCTestCase { let eventLoop = eventLoopGroup.next() let logger = Logger(label: "TestLogger") - let runtime = LambdaRuntime(StartupErrorHandler.self, eventLoop: eventLoop, logger: logger) + let runtime = LambdaRuntimeFactory.makeRuntime(StartupErrorHandler.self, eventLoop: eventLoop, logger: logger) // eventLoop.submit in this case returns an EventLoopFuture> // which is why we need `wait().wait()` @@ -51,7 +51,7 @@ class LambdaRuntimeTest: XCTestCase { let eventLoop = eventLoopGroup.next() let logger = Logger(label: "TestLogger") - let runtime = LambdaRuntime(EchoHandler.self, eventLoop: eventLoop, logger: logger) + let runtime = LambdaRuntimeFactory.makeRuntime(EchoHandler.self, eventLoop: eventLoop, logger: logger) XCTAssertNoThrow(_ = try eventLoop.flatSubmit { runtime.start() }.wait()) XCTAssertThrowsError(_ = try runtime.shutdownFuture.wait()) { @@ -71,9 +71,6 @@ class LambdaRuntimeTest: XCTestCase { } struct ShutdownErrorHandler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = Void - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { // register shutdown operation context.terminator.register(name: "test 1", handler: { eventLoop in @@ -101,7 +98,7 @@ class LambdaRuntimeTest: XCTestCase { let eventLoop = eventLoopGroup.next() let logger = Logger(label: "TestLogger") - let runtime = LambdaRuntime(ShutdownErrorHandler.self, eventLoop: eventLoop, logger: logger) + let runtime = LambdaRuntimeFactory.makeRuntime(ShutdownErrorHandler.self, eventLoop: eventLoop, logger: logger) XCTAssertNoThrow(try eventLoop.flatSubmit { runtime.start() }.wait()) XCTAssertThrowsError(try runtime.shutdownFuture.wait()) { error in diff --git a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 1cf6aa1a..fefa6c4f 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -13,14 +13,9 @@ //===----------------------------------------------------------------------===// @testable import AWSLambdaRuntimeCore -#if compiler(>=5.6) -@preconcurrency import Logging -@preconcurrency import NIOPosix -#else import Logging -import NIOPosix -#endif import NIOCore +import NIOPosix import XCTest class LambdaTest: XCTestCase { @@ -32,7 +27,7 @@ class LambdaTest: XCTestCase { let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testFailure() { @@ -43,7 +38,7 @@ class LambdaTest: XCTestCase { let maxTimes = Int.random(in: 10 ... 20) let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: RuntimeErrorHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testBootstrapFailure() { @@ -141,7 +136,7 @@ class LambdaTest: XCTestCase { let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: 1)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: 1) + assertLambdaRuntimeResult(result, shouldHaveRun: 1) } func testKeepAliveServer() { @@ -152,7 +147,7 @@ class LambdaTest: XCTestCase { let maxTimes = 10 let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testNoKeepAliveServer() { @@ -163,7 +158,7 @@ class LambdaTest: XCTestCase { let maxTimes = 10 let configuration = LambdaConfiguration(lifecycle: .init(maxTimes: maxTimes)) let result = Lambda.run(configuration: configuration, handlerType: EchoHandler.self) - assertLambdaRuntimeResult(result, shoudHaveRun: maxTimes) + assertLambdaRuntimeResult(result, shouldHaveRun: maxTimes) } func testServerFailure() { @@ -259,9 +254,6 @@ class LambdaTest: XCTestCase { #if compiler(>=5.6) func testSendable() async throws { struct Handler: EventLoopLambdaHandler { - typealias Event = String - typealias Output = String - static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { context.eventLoop.makeSucceededFuture(Handler()) } @@ -286,10 +278,16 @@ class LambdaTest: XCTestCase { logger.info("hello") let runner = LambdaRunner(eventLoop: eventLoopGroup.next(), configuration: configuration) - try runner.run(logger: logger, handler: handler1).wait() + try runner.run( + handler: CodableEventLoopLambdaHandler( + handler: handler1, + allocator: ByteBufferAllocator() + ), + logger: logger + ).wait() - try runner.initialize(logger: logger, terminator: LambdaTerminator(), handlerType: Handler.self).flatMap { handler2 in - runner.run(logger: logger, handler: handler2) + try runner.initialize(handlerType: CodableEventLoopLambdaHandler.self, logger: logger, terminator: LambdaTerminator()).flatMap { handler2 in + runner.run(handler: handler2, logger: logger) }.wait() } diff --git a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift index 49cd7708..aecd3186 100644 --- a/Tests/AWSLambdaRuntimeCoreTests/Utils.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift @@ -18,7 +18,19 @@ import NIOCore import NIOPosix import XCTest -func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { +func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { + try runLambda(behavior: behavior, handlerType: CodableSimpleLambdaHandler.self) +} + +func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { + try runLambda(behavior: behavior, handlerType: CodableLambdaHandler.self) +} + +func runLambda(behavior: LambdaServerBehavior, handlerType: Handler.Type) throws { + try runLambda(behavior: behavior, handlerType: CodableEventLoopLambdaHandler.self) +} + +func runLambda(behavior: LambdaServerBehavior, handlerType: (some ByteBufferLambdaHandler).Type) throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } let logger = Logger(label: "TestLogger") @@ -27,17 +39,17 @@ func runLambda(behavior: LambdaServerBehavior, let runner = LambdaRunner(eventLoop: eventLoopGroup.next(), configuration: configuration) let server = try MockLambdaServer(behavior: behavior).start().wait() defer { XCTAssertNoThrow(try server.stop().wait()) } - try runner.initialize(logger: logger, terminator: terminator, handlerType: handlerType).flatMap { handler in - runner.run(logger: logger, handler: handler) + try runner.initialize(handlerType: handlerType, logger: logger, terminator: terminator).flatMap { handler in + runner.run(handler: handler, logger: logger) }.wait() } -func assertLambdaRuntimeResult(_ result: Result, shoudHaveRun: Int = 0, shouldFailWithError: Error? = nil, file: StaticString = #file, line: UInt = #line) { +func assertLambdaRuntimeResult(_ result: Result, shouldHaveRun: Int = 0, shouldFailWithError: Error? = nil, file: StaticString = #file, line: UInt = #line) { switch result { case .success where shouldFailWithError != nil: XCTFail("should fail with \(shouldFailWithError!)", file: file, line: line) case .success(let count) where shouldFailWithError == nil: - XCTAssertEqual(shoudHaveRun, count, "should have run \(shoudHaveRun) times", file: file, line: line) + XCTAssertEqual(shouldHaveRun, count, "should have run \(shouldHaveRun) times", file: file, line: line) case .failure(let error) where shouldFailWithError == nil: XCTFail("should succeed, but failed with \(error)", file: file, line: line) case .failure(let error) where shouldFailWithError != nil: diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift index eceaa2d8..43e50423 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTest.swift @@ -33,14 +33,7 @@ class CodableLambdaTest: XCTestCase { } func testCodableVoidEventLoopFutureHandler() { - let request = Request(requestId: UUID().uuidString) - var inputBuffer: ByteBuffer? - var outputBuffer: ByteBuffer? - struct Handler: EventLoopLambdaHandler { - typealias Event = Request - typealias Output = Void - var expected: Request? static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { @@ -53,23 +46,23 @@ class CodableLambdaTest: XCTestCase { } } - let handler = Handler(expected: request) + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) - XCTAssertNil(outputBuffer) - } + let handler = CodableEventLoopLambdaHandler( + handler: Handler(expected: request), + allocator: context.allocator + ) - func testCodableEventLoopFutureHandler() { - let request = Request(requestId: UUID().uuidString) - var inputBuffer: ByteBuffer? + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) var outputBuffer: ByteBuffer? - var response: Response? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(outputBuffer?.readableBytes, 0) + } + func testCodableEventLoopFutureHandler() { struct Handler: EventLoopLambdaHandler { - typealias Event = Request - typealias Output = Response - var expected: Request? static func makeHandler(context: LambdaInitializationContext) -> EventLoopFuture { @@ -82,76 +75,140 @@ class CodableLambdaTest: XCTestCase { } } - let handler = Handler(expected: request) + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + var response: Response? + + let handler = CodableEventLoopLambdaHandler( + handler: Handler(expected: request), + allocator: context.allocator + ) - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) XCTAssertEqual(response?.requestId, request.requestId) } - #if compiler(>=5.5) && canImport(_Concurrency) - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testCodableVoidHandler() { + func testCodableVoidHandler() async throws { struct Handler: LambdaHandler { - typealias Event = Request - typealias Output = Void + init(context: AWSLambdaRuntimeCore.LambdaInitializationContext) async throws {} var expected: Request? - init(context: LambdaInitializationContext) async throws {} - func handle(_ event: Request, context: LambdaContext) async throws { XCTAssertEqual(event, self.expected) } } - XCTAsyncTest { - let request = Request(requestId: UUID().uuidString) - var inputBuffer: ByteBuffer? - var outputBuffer: ByteBuffer? + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) - var handler = try await Handler(context: self.newInitContext()) - handler.expected = request + var underlying = try await Handler(context: self.newInitContext()) + underlying.expected = request + let handler = CodableLambdaHandler( + handler: underlying, + allocator: context.allocator + ) - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) - XCTAssertNil(outputBuffer) - } + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(outputBuffer?.readableBytes, 0) } - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func testCodableHandler() { + func testCodableHandler() async throws { struct Handler: LambdaHandler { - typealias Event = Request - typealias Output = Response + init(context: AWSLambdaRuntimeCore.LambdaInitializationContext) async throws {} var expected: Request? - init(context: LambdaInitializationContext) async throws {} - func handle(_ event: Request, context: LambdaContext) async throws -> Response { XCTAssertEqual(event, self.expected) return Response(requestId: event.requestId) } } - XCTAsyncTest { - let request = Request(requestId: UUID().uuidString) - var response: Response? - var inputBuffer: ByteBuffer? - var outputBuffer: ByteBuffer? + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + var response: Response? + + var underlying = try await Handler(context: self.newInitContext()) + underlying.expected = request + let handler = CodableLambdaHandler( + handler: underlying, + allocator: context.allocator + ) + + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) + XCTAssertNoThrow(try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(response?.requestId, request.requestId) + } + + func testCodableVoidSimpleHandler() async throws { + struct Handler: SimpleLambdaHandler { + var expected: Request? + + func handle(_ event: Request, context: LambdaContext) async throws { + XCTAssertEqual(event, self.expected) + } + } + + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) - var handler = try await Handler(context: self.newInitContext()) - handler.expected = request + var underlying = Handler() + underlying.expected = request + let handler = CodableSimpleLambdaHandler( + handler: underlying, + allocator: context.allocator + ) + + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(outputBuffer?.readableBytes, 0) + } - XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) - XCTAssertNoThrow(outputBuffer = try handler.handle(XCTUnwrap(inputBuffer), context: self.newContext()).wait()) - XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) - XCTAssertEqual(response?.requestId, request.requestId) + func testCodableSimpleHandler() async throws { + struct Handler: SimpleLambdaHandler { + var expected: Request? + + func handle(_ event: Request, context: LambdaContext) async throws -> Response { + XCTAssertEqual(event, self.expected) + return Response(requestId: event.requestId) + } } + + let context = self.newContext() + let request = Request(requestId: UUID().uuidString) + var response: Response? + + var underlying = Handler() + underlying.expected = request + let handler = CodableSimpleLambdaHandler( + handler: underlying, + allocator: context.allocator + ) + + var inputBuffer = context.allocator.buffer(capacity: 1024) + XCTAssertNoThrow(try JSONEncoder().encode(request, into: &inputBuffer)) + + var outputBuffer: ByteBuffer? + XCTAssertNoThrow(outputBuffer = try handler.handle(inputBuffer, context: context).wait()) + XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) + XCTAssertNoThrow(try handler.handle(inputBuffer, context: context).wait()) + XCTAssertEqual(response?.requestId, request.requestId) } - #endif // convenience method func newContext() -> LambdaContext { @@ -191,29 +248,3 @@ private struct Response: Codable, Equatable { self.requestId = requestId } } - -#if compiler(>=5.5) && canImport(_Concurrency) -// NOTE: workaround until we have async test support on linux -// https://github.com/apple/swift-corelibs-xctest/pull/326 -extension XCTestCase { - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func XCTAsyncTest( - expectationDescription: String = "Async operation", - timeout: TimeInterval = 3, - file: StaticString = #file, - line: Int = #line, - operation: @escaping () async throws -> Void - ) { - let expectation = self.expectation(description: expectationDescription) - Task { - do { try await operation() } - catch { - XCTFail("Error thrown while executing async function @ \(file):\(line): \(error)") - Thread.callStackSymbols.forEach { print($0) } - } - expectation.fulfill() - } - self.wait(for: [expectation], timeout: timeout) - } -} -#endif diff --git a/Tests/AWSLambdaTestingTests/Tests.swift b/Tests/AWSLambdaTestingTests/Tests.swift index f4520aa2..dd152ac2 100644 --- a/Tests/AWSLambdaTestingTests/Tests.swift +++ b/Tests/AWSLambdaTestingTests/Tests.swift @@ -12,15 +12,25 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=5.5) && canImport(_Concurrency) import AWSLambdaRuntime import AWSLambdaTesting import NIOCore import XCTest -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) class LambdaTestingTests: XCTestCase { - func testCodableClosure() { + func testBasics() async throws { + struct MyLambda: SimpleLambdaHandler { + func handle(_ event: String, context: LambdaContext) async throws -> String { + event + } + } + + let uuid = UUID().uuidString + let result = try await Lambda.test(MyLambda.self, with: uuid) + XCTAssertEqual(result, uuid) + } + + func testCodableClosure() async throws { struct Request: Codable { let name: String } @@ -29,73 +39,56 @@ class LambdaTestingTests: XCTestCase { let message: String } - struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Response - - init(context: LambdaInitializationContext) {} - + struct MyLambda: SimpleLambdaHandler { func handle(_ event: Request, context: LambdaContext) async throws -> Response { Response(message: "echo" + event.name) } } let request = Request(name: UUID().uuidString) - var response: Response? - XCTAssertNoThrow(response = try Lambda.test(MyLambda.self, with: request)) - XCTAssertEqual(response?.message, "echo" + request.name) + let response = try await Lambda.test(MyLambda.self, with: request) + XCTAssertEqual(response.message, "echo" + request.name) } - // DIRTY HACK: To verify the handler was actually invoked, we change a global variable. - static var VoidLambdaHandlerInvokeCount: Int = 0 - func testCodableVoidClosure() { + func testCodableVoidClosure() async throws { struct Request: Codable { let name: String } - struct MyLambda: LambdaHandler { - typealias Event = Request - typealias Output = Void - - init(context: LambdaInitializationContext) {} + struct MyLambda: SimpleLambdaHandler { + // DIRTY HACK: To verify the handler was actually invoked, we change a global variable. + static var VoidLambdaHandlerInvokeCount: Int = 0 func handle(_ event: Request, context: LambdaContext) async throws { - LambdaTestingTests.VoidLambdaHandlerInvokeCount += 1 + Self.VoidLambdaHandlerInvokeCount += 1 } } - Self.VoidLambdaHandlerInvokeCount = 0 let request = Request(name: UUID().uuidString) - XCTAssertNoThrow(try Lambda.test(MyLambda.self, with: request)) - XCTAssertEqual(Self.VoidLambdaHandlerInvokeCount, 1) + MyLambda.VoidLambdaHandlerInvokeCount = 0 + try await Lambda.test(MyLambda.self, with: request) + XCTAssertEqual(MyLambda.VoidLambdaHandlerInvokeCount, 1) } - func testInvocationFailure() { + func testInvocationFailure() async throws { struct MyError: Error {} - struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = Void - - init(context: LambdaInitializationContext) {} - + struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws { throw MyError() } } - XCTAssertThrowsError(try Lambda.test(MyLambda.self, with: UUID().uuidString)) { error in + do { + try await Lambda.test(MyLambda.self, with: UUID().uuidString) + XCTFail("expected to throw") + } catch { XCTAssert(error is MyError) } } - func testAsyncLongRunning() { - struct MyLambda: LambdaHandler { - typealias Event = String - typealias Output = String - - init(context: LambdaInitializationContext) {} - + func testAsyncLongRunning() async throws { + struct MyLambda: SimpleLambdaHandler { func handle(_ event: String, context: LambdaContext) async throws -> String { try await Task.sleep(nanoseconds: 500 * 1000 * 1000) return event @@ -103,7 +96,7 @@ class LambdaTestingTests: XCTestCase { } let uuid = UUID().uuidString - XCTAssertEqual(try Lambda.test(MyLambda.self, with: uuid), uuid) + let result = try await Lambda.test(MyLambda.self, with: uuid) + XCTAssertEqual(result, uuid) } } -#endif diff --git a/docker/Dockerfile b/docker/Dockerfile index 28d65fc1..ec97cef2 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG swift_version=5.4 +ARG swift_version=5.7 ARG base_image=swift:$swift_version-amazonlinux2 FROM $base_image # needed to do again after FROM due to docker limitation @@ -14,7 +14,7 @@ RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile # swiftformat (until part of the toolchain) -ARG swiftformat_version=0.47.3 +ARG swiftformat_version=0.50.1 RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format RUN cd $HOME/.tools/swift-format && swift build -c release RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat diff --git a/docker/docker-compose.al2.54.yaml b/docker/docker-compose.al2.54.yaml deleted file mode 100644 index cb300eb7..00000000 --- a/docker/docker-compose.al2.54.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-aws-lambda:al2-5.4 - build: - args: - swift_version: "5.4" - - test: - image: swift-aws-lambda:al2-5.4 - - test-samples: - image: swift-aws-lambda:al2-5.4 - - shell: - image: swift-aws-lambda:al2-5.4 diff --git a/docker/docker-compose.al2.55.yaml b/docker/docker-compose.al2.55.yaml deleted file mode 100644 index 488c629a..00000000 --- a/docker/docker-compose.al2.55.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-aws-lambda:al2-5.5 - build: - args: - swift_version: "5.5" - - test: - image: swift-aws-lambda:al2-5.5 - - test-samples: - image: swift-aws-lambda:al2-5.5 - - shell: - image: swift-aws-lambda:al2-5.5 diff --git a/docker/docker-compose.al2.56.yaml b/docker/docker-compose.al2.56.yaml deleted file mode 100644 index 561ea804..00000000 --- a/docker/docker-compose.al2.56.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-aws-lambda:al2-5.6 - build: - args: - swift_version: "5.6" - - test: - image: swift-aws-lambda:al2-5.6 - - test-samples: - image: swift-aws-lambda:al2-5.6 - - shell: - image: swift-aws-lambda:al2-5.6 diff --git a/docker/docker-compose.al2.57.yaml b/docker/docker-compose.al2.57.yaml index 0e280837..dc5044fa 100644 --- a/docker/docker-compose.al2.57.yaml +++ b/docker/docker-compose.al2.57.yaml @@ -6,7 +6,7 @@ services: image: swift-aws-lambda:al2-5.7 build: args: - base_image: "swiftlang/swift:nightly-main-amazonlinux2" + swift_version: "5.7" test: image: swift-aws-lambda:al2-5.7 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index e5ba9ddb..7e41db16 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -28,7 +28,7 @@ services: test: <<: *common - command: /bin/bash -cl "swift test --enable-test-discovery -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" + command: /bin/bash -cl "swift test -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" test-samples: <<: *common diff --git a/scripts/soundness.sh b/scripts/soundness.sh index 26b348ad..bd321f62 100755 --- a/scripts/soundness.sh +++ b/scripts/soundness.sh @@ -129,6 +129,7 @@ EOF cd "$here/.." find . \ \( \! -path '*/.build/*' -a \ + \( \! -path '*/.git/*' \) -a \ \( "${matching_files[@]}" \) -a \ \( \! \( "${exceptions[@]}" \) \) \) | while read line; do if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then